From e7e40a825541d51073ea2f8c954ae4d90fbd6a44 Mon Sep 17 00:00:00 2001 From: messense Date: Fri, 9 Dec 2022 11:05:03 +0800 Subject: [PATCH] Allow Rust crate to be placed outside of the directory containing `pyproject.toml` --- src/project_layout.rs | 9 ---- src/source_distribution.rs | 88 +++++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/project_layout.rs b/src/project_layout.rs index 858df08d3..30d8fd2de 100644 --- a/src/project_layout.rs +++ b/src/project_layout.rs @@ -232,15 +232,6 @@ impl ProjectResolver { let pyproject = PyProjectToml::new(&pyproject_file).context("pyproject.toml is invalid")?; if let Some(path) = pyproject.manifest_path() { - // pyproject.toml must be placed at top directory - let manifest_dir = path - .parent() - .context("missing parent directory")? - .normalize()? - .into_path_buf(); - if !manifest_dir.starts_with(¤t_dir) { - bail!("Cargo.toml can not be placed outside of the directory containing pyproject.toml"); - } debug!("Using cargo manifest path from pyproject.toml {:?}", path); return Ok((path.normalize()?.into_path_buf(), pyproject_file)); } else { diff --git a/src/source_distribution.rs b/src/source_distribution.rs index b1df251c4..ee846f5e8 100644 --- a/src/source_distribution.rs +++ b/src/source_distribution.rs @@ -12,6 +12,8 @@ use std::str; use tracing::debug; const LOCAL_DEPENDENCIES_FOLDER: &str = "local_dependencies"; +const RUST_SRC_FOLDER: &str = "rust_src"; + /// 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] = &[ @@ -256,6 +258,38 @@ fn ensure_dep_is_inline_table(dep: &mut toml_edit::Item) { } } +/// When `Cargo.toml` is outside of the directory containing `pyproject.toml`, +/// we put Rust crate source to `RUST_SRC_FOLDER` and +/// update `tool.maturin.manifest-path` in `pyproject.toml`. +fn rewrite_pyproject_toml(pyproject_toml_path: &Path) -> Result { + let text = fs::read_to_string(pyproject_toml_path).context(format!( + "Can't read pyproject.toml at {}", + pyproject_toml_path.display(), + ))?; + let mut data = text.parse::().context(format!( + "Failed to parse pyproject.toml at {}", + pyproject_toml_path.display() + ))?; + if let Some(tool) = data.get_mut("tool").and_then(|x| x.as_table_mut()) { + if let Some(maturin) = tool.get_mut("maturin").and_then(|x| x.as_table_mut()) { + if let Some(manifest_path) = maturin.get_mut("manifest-path") { + // original: ../$crate/Cargo.toml or ../../$crate/Cargo.toml + // rewrite to: $RUST_SRC_FOLDER/$crate/Cargo.toml + let path = + Path::new(manifest_path.as_str().context( + "tool.maturin.manifest-path in pyproject.toml must be a string", + )?); + let crate_name = path.parent().unwrap().file_name().unwrap(); + let new_path = Path::new(RUST_SRC_FOLDER) + .join(crate_name) + .join("Cargo.toml"); + *manifest_path = toml_edit::value(new_path.to_str().unwrap()); + } + } + } + Ok(data.to_string()) +} + /// Copies the files of a crate to a source distribution, recursively adding path dependencies /// and rewriting path entries in Cargo.toml /// @@ -340,26 +374,57 @@ fn add_crate_to_source_distribution( }) .collect(); + let prefix = prefix.as_ref(); + writer.add_directory(prefix)?; + + let mut cargo_toml_in_rust_src = false; + if root_crate && !target_source .iter() .any(|(target, _)| target == Path::new("pyproject.toml")) { // Add pyproject.toml to the source distribution - // if Cargo.toml is in subdirectory of pyproject.toml directory if cargo_toml_in_subdir { + // if Cargo.toml is in subdirectory of pyproject.toml directory target_source.push(( PathBuf::from("pyproject.toml"), pyproject_toml_path.to_path_buf(), )); } else { - bail!( - "pyproject.toml was not included by `cargo package`. \ - Please make sure pyproject.toml is not excluded or build without `--sdist`" - ) + // if pyproject.toml was not included by `cargo package --list` + // (e.g. because it is in a parent directory) + // we need to add `cargo package --list` files to a subdirectory + // and rewrite `tool.maturin.manifest-path` in pyproject.toml + let crate_name = abs_manifest_dir.file_name().unwrap(); + target_source.iter_mut().for_each(|(target, _)| { + *target = PathBuf::from(RUST_SRC_FOLDER) + .join(crate_name) + .join(&target); + }); + // rewrite `tool.maturin.manifest-path` in pyproject.toml + let rewritten_pyproject_toml = rewrite_pyproject_toml(pyproject_toml_path)?; + writer.add_bytes( + prefix.join("pyproject.toml"), + rewritten_pyproject_toml.as_bytes(), + )?; + cargo_toml_in_rust_src = true; } } + let cargo_toml_path = if cargo_toml_in_subdir { + let relative_manifest_path = abs_manifest_path.strip_prefix(pyproject_dir).unwrap(); + prefix.join(relative_manifest_path) + } else if cargo_toml_in_rust_src { + let crate_name = abs_manifest_dir.file_name().unwrap(); + prefix + .join(RUST_SRC_FOLDER) + .join(crate_name) + .join(manifest_path.file_name().unwrap()) + } else { + prefix.join(manifest_path.file_name().unwrap()) + }; + let local_deps_folder = if cargo_toml_in_subdir { let level = abs_manifest_dir .strip_prefix(pyproject_dir) @@ -367,6 +432,8 @@ fn add_crate_to_source_distribution( .components() .count(); format!("{}{}", "../".repeat(level), LOCAL_DEPENDENCIES_FOLDER) + } else if cargo_toml_in_rust_src { + format!("../../{}", LOCAL_DEPENDENCIES_FOLDER) } else { LOCAL_DEPENDENCIES_FOLDER.to_string() }; @@ -378,16 +445,7 @@ fn add_crate_to_source_distribution( root_crate, )?; - let prefix = prefix.as_ref(); - writer.add_directory(prefix)?; - - let cargo_toml = if cargo_toml_in_subdir { - let relative_manifest_path = abs_manifest_path.strip_prefix(pyproject_dir).unwrap(); - prefix.join(relative_manifest_path) - } else { - prefix.join(manifest_path.file_name().unwrap()) - }; - writer.add_bytes(cargo_toml, rewritten_cargo_toml.as_bytes())?; + writer.add_bytes(cargo_toml_path, rewritten_cargo_toml.as_bytes())?; for (target, source) in target_source { writer.add_file(prefix.join(target), source)?;