Skip to content

Commit

Permalink
Add support for building bin bindings wheels with multiple platform tags
Browse files Browse the repository at this point in the history
  • Loading branch information
messense committed May 19, 2022
1 parent 922459f commit 911927e
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 65 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

* Add support for building bin bindings wheels with multiple platform tags in [#928](https://github.com/PyO3/maturin/pull/928)

## [0.12.17] - 2022-05-18

* Don't consider compile to i686 on x86_64 Windows cross compiling in [#923](https://github.com/PyO3/maturin/pull/923)
Expand Down
12 changes: 11 additions & 1 deletion src/auditwheel/platform_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fmt;
use std::str::FromStr;

/// Decides how to handle manylinux and musllinux compliance
#[derive(Serialize, Debug, Clone, Eq, PartialEq, Copy)]
#[derive(Serialize, Debug, Clone, Eq, PartialEq, Copy, Ord, PartialOrd)]
pub enum PlatformTag {
/// Use the manylinux_x_y tag
Manylinux {
Expand Down Expand Up @@ -61,6 +61,16 @@ impl PlatformTag {
pub fn is_portable(&self) -> bool {
!matches!(self, PlatformTag::Linux)
}

/// Is this a manylinux platform tag
pub fn is_manylinux(&self) -> bool {
matches!(self, PlatformTag::Manylinux { .. })
}

/// Is this a musllinux platform tag
pub fn is_musllinux(&self) -> bool {
matches!(self, PlatformTag::Musllinux { .. })
}
}

impl fmt::Display for PlatformTag {
Expand Down
90 changes: 69 additions & 21 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use lddtree::Library;
use sha2::{Digest, Sha256};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::io;
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -51,6 +52,17 @@ impl BridgeModel {
}
}

impl Display for BridgeModel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
BridgeModel::Cffi => write!(f, "cffi"),
BridgeModel::Bin => write!(f, "bin"),
BridgeModel::Bindings(name, _) => write!(f, "{}", name),
BridgeModel::BindingsAbi3(..) => write!(f, "pyo3"),
}
}
}

/// Whether this project is pure rust or rust mixed with python and whether it has wheel data
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProjectLayout {
Expand Down Expand Up @@ -171,7 +183,7 @@ pub struct BuildContext {
/// When compiling for manylinux, use zig as linker to ensure glibc version compliance
pub zig: bool,
/// Whether to use the the manylinux/musllinux or use the native linux tag (off)
pub platform_tag: Option<PlatformTag>,
pub platform_tag: Vec<PlatformTag>,
/// Extra arguments that will be passed to cargo as `cargo rustc [...] [arg1] [arg2] --`
pub cargo_extra_args: Vec<String>,
/// Extra arguments that will be passed to rustc as `cargo rustc [...] -- [arg1] [arg2]`
Expand Down Expand Up @@ -264,13 +276,31 @@ impl BuildContext {
fn auditwheel(
&self,
artifact: &Path,
platform_tag: Option<PlatformTag>,
platform_tag: &[PlatformTag],
) -> Result<(Policy, Vec<Library>)> {
if self.skip_auditwheel || self.editable {
return Ok((Policy::default(), Vec::new()));
}

get_policy_and_libs(artifact, platform_tag, &self.target)
let mut musllinux: Vec<_> = platform_tag
.iter()
.filter(|tag| tag.is_musllinux())
.copied()
.collect();
musllinux.sort();
let mut others: Vec<_> = platform_tag
.iter()
.filter(|tag| !tag.is_musllinux())
.copied()
.collect();
others.sort();

if matches!(self.bridge, BridgeModel::Bin) && !musllinux.is_empty() {
return get_policy_and_libs(artifact, Some(musllinux[0]), &self.target);
}

let tag = others.get(0).or_else(|| musllinux.get(0)).copied();
get_policy_and_libs(artifact, tag, &self.target)
}

fn add_external_libs(
Expand Down Expand Up @@ -378,14 +408,14 @@ impl BuildContext {
fn write_binding_wheel_abi3(
&self,
artifact: &Path,
platform_tag: PlatformTag,
platform_tags: &[PlatformTag],
ext_libs: &[Library],
major: u8,
min_minor: u8,
) -> Result<BuiltWheelMetadata> {
let platform = self
.target
.get_platform_tag(platform_tag, self.universal2)?;
.get_platform_tag(platform_tags, self.universal2)?;
let tag = format!("cp{}{}-abi3-{}", major, min_minor, platform);

let mut writer = WheelWriter::new(&tag, &self.out, &self.metadata21, &[tag.clone()])?;
Expand Down Expand Up @@ -424,10 +454,15 @@ impl BuildContext {
python_interpreter,
Some(&self.project_layout.extension_name),
)?;
let (policy, external_libs) = self.auditwheel(&artifact, self.platform_tag)?;
let (policy, external_libs) = self.auditwheel(&artifact, &self.platform_tag)?;
let platform_tags = if self.platform_tag.is_empty() {
vec![policy.platform_tag()]
} else {
self.platform_tag.clone()
};
let (wheel_path, tag) = self.write_binding_wheel_abi3(
&artifact,
policy.platform_tag(),
&platform_tags,
&external_libs,
major,
min_minor,
Expand All @@ -448,10 +483,10 @@ impl BuildContext {
&self,
python_interpreter: &PythonInterpreter,
artifact: &Path,
platform_tag: PlatformTag,
platform_tags: &[PlatformTag],
ext_libs: &[Library],
) -> Result<BuiltWheelMetadata> {
let tag = python_interpreter.get_tag(&self.target, platform_tag, self.universal2)?;
let tag = python_interpreter.get_tag(&self.target, platform_tags, self.universal2)?;

let mut writer = WheelWriter::new(&tag, &self.out, &self.metadata21, &[tag.clone()])?;
self.add_external_libs(&mut writer, artifact, ext_libs)?;
Expand Down Expand Up @@ -493,11 +528,16 @@ impl BuildContext {
Some(python_interpreter),
Some(&self.project_layout.extension_name),
)?;
let (policy, external_libs) = self.auditwheel(&artifact, self.platform_tag)?;
let (policy, external_libs) = self.auditwheel(&artifact, &self.platform_tag)?;
let platform_tags = if self.platform_tag.is_empty() {
vec![policy.platform_tag()]
} else {
self.platform_tag.clone()
};
let (wheel_path, tag) = self.write_binding_wheel(
python_interpreter,
&artifact,
policy.platform_tag(),
&platform_tags,
&external_libs,
)?;
println!(
Expand Down Expand Up @@ -553,12 +593,12 @@ impl BuildContext {
fn write_cffi_wheel(
&self,
artifact: &Path,
platform_tag: PlatformTag,
platform_tags: &[PlatformTag],
ext_libs: &[Library],
) -> Result<BuiltWheelMetadata> {
let (tag, tags) = self
.target
.get_universal_tags(platform_tag, self.universal2)?;
.get_universal_tags(platform_tags, self.universal2)?;

let mut writer = WheelWriter::new(&tag, &self.out, &self.metadata21, &tags)?;
self.add_external_libs(&mut writer, artifact, ext_libs)?;
Expand All @@ -584,9 +624,13 @@ impl BuildContext {
pub fn build_cffi_wheel(&self) -> Result<Vec<BuiltWheelMetadata>> {
let mut wheels = Vec::new();
let artifact = self.compile_cdylib(None, None)?;
let (policy, external_libs) = self.auditwheel(&artifact, self.platform_tag)?;
let (wheel_path, tag) =
self.write_cffi_wheel(&artifact, policy.platform_tag(), &external_libs)?;
let (policy, external_libs) = self.auditwheel(&artifact, &self.platform_tag)?;
let platform_tags = if self.platform_tag.is_empty() {
vec![policy.platform_tag()]
} else {
self.platform_tag.clone()
};
let (wheel_path, tag) = self.write_cffi_wheel(&artifact, &platform_tags, &external_libs)?;

// Warn if cffi isn't specified in the requirements
if !self
Expand All @@ -610,12 +654,12 @@ impl BuildContext {
fn write_bin_wheel(
&self,
artifact: &Path,
platform_tag: PlatformTag,
platform_tags: &[PlatformTag],
ext_libs: &[Library],
) -> Result<BuiltWheelMetadata> {
let (tag, tags) = self
.target
.get_universal_tags(platform_tag, self.universal2)?;
.get_universal_tags(platform_tags, self.universal2)?;

if !self.metadata21.scripts.is_empty() {
bail!("Defining entrypoints and working with a binary doesn't mix well");
Expand Down Expand Up @@ -658,10 +702,14 @@ impl BuildContext {
.cloned()
.ok_or_else(|| anyhow!("Cargo didn't build a binary"))?;

let (policy, external_libs) = self.auditwheel(&artifact, self.platform_tag)?;
let (policy, external_libs) = self.auditwheel(&artifact, &self.platform_tag)?;
let platform_tags = if self.platform_tag.is_empty() {
vec![policy.platform_tag()]
} else {
self.platform_tag.clone()
};

let (wheel_path, tag) =
self.write_bin_wheel(&artifact, policy.platform_tag(), &external_libs)?;
let (wheel_path, tag) = self.write_bin_wheel(&artifact, &platform_tags, &external_libs)?;
println!("📦 Built wheel to {}", wheel_path.display());
wheels.push((wheel_path, tag));

Expand Down
104 changes: 80 additions & 24 deletions src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ pub struct BuildOptions {
name = "compatibility",
long = "compatibility",
alias = "manylinux",
parse(try_from_str)
parse(try_from_str),
multiple_values = true,
multiple_occurrences = true
)]
pub platform_tag: Option<PlatformTag>,
pub platform_tag: Vec<PlatformTag>,

/// The python versions to build wheels for, given as the names of the
/// interpreters. Uses autodiscovery if not explicitly set.
Expand Down Expand Up @@ -328,37 +330,91 @@ 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 platform_tag = self
.platform_tag
.or_else(|| {
pyproject.and_then(|x| {
let platform_tags = if self.platform_tag.is_empty() {
let compatibility = pyproject
.and_then(|x| {
if x.compatibility().is_some() {
args_from_pyproject.push("compatibility");
}
x.compatibility()
})
})
.or(if self.zig {
if target.is_musl_target() {
// Zig bundles musl 1.2
Some(PlatformTag::Musllinux { x: 1, y: 2 })
.or(if self.zig {
if target.is_musl_target() {
// Zig bundles musl 1.2
Some(PlatformTag::Musllinux { x: 1, y: 2 })
} else {
// With zig we can compile to any glibc version that we want, so we pick the lowest
// one supported by the rust compiler
Some(target.get_minimum_manylinux_tag())
}
} else {
// With zig we can compile to any glibc version that we want, so we pick the lowest
// one supported by the rust compiler
Some(target.get_minimum_manylinux_tag())
}
// Defaults to musllinux_1_2 for musl target if it's not bin bindings
if target.is_musl_target() && !matches!(bridge, BridgeModel::Bin) {
Some(PlatformTag::Musllinux { x: 1, y: 2 })
} else {
None
}
});
if let Some(platform_tag) = compatibility {
vec![platform_tag]
} else {
// Defaults to musllinux_1_2 for musl target if it's not bin bindings
if target.is_musl_target() && !matches!(bridge, BridgeModel::Bin) {
Some(PlatformTag::Musllinux { x: 1, y: 2 })
} else {
None
}
});
if platform_tag == Some(PlatformTag::manylinux1()) {
Vec::new()
}
} else {
self.platform_tag
};
if platform_tags
.iter()
.any(|tag| tag == &PlatformTag::manylinux1())
{
eprintln!("⚠️ Warning: manylinux1 is unsupported by the Rust compiler.");
}

match bridge {
BridgeModel::Bin => {
// Only support two different kind of platform tags when compiling to musl target
if platform_tags.iter().any(|tag| tag.is_musllinux()) && !target.is_musl_target() {
bail!(
"Cannot mix musllinux and manylinux platform tags when compiling to {}",
target.target_triple()
);
}

#[allow(clippy::comparison_chain)]
if platform_tags.len() > 2 {
bail!(
"Expected only one or two platform tags but found {}",
platform_tags.len()
);
} else if platform_tags.len() == 2 {
// The two platform tags can't be the same kind
let tag_types = platform_tags
.iter()
.map(|tag| tag.is_musllinux())
.collect::<HashSet<_>>();
if tag_types.len() == 1 {
bail!(
"Expected only one platform tag but found {}",
platform_tags.len()
);
}
}
}
_ => {
if platform_tags.len() > 1 {
bail!(
"Expected only one platform tag but found {}",
platform_tags.len()
);
}
}
}

// linux tag can not be mixed with manylinux and musllinux tags
if platform_tags.len() > 1 && platform_tags.iter().any(|tag| !tag.is_portable()) {
bail!("Cannot mix linux and manylinux/musllinux platform tags",);
}

if !args_from_pyproject.is_empty() {
eprintln!(
"📡 Using build options {} from pyproject.toml",
Expand Down Expand Up @@ -387,7 +443,7 @@ impl BuildOptions {
strip,
skip_auditwheel,
zig: self.zig,
platform_tag,
platform_tag: platform_tags,
cargo_extra_args,
rustc_extra_args,
interpreter,
Expand Down
4 changes: 2 additions & 2 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ fn compile_target(
build.target = vec![target_triple.to_string()];
}
} else {
let zig_triple = if target.is_linux() {
match context.platform_tag {
let zig_triple = if target.is_linux() && !target.is_musl_target() {
match context.platform_tag.iter().find(|tag| tag.is_manylinux()) {
Some(PlatformTag::Manylinux { x, y }) => {
format!("{}.{}.{}", target_triple, x, y)
}
Expand Down
2 changes: 1 addition & 1 deletion src/develop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub fn develop(
let wheel_dir = TempDir::new().context("Failed to create temporary directory")?;

let build_options = BuildOptions {
platform_tag: Some(PlatformTag::Linux),
platform_tag: vec![PlatformTag::Linux],
interpreter: vec![python.clone()],
bindings,
manifest_path: Some(manifest_file.to_path_buf()),
Expand Down
Loading

0 comments on commit 911927e

Please sign in to comment.