Origin: upstream, dcf4a828ec7fc6d49ba505fe1e1b500591fc4186
From: messense <messense@icloud.com>
Date: Sat, 11 Jan 2025 14:37:45 +0800
Subject: Upgrade pyproject-toml to 0.13.4 (#2342)

Backport to v1.7.4 and relax versions
---
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -70,7 +70,7 @@ rustc_version = "0.4.0"
 semver = "1"
 target-lexicon = "0.12.14"
 indexmap = "2.2.3"
-pyproject-toml = "0.11.0"
+pyproject-toml = "0.13.4"
 python-pkginfo = "0.6.0"
 textwrap = "0.16.1"
 ignore = "0.4.20"
@@ -80,8 +80,8 @@ cc = "1.0.88"
 dunce = "1.0.2"
 normpath = "1.1.1"
 path-slash = "0.2.1"
-pep440_rs = { version = "0.6.0", features = ["serde", "tracing"] }
-pep508_rs = { version = "0.6.0", features = ["serde", "tracing"] }
+pep440_rs = { version = ">=0.7.2", features = ["tracing"] }
+pep508_rs = { version = ">=0.9.1", features = ["tracing"] }
 same-file = "1.0.6"
 time = "0.3.17"
 url = "2.5.0"
--- a/src/develop.rs
+++ b/src/develop.rs
@@ -10,7 +10,6 @@ use anyhow::ensure;
 use anyhow::{anyhow, bail, Context, Result};
 use cargo_options::heading;
 use fs_err as fs;
-use pep508_rs::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValue};
 use regex::Regex;
 use std::path::Path;
 use std::path::PathBuf;
@@ -229,31 +228,23 @@ fn install_dependencies(
     install_backend: &InstallBackend,
 ) -> Result<()> {
     if !build_context.metadata23.requires_dist.is_empty() {
+        let mut extra_names = Vec::with_capacity(extras.len());
+        for extra in extras {
+            extra_names.push(
+                pep508_rs::ExtraName::new(extra.clone())
+                    .with_context(|| format!("invalid extra name: {extra}"))?,
+            );
+        }
         let mut args = vec!["install".to_string()];
         args.extend(build_context.metadata23.requires_dist.iter().map(|x| {
             let mut pkg = x.clone();
-            // Remove extra marker to make it installable with pip
-            // Keep in sync with `Metadata21::merge_pyproject_toml()`!
-            for extra in extras {
-                pkg.marker = pkg.marker.and_then(|marker| -> Option<MarkerTree> {
-                    match marker.clone() {
-                        MarkerTree::Expression(MarkerExpression {
-                            l_value: MarkerValue::Extra,
-                            operator: MarkerOperator::Equal,
-                            r_value: MarkerValue::QuotedString(extra_value),
-                        }) if &extra_value == extra => None,
-                        MarkerTree::And(and) => match &*and {
-                            [existing, MarkerTree::Expression(MarkerExpression {
-                                l_value: MarkerValue::Extra,
-                                operator: MarkerOperator::Equal,
-                                r_value: MarkerValue::QuotedString(extra_value),
-                            })] if extra_value == extra => Some(existing.clone()),
-                            _ => Some(marker),
-                        },
-                        _ => Some(marker),
-                    }
-                });
-            }
+            // Remove extra marker to make it installable with pip:
+            //
+            // * ` and extra == 'EXTRA_NAME'`
+            // * `; extra == 'EXTRA_NAME'`
+            //
+            // Keep in sync with `Metadata23::merge_pyproject_toml()`
+            pkg.marker = pkg.marker.simplify_extras(&extra_names);
             pkg.to_string()
         }));
         let status = install_backend
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -3,7 +3,9 @@ use anyhow::{bail, format_err, Context, Result};
 use fs_err as fs;
 use indexmap::IndexMap;
 use pep440_rs::{Version, VersionSpecifiers};
-use pep508_rs::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, Requirement};
+use pep508_rs::{
+    ExtraName, ExtraOperator, MarkerExpression, MarkerTree, MarkerValueExtra, Requirement,
+};
 use pyproject_toml::License;
 use regex::Regex;
 use serde::{Deserialize, Serialize};
@@ -203,18 +205,12 @@ impl Metadata24 {
             if let Some(license) = &project.license {
                 match license {
                     // TODO: switch to License-Expression core metadata, see https://peps.python.org/pep-0639/#add-license-expression-field
-                    License::String(license_expr) => self.license = Some(license_expr.clone()),
-                    License::Table { file, text } => match (file, text) {
-                        (Some(_), Some(_)) => {
-                            bail!("file and text fields of 'project.license' are mutually-exclusive, only one of them should be specified");
-                        }
-                        (Some(license_path), None) => {
-                            let license_path = pyproject_dir.join(license_path);
-                            self.license_files.push(license_path);
-                        }
-                        (None, Some(license_text)) => self.license = Some(license_text.clone()),
-                        (None, None) => {}
-                    },
+                    License::Spdx(license_expr) => self.license = Some(license_expr.clone()),
+                    License::File { file } => {
+                        let license_path = pyproject_dir.join(file);
+                        self.license_files.push(license_path);
+                    }
+                    License::Text { text } => self.license = Some(text.clone()),
                 }
             }
 
@@ -246,15 +242,15 @@ impl Metadata24 {
                 let mut names = Vec::with_capacity(authors.len());
                 let mut emails = Vec::with_capacity(authors.len());
                 for author in authors {
-                    match (&author.name, &author.email) {
+                    match (author.name(), author.email()) {
                         (Some(name), Some(email)) => {
                             emails.push(escape_email_with_display_name(name, email));
                         }
                         (Some(name), None) => {
-                            names.push(name.as_str());
+                            names.push(name);
                         }
                         (None, Some(email)) => {
-                            emails.push(email.clone());
+                            emails.push(email.to_string());
                         }
                         (None, None) => {}
                     }
@@ -271,15 +267,15 @@ impl Metadata24 {
                 let mut names = Vec::with_capacity(maintainers.len());
                 let mut emails = Vec::with_capacity(maintainers.len());
                 for maintainer in maintainers {
-                    match (&maintainer.name, &maintainer.email) {
+                    match (maintainer.name(), maintainer.email()) {
                         (Some(name), Some(email)) => {
                             emails.push(escape_email_with_display_name(name, email));
                         }
                         (Some(name), None) => {
-                            names.push(name.as_str());
+                            names.push(name);
                         }
                         (None, Some(email)) => {
-                            emails.push(email.clone());
+                            emails.push(email.to_string());
                         }
                         (None, None) => {}
                     }
@@ -315,16 +311,14 @@ impl Metadata24 {
                     for dep in deps {
                         let mut dep = dep.clone();
                         // Keep in sync with `develop()`!
-                        let new_extra = MarkerTree::Expression(MarkerExpression {
-                            l_value: MarkerValue::Extra,
-                            operator: MarkerOperator::Equal,
-                            r_value: MarkerValue::QuotedString(extra.to_string()),
-                        });
-                        if let Some(existing) = dep.marker.take() {
-                            dep.marker = Some(MarkerTree::And(vec![existing, new_extra]));
-                        } else {
-                            dep.marker = Some(new_extra);
-                        }
+                        let new_extra = MarkerExpression::Extra {
+                            operator: ExtraOperator::Equal,
+                            name: MarkerValueExtra::Extra(
+                                ExtraName::new(extra.clone())
+                                    .with_context(|| format!("invalid extra name: {extra}"))?,
+                            ),
+                        };
+                        dep.marker.and(MarkerTree::expression(new_extra));
                         self.requires_dist.push(dep);
                     }
                 }
--- a/src/pyproject_toml.rs
+++ b/src/pyproject_toml.rs
@@ -707,11 +707,13 @@ mod tests {
         let inner_error = outer_error.source().unwrap();
 
         let expected = expect![[r#"
-            TOML parse error at line 7, column 17
-              |
-            7 | license-files = [ "license.txt",]
-              |                 ^^^^^^^^^^^^^^^^^
-            wanted string or table
+            TOML parse error at line 10, column 16
+               |
+            10 | dependencies = [ "packaging", "...",]
+               |                ^^^^^^^^^^^^^^^^^^^^^^
+            URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ /path/to/file`).
+            ...
+            ^^^
         "#]];
         expected.assert_eq(&inner_error.to_string());
     }
--- a/src/source_distribution.rs
+++ b/src/source_distribution.rs
@@ -771,12 +771,8 @@ pub fn source_distribution(
         if let Some(pyproject_toml::ReadMe::RelativePath(readme)) = project.readme.as_ref() {
             writer.add_file(root_dir.join(readme), pyproject_dir.join(readme))?;
         }
-        if let Some(pyproject_toml::License::Table {
-            file: Some(license),
-            text: None,
-        }) = project.license.as_ref()
-        {
-            writer.add_file(root_dir.join(license), pyproject_dir.join(license))?;
+        if let Some(pyproject_toml::License::File { file }) = project.license.as_ref() {
+            writer.add_file(root_dir.join(file), pyproject_dir.join(file))?;
         }
     }
 
