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

Use [tool.maturin] options from pyproject.toml in build command #605

Merged
merged 3 commits into from
Aug 13, 2021
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
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ To include arbitrary files in the sdist for use during compilation specify `sdis
sdist-include = ["path/**/*"]
```

There's a `cargo sdist` command for only building a source distribution as workaround for [pypa/pip#6041](https://github.com/pypa/pip/issues/6041).
There's a `maturin sdist` command for only building a source distribution as workaround for [pypa/pip#6041](https://github.com/pypa/pip/issues/6041).

## Manylinux and auditwheel

Expand Down
32 changes: 1 addition & 31 deletions maturin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,22 @@
import subprocess
import sys
from subprocess import SubprocessError
from typing import List, Dict
from typing import Dict

import toml

# these are only used when creating the sdist, not when building it
create_only_options = [
"sdist-include",
]

available_options = [
"bindings",
"cargo-extra-args",
"compatibility",
"manylinux",
"rustc-extra-args",
"skip-auditwheel",
"strip",
]


def get_config() -> Dict[str, str]:
with open("pyproject.toml", encoding="utf-8") as fp:
pyproject_toml = toml.load(fp)
return pyproject_toml.get("tool", {}).get("maturin", {})


def get_config_options() -> List[str]:
config = get_config()
options = []
for key, value in config.items():
if key in create_only_options:
continue
if key not in available_options:
# attempt to install even if keys from newer or older versions are present
sys.stderr.write(f"WARNING: {key} is not a recognized option for maturin\n")
options.append("--{}={}".format(key, value))
return options


# noinspection PyUnusedLocal
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
# PEP 517 specifies that only `sys.executable` points to the correct
# python interpreter
command = ["maturin", "pep517", "build-wheel", "-i", sys.executable]
command.extend(get_config_options())

print("Running `{}`".format(" ".join(command)))
sys.stdout.flush()
Expand Down Expand Up @@ -140,7 +111,6 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
"--interpreter",
sys.executable,
]
command.extend(get_config_options())

print("Running `{}`".format(" ".join(command)))
try:
Expand Down
68 changes: 60 additions & 8 deletions src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::python_interpreter::InterpreterKind;
use crate::BuildContext;
use crate::CargoToml;
use crate::Metadata21;
use crate::PyProjectToml;
use crate::PythonInterpreter;
use crate::Target;
use anyhow::{bail, format_err, Context, Result};
Expand Down Expand Up @@ -106,9 +107,6 @@ impl Default for BuildOptions {
impl BuildOptions {
/// Tries to fill the missing metadata for a BuildContext by querying cargo and python
pub fn into_build_context(self, release: bool, strip: bool) -> Result<BuildContext> {
if self.platform_tag == Some(PlatformTag::manylinux1()) {
eprintln!("⚠ Warning: manylinux1 is unsupported by the Rust compiler.");
}
let manifest_file = &self.manifest_path;
if !manifest_file.exists() {
let current_dir =
Expand All @@ -130,6 +128,12 @@ impl BuildOptions {

let cargo_toml = CargoToml::from_path(&manifest_file)?;
let manifest_dir = manifest_file.parent().unwrap();
let pyproject: Option<PyProjectToml> = if manifest_dir.join("pyproject.toml").is_file() {
Some(PyProjectToml::new(manifest_dir).context("pyproject.toml is invalid")?)
} else {
None
};
let pyproject = pyproject.as_ref();
let metadata21 = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir)
.context("Failed to parse Cargo.toml into python metadata")?;
let extra_metadata = cargo_toml.remaining_core_metadata();
Expand Down Expand Up @@ -158,7 +162,16 @@ impl BuildOptions {
extra_metadata.python_source.as_deref(),
)?;

let mut cargo_extra_args = split_extra_args(&self.cargo_extra_args)?;
let mut args_from_pyproject = Vec::new();
let mut cargo_extra_args = self.cargo_extra_args.clone();
if cargo_extra_args.is_empty() {
// if not supplied on command line, try pyproject.toml
if let Some(args) = pyproject.and_then(|x| x.cargo_extra_args()) {
cargo_extra_args.push(args.to_string());
args_from_pyproject.push("cargo-extra-args");
}
}
cargo_extra_args = split_extra_args(&cargo_extra_args)?;
if let Some(ref target) = self.target {
cargo_extra_args.extend(vec!["--target".to_string(), target.clone()]);
}
Expand All @@ -183,7 +196,17 @@ impl BuildOptions {
}
};

let bridge = find_bridge(&cargo_metadata, self.bindings.as_deref())?;
let bridge = find_bridge(
&cargo_metadata,
self.bindings.as_deref().or_else(|| {
pyproject.and_then(|x| {
if x.bindings().is_some() {
args_from_pyproject.push("bindings");
}
x.bindings()
})
}),
)?;

if bridge != BridgeModel::Bin && module_name.contains('-') {
bail!(
Expand All @@ -208,7 +231,15 @@ impl BuildOptions {
None => find_interpreter(&bridge, &[], &target, get_min_python_minor(&metadata21))?,
};

let rustc_extra_args = split_extra_args(&self.rustc_extra_args)?;
let mut rustc_extra_args = self.rustc_extra_args.clone();
if rustc_extra_args.is_empty() {
// if not supplied on command line, try pyproject.toml
if let Some(args) = pyproject.and_then(|x| x.rustc_extra_args()) {
rustc_extra_args.push(args.to_string());
args_from_pyproject.push("rustc-extra-args");
}
}
rustc_extra_args = split_extra_args(&rustc_extra_args)?;

let mut universal2 = self.universal2;
// Also try to determine universal2 from ARCHFLAGS environment variable
Expand All @@ -228,6 +259,27 @@ impl BuildOptions {
universal2 = true;
}
};
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| {
if x.compatibility().is_some() {
args_from_pyproject.push("compatibility");
}
x.compatibility()
})
});
if platform_tag == Some(PlatformTag::manylinux1()) {
eprintln!("⚠ Warning: manylinux1 is unsupported by the Rust compiler.");
}

if !args_from_pyproject.is_empty() {
eprintln!(
"📡 Using build options {} from pyproject.toml",
Copy link
Member

Choose a reason for hiding this comment

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

💯 emoji choice

args_from_pyproject.join(", ")
);
}

Ok(BuildContext {
target,
Expand All @@ -240,8 +292,8 @@ impl BuildOptions {
out: wheel_dir,
release,
strip,
skip_auditwheel: self.skip_auditwheel,
platform_tag: self.platform_tag,
skip_auditwheel,
platform_tag,
cargo_extra_args,
rustc_extra_args,
interpreter,
Expand Down
60 changes: 59 additions & 1 deletion src/pyproject_toml.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::PlatformTag;
use anyhow::{format_err, Context, Result};
use pyproject_toml::PyProjectToml as ProjectToml;
use serde::{Deserialize, Serialize};
Expand All @@ -16,6 +17,15 @@ pub struct Tool {
#[serde(rename_all = "kebab-case")]
pub struct ToolMaturin {
sdist_include: Option<Vec<String>>,
bindings: Option<String>,
cargo_extra_args: Option<String>,
#[serde(alias = "manylinux")]
compatibility: Option<PlatformTag>,
rustc_extra_args: Option<String>,
#[serde(default)]
skip_auditwheel: bool,
#[serde(default)]
strip: bool,
}

/// A pyproject.toml as specified in PEP 517
Expand Down Expand Up @@ -57,11 +67,59 @@ impl PyProjectToml {
Ok(pyproject)
}

/// Returns the value of `[maturin.sdist-include]` in pyproject.toml
/// Returns the value of `[tool.maturin.sdist-include]` in pyproject.toml
pub fn sdist_include(&self) -> Option<&Vec<String>> {
self.tool.as_ref()?.maturin.as_ref()?.sdist_include.as_ref()
}

/// Returns the value of `[tool.maturin.bindings]` in pyproject.toml
pub fn bindings(&self) -> Option<&str> {
self.tool.as_ref()?.maturin.as_ref()?.bindings.as_deref()
}

/// Returns the value of `[tool.maturin.cargo-extra-args]` in pyproject.toml
pub fn cargo_extra_args(&self) -> Option<&str> {
self.tool
.as_ref()?
.maturin
.as_ref()?
.cargo_extra_args
.as_deref()
}

/// Returns the value of `[tool.maturin.compatibility]` in pyproject.toml
pub fn compatibility(&self) -> Option<PlatformTag> {
self.tool.as_ref()?.maturin.as_ref()?.compatibility
}

/// Returns the value of `[tool.maturin.rustc-extra-args]` in pyproject.toml
pub fn rustc_extra_args(&self) -> Option<&str> {
self.tool
.as_ref()?
.maturin
.as_ref()?
.rustc_extra_args
.as_deref()
}

/// Returns the value of `[tool.maturin.skip-auditwheel]` in pyproject.toml
pub fn skip_auditwheel(&self) -> bool {
self.tool
.as_ref()
.and_then(|tool| tool.maturin.as_ref())
.map(|maturin| maturin.skip_auditwheel)
.unwrap_or_default()
}

/// Returns the value of `[tool.maturin.strip]` in pyproject.toml
pub fn strip(&self) -> bool {
self.tool
.as_ref()
.and_then(|tool| tool.maturin.as_ref())
.map(|maturin| maturin.strip)
.unwrap_or_default()
}

/// Having a pyproject.toml without a version constraint is a bad idea
/// because at some point we'll have to do breaking changes and then source
/// distributions would break
Expand Down
3 changes: 3 additions & 0 deletions test-crates/pyo3-pure/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
requires = ["maturin>=0.11,<0.12"]
build-backend = "maturin"

[tool.maturin]
bindings = "pyo3"

[project]
name = "pyo3-pure"
classifiers = [
Expand Down