Skip to content

Commit

Permalink
Better cargo arguments handling
Browse files Browse the repository at this point in the history
  • Loading branch information
konstin committed Aug 21, 2018
1 parent 84c4131 commit a20dd81
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 134 deletions.
6 changes: 0 additions & 6 deletions .cargo/config

This file was deleted.

4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ toml = "0.4.6"
zip = "0.4.2"
serde = "1.0.71"
elfkit = { version = "0.0.6", optional = true }
# crates.io doesn't like git dependencies, but it's not that bad because that feature is blocked on pip anyway (see #2)
#capybara = { git = "https://github.com/konstin/capybara", rev = "6d8052b8d4dd06de02de05e65e1a40fec8489b2e", features = ["python"], optional = true }
tar = { version = "0.4.16", optional = true }
libflate = { version = "0.1.16", optional = true }
human-panic = "1.0.0"
Expand All @@ -54,7 +52,7 @@ indoc = "0.2.6"
default = ["auditwheel"]
auditwheel = ["elfkit"]
# sdist can be written, but those can not be installed
sdist = ["tar", "libflate"] # capybara
sdist = ["tar", "libflate"]

[workspace]
members = [
Expand Down
16 changes: 10 additions & 6 deletions src/auditwheel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::fs::File;
use std::io;
use std::path::Path;

// As specified in "PEP 513 -- A Platform Tag for Portable Linux Built Distributions"
// As specified in "PEP 513 -- A Platform Tag for Portable Linux Built
// Distributions"
const MANYLINUX1: &[&str] = &[
"libpanelw.so.5",
"libncursesw.so.5",
Expand Down Expand Up @@ -66,15 +67,17 @@ pub enum AuditWheelError {
/// Reexports elfkit parsing erorrs
#[fail(display = "Elfkit failed to parse the elf file: {:?}", _0)]
ElfkitError(elfkit::Error),
/// The elf file isn't manylinux compatible. Contains the list of offending libraries.
/// The elf file isn't manylinux compatible. Contains the list of offending
/// libraries.
#[fail(
display = "Your library is not manylinux compliant because it links the following forbidden libraries: {:?}",
_0
)]
ManylinuxValidationError(Vec<String>),
}

/// Similar to the `ldd` command: Returns all libraries that an elf dynamically links to
/// Similar to the `ldd` command: Returns all libraries that an elf dynamically
/// links to
fn get_deps_from_elf(path: &Path) -> Result<Vec<String>, AuditWheelError> {
let mut file = File::open(path).map_err(AuditWheelError::IOError)?;
let mut elf = elfkit::Elf::from_reader(&mut file).map_err(AuditWheelError::ElfkitError)?;
Expand All @@ -97,16 +100,17 @@ fn get_deps_from_elf(path: &Path) -> Result<Vec<String>, AuditWheelError> {
Ok(deps)
}

/// An (incomplete) reimplementation of auditwheel, which checks elf files for manylinux compliance
/// An (incomplete) reimplementation of auditwheel, which checks elf files for
/// manylinux compliance
///
/// Only checks for the libraries marked as NEEDED.
pub fn auditwheel_rs(path: &Path) -> Result<(), AuditWheelError> {
let deps = get_deps_from_elf(&path)?;

let mut offenders = Vec::new();
for dep in deps {
// I'm not 100% what exactely this line does, but auditwheel also seems to skip everything
// with ld-linux in its name
// I'm not 100% what exactely this line does, but auditwheel also seems to skip
// everything with ld-linux in its name
if dep == "ld-linux-x86-64.so.2" {
continue;
}
Expand Down
34 changes: 22 additions & 12 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ use PythonInterpreter;
/// The successful return type of [build_wheels]
pub type Wheels = (Vec<(PathBuf, Option<PythonInterpreter>)>, WheelMetadata);

/// High level API for building wheels from a crate which can be also used for the CLI
/// High level API for building wheels from a crate which can be also used for
/// the CLI
#[derive(Debug, Serialize, Deserialize, StructOpt, Clone, Eq, PartialEq)]
#[serde(default)]
pub struct BuildContext {
#[structopt(short = "i", long = "interpreter")]
/// The python versions to build wheels for, given as the names of the interpreters.
/// Uses a built-in list if not explicitly set.
/// The python versions to build wheels for, given as the names of the
/// interpreters. Uses a built-in list if not explicitly set.
pub interpreter: Vec<String>,
/// The crate providing the python bindings
#[structopt(short = "b", long = "bindings-crate", default_value = "pyo3")]
Expand All @@ -41,8 +42,8 @@ pub struct BuildContext {
)]
/// The path to the Cargo.toml
pub manifest_path: PathBuf,
/// The directory to store the built wheels in. Defaults to a new "wheels" directory in the
/// project's target directory
/// The directory to store the built wheels in. Defaults to a new "wheels"
/// directory in the project's target directory
#[structopt(short = "w", long = "wheel-dir", parse(from_os_str))]
pub wheel_dir: Option<PathBuf>,
/// Don't rebuild if a wheel with the same name is already present
Expand All @@ -54,6 +55,12 @@ pub struct BuildContext {
/// Don't check for manylinux compliance
#[structopt(long = "skip-auditwheel")]
pub skip_auditwheel: bool,
/// Extra arguments that will be passed to cargo as `cargo rustc [...] [arg1] [arg2] --`
#[structopt(long = "cargo-extra-args")]
pub cargo_extra_args: Vec<String>,
/// Extra arguments that will be passed to rustc as `cargo rustc [...] -- [arg1] [arg2]`
#[structopt(long = "rustc-extra-args")]
pub rustc_extra_args: Vec<String>,
}

impl Default for BuildContext {
Expand All @@ -66,6 +73,8 @@ impl Default for BuildContext {
use_cached: false,
debug: false,
skip_auditwheel: false,
cargo_extra_args: Vec::new(),
rustc_extra_args: Vec::new(),
}
}
}
Expand Down Expand Up @@ -96,8 +105,8 @@ impl BuildContext {
_ => HashMap::new(),
};

// If the package name contains minuses, you must declare a module with underscores
// as lib name
// If the package name contains minuses, you must declare a module with
// underscores as lib name
let module_name = cargo_toml
.lib
.name
Expand All @@ -114,12 +123,13 @@ impl BuildContext {
Ok((manifest_file, metadata))
}

/// Builds wheels for a Cargo project for all given python versions. Returns the paths where
/// the wheels are saved and the Python metadata describing the cargo project
/// Builds wheels for a Cargo project for all given python versions.
/// Returns the paths where the wheels are saved and the Python
/// metadata describing the cargo project
///
/// Defaults to 2.7 and 3.{5, 6, 7, 8, 9} if no python versions are given and silently
/// ignores all non-existent python versions. Runs [auditwheel_rs()].if the auditwheel feature
/// isn't deactivated
/// Defaults to 2.7 and 3.{5, 6, 7, 8, 9} if no python versions are given
/// and silently ignores all non-existent python versions. Runs
/// [auditwheel_rs()].if the auditwheel feature isn't deactivated
pub fn build_wheels(self) -> Result<Wheels, Error> {
let (manifest_file, metadata) = self.get_wheel_metadata()?;

Expand Down
32 changes: 23 additions & 9 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ struct SerializedBuildPlan {
inputs: Vec<PathBuf>,
}

/// This kind of message is printed by `cargo build --message-format=json --quiet` for a build
/// script
/// This kind of message is printed by `cargo build --message-format=json
/// --quiet` for a build script
///
/// Example with python3.6 on ubuntu 18.04.1:
///
Expand All @@ -32,7 +32,7 @@ struct SerializedBuildPlan {
/// linked_libs: ["python3.6m"],
/// linked_paths: ["native=/usr/lib"],
/// package_id: "pyo3 0.2.5 (path+file:///home/konsti/capybara/pyo3)",
/// reason: "build-script-executed"
/// reason: "build-script-executed",
/// }
/// ```
#[derive(Serialize, Deserialize)]
Expand All @@ -45,8 +45,8 @@ struct CargoBuildOutput {
reason: String,
}

/// This kind of message is printed by `cargo build --message-format=json --quiet` for an artifact
/// such as an .so/.dll
/// This kind of message is printed by `cargo build --message-format=json
/// --quiet` for an artifact such as an .so/.dll
#[derive(Serialize, Deserialize)]
struct CompilerArtifactMessage {
filenames: Vec<PathBuf>,
Expand All @@ -70,8 +70,8 @@ struct CompilerErrorMessageMessage {
rendered: String,
}

/// Queries the number of tasks through the build plan. This only works on nightly, but that isn't
/// a problem, since pyo3 also only works on nightly
/// Queries the number of tasks through the build plan. This only works on
/// nightly, but that isn't a problem, since pyo3 also only works on nightly
fn get_tasks(shared_args: &[&str]) -> Result<usize, Error> {
let build_plan = Command::new("cargo")
// Eventually we want to get rid of the nightly, but for now it's required because
Expand All @@ -92,7 +92,8 @@ fn get_tasks(shared_args: &[&str]) -> Result<usize, Error> {
Ok(tasks)
}

/// Builds the rust crate into a native module (i.e. an .so or .dll) for a specific python version
/// Builds the rust crate into a native module (i.e. an .so or .dll) for a
/// specific python version
///
/// Shows a progress bar on a tty
pub fn compile(
Expand All @@ -119,6 +120,8 @@ pub fn compile(
&python_version_feature,
];

shared_args.extend(context.cargo_extra_args.iter().map(|x| x.as_str()));

if atty::is(Stream::Stderr) {
// Makes cargo only print to stderr on error
shared_args.push("--quiet");
Expand All @@ -130,9 +133,20 @@ pub fn compile(

let tasks = get_tasks(&shared_args)?;

let mut rustc_args = context.rustc_extra_args.clone();
if python_interpreter.target == "macos" {
rustc_args.extend(
["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
.iter()
.map(ToString::to_string),
);
}

let mut cargo_build = Command::new("cargo")
.args(&["+nightly", "build", "--message-format", "json"])
.args(&["+nightly", "rustc", "--message-format", "json"])
.args(&shared_args)
.arg("--")
.args(rustc_args)
.env("PYTHON_SYS_EXECUTABLE", &python_interpreter.executable)
.stdout(Stdio::piped()) // We need to capture the json messages
.stderr(Stdio::inherit()) // We want to show error messages
Expand Down
47 changes: 16 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
//! Builds wheels from a crate that exposes python bindings through pyo3
//!
//! The high-level api is [BuildContext], which internally calls [build_rust()] and [build_wheel()].
//! The high-level api is [BuildContext], which internally calls [build_rust()]
//! and [build_wheel()].
//!
//! # Cargo features
//!
//! - auditwheel: Reimplements the more important part of the auditwheel package in rust. This
//! feature is enabled by default and means that every wheel is check unless
//! [skip_auditwheel](BuildContext.skip_auditwheel) is set to true.
//! - sdist: Allows creating sdist archives. Those archives can not be installed yet, since
//! (at least in the current 10.0.1) doesn't implement PEP 517 and pyo3-pack doesn't implement
//! the build backend api from that PEP. It is therefore disabled by default. It also currently
//! requires nightly as it uses pyo3 for bindings and setting the crate type for lib to rlib and
//! cdylib.
//! - auditwheel: Reimplements the more important part of the auditwheel
//! package in rust. This feature is enabled by default and means that every
//! wheel is check unless [skip_auditwheel](BuildContext.skip_auditwheel) is
//! set to true.
//!
//! - sdist: Allows creating sdist archives. Those archives can not be
//! installed yet, since (at least in the current 10.0.1) doesn't implement
//! PEP 517 and pyo3-pack doesn't implement the build backend api from that
//! PEP. It is therefore disabled by default. It also currently requires
//! nightly as it uses pyo3 for bindings and setting the crate type for lib to
//! rlib and cdylib.

#![cfg_attr(
feature = "sdist",
feature(proc_macro, spezialization, concat_idents)
)]
#![deny(missing_docs)]
extern crate base64;
extern crate cargo_metadata;
Expand All @@ -35,17 +35,14 @@ extern crate serde_json;
extern crate sha2;
#[macro_use]
extern crate structopt;
extern crate target_info;
extern crate toml;
extern crate zip;
#[cfg(feature = "sdist")]
#[macro_use]
extern crate capybara;
extern crate indicatif;
#[cfg(feature = "sdist")]
extern crate libflate;
#[cfg(feature = "sdist")]
extern crate tar;
extern crate target_info;
extern crate toml;
extern crate zip;

#[cfg(feature = "auditwheel")]
pub use auditwheel::{auditwheel_rs, AuditWheelError};
Expand Down Expand Up @@ -76,17 +73,5 @@ mod sdist;
mod upload;
mod wheel;

#[cfg(feature = "sdist")]
#[capybara]
/// This function is meant to build wheels form source distribtion once pip is ready
pub fn install_sdist() -> () {
let pyproject_toml =
std::fs::read_to_string("pyproject.toml").expect("Couldn't read pyproject.toml");
let _pyproject: sdist::Pyproject =
toml::from_str(&pyproject_toml).expect("The pyproject.toml has an invalid format");

println!("Hello World from install sdist");
}

#[cfg(feature = "sdist")]
capybara_init! {pyo3_pack, [], [install_sdist]}
14 changes: 8 additions & 6 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ pub struct WheelMetadata {
pub metadata21: Metadata21,
/// The `[console_scripts]` for the entry_points.txt
pub scripts: HashMap<String, String>,
/// The name of the module can be distinct from the package name, mostly because
/// package names normally contain minuses while module names have underscores.
/// The package name is part of metadata21
/// The name of the module can be distinct from the package name, mostly
/// because package names normally contain minuses while module names
/// have underscores. The package name is part of metadata21
pub module_name: String,
}

Expand Down Expand Up @@ -113,8 +113,9 @@ impl Metadata21 {
})
}

/// Formats the metadata into a list of key with multiple values have mutliple singel-valued
/// pairs. This format is needed for the pypi uploader and for the metadata file inside wheels
/// Formats the metadata into a list of key with multiple values have
/// mutliple singel-valued pairs. This format is needed for the pypi
/// uploader and for the metadata file inside wheels
pub fn to_vec(&self) -> Vec<(String, String)> {
let mut fields = vec![
("Metadata-Version", self.metadata_version.clone()),
Expand Down Expand Up @@ -203,7 +204,8 @@ impl Metadata21 {
out
}

/// Returns the distribution name according to PEP 427, Section "Escaping and Unicode"
/// Returns the distribution name according to PEP 427, Section "Escaping
/// and Unicode"
pub fn get_distribution_escaped(&self) -> String {
let re = Regex::new(r"[^\w\d.]+").unwrap();
re.replace_all(&self.name, "_").to_string()
Expand Down
Loading

0 comments on commit a20dd81

Please sign in to comment.