Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/develop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl InstallBackend {
};
if let Some(captures) = re.expect("regex should be valid").captures(stdout) {
Ok(semver::Version::parse(&captures[1])
.with_context(|| format!("failed to parse semver from {:?}", stdout))?)
.with_context(|| format!("failed to parse semver from {stdout:?}"))?)
} else {
bail!("failed to parse version from {:?}", stdout);
}
Expand Down Expand Up @@ -321,7 +321,7 @@ fn install_wheel(
);
}
if let Err(err) = configure_as_editable(build_context, python, install_backend) {
eprintln!("⚠️ Warning: failed to set package as editable: {}", err);
eprintln!("⚠️ Warning: failed to set package as editable: {err}");
}
Ok(())
}
Expand Down
181 changes: 181 additions & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,41 @@ impl Metadata24 {
bail!("`project.dynamic` must not specify `name` in pyproject.toml");
}

// According to PEP 621, build backends must not add metadata fields
// that are not declared in the dynamic list. Clear fields from Cargo.toml
// that are not in the dynamic list.
if !dynamic.contains("description") {
self.summary = None;
}
if !dynamic.contains("authors") {
self.author = None;
self.author_email = None;
}
if !dynamic.contains("maintainers") {
self.maintainer = None;
self.maintainer_email = None;
}
if !dynamic.contains("keywords") {
self.keywords = None;
}
if !dynamic.contains("urls") {
self.project_url.clear();
}
if !dynamic.contains("license") {
self.license = None;
// Don't clear license_files as they may come from auto-discovery
}
if !dynamic.contains("classifiers") {
self.classifiers.clear();
}
if !dynamic.contains("readme") {
self.description = None;
self.description_content_type = None;
}
if !dynamic.contains("requires-python") {
self.requires_python = None;
}

self.name.clone_from(&project.name);

let version_ok = pyproject_toml.warn_invalid_version_info();
Expand Down Expand Up @@ -681,6 +716,7 @@ mod test {
use expect_test::{expect, Expect};
use indoc::indoc;
use pretty_assertions::assert_eq;
use tempfile::TempDir;

fn assert_metadata_from_cargo_toml(
readme: &str,
Expand Down Expand Up @@ -948,4 +984,149 @@ A test project
assert_eq!(result, expected);
}
}

#[test]
fn test_issue_2544_respect_pyproject_dynamic_without_dynamic_fields() {
let temp_dir = TempDir::new().unwrap();
let crate_path = temp_dir.path();
let manifest_path = crate_path.join("Cargo.toml");
let pyproject_path = crate_path.join("pyproject.toml");

// Create basic src structure
fs::create_dir(crate_path.join("src")).unwrap();
fs::write(crate_path.join("src/lib.rs"), "").unwrap();

// Write Cargo.toml with metadata that should NOT be included
// because pyproject.toml doesn't declare them as dynamic
let cargo_toml = indoc!(
r#"
[package]
name = "test-package"
version = "0.1.0"
description = "Description from Cargo.toml - should not appear"
authors = ["author from cargo.toml <author@example.com>"]
keywords = ["cargo", "toml", "keyword"]
repository = "https://github.com/example/repo"

[lib]
crate-type = ["cdylib"]
"#
);
fs::write(&manifest_path, cargo_toml).unwrap();

// Write pyproject.toml WITHOUT declaring the fields as dynamic
let pyproject_toml_content = indoc!(
r#"
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[project]
name = "test-package"
version = "0.1.0"
# Note: no description, authors, keywords, urls in dynamic list
# dynamic = [] # Not specified, so defaults to empty
"#
);
fs::write(&pyproject_path, pyproject_toml_content).unwrap();

// Load metadata as maturin does
let cargo_metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()
.unwrap();
let mut metadata = Metadata24::from_cargo_toml(crate_path, &cargo_metadata).unwrap();
let pyproject_toml = PyProjectToml::new(&pyproject_path).unwrap();
metadata
.merge_pyproject_toml(crate_path, &pyproject_toml)
.unwrap();

assert_eq!(
metadata.summary, None,
"summary should be None when not in dynamic list"
);
assert_eq!(
metadata.author, None,
"author should be None when not in dynamic list"
);
assert_eq!(
metadata.keywords, None,
"keywords should be None when not in dynamic list"
);
assert!(
metadata.project_url.is_empty(),
"project_url should be empty when not in dynamic list"
);
}

#[test]
fn test_issue_2544_respect_pyproject_dynamic_with_dynamic_fields() {
let temp_dir = TempDir::new().unwrap();
let crate_path = temp_dir.path();
let manifest_path = crate_path.join("Cargo.toml");
let pyproject_path = crate_path.join("pyproject.toml");

// Create basic src structure
fs::create_dir(crate_path.join("src")).unwrap();
fs::write(crate_path.join("src/lib.rs"), "").unwrap();

// Write Cargo.toml with metadata
let cargo_toml = indoc!(
r#"
[package]
name = "test-package"
version = "0.1.0"
description = "Description from Cargo.toml - should appear"
authors = ["author from cargo.toml <author@example.com>"]
keywords = ["cargo", "toml", "keyword"]
repository = "https://github.com/example/repo"

[lib]
crate-type = ["cdylib"]
"#
);
fs::write(&manifest_path, cargo_toml).unwrap();

// Write pyproject.toml WITH fields declared as dynamic
let pyproject_toml_content = indoc!(
r#"
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[project]
name = "test-package"
version = "0.1.0"
# Fields declared as dynamic - should come from Cargo.toml
dynamic = ["description", "authors", "keywords", "urls"]
"#
);
fs::write(&pyproject_path, pyproject_toml_content).unwrap();

// Load metadata as maturin does
let cargo_metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()
.unwrap();
let mut metadata = Metadata24::from_cargo_toml(crate_path, &cargo_metadata).unwrap();
let pyproject_toml = PyProjectToml::new(&pyproject_path).unwrap();
metadata
.merge_pyproject_toml(crate_path, &pyproject_toml)
.unwrap();

// These fields SHOULD be set because they are in dynamic list
assert_eq!(
metadata.summary,
Some("Description from Cargo.toml - should appear".to_string())
);
assert_eq!(
metadata.author,
Some("author from cargo.toml <author@example.com>".to_string())
);
assert_eq!(metadata.keywords, Some("cargo,toml,keyword".to_string()));
assert_eq!(
metadata.project_url.get("Source Code"),
Some(&"https://github.com/example/repo".to_string())
);
}
}
9 changes: 3 additions & 6 deletions src/new_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,23 +301,20 @@ mod package_name_validations {
}
if ["core", "std", "alloc", "proc_macro", "proc-macro"].contains(&name) {
eprintln!(
"⚠️ Warning: the name `{}` is part of Rust's standard library\n\
"⚠️ Warning: the name `{name}` is part of Rust's standard library\n\
It is recommended to use a different name to avoid problems.",
name,
);
}
if is_windows_reserved(name) {
eprintln!(
"⚠️ Warning: the name `{}` is a reserved Windows filename\n\
"⚠️ Warning: the name `{name}` is a reserved Windows filename\n\
This package will not work on Windows platforms.",
name
);
}
if is_non_ascii_name(name) {
eprintln!(
"⚠️ Warning: the name `{}` contains non-ASCII characters\n\
"⚠️ Warning: the name `{name}` contains non-ASCII characters\n\
Non-ASCII crate names are not supported by Rust.",
name
);
}
let name_in_lowercase = name.to_lowercase();
Expand Down
5 changes: 1 addition & 4 deletions src/pyproject_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,7 @@ impl PyProjectToml {
if !version_specifier.contains(&self_version) {
eprintln!(
"⚠️ Warning: You specified {requires_maturin} in pyproject.toml under \
`build-system.requires`, but the current {maturin} version is {version}",
requires_maturin = requires_maturin,
maturin = maturin,
version = self_version,
`build-system.requires`, but the current {maturin} version is {self_version}",
);
return false;
}
Expand Down
5 changes: 2 additions & 3 deletions src/source_distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,7 @@ fn find_path_deps(cargo_metadata: &Metadata) -> Result<HashMap<String, PathDepen
.exec()
.with_context(|| {
format!(
"Failed to resolve workspace root for {} at '{}'",
dep_id, dep_manifest_path
"Failed to resolve workspace root for {dep_id} at '{dep_manifest_path}'"
)
})?;

Expand Down Expand Up @@ -481,7 +480,7 @@ fn add_cargo_package_files_to_sdist(
name,
path_dep,
)
.with_context(|| format!("Failed to add path dependency {}", name))?;
.with_context(|| format!("Failed to add path dependency {name}"))?;
}

debug!("Adding the main crate {}", manifest_path.display());
Expand Down
2 changes: 1 addition & 1 deletion src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ fn resolve_pypi_cred(
return Ok(("__token__".to_string(), token));
}
Ok(None) => {}
Err(e) => eprintln!("⚠️ Warning: Failed to resolve PyPI token via OIDC: {}", e),
Err(e) => eprintln!("⚠️ Warning: Failed to resolve PyPI token via OIDC: {e}"),
}

if let Some((username, password)) =
Expand Down
1 change: 1 addition & 0 deletions test-crates/hello-world/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "hello-world"
version = "0.1.0"
license-files = ["LICENSE", "licenses/*"]
dynamic = ["authors", "readme"] # Allow authors and readme from Cargo.toml to be used

[build-system]
requires = ["maturin>=1.0,<2.0"]
Expand Down
2 changes: 1 addition & 1 deletion test-crates/license-test/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "maturin"
[project]
name = "license-test"
license = { file = "LICENCE.txt" }
dynamic = ["version"]
dynamic = ["version", "license"] # Allow license from Cargo.toml to be used

[tool.maturin]
bindings = "bin"
2 changes: 1 addition & 1 deletion test-crates/pyo3-mixed-py-subdir/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ classifiers = [
"Programming Language :: Rust"
]
requires-python = ">=3.6"
dynamic = ["version"]
dynamic = ["version", "description"] # Allow description from Cargo.toml to be used

[project.scripts]
get_42 = "pyo3_mixed_py_subdir:get_42"
Expand Down
1 change: 1 addition & 0 deletions test-crates/pyo3-pure/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ maintainers = [
{name = "messense", email = "messense@icloud.com"}
]
license = { file = "LICENSE" }
dynamic = ["license"] # Allow license from Cargo.toml to be used

[project.optional-dependencies]
test = [
Expand Down
4 changes: 2 additions & 2 deletions tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ fn develop_hello_world(#[case] backend: TestInstallBackend, #[case] name: &str)
handle_result(develop::test_develop(
"test-crates/hello-world",
None,
format!("develop-hello-world-{}", name).as_str(),
format!("develop-hello-world-{name}").as_str(),
false,
backend,
));
Expand All @@ -246,7 +246,7 @@ fn develop_pyo3_ffi_pure(#[case] backend: TestInstallBackend, #[case] name: &str
handle_result(develop::test_develop(
"test-crates/pyo3-ffi-pure",
None,
format!("develop-pyo3-ffi-pure-{}", name).as_str(),
format!("develop-pyo3-ffi-pure-{name}").as_str(),
false,
backend,
));
Expand Down
Loading