From bb4e7553db85fee2988382cf7127c9a857af2790 Mon Sep 17 00:00:00 2001 From: messense Date: Tue, 6 Dec 2022 15:30:50 +0800 Subject: [PATCH] Add support for filtering multiple bin targets --- src/build_context.rs | 70 +++++++---------------------- src/build_options.rs | 87 ++++++++++++++++++++++++++++++++++++- src/compile.rs | 12 ++--- src/develop.rs | 8 ++-- src/main.rs | 2 +- tests/common/integration.rs | 2 +- 6 files changed, 112 insertions(+), 69 deletions(-) diff --git a/src/build_context.rs b/src/build_context.rs index b14f6d0d5..c03989c3a 100644 --- a/src/build_context.rs +++ b/src/build_context.rs @@ -1,7 +1,7 @@ use crate::auditwheel::{get_policy_and_libs, patchelf, relpath}; use crate::auditwheel::{PlatformTag, Policy}; use crate::build_options::CargoOptions; -use crate::compile::{warn_missing_py_init, CompileTarget, LIB_CRATE_TYPES}; +use crate::compile::{warn_missing_py_init, CompileTarget}; use crate::module_writer::{ add_data, write_bin, write_bindings_module, write_cffi_module, write_python_part, write_uniffi_module, write_wasm_launcher, WheelWriter, @@ -141,8 +141,8 @@ fn bin_wasi_helper( pub struct BuildContext { /// The platform, i.e. os and pointer width pub target: Target, - /// Whether to use cffi or pyo3/rust-cpython - pub bridge: BridgeModel, + /// List of Cargo targets to compile + pub cargo_targets: Vec, /// Whether this project is pure rust or rust mixed with python pub project_layout: ProjectLayout, /// The path to pyproject.toml. Required for the source distribution @@ -202,7 +202,7 @@ impl BuildContext { fs::create_dir_all(&self.out) .context("Failed to create the target directory for the wheels")?; - let wheels = match &self.bridge { + let wheels = match self.bridge() { BridgeModel::Bin(None) => self.build_bin_wheel(None)?, BridgeModel::Bin(Some(..)) => self.build_bin_wheels(&self.interpreter)?, BridgeModel::Bindings(..) => self.build_binding_wheels(&self.interpreter)?, @@ -250,6 +250,12 @@ impl BuildContext { Ok(wheels) } + /// Bridge model + pub fn bridge(&self) -> &BridgeModel { + // FIXME: currently we only allow multiple bin targets so bridges are all the same + &self.cargo_targets[0].1 + } + /// Builds a source distribution and returns the same metadata as [BuildContext::build_wheels] pub fn build_source_distribution(&self) -> Result> { fs::create_dir_all(&self.out) @@ -303,8 +309,8 @@ impl BuildContext { others.sort(); // only bin bindings allow linking to libpython, extension modules must not - let allow_linking_libpython = self.bridge.is_bin(); - if self.bridge.is_bin() && !musllinux.is_empty() { + let allow_linking_libpython = self.bridge().is_bin(); + if self.bridge().is_bin() && !musllinux.is_empty() { return get_policy_and_libs( artifact, Some(musllinux[0]), @@ -658,52 +664,6 @@ impl BuildContext { Ok(wheels) } - fn cargo_compile_targets(&self) -> Vec { - let root_pkg = self.cargo_metadata.root_package().unwrap(); - let resolved_features = self - .cargo_metadata - .resolve - .as_ref() - .and_then(|resolve| resolve.nodes.iter().find(|node| node.id == root_pkg.id)) - .map(|node| node.features.clone()) - .unwrap_or_default(); - let mut targets: Vec<_> = root_pkg - .targets - .iter() - .filter(|target| match self.bridge { - BridgeModel::Bin(_) => { - let is_bin = target.kind.contains(&"bin".to_string()); - if target.required_features.is_empty() { - is_bin - } else { - // Check all required features are enabled for this bin target - is_bin - && target - .required_features - .iter() - .all(|f| resolved_features.contains(f)) - } - } - _ => target.kind.contains(&"cdylib".to_string()), - }) - .map(|target| (target, &self.bridge)) - .collect(); - if targets.is_empty() && !self.bridge.is_bin() { - // No `crate-type = ["cdylib"]` in `Cargo.toml` - // Let's try compile one of the target with `--crate-type cdylib` - let lib_target = root_pkg.targets.iter().find(|target| { - target - .kind - .iter() - .any(|k| LIB_CRATE_TYPES.contains(&k.as_str())) - }); - if let Some(target) = lib_target { - targets.push((target, &self.bridge)); - } - } - targets - } - /// Runs cargo build, extracts the cdylib from the output and returns the path to it /// /// The module name is used to warn about missing a `PyInit_` function for @@ -713,7 +673,7 @@ impl BuildContext { python_interpreter: Option<&PythonInterpreter>, extension_name: Option<&str>, ) -> Result { - let artifacts = compile(self, python_interpreter, &self.cargo_compile_targets()) + let artifacts = compile(self, python_interpreter, &self.cargo_targets) .context("Failed to build a native library through cargo")?; let error_msg = "Cargo didn't build a cdylib. Did you miss crate-type = [\"cdylib\"] \ in the lib section of your Cargo.toml?"; @@ -873,7 +833,7 @@ impl BuildContext { platform_tags: &[PlatformTag], ext_libs: &[Vec], ) -> Result { - let (tag, tags) = match (&self.bridge, python_interpreter) { + let (tag, tags) = match (self.bridge(), python_interpreter) { (BridgeModel::Bin(None), _) => self .target .get_universal_tags(platform_tags, self.universal2)?, @@ -962,7 +922,7 @@ impl BuildContext { python_interpreter: Option<&PythonInterpreter>, ) -> Result> { let mut wheels = Vec::new(); - let artifacts = compile(self, python_interpreter, &self.cargo_compile_targets()) + let artifacts = compile(self, python_interpreter, &self.cargo_targets) .context("Failed to build a native library through cargo")?; if artifacts.is_empty() { bail!("Cargo didn't build a binary") diff --git a/src/build_options.rs b/src/build_options.rs index 9377c75ba..23b7ee9e8 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -1,5 +1,6 @@ use crate::auditwheel::PlatformTag; use crate::build_context::BridgeModel; +use crate::compile::{CompileTarget, LIB_CRATE_TYPES}; use crate::cross_compile::{find_sysconfigdata, parse_sysconfigdata}; use crate::project_layout::ProjectResolver; use crate::pyproject_toml::ToolMaturin; @@ -664,11 +665,15 @@ 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 remaining_core_metadata = cargo_toml.remaining_core_metadata(); + let config_targets = remaining_core_metadata.targets.as_deref(); + let cargo_targets = filter_cargo_targets(&cargo_metadata, bridge, config_targets)?; + + let crate_name = cargo_toml.package.name; Ok(BuildContext { target, - bridge, + cargo_targets, project_layout, pyproject_toml_path, pyproject_toml, @@ -741,6 +746,84 @@ fn validate_bridge_type( Ok(()) } +fn filter_cargo_targets( + cargo_metadata: &Metadata, + bridge: BridgeModel, + config_targets: Option<&[crate::cargo_toml::CargoTarget]>, +) -> Result> { + let root_pkg = cargo_metadata.root_package().unwrap(); + let resolved_features = cargo_metadata + .resolve + .as_ref() + .and_then(|resolve| resolve.nodes.iter().find(|node| node.id == root_pkg.id)) + .map(|node| node.features.clone()) + .unwrap_or_default(); + let mut targets: Vec<_> = root_pkg + .targets + .iter() + .filter(|target| match bridge { + BridgeModel::Bin(_) => { + let is_bin = target.kind.contains(&"bin".to_string()); + if target.required_features.is_empty() { + is_bin + } else { + // Check all required features are enabled for this bin target + is_bin + && target + .required_features + .iter() + .all(|f| resolved_features.contains(f)) + } + } + _ => target.kind.contains(&"cdylib".to_string()), + }) + .map(|target| (target.clone(), bridge.clone())) + .collect(); + if targets.is_empty() && !bridge.is_bin() { + // No `crate-type = ["cdylib"]` in `Cargo.toml` + // Let's try compile one of the target with `--crate-type cdylib` + let lib_target = root_pkg.targets.iter().find(|target| { + target + .kind + .iter() + .any(|k| LIB_CRATE_TYPES.contains(&k.as_str())) + }); + if let Some(target) = lib_target { + targets.push((target.clone(), bridge)); + } + } + + // Filter targets by config_targets + if let Some(config_targets) = config_targets { + targets.retain(|(target, _)| { + config_targets.iter().any(|config_target| { + let name_eq = config_target.name == target.name; + match &config_target.kind { + Some(kind) => name_eq && target.kind.contains(kind), + None => name_eq, + } + }) + }); + if targets.is_empty() { + bail!( + "No Cargo targets matched by `package.metadata.maturin.targets`, please check your `Cargo.toml`" + ); + } else { + let target_names = targets + .iter() + .map(|(target, _)| target.name.as_str()) + .collect::>(); + eprintln!( + "🎯 Found {} Cargo targets in `Cargo.toml`: {}", + targets.len(), + target_names.join(", ") + ); + } + } + + Ok(targets) +} + /// Uses very simple PEP 440 subset parsing to determine the /// minimum supported python minor version for interpreter search fn get_min_python_minor(metadata21: &Metadata21) -> Option { diff --git a/src/compile.rs b/src/compile.rs index 882b3e125..fb25badc5 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -21,7 +21,7 @@ const PYO3_ABI3_NO_PYTHON_VERSION: (u64, u64, u64) = (0, 16, 4); /// crate types excluding `bin`, `cdylib` and `proc-macro` pub(crate) const LIB_CRATE_TYPES: [&str; 4] = ["lib", "dylib", "rlib", "staticlib"]; -pub(crate) type CompileTarget<'a> = (&'a cargo_metadata::Target, &'a BridgeModel); +pub(crate) type CompileTarget = (cargo_metadata::Target, BridgeModel); /// A cargo build artifact #[derive(Debug, Clone)] @@ -39,7 +39,7 @@ pub struct BuildArtifact { pub fn compile<'a>( context: &'a BuildContext, python_interpreter: Option<&PythonInterpreter>, - targets: &[CompileTarget<'a>], + targets: &[CompileTarget], ) -> Result>> { if context.target.is_macos() && context.universal2 { compile_universal2(context, python_interpreter, targets) @@ -52,7 +52,7 @@ pub fn compile<'a>( fn compile_universal2<'a>( context: &'a BuildContext, python_interpreter: Option<&PythonInterpreter>, - targets: &[CompileTarget<'a>], + targets: &[CompileTarget], ) -> Result>> { let mut aarch64_context = context.clone(); aarch64_context.target = Target::from_target_triple(Some("aarch64-apple-darwin".to_string()))?; @@ -126,10 +126,10 @@ fn compile_universal2<'a>( Ok(universal_artifacts) } -fn compile_targets<'a>( - context: &'a BuildContext, +fn compile_targets( + context: &BuildContext, python_interpreter: Option<&PythonInterpreter>, - targets: &[CompileTarget<'a>], + targets: &[CompileTarget], ) -> Result>> { let mut artifacts = Vec::with_capacity(targets.len()); for (target, bridge_model) in targets { diff --git a/src/develop.rs b/src/develop.rs index 98514792a..7443cdb32 100644 --- a/src/develop.rs +++ b/src/develop.rs @@ -68,10 +68,10 @@ pub fn develop( let build_context = build_options.into_build_context(release, strip, true)?; - let interpreter = PythonInterpreter::check_executable(&python, &target, &build_context.bridge)? - .ok_or_else(|| { - anyhow!("Expected `python` to be a python interpreter inside a virtualenv ಠ_ಠ") - })?; + let interpreter = + PythonInterpreter::check_executable(&python, &target, build_context.bridge())?.ok_or_else( + || anyhow!("Expected `python` to be a python interpreter inside a virtualenv ಠ_ಠ"), + )?; // Install dependencies if !build_context.metadata21.requires_dist.is_empty() { diff --git a/src/main.rs b/src/main.rs index 3f4b378a5..c4c6e18f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -221,7 +221,7 @@ fn pep517(subcommand: Pep517Command) -> Result<()> { let context = build_options.into_build_context(true, strip, false)?; // Since afaik all other PEP 517 backends also return linux tagged wheels, we do so too - let tags = match context.bridge { + let tags = match context.bridge() { BridgeModel::Bindings(..) | BridgeModel::Bin(Some(..)) => { vec![context.interpreter[0].get_tag( &context.target, diff --git a/tests/common/integration.rs b/tests/common/integration.rs index e4355d01d..c4643e28d 100644 --- a/tests/common/integration.rs +++ b/tests/common/integration.rs @@ -76,7 +76,7 @@ pub fn test_integration( let venv_interpreter = PythonInterpreter::check_executable( python_interp.as_deref().unwrap_or("python3"), &build_context.target, - &build_context.bridge, + build_context.bridge(), ) .context(error_message)? .context(error_message)?;