diff --git a/src/build_options.rs b/src/build_options.rs index 6df3dacb..ca06cc99 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -487,12 +487,59 @@ impl BuildOptions { /// Tries to fill the missing metadata for a BuildContext by querying cargo and python #[instrument(skip_all)] - pub fn into_build_context( - self, - release: bool, - strip: bool, - editable: bool, - ) -> Result { + pub fn into_build_context(self) -> BuildContextBuilder { + BuildContextBuilder::new(self) + } +} + +#[derive(Debug)] +pub struct BuildContextBuilder { + build_options: BuildOptions, + release: bool, + strip: bool, + editable: bool, + sdist_only: bool, +} + +impl BuildContextBuilder { + fn new(build_options: BuildOptions) -> Self { + Self { + build_options, + release: false, + strip: false, + editable: false, + sdist_only: false, + } + } + + pub fn release(mut self, release: bool) -> Self { + self.release = release; + self + } + + pub fn strip(mut self, strip: bool) -> Self { + self.strip = strip; + self + } + + pub fn editable(mut self, editable: bool) -> Self { + self.editable = editable; + self + } + + pub fn sdist_only(mut self, sdist_only: bool) -> Self { + self.sdist_only = sdist_only; + self + } + + pub fn build(self) -> Result { + let Self { + build_options, + release, + strip, + editable, + sdist_only, + } = self; let ProjectResolver { project_layout, cargo_toml_path, @@ -504,12 +551,15 @@ impl BuildOptions { mut cargo_options, cargo_metadata, mut pyproject_toml_maturin_options, - } = ProjectResolver::resolve(self.manifest_path.clone(), self.cargo.clone())?; + } = ProjectResolver::resolve( + build_options.manifest_path.clone(), + build_options.cargo.clone(), + )?; let pyproject = pyproject_toml.as_ref(); let bridge = find_bridge( &cargo_metadata, - self.bindings.as_deref().or_else(|| { + build_options.bindings.as_deref().or_else(|| { pyproject.and_then(|x| { if x.bindings().is_some() { pyproject_toml_maturin_options.push("bindings"); @@ -527,7 +577,7 @@ impl BuildOptions { ); } - let mut target_triple = self.target.clone(); + let mut target_triple = build_options.target.clone(); let mut universal2 = target_triple.as_deref() == Some("universal2-apple-darwin"); // Also try to determine universal2 from ARCHFLAGS environment variable @@ -560,7 +610,7 @@ impl BuildOptions { let mut target = Target::from_target_triple(target_triple)?; if !target.user_specified && !universal2 { - if let Some(interpreter) = self.interpreter.first() { + if let Some(interpreter) = build_options.interpreter.first() { if let Some(detected_target) = crate::target::detect_arch_from_python(interpreter, &target) { @@ -569,38 +619,23 @@ impl BuildOptions { } } - let wheel_dir = match self.out { + let wheel_dir = match build_options.out { Some(ref dir) => dir.clone(), None => PathBuf::from(&cargo_metadata.target_directory).join("wheels"), }; let generate_import_lib = is_generating_import_lib(&cargo_metadata)?; - let interpreter = if self.find_interpreter { - // Auto-detect interpreters - self.find_interpreters( + let interpreter = if sdist_only { + // We don't need a python interpreter to build sdist only + Vec::new() + } else { + resolve_interpreters( + &build_options, &bridge, - &[], &target, metadata23.requires_python.as_ref(), generate_import_lib, )? - } else { - // User given list of interpreters - let interpreter = if self.interpreter.is_empty() && !target.cross_compiling() { - if cfg!(test) { - match env::var_os("MATURIN_TEST_PYTHON") { - Some(python) => vec![python.into()], - None => vec![target.get_python()], - } - } else { - vec![target.get_python()] - } - } else { - // XXX: False positive clippy warning - #[allow(clippy::redundant_clone)] - self.interpreter.clone() - }; - self.find_interpreters(&bridge, &interpreter, &target, None, generate_import_lib)? }; if cargo_options.args.is_empty() { @@ -613,9 +648,9 @@ impl BuildOptions { } let strip = pyproject.map(|x| x.strip()).unwrap_or_default() || strip; - let skip_auditwheel = - pyproject.map(|x| x.skip_auditwheel()).unwrap_or_default() || self.skip_auditwheel; - let auditwheel = self + let skip_auditwheel = pyproject.map(|x| x.skip_auditwheel()).unwrap_or_default() + || build_options.skip_auditwheel; + let auditwheel = build_options .auditwheel .or_else(|| pyproject.and_then(|x| x.auditwheel())) .unwrap_or(if skip_auditwheel { @@ -623,9 +658,9 @@ impl BuildOptions { } else { AuditWheelMode::Repair }); - let platform_tags = if self.platform_tag.is_empty() { + let platform_tags = if build_options.platform_tag.is_empty() { #[cfg(feature = "zig")] - let use_zig = self.zig; + let use_zig = build_options.zig; #[cfg(not(feature = "zig"))] let use_zig = false; let compatibility = pyproject @@ -658,7 +693,7 @@ impl BuildOptions { Vec::new() } } else { - self.platform_tag + build_options.platform_tag }; for platform_tag in &platform_tags { @@ -683,7 +718,7 @@ impl BuildOptions { ); } - let target_dir = self + let target_dir = build_options .cargo .target_dir .clone() @@ -713,7 +748,7 @@ impl BuildOptions { strip, auditwheel, #[cfg(feature = "zig")] - zig: self.zig, + zig: build_options.zig, platform_tag: platform_tags, interpreter, cargo_metadata, @@ -724,6 +759,43 @@ impl BuildOptions { } } +fn resolve_interpreters( + build_options: &BuildOptions, + bridge: &BridgeModel, + target: &Target, + requires_python: Option<&VersionSpecifiers>, + generate_import_lib: bool, +) -> Result, anyhow::Error> { + let interpreter = if build_options.find_interpreter { + // Auto-detect interpreters + build_options.find_interpreters( + bridge, + &[], + target, + requires_python, + generate_import_lib, + )? + } else { + // User given list of interpreters + let interpreter = if build_options.interpreter.is_empty() && !target.cross_compiling() { + if cfg!(test) { + match env::var_os("MATURIN_TEST_PYTHON") { + Some(python) => vec![python.into()], + None => vec![target.get_python()], + } + } else { + vec![target.get_python()] + } + } else { + // XXX: False positive clippy warning + #[allow(clippy::redundant_clone)] + build_options.interpreter.clone() + }; + build_options.find_interpreters(bridge, &interpreter, target, None, generate_import_lib)? + }; + Ok(interpreter) +} + /// Checks for bridge/platform type edge cases fn validate_bridge_type( bridge: &BridgeModel, diff --git a/src/develop.rs b/src/develop.rs index 6884d56f..517595d0 100644 --- a/src/develop.rs +++ b/src/develop.rs @@ -332,7 +332,12 @@ pub fn develop(develop_options: DevelopOptions, venv_dir: &Path) -> Result<()> { }, }; - let build_context = build_options.into_build_context(release, strip, true)?; + let build_context = build_options + .into_build_context() + .release(release) + .strip(strip) + .editable(true) + .build()?; let interpreter = PythonInterpreter::check_executable(&python, &target, build_context.bridge())?.ok_or_else( diff --git a/src/main.rs b/src/main.rs index 8836ef55..dbdac3fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -273,7 +273,13 @@ fn pep517(subcommand: Pep517Command) -> Result<()> { strip, } => { assert_eq!(build_options.interpreter.len(), 1); - let context = build_options.into_build_context(true, strip, false)?; + let context = build_options + .into_build_context() + .release(true) + .strip(strip) + .editable(false) + .sdist_only(true) + .build()?; // Since afaik all other PEP 517 backends also return linux tagged wheels, we do so too let tags = match context.bridge() { @@ -298,7 +304,12 @@ fn pep517(subcommand: Pep517Command) -> Result<()> { strip, editable, } => { - let build_context = build_options.into_build_context(true, strip, editable)?; + let build_context = build_options + .into_build_context() + .release(true) + .strip(strip) + .editable(editable) + .build()?; let wheels = build_context.build_wheels()?; assert_eq!(wheels.len(), 1); println!("{}", wheels[0].0.to_str().unwrap()); @@ -318,7 +329,13 @@ fn pep517(subcommand: Pep517Command) -> Result<()> { }, ..Default::default() }; - let build_context = build_options.into_build_context(false, false, false)?; + let build_context = build_options + .into_build_context() + .release(false) + .strip(false) + .editable(false) + .sdist_only(true) + .build()?; let (path, _) = build_context .build_source_distribution()? .context("Failed to build source distribution, pyproject.toml not found")?; @@ -361,7 +378,12 @@ fn run() -> Result<()> { strip, sdist, } => { - let build_context = build.into_build_context(release, strip, false)?; + let build_context = build + .into_build_context() + .release(release) + .strip(strip) + .editable(false) + .build()?; if sdist { build_context .build_source_distribution()? @@ -378,7 +400,12 @@ fn run() -> Result<()> { no_strip, no_sdist, } => { - let build_context = build.into_build_context(!debug, !no_strip, false)?; + let build_context = build + .into_build_context() + .release(!debug) + .strip(!no_strip) + .editable(false) + .build()?; if !build_context.release { eprintln!("⚠️ Warning: You're publishing debug wheels"); @@ -427,7 +454,13 @@ fn run() -> Result<()> { }, ..Default::default() }; - let build_context = build_options.into_build_context(false, false, false)?; + let build_context = build_options + .into_build_context() + .release(false) + .strip(false) + .editable(false) + .sdist_only(true) + .build()?; build_context .build_source_distribution()? .context("Failed to build source distribution, pyproject.toml not found")?; diff --git a/tests/common/errors.rs b/tests/common/errors.rs index 0c9cfabe..98b2760d 100644 --- a/tests/common/errors.rs +++ b/tests/common/errors.rs @@ -19,7 +19,12 @@ pub fn abi3_without_version() -> Result<()> { ]; let options = BuildOptions::try_parse_from(cli)?; - let result = options.into_build_context(false, cfg!(feature = "faster-tests"), false); + let result = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build(); if let Err(err) = result { assert_eq!(err.to_string(), "You have selected the `abi3` feature but not a minimum version (e.g. the `abi3-py36` feature). \ @@ -48,7 +53,11 @@ pub fn pyo3_no_extension_module() -> Result<()> { let options = BuildOptions::try_parse_from(cli)?; let result = options - .into_build_context(false, cfg!(feature = "faster-tests"), false)? + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build()? .build_wheels(); if let Err(err) = result { if !(err @@ -81,7 +90,12 @@ pub fn locked_doesnt_build_without_cargo_lock() -> Result<()> { "test-crates/targets/locked_doesnt_build_without_cargo_lock", ]; let options = BuildOptions::try_parse_from(cli)?; - let result = options.into_build_context(false, cfg!(feature = "faster-tests"), false); + let result = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build(); if let Err(err) = result { let err_string = err .source() @@ -115,7 +129,11 @@ pub fn invalid_manylinux_does_not_panic() -> Result<()> { ]; let options: BuildOptions = BuildOptions::try_parse_from(cli)?; let result = options - .into_build_context(false, cfg!(feature = "faster-tests"), false)? + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build()? .build_wheels(); if let Err(err) = result { assert_eq!(err.to_string(), "Error ensuring manylinux_2_99 compliance"); diff --git a/tests/common/integration.rs b/tests/common/integration.rs index 9ff3803a..de5144db 100644 --- a/tests/common/integration.rs +++ b/tests/common/integration.rs @@ -125,7 +125,12 @@ pub fn test_integration( } let options: BuildOptions = BuildOptions::try_parse_from(cli)?; - let build_context = options.into_build_context(false, cfg!(feature = "faster-tests"), false)?; + let build_context = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build()?; let wheels = build_context.build_wheels()?; // For abi3 on unix, we didn't use a python interpreter, but we need one here @@ -248,7 +253,12 @@ pub fn test_integration_conda(package: impl AsRef, bindings: Option = vec![]; diff --git a/tests/common/other.rs b/tests/common/other.rs index 2ab2136a..4448885d 100644 --- a/tests/common/other.rs +++ b/tests/common/other.rs @@ -68,7 +68,12 @@ pub fn test_musl() -> Result { "test-crates/wheels/test_musl", ])?; - let build_context = options.into_build_context(false, cfg!(feature = "faster-tests"), false)?; + let build_context = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build()?; let built_lib = PathBuf::from("test-crates/targets/test_musl/x86_64-unknown-linux-musl/debug/hello-world"); if built_lib.is_file() { @@ -107,7 +112,12 @@ pub fn test_workspace_cargo_lock() -> Result<()> { "test-crates/wheels/test_workspace_cargo_lock", ])?; - let build_context = options.into_build_context(false, false, false)?; + let build_context = options + .into_build_context() + .release(false) + .strip(false) + .editable(false) + .build()?; let source_distribution = build_context.build_source_distribution()?; assert!(source_distribution.is_some()); @@ -137,7 +147,13 @@ pub fn test_source_distribution( ..Default::default() }; - let mut build_context = build_options.into_build_context(false, false, false)?; + let mut build_context = build_options + .into_build_context() + .release(false) + .strip(false) + .editable(false) + .sdist_only(true) + .build()?; // Override the sdist generator for testing let mut pyproject_toml = build_context.pyproject_toml.take().unwrap(); @@ -207,7 +223,12 @@ fn build_wheel_files(package: impl AsRef, unique_name: &str) -> Result Result<()> { "test-crates/pyo3-pure/Cargo.toml", "--quiet", ])?; - let result = options.into_build_context(false, cfg!(feature = "faster-tests"), false); + let result = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build(); assert!(result.is_ok()); // Case 2: maturin build -i python3.10, should work because python3.10 is in bundled sysconfigs @@ -275,7 +301,12 @@ pub fn abi3_python_interpreter_args() -> Result<()> { "-i", "python3.10", ])?; - let result = options.into_build_context(false, cfg!(feature = "faster-tests"), false); + let result = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build(); assert!(result.is_ok()); // Windows is a bit different so we exclude it from case 3 & 4 @@ -291,7 +322,12 @@ pub fn abi3_python_interpreter_args() -> Result<()> { "-i", "python2.7", ])?; - let result = options.into_build_context(false, cfg!(feature = "faster-tests"), false); + let result = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build(); assert!(result.is_err()); // Case 4: maturin build -i python-does-not-exists, errors because python executable is not found @@ -303,7 +339,12 @@ pub fn abi3_python_interpreter_args() -> Result<()> { "-i", "python-does-not-exists", ])?; - let result = options.into_build_context(false, cfg!(feature = "faster-tests"), false); + let result = options + .into_build_context() + .release(false) + .strip(cfg!(feature = "faster-tests")) + .editable(false) + .build(); assert!(result.is_err()); }