Skip to content

Commit

Permalink
Add support for filtering multiple bin targets
Browse files Browse the repository at this point in the history
  • Loading branch information
messense committed Dec 12, 2022
1 parent 5e10b05 commit bb4e755
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 69 deletions.
70 changes: 15 additions & 55 deletions src/build_context.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<CompileTarget>,
/// 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
Expand Down Expand Up @@ -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)?,
Expand Down Expand Up @@ -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<Option<BuiltWheelMetadata>> {
fs::create_dir_all(&self.out)
Expand Down Expand Up @@ -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]),
Expand Down Expand Up @@ -658,52 +664,6 @@ impl BuildContext {
Ok(wheels)
}

fn cargo_compile_targets(&self) -> Vec<CompileTarget> {
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_<module name>` function for
Expand All @@ -713,7 +673,7 @@ impl BuildContext {
python_interpreter: Option<&PythonInterpreter>,
extension_name: Option<&str>,
) -> Result<BuildArtifact> {
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?";
Expand Down Expand Up @@ -873,7 +833,7 @@ impl BuildContext {
platform_tags: &[PlatformTag],
ext_libs: &[Vec<Library>],
) -> Result<BuiltWheelMetadata> {
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)?,
Expand Down Expand Up @@ -962,7 +922,7 @@ impl BuildContext {
python_interpreter: Option<&PythonInterpreter>,
) -> Result<Vec<BuiltWheelMetadata>> {
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")
Expand Down
87 changes: 85 additions & 2 deletions src/build_options.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<Vec<CompileTarget>> {
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::<Vec<_>>();
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<usize> {
Expand Down
12 changes: 6 additions & 6 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -39,7 +39,7 @@ pub struct BuildArtifact {
pub fn compile<'a>(
context: &'a BuildContext,
python_interpreter: Option<&PythonInterpreter>,
targets: &[CompileTarget<'a>],
targets: &[CompileTarget],
) -> Result<Vec<HashMap<String, BuildArtifact>>> {
if context.target.is_macos() && context.universal2 {
compile_universal2(context, python_interpreter, targets)
Expand All @@ -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<Vec<HashMap<String, BuildArtifact>>> {
let mut aarch64_context = context.clone();
aarch64_context.target = Target::from_target_triple(Some("aarch64-apple-darwin".to_string()))?;
Expand Down Expand Up @@ -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<Vec<HashMap<String, BuildArtifact>>> {
let mut artifacts = Vec::with_capacity(targets.len());
for (target, bridge_model) in targets {
Expand Down
8 changes: 4 additions & 4 deletions src/develop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion tests/common/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down

0 comments on commit bb4e755

Please sign in to comment.