diff --git a/Changelog.md b/Changelog.md index 0bd1f6fa3..527cbb5fa 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fix `maturin develop` on Windows when using Python installed from msys2 in [#1112](https://github.com/PyO3/maturin/pull/1112) * Fix duplicated `Cargo.toml` of local dependencies in sdist in [#1114](https://github.com/PyO3/maturin/pull/1114) * Add support for Cargo workspace dependencies inheritance in [#1123](https://github.com/PyO3/maturin/pull/1123) +* Add support for Cargo workspace metadata inheritance in [#1131](https://github.com/PyO3/maturin/pull/1131) ## [0.13.3] - 2022-09-15 diff --git a/src/build_options.rs b/src/build_options.rs index 249c26b4d..c63effba3 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -488,7 +488,6 @@ impl BuildOptions { let ProjectResolver { project_layout, cargo_toml_path, - cargo_toml, pyproject_toml_path, pyproject_toml, module_name, @@ -685,7 +684,7 @@ impl BuildOptions { .target_dir .clone() .unwrap_or_else(|| cargo_metadata.target_directory.clone().into_std_path_buf()); - let crate_name = cargo_toml.package.name; + let crate_name = metadata21.name.clone(); Ok(BuildContext { target, @@ -1339,9 +1338,15 @@ mod test { use crate::CargoToml; // Nothing specified - let cargo_toml = CargoToml::from_path("test-crates/pyo3-pure/Cargo.toml").unwrap(); + let manifest_path = "test-crates/pyo3-pure/Cargo.toml"; + let cargo_toml = CargoToml::from_path(manifest_path).unwrap(); + let cargo_metadata = MetadataCommand::new() + .manifest_path(manifest_path) + .exec() + .unwrap(); let metadata21 = - Metadata21::from_cargo_toml(&cargo_toml, &"test-crates/pyo3-pure").unwrap(); + Metadata21::from_cargo_toml(&cargo_toml, &"test-crates/pyo3-pure", &cargo_metadata) + .unwrap(); assert_eq!(get_min_python_minor(&metadata21), None); } } diff --git a/src/cargo_toml.rs b/src/cargo_toml.rs index 523f60c7c..4bfff0f7c 100644 --- a/src/cargo_toml.rs +++ b/src/cargo_toml.rs @@ -16,20 +16,6 @@ pub(crate) struct CargoTomlLib { #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub(crate) struct CargoTomlPackage { - // Those three fields are mandatory - // https://doc.rust-lang.org/cargo/reference/manifest.html#the-package-section - pub(crate) name: String, - pub(crate) version: String, - // All other fields are optional - pub(crate) authors: Option>, - pub(crate) description: Option, - pub(crate) documentation: Option, - pub(crate) homepage: Option, - pub(crate) repository: Option, - pub(crate) readme: Option, - pub(crate) keywords: Option>, - pub(crate) categories: Option>, - pub(crate) license: Option, metadata: Option, } diff --git a/src/metadata.rs b/src/metadata.rs index aca905aac..17b5aced4 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -283,22 +283,20 @@ impl Metadata21 { pub fn from_cargo_toml( cargo_toml: &CargoToml, manifest_path: impl AsRef, + cargo_metadata: &cargo_metadata::Metadata, ) -> Result { - let authors = cargo_toml - .package - .authors - .as_ref() - .map(|authors| authors.join(", ")); + let package = cargo_metadata + .root_package() + .context("Expected cargo to return metadata with root_package")?; + let authors = package.authors.join(", "); let classifiers = cargo_toml.classifiers(); - let author_email = authors.as_ref().and_then(|authors| { - if authors.contains('@') { - Some(authors.clone()) - } else { - None - } - }); + let author_email = if authors.contains('@') { + Some(authors.clone()) + } else { + None + }; let extra_metadata = cargo_toml.remaining_core_metadata(); @@ -306,9 +304,9 @@ impl Metadata21 { let mut description_content_type: Option = None; // See https://packaging.python.org/specifications/core-metadata/#description // and https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field - if cargo_toml.package.readme == Some("false".to_string()) { + if package.readme == Some("false".into()) { // > You can suppress this behavior by setting this field to false - } else if let Some(ref readme) = cargo_toml.package.readme { + } else if let Some(ref readme) = package.readme { let readme_path = manifest_path.as_ref().join(readme); description = Some(fs::read_to_string(&readme_path).context(format!( "Failed to read Readme specified in Cargo.toml, which should be at {}", @@ -345,9 +343,9 @@ impl Metadata21 { name.clone() } }) - .unwrap_or_else(|| cargo_toml.package.name.clone()); + .unwrap_or_else(|| package.name.clone()); let mut project_url = extra_metadata.project_url.unwrap_or_default(); - if let Some(repository) = cargo_toml.package.repository.as_ref() { + if let Some(repository) = package.repository.as_ref() { project_url.insert("Source Code".to_string(), repository.clone()); } @@ -356,21 +354,25 @@ impl Metadata21 { // Mapped from cargo metadata name, - version: cargo_toml.package.version.clone(), - summary: cargo_toml.package.description.clone(), + version: package.version.to_string(), + summary: package.description.clone(), description, description_content_type, - keywords: cargo_toml - .package - .keywords - .clone() - .map(|keywords| keywords.join(",")), - home_page: cargo_toml.package.homepage.clone(), + keywords: if package.keywords.is_empty() { + None + } else { + Some(package.keywords.join(",")) + }, + home_page: package.homepage.clone(), download_url: None, // Cargo.toml has no distinction between author and author email - author: authors, + author: if package.authors.is_empty() { + None + } else { + Some(authors) + }, author_email, - license: cargo_toml.package.license.clone(), + license: package.license.clone(), license_files: Vec::new(), // Values provided through `[project.metadata.maturin]` @@ -554,28 +556,41 @@ fn fold_header(text: &str) -> String { #[cfg(test)] mod test { use super::*; + use cargo_metadata::MetadataCommand; use indoc::indoc; use pretty_assertions::assert_eq; - use std::io::Write; - fn assert_metadata_from_cargo_toml(readme: &str, cargo_toml: &str, expected: &str) { - let mut readme_md = tempfile::NamedTempFile::new().unwrap(); + fn assert_metadata_from_cargo_toml( + readme: &str, + cargo_toml: &str, + expected: &str, + ) -> Metadata21 { + let crate_dir = tempfile::tempdir().unwrap(); + let crate_path = crate_dir.path(); + let manifest_path = crate_path.join("Cargo.toml"); + fs::create_dir(crate_path.join("src")).unwrap(); + fs::write(crate_path.join("src/lib.rs"), "").unwrap(); + + let readme_path = crate_path.join("README.md"); + fs::write(&readme_path, readme.as_bytes()).unwrap(); let readme_path = if cfg!(windows) { - readme_md.path().to_str().unwrap().replace('\\', "/") + readme_path.to_str().unwrap().replace('\\', "/") } else { - readme_md.path().to_str().unwrap().to_string() + readme_path.to_str().unwrap().to_string() }; - readme_md.write_all(readme.as_bytes()).unwrap(); - let toml_with_path = cargo_toml.replace("REPLACE_README_PATH", &readme_path); + fs::write(&manifest_path, &toml_with_path).unwrap(); let cargo_toml_struct: CargoToml = toml_edit::easy::from_str(&toml_with_path).unwrap(); + let cargo_metadata = MetadataCommand::new() + .manifest_path(manifest_path) + .exec() + .unwrap(); let metadata = - Metadata21::from_cargo_toml(&cargo_toml_struct, &readme_md.path().parent().unwrap()) - .unwrap(); + Metadata21::from_cargo_toml(&cargo_toml_struct, crate_path, &cargo_metadata).unwrap(); let actual = metadata.to_file_contents().unwrap(); @@ -593,11 +608,15 @@ mod test { && cargo_toml.contains("version = \"0.1.0\""), "cargo_toml name and version string do not match hardcoded values, test will fail", ); - assert_eq!( - metadata.get_dist_info_dir(), - PathBuf::from("info_project-0.1.0.dist-info"), - "Dist info dir differed from expected" - ); + + if cargo_toml_struct.remaining_core_metadata().name.is_none() { + assert_eq!( + metadata.get_dist_info_dir(), + PathBuf::from("info_project-0.1.0.dist-info"), + "Dist info dir differed from expected" + ); + } + metadata } #[test] @@ -648,7 +667,7 @@ mod test { Home-Page: https://example.org Author: konstin Author-email: konstin - Description-Content-Type: text/plain; charset=UTF-8 + Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM Project-URL: Bug Tracker, http://bitbucket.org/tarek/distribute/issues/ # Some test package @@ -721,6 +740,13 @@ mod test { #[test] fn test_metadata_from_cargo_toml_name_override() { + let readme = indoc!( + r#" + Some test package + ================= + "# + ); + let cargo_toml = indoc!( r#" [package] @@ -729,6 +755,7 @@ mod test { version = "0.1.0" description = "A test project" homepage = "https://example.org" + readme = "REPLACE_README_PATH" [lib] crate-type = ["cdylib"] @@ -754,21 +781,14 @@ mod test { Home-Page: https://example.org Author: konstin Author-email: konstin + Description-Content-Type: text/x-rst + + Some test package + ================= "# ); - let cargo_toml_struct: CargoToml = toml_edit::easy::from_str(cargo_toml).unwrap(); - let metadata = - Metadata21::from_cargo_toml(&cargo_toml_struct, "/not/exist/manifest/path").unwrap(); - let actual = metadata.to_file_contents().unwrap(); - - assert_eq!( - actual.trim(), - expected.trim(), - "Actual metadata differed from expected\nEXPECTED:\n{}\n\nGOT:\n{}", - expected, - actual - ); + let metadata = assert_metadata_from_cargo_toml(readme, cargo_toml, expected); assert_eq!( metadata.get_dist_info_dir(), @@ -804,7 +824,12 @@ mod test { let manifest_dir = PathBuf::from("test-crates").join("pyo3-pure"); let cargo_toml_str = fs_err::read_to_string(manifest_dir.join("Cargo.toml")).unwrap(); let cargo_toml: CargoToml = toml_edit::easy::from_str(&cargo_toml_str).unwrap(); - let mut metadata = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir).unwrap(); + let cargo_metadata = MetadataCommand::new() + .manifest_path(manifest_dir.join("Cargo.toml")) + .exec() + .unwrap(); + let mut metadata = + Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir, &cargo_metadata).unwrap(); let pyproject_toml = PyProjectToml::new(manifest_dir.join("pyproject.toml")).unwrap(); metadata .merge_pyproject_toml(&manifest_dir, &pyproject_toml) @@ -850,7 +875,12 @@ mod test { let manifest_dir = PathBuf::from("test-crates").join("pyo3-mixed-py-subdir"); let cargo_toml_str = fs_err::read_to_string(manifest_dir.join("Cargo.toml")).unwrap(); let cargo_toml: CargoToml = toml_edit::easy::from_str(&cargo_toml_str).unwrap(); - let mut metadata = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir).unwrap(); + let cargo_metadata = MetadataCommand::new() + .manifest_path(manifest_dir.join("Cargo.toml")) + .exec() + .unwrap(); + let mut metadata = + Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir, &cargo_metadata).unwrap(); let pyproject_toml = PyProjectToml::new(manifest_dir.join("pyproject.toml")).unwrap(); metadata .merge_pyproject_toml(&manifest_dir, &pyproject_toml) @@ -866,9 +896,15 @@ mod test { #[test] fn test_implicit_readme() { - let cargo_toml_str = fs_err::read_to_string("test-crates/pyo3-mixed/Cargo.toml").unwrap(); + let manifest_dir = PathBuf::from("test-crates").join("pyo3-mixed"); + let cargo_toml_str = fs_err::read_to_string(manifest_dir.join("Cargo.toml")).unwrap(); let cargo_toml = toml_edit::easy::from_str(&cargo_toml_str).unwrap(); - let metadata = Metadata21::from_cargo_toml(&cargo_toml, "test-crates/pyo3-mixed").unwrap(); + let cargo_metadata = MetadataCommand::new() + .manifest_path(manifest_dir.join("Cargo.toml")) + .exec() + .unwrap(); + let metadata = + Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir, &cargo_metadata).unwrap(); assert!(metadata.description.unwrap().starts_with("# pyo3-mixed")); assert_eq!( metadata.description_content_type.unwrap(), @@ -881,7 +917,12 @@ mod test { let manifest_dir = PathBuf::from("test-crates").join("license-test"); let cargo_toml_str = fs_err::read_to_string(&manifest_dir.join("Cargo.toml")).unwrap(); let cargo_toml: CargoToml = toml_edit::easy::from_str(&cargo_toml_str).unwrap(); - let mut metadata = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir).unwrap(); + let cargo_metadata = MetadataCommand::new() + .manifest_path(manifest_dir.join("Cargo.toml")) + .exec() + .unwrap(); + let mut metadata = + Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir, &cargo_metadata).unwrap(); let pyproject_toml = PyProjectToml::new(manifest_dir.join("pyproject.toml")).unwrap(); metadata .merge_pyproject_toml(&manifest_dir, &pyproject_toml) diff --git a/src/project_layout.rs b/src/project_layout.rs index 14050c90f..98b882ce3 100644 --- a/src/project_layout.rs +++ b/src/project_layout.rs @@ -31,8 +31,6 @@ pub struct ProjectResolver { pub project_layout: ProjectLayout, /// Cargo.toml path pub cargo_toml_path: PathBuf, - /// Parsed Cargo.toml - pub cargo_toml: CargoToml, /// pyproject.toml path pub pyproject_toml_path: PathBuf, /// Parsed pyproject.toml @@ -87,15 +85,16 @@ impl ProjectResolver { let cargo_metadata = Self::resolve_cargo_metadata(&manifest_file, &cargo_options)?; - let mut metadata21 = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir) - .context("Failed to parse Cargo.toml into python metadata")?; + let mut metadata21 = + Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir, &cargo_metadata) + .context("Failed to parse Cargo.toml into python metadata")?; if let Some(pyproject) = pyproject { let pyproject_dir = pyproject_file.parent().unwrap(); metadata21.merge_pyproject_toml(&pyproject_dir, pyproject)?; } let extra_metadata = cargo_toml.remaining_core_metadata(); - let crate_name = &cargo_toml.package.name; + let crate_name = &metadata21.name; // If the package name contains minuses, you must declare a module with // underscores as lib name @@ -146,7 +145,6 @@ impl ProjectResolver { Ok(Self { project_layout, cargo_toml_path: manifest_file, - cargo_toml, pyproject_toml_path: pyproject_file, pyproject_toml, module_name, diff --git a/src/source_distribution.rs b/src/source_distribution.rs index 71e27bdad..850f8ae90 100644 --- a/src/source_distribution.rs +++ b/src/source_distribution.rs @@ -9,6 +9,27 @@ use std::process::Command; use std::str; const LOCAL_DEPENDENCIES_FOLDER: &str = "local_dependencies"; +/// Inheritable workspace fields, see +/// https://github.com/rust-lang/cargo/blob/13ae438cf079da58272edc71f4d4968043dbd27b/src/cargo/util/toml/mod.rs#L1140-L1158 +const WORKSPACE_INHERITABLE_FIELDS: &[&str] = &[ + "version", + "authors", + "description", + "homepage", + "documentation", + "readme", + "keywords", + "categories", + "license", + "license-file", + "repository", + "publish", + "edition", + "badges", + "exclude", + "include", + "rust-version", +]; /// We need cargo to load the local dependencies from the location where we put them in the source /// distribution. Since there is no cargo-backed way to replace dependencies @@ -44,7 +65,7 @@ fn rewrite_cargo_toml( let workspace_deps = workspace_manifest .get("workspace") .and_then(|x| x.get(dep_category)) - .and_then(|x| x.as_table()); + .and_then(|x| x.as_table_like()); let dep_names: Vec<_> = table.iter().map(|(key, _)| key.to_string()).collect(); for dep_name in dep_names { let workspace_inherit = table @@ -138,6 +159,28 @@ fn rewrite_cargo_toml( } } } + + // Update workspace inherited metadata + if let Some(package) = data.get_mut("package").and_then(|x| x.as_table_mut()) { + let workspace_package = workspace_manifest + .get("workspace") + .and_then(|x| x.get("package")) + .and_then(|x| x.as_table_like()); + for key in WORKSPACE_INHERITABLE_FIELDS.iter().copied() { + let workspace_inherited = package + .get(key) + .and_then(|x| x.get("workspace")) + .and_then(|x| x.as_bool()) + .unwrap_or_default(); + if workspace_inherited { + if let Some(workspace_value) = workspace_package.and_then(|ws| ws.get(key)) { + package[key] = workspace_value.clone(); + rewritten = true; + } + } + } + } + if root_crate { // Update workspace members if let Some(workspace) = data.get_mut("workspace").and_then(|x| x.as_table_mut()) { diff --git a/test-crates/workspace-inheritance/Cargo.toml b/test-crates/workspace-inheritance/Cargo.toml index 21af48430..418fd5aee 100644 --- a/test-crates/workspace-inheritance/Cargo.toml +++ b/test-crates/workspace-inheritance/Cargo.toml @@ -4,6 +4,9 @@ members = [ "python" ] +[workspace.package] +version = "0.1.0" + [workspace.dependencies] libc = { version = "0.2", features = ["std"] } generic_lib = { path = "generic_lib" } diff --git a/test-crates/workspace-inheritance/python/Cargo.toml b/test-crates/workspace-inheritance/python/Cargo.toml index 7b25fcf6f..be82cf64f 100644 --- a/test-crates/workspace-inheritance/python/Cargo.toml +++ b/test-crates/workspace-inheritance/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "workspace-inheritance" -version = "0.1.0" +version.workspace = true edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html