Skip to content

Commit

Permalink
Support link mode control
Browse files Browse the repository at this point in the history
As part of developing PyOxidizer, I needed to force python3-sys to
statically link against a Python library on Windows in a downstream
crate of python3-sys. This requires the unstable `static-nobundle`
link type so Cargo leaves symbols as unresolved when python3-sys
is built. (Currently, the `static` linkage type verifies referenced
symbols are present at crate build time.) See
rust-lang/rust#37403 for more. Look for
comments by me (@indygreg) to describe the issue in more detail.

This commit teaches python3-sys a pair of new build features which
enable more explicit control over the linker directives emitted by
its build script. If no directive is specified, `link-mode-default`
is used and the existing logic for linker directive emission is
used. If `link-mode-unresolved-static` is used and we're on Windows,
we emit a `static-nobundle=pythonXY` linker directive and
omit the location of the library. This effectively says "I depend
on a static `pythonXY` library but don't resolve the symbols
when you build me and require someone else to specify the location
to that library." What PyOxidizer does is emit its own linker
directive that defines the location of a static `pythonXY` library,
satisfying the linker constraint and enabling the build to work.
If a downstream crate doesn't do this, the build should fail due
to a missing library or symbols.

I have purposefully designed the crate features to be extensible.
If we want to add additional, mutually exclusive features in the
future, we could do that. e.g. we could add a `link-mode-static`
that force emits a `rustc-link-lib=static=pythonXY` directive
to force static linking, even if a shared library is detected.
But I have no need for this today and don't want to complicate
the code, so I haven't added it.

To round out the new feature, features have been added to the
cpython crate to toggle the new features.

Because Python 2.7 is end of life, I have not implemented the new
feature for Python 2.7. I suspect very few people will use this
feature anyway and I'm pretty confident that nobody will request
this feature on Python 2.7.

I concede that adding this feature to the crate to support
PyOxidizer's esoteric use case is a bit unfortunate. I really wish
Cargo allowed a crate to wholesale replace the build script output
of a dependency, as PyOxidizer could statically resolve the
Python settings for python3-sys since it brings its own Python
library. But Cargo doesn't have this feature. So I'm stuck
having to land this feature in the upstream crate to avoid having
to maintain a permanent fork of `rust-cpython`. Sorry :/
  • Loading branch information
indygreg committed Jan 20, 2020
1 parent dabc06c commit 10d667f
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 9 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ extension-module = [ "python3-sys/extension-module" ]
# or python3-sys. (honestly, we should probably merge both crates into 'python-sys')
extension-module-2-7 = [ "python27-sys/extension-module" ]

# Use these features to explicitly control linking for Python 3.
# (See the documentation in python3-sys/Cargo.toml for more info.)
py-link-mode-default = [ "python3-sys/link-mode-default" ]
py-link-mode-unresolved-static = [ "python3-sys/link-mode-unresolved-static" ]

# Optional features to support explicitly specifying python minor version.
# If you don't care which minor version, just specify python3-sys as a
Expand Down
11 changes: 10 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ const PYTHONSYS_ENV_VAR: &'static str = "DEP_PYTHON27_PYTHON_FLAGS";
const PYTHONSYS_ENV_VAR: &'static str = "DEP_PYTHON3_PYTHON_FLAGS";

fn main() {
// python{27,3.x}-sys/build.rs passes python interpreter compile flags via
if cfg!(feature="python27-sys") {
if env::var_os("CARGO_FEATURE_PY_LINK_MODE_DEFAULT").is_some() ||
env::var_os("CARGO_FEATURE_PY_LINK_MODE_UNRESOLVED_STATIC").is_some() {
writeln!(std::io::stderr(),
"Cannot use link mode control with Python 2.7");
std::process::exit(1);
}
}

// python{27,3.x}-sys/build.rs passes python interpreter compile flags via
// environment variable (using the 'links' mechanism in the cargo.toml).
let flags = match env::var(PYTHONSYS_ENV_VAR) {
Ok(flags) => flags,
Expand Down
24 changes: 24 additions & 0 deletions python3-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@ default = ["python-3"]
# so that the module can also be used with statically linked python interpreters.
extension-module = [ ]

# This feature implies default linking behavior.
#
# If not an extension module or on Windows, the crate will link against
# pythonXY where XY is derived from the discovered Python version. The link
# type will be static, shared, or framework depending on the discovered Python.
#
# The path to pythonXY from the discovered Python install may also be
# added to the linker search path.
#
# This link mode is used by default unless an alternate link mode feature is
# used.
link-mode-default = []

# This feature forces Python symbols to be unresolved by emitting a
# `rustc-link-lib=static-nobundle=pythonXY` directive on Windows (which
# is the only platform where it makes sense).
#
# This mode is useful for scenarios where you want another crate to emit
# the linker directives that define the location of a static Python library.
#
# This mode is typically not needed, as Python distributions on Windows
# rarely use a static Python library.
link-mode-unresolved-static = []

# Bind to any python 3.x.
python-3 = []

Expand Down
43 changes: 35 additions & 8 deletions python3-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,16 +316,43 @@ fn configure_from_path(expected_version: &PythonVersion) -> Result<String, Strin
let exec_prefix: &str = &lines[3];

let is_extension_module = env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some();
if !is_extension_module || cfg!(target_os="windows") {
println!("{}", get_rustc_link_lib(&interpreter_version,
ld_version, enable_shared == "1").unwrap());
if libpath != "None" {
println!("cargo:rustc-link-search=native={}", libpath);
} else if cfg!(target_os="windows") {
println!("cargo:rustc-link-search=native={}\\libs", exec_prefix);
}
let mut link_mode_default = env::var_os("CARGO_FEATURE_LINK_MODE_DEFAULT").is_some();
let link_mode_unresolved_static = env::var_os("CARGO_FEATURE_LINK_MODE_UNRESOLVED_STATIC").is_some();

if link_mode_default && link_mode_unresolved_static {
return Err("link-mode-default and link-mode-unresolved-static are mutually exclusive".to_owned());
}

if !link_mode_default && !link_mode_unresolved_static {
link_mode_default = true;
}

if link_mode_default {
if !is_extension_module || cfg!(target_os="windows") {
println!("{}", get_rustc_link_lib(&interpreter_version,
ld_version, enable_shared == "1").unwrap());
if libpath != "None" {
println!("cargo:rustc-link-search=native={}", libpath);
} else if cfg!(target_os="windows") {
println!("cargo:rustc-link-search=native={}\\libs", exec_prefix);
}
}
}
else if link_mode_unresolved_static {
if cfg!(target_os="windows") {
// static-nobundle requires a Nightly rustc up to at least
// Rust 1.39 (https://github.com/rust-lang/rust/issues/37403).
//
// We need to use static linking on Windows to prevent symbol
// name mangling. Otherwise Rust will prefix extern {} symbols
// with __imp_. But if we used normal "static," we need a
// pythonXY.lib at build time to package into the rlib.
//
// static-nobundle removes the build-time library requirement,
// allowing a downstream consumer to provide the pythonXY library.
println!("cargo:rustc-link-lib=static-nobundle=pythonXY");
}
}

if let PythonVersion { major: 3, minor: some_minor} = interpreter_version {
if env::var_os("CARGO_FEATURE_PEP_384").is_some() {
Expand Down

0 comments on commit 10d667f

Please sign in to comment.