Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for building bin bindings wheels with multiple platform tags #928

Merged
merged 2 commits into from
May 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,7 @@ jobs:
- name: Build wheel (with sdist)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: |
# manylinux
cargo run -- build --release -b bin -o dist --target ${{ matrix.target }} --cargo-extra-args="--features password-storage"
# musllinux
cargo run -- build --release -b bin -o dist --target ${{ matrix.target }} --cargo-extra-args="--features password-storage" --no-sdist --compatibility musllinux_1_1
cargo run -- build --release -b bin -o dist --target ${{ matrix.target }} --cargo-extra-args="--features password-storage" --no-sdist --compatibility manylinux2010 musllinux_1_1

# ring doesn't support aarch64 windows yet
- name: Build wheel (windows aarch64)
Expand Down Expand Up @@ -184,7 +181,7 @@ jobs:
# musllinux
maturin build --release -b bin -o dist --no-sdist \
--target ${{ matrix.platform.target }} \
--compatibility musllinux_1_1 \
--compatibility musllinux_1_1 \
--cargo-extra-args="--features password-storage"
- name: Archive binary
run: tar czvf target/release/maturin-${{ matrix.platform.target }}.tar.gz -C target/${{ matrix.platform.target }}/release maturin
Expand Down
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can lift this limitation if we want to in the future.

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
Loading