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
14 changes: 9 additions & 5 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ use tracing::instrument;
use zip::DateTime;

/// Unpacks an sdist tarball into a temporary directory and returns the path
/// to the Cargo.toml inside it, along with the tempdir handle (which must
/// be kept alive for the duration of the build).
/// to the Cargo.toml and pyproject.toml inside it, along with the tempdir
/// handle (which must be kept alive for the duration of the build).
///
/// The Cargo.toml path is resolved by checking `[tool.maturin.manifest-path]`
/// in the sdist's `pyproject.toml`, falling back to `Cargo.toml` at the
/// sdist root directory.
pub fn unpack_sdist(sdist_path: &Path) -> Result<(tempfile::TempDir, PathBuf)> {
pub fn unpack_sdist(sdist_path: &Path) -> Result<(tempfile::TempDir, PathBuf, PathBuf)> {
let tmp = tempfile::tempdir().context("Failed to create temporary directory")?;
let gz = flate2::read::GzDecoder::new(
fs::File::open(sdist_path)
Expand All @@ -66,7 +66,11 @@ pub fn unpack_sdist(sdist_path: &Path) -> Result<(tempfile::TempDir, PathBuf)> {
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.collect();
let top_dir = match entries.len() {
1 => entries[0].path(),
// Canonicalize to resolve symlinks (e.g. /var -> /private/var on macOS).
// Without this, `project_root` and `python_dir` may disagree after
// `normalize()` is applied to only some paths, causing python source
// files to be silently excluded from wheels.
1 => dunce::canonicalize(entries[0].path()).unwrap_or_else(|_| entries[0].path()),
n => bail!(
"Expected exactly one top-level directory in sdist, found {}",
n
Expand All @@ -92,7 +96,7 @@ pub fn unpack_sdist(sdist_path: &Path) -> Result<(tempfile::TempDir, PathBuf)> {
cargo_toml.display()
);
}
Ok((tmp, cargo_toml))
Ok((tmp, cargo_toml, pyproject_file))
}

/// Contains all the metadata required to build the crate
Expand Down
9 changes: 9 additions & 0 deletions src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ pub struct BuildContextBuilder {
strip: Option<bool>,
editable: bool,
sdist_only: bool,
pyproject_toml_path: Option<PathBuf>,
}

impl BuildContextBuilder {
Expand All @@ -619,6 +620,7 @@ impl BuildContextBuilder {
strip: None,
editable: false,
sdist_only: false,
pyproject_toml_path: None,
}
}

Expand All @@ -637,12 +639,18 @@ impl BuildContextBuilder {
self
}

pub fn pyproject_toml_path(mut self, path: Option<PathBuf>) -> Self {
self.pyproject_toml_path = path;
self
}

pub fn build(self) -> Result<BuildContext> {
let Self {
build_options,
strip,
editable,
sdist_only,
pyproject_toml_path: explicit_pyproject_path,
} = self;
build_options.compression.validate();
let ProjectResolver {
Expand All @@ -660,6 +668,7 @@ impl BuildContextBuilder {
build_options.manifest_path.clone(),
build_options.cargo.clone(),
editable,
explicit_pyproject_path,
)?;
let pyproject = pyproject_toml.as_ref();

Expand Down
2 changes: 1 addition & 1 deletion src/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl GenerateCI {
pyproject_toml,
project_layout,
..
} = ProjectResolver::resolve(self.manifest_path.clone(), cargo_options, false)?;
} = ProjectResolver::resolve(self.manifest_path.clone(), cargo_options, false, None)?;
let pyproject = pyproject_toml.as_ref();
let extra_pyo3_features = crate::build_options::pyo3_features_from_conditional(pyproject);
let bridge = find_bridge(
Expand Down
12 changes: 10 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,24 +421,28 @@ fn run() -> Result<()> {
}
// Keep tempdir alive for the duration of the build
let _sdist_tmp;
let sdist_pyproject_path;
if sdist {
// Build sdist first, then build wheels from the unpacked sdist
// to verify that the source distribution is complete.
let sdist_path = build_sdist(&build, strip)?;
let (tmp, cargo_toml) = unpack_sdist(&sdist_path)?;
let (tmp, cargo_toml, pyproject_toml) = unpack_sdist(&sdist_path)?;
_sdist_tmp = Some(tmp);
eprintln!(
"📦 Building wheels from source distribution at {}",
cargo_toml.parent().unwrap().display()
);
build.cargo.manifest_path = Some(cargo_toml);
sdist_pyproject_path = Some(pyproject_toml);
} else {
_sdist_tmp = None;
sdist_pyproject_path = None;
}
let build_context = build
.into_build_context()
.strip(strip)
.editable(false)
.pyproject_toml_path(sdist_pyproject_path)
.build()?;
let wheels = build_context.build_wheels()?;
assert!(!wheels.is_empty());
Expand All @@ -461,25 +465,29 @@ fn run() -> Result<()> {
// Keep tempdir alive for the duration of the build
let _sdist_tmp;
let mut sdist_path = None;
let sdist_pyproject_path;
if !no_sdist {
// Build sdist first, then build wheels from the unpacked sdist
let path = build_sdist(&build, Some(!no_strip))?;
let (tmp, cargo_toml) = unpack_sdist(&path)?;
let (tmp, cargo_toml, pyproject_toml) = unpack_sdist(&path)?;
_sdist_tmp = Some(tmp);
eprintln!(
"📦 Building wheels from source distribution at {}",
cargo_toml.parent().unwrap().display()
);
build.cargo.manifest_path = Some(cargo_toml);
sdist_pyproject_path = Some(pyproject_toml);
sdist_path = Some(path);
} else {
_sdist_tmp = None;
sdist_pyproject_path = None;
}

let mut build_context = build
.into_build_context()
.strip(Some(!no_strip))
.editable(false)
.pyproject_toml_path(sdist_pyproject_path)
.build()?;

// ensure profile always set when publishing
Expand Down
24 changes: 22 additions & 2 deletions src/project_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,29 @@ impl ProjectResolver {
cargo_manifest_path: Option<PathBuf>,
mut cargo_options: CargoOptions,
editable_install: bool,
pyproject_toml_path: Option<PathBuf>,
) -> Result<Self> {
let (manifest_file, pyproject_file) =
Self::resolve_manifest_paths(cargo_manifest_path, &cargo_options)?;
let (manifest_file, pyproject_file) = if let Some(pyproject_path) = pyproject_toml_path {
// When an explicit pyproject.toml path is provided (e.g. from an
// unpacked sdist), use it directly instead of discovering it by
// walking up from the manifest. This is needed when the Cargo
// crate is excluded from the workspace and the pyproject.toml
// lives outside the cargo workspace boundary.
let cargo_toml = cargo_manifest_path
.expect("manifest_path must be set when pyproject_toml_path is provided");
let cargo_toml = cargo_toml
.normalize()
.with_context(|| {
format!(
"manifest path `{}` does not exist or is invalid",
cargo_toml.display()
)
})?
.into_path_buf();
(cargo_toml, pyproject_path)
} else {
Self::resolve_manifest_paths(cargo_manifest_path, &cargo_options)?
};
if !manifest_file.is_file() {
bail!(
"{} is not the path to a Cargo.toml",
Expand Down
3 changes: 2 additions & 1 deletion tests/common/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ pub fn test_build_wheels_from_sdist(package: impl AsRef<Path>, unique_name: &str
.context("Failed to build source distribution")?;

// Step 2: Unpack sdist and build wheels from it
let (_tmp, cargo_toml) = unpack_sdist(&sdist_path)?;
let (_tmp, cargo_toml, pyproject_toml) = unpack_sdist(&sdist_path)?;
let wheel_options = BuildOptions {
out: Some(wheel_dir),
cargo: CargoOptions {
Expand All @@ -579,6 +579,7 @@ pub fn test_build_wheels_from_sdist(package: impl AsRef<Path>, unique_name: &str
.into_build_context()
.strip(Some(cfg!(feature = "faster-tests")))
.editable(false)
.pyproject_toml_path(Some(pyproject_toml))
.build()?;
let wheels = wheel_context.build_wheels()?;
assert!(
Expand Down
Loading