Skip to content

Commit 2c6b8a7

Browse files
authored
Exclude packages not in the dependency tree when finding bindings (#2426)
This fixes #2423. I've added a second (optional) PR that makes `has_abi3` reuse the same datastructure for consistent lookups. :) Thank you for your fantastic work on maturin! 😃 ⭐ 🍰
1 parent b1ccad4 commit 2c6b8a7

File tree

1 file changed

+61
-44
lines changed

1 file changed

+61
-44
lines changed

src/build_options.rs

+61-44
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::pyproject_toml::ToolMaturin;
66
use crate::python_interpreter::{InterpreterConfig, InterpreterKind};
77
use crate::{Bindings, BridgeModel, BuildContext, PythonInterpreter, Target};
88
use anyhow::{bail, format_err, Context, Result};
9-
use cargo_metadata::{CrateType, TargetKind};
9+
use cargo_metadata::{CrateType, PackageId, TargetKind};
1010
use cargo_metadata::{Metadata, Node};
1111
use cargo_options::heading;
1212
use pep440_rs::VersionSpecifiers;
@@ -934,46 +934,34 @@ fn filter_cargo_targets(
934934
}
935935

936936
/// pyo3 supports building abi3 wheels if the unstable-api feature is not selected
937-
fn has_abi3(cargo_metadata: &Metadata) -> Result<Option<(u8, u8)>> {
938-
let resolve = cargo_metadata
939-
.resolve
940-
.as_ref()
941-
.context("Expected cargo to return metadata with resolve")?;
937+
fn has_abi3(deps: &HashMap<&str, &Node>) -> Result<Option<(u8, u8)>> {
942938
for &lib in PYO3_BINDING_CRATES.iter() {
943-
let pyo3_packages = resolve
944-
.nodes
945-
.iter()
946-
.filter(|package| cargo_metadata[&package.id].name.as_str() == lib)
947-
.collect::<Vec<_>>();
948-
match pyo3_packages.as_slice() {
949-
[pyo3_crate] => {
950-
// Find the minimal abi3 python version. If there is none, abi3 hasn't been selected
951-
// This parser abi3-py{major}{minor} and returns the minimal (major, minor) tuple
952-
let abi3_selected = pyo3_crate.features.iter().any(|x| x == "abi3");
939+
if let Some(pyo3_crate) = deps.get(lib) {
940+
// Find the minimal abi3 python version. If there is none, abi3 hasn't been selected
941+
// This parser abi3-py{major}{minor} and returns the minimal (major, minor) tuple
942+
let abi3_selected = pyo3_crate.features.iter().any(|x| x == "abi3");
953943

954-
let min_abi3_version = pyo3_crate
955-
.features
956-
.iter()
957-
.filter(|x| x.starts_with("abi3-py") && x.len() >= "abi3-pyxx".len())
958-
.map(|x| {
959-
Ok((
960-
(x.as_bytes()[7] as char).to_string().parse::<u8>()?,
961-
x[8..].parse::<u8>()?,
962-
))
963-
})
964-
.collect::<Result<Vec<(u8, u8)>>>()
965-
.context(format!("Bogus {lib} cargo features"))?
966-
.into_iter()
967-
.min();
968-
if abi3_selected && min_abi3_version.is_none() {
969-
bail!(
944+
let min_abi3_version = pyo3_crate
945+
.features
946+
.iter()
947+
.filter(|x| x.starts_with("abi3-py") && x.len() >= "abi3-pyxx".len())
948+
.map(|x| {
949+
Ok((
950+
(x.as_bytes()[7] as char).to_string().parse::<u8>()?,
951+
x[8..].parse::<u8>()?,
952+
))
953+
})
954+
.collect::<Result<Vec<(u8, u8)>>>()
955+
.context(format!("Bogus {lib} cargo features"))?
956+
.into_iter()
957+
.min();
958+
if abi3_selected && min_abi3_version.is_none() {
959+
bail!(
970960
"You have selected the `abi3` feature but not a minimum version (e.g. the `abi3-py36` feature). \
971961
maturin needs a minimum version feature to build abi3 wheels."
972962
)
973-
}
974-
return Ok(min_abi3_version);
975963
}
976-
_ => continue,
964+
return Ok(min_abi3_version);
977965
}
978966
}
979967
Ok(None)
@@ -1034,18 +1022,47 @@ fn find_bindings(
10341022
}
10351023
}
10361024

1037-
/// Tries to determine the [BridgeModel] for the target crate
1038-
pub fn find_bridge(cargo_metadata: &Metadata, bridge: Option<&str>) -> Result<BridgeModel> {
1025+
/// Return a map with all (transitive) dependencies of the *current* crate.
1026+
/// This is different from `metadata.resolve`, which also includes packages
1027+
/// that are used in the same workspace, but on which the current crate does not depend.
1028+
fn current_crate_dependencies(cargo_metadata: &Metadata) -> Result<HashMap<&str, &Node>> {
10391029
let resolve = cargo_metadata
10401030
.resolve
10411031
.as_ref()
1042-
.ok_or_else(|| format_err!("Expected to get a dependency graph from cargo"))?;
1032+
.context("Expected to get a dependency graph from cargo")?;
1033+
let root = resolve
1034+
.root
1035+
.as_ref()
1036+
.context("expected to get a root package")?;
1037+
let nodes: HashMap<&PackageId, &Node> =
1038+
resolve.nodes.iter().map(|node| (&node.id, node)).collect();
1039+
1040+
// Walk the dependency tree to get all (in)direct children.
1041+
let mut dep_ids = HashSet::with_capacity(nodes.len());
1042+
let mut todo = Vec::from([root]);
1043+
while let Some(id) = todo.pop() {
1044+
for dep in nodes[id].deps.iter() {
1045+
if dep_ids.contains(&dep.pkg) {
1046+
continue;
1047+
}
1048+
dep_ids.insert(&dep.pkg);
1049+
todo.push(&dep.pkg);
1050+
}
1051+
}
10431052

1044-
let deps: HashMap<&str, &Node> = resolve
1045-
.nodes
1046-
.iter()
1047-
.map(|node| (cargo_metadata[&node.id].name.as_ref(), node))
1048-
.collect();
1053+
Ok(nodes
1054+
.into_iter()
1055+
.filter_map(|(id, node)| {
1056+
dep_ids
1057+
.contains(&id)
1058+
.then_some((cargo_metadata[id].name.as_ref(), node))
1059+
})
1060+
.collect())
1061+
}
1062+
1063+
/// Tries to determine the [BridgeModel] for the target crate
1064+
pub fn find_bridge(cargo_metadata: &Metadata, bridge: Option<&str>) -> Result<BridgeModel> {
1065+
let deps = current_crate_dependencies(cargo_metadata)?;
10491066
let packages: HashMap<&str, &cargo_metadata::Package> = cargo_metadata
10501067
.packages
10511068
.iter()
@@ -1141,7 +1158,7 @@ pub fn find_bridge(cargo_metadata: &Metadata, bridge: Option<&str>) -> Result<Br
11411158
);
11421159
}
11431160

1144-
return if let Some((major, minor)) = has_abi3(cargo_metadata)? {
1161+
return if let Some((major, minor)) = has_abi3(&deps)? {
11451162
eprintln!("🔗 Found {lib} bindings with abi3 support for Python ≥ {major}.{minor}");
11461163
let version = packages[lib].version.clone();
11471164
let bindings = Bindings {

0 commit comments

Comments
 (0)