pyo3-build-config: improve config file format
This commit is contained in:
parent
fcd35c1c3b
commit
ed994ca44f
183
build.rs
183
build.rs
|
@ -1,9 +1,9 @@
|
||||||
use std::{env, process::Command};
|
use std::{env, ffi::OsString, path::Path, process::Command};
|
||||||
|
|
||||||
use pyo3_build_config::{
|
use pyo3_build_config::{
|
||||||
bail, cargo_env_var, ensure, env_var,
|
bail, cargo_env_var, ensure, env_var,
|
||||||
errors::{Context, Result},
|
errors::{Context, Result},
|
||||||
InterpreterConfig, PythonImplementation, PythonVersion,
|
InterpreterConfig, PythonVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Minimum Python version PyO3 supports.
|
/// Minimum Python version PyO3 supports.
|
||||||
|
@ -20,80 +20,27 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_target_architecture(interpreter_config: &InterpreterConfig) -> Result<()> {
|
fn ensure_target_pointer_width(pointer_width: u32) -> Result<()> {
|
||||||
// Try to check whether the target architecture matches the python library
|
// Try to check whether the target architecture matches the python library
|
||||||
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
|
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
"64" => "64-bit",
|
"64" => 64,
|
||||||
"32" => "32-bit",
|
"32" => 32,
|
||||||
x => bail!("unexpected Rust target pointer width: {}", x),
|
x => bail!("unexpected Rust target pointer width: {}", x),
|
||||||
};
|
};
|
||||||
|
|
||||||
// The reason we don't use platform.architecture() here is that it's not
|
|
||||||
// reliable on macOS. See https://stackoverflow.com/a/1405971/823869.
|
|
||||||
// Similarly, sys.maxsize is not reliable on Windows. See
|
|
||||||
// https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971
|
|
||||||
// and https://stackoverflow.com/a/3411134/823869.
|
|
||||||
let python_target = match interpreter_config.calcsize_pointer {
|
|
||||||
Some(8) => "64-bit",
|
|
||||||
Some(4) => "32-bit",
|
|
||||||
None => {
|
|
||||||
// Unset, e.g. because we're cross-compiling. Don't check anything
|
|
||||||
// in this case.
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Some(n) => bail!("unexpected Python calcsize_pointer value: {}", n),
|
|
||||||
};
|
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
rust_target == python_target,
|
rust_target == pointer_width,
|
||||||
"Your Rust target architecture ({}) does not match your python interpreter ({})",
|
"your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)",
|
||||||
rust_target,
|
rust_target,
|
||||||
python_target
|
pointer_width
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rustc_link_lib(config: &InterpreterConfig) -> Result<String> {
|
|
||||||
let link_name = if cargo_env_var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
|
|
||||||
if config.abi3 {
|
|
||||||
// Link against python3.lib for the stable ABI on Windows.
|
|
||||||
// See https://www.python.org/dev/peps/pep-0384/#linkage
|
|
||||||
//
|
|
||||||
// This contains only the limited ABI symbols.
|
|
||||||
"pythonXY:python3".to_owned()
|
|
||||||
} else if cargo_env_var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu" {
|
|
||||||
// https://packages.msys2.org/base/mingw-w64-python
|
|
||||||
format!(
|
|
||||||
"pythonXY:python{}.{}",
|
|
||||||
config.version.major, config.version.minor
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"pythonXY:python{}{}",
|
|
||||||
config.version.major, config.version.minor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match config.implementation {
|
|
||||||
PythonImplementation::CPython => match &config.ld_version {
|
|
||||||
Some(ld_version) => format!("python{}", ld_version),
|
|
||||||
None => bail!("failed to configure `ld_version` when compiling for unix"),
|
|
||||||
},
|
|
||||||
PythonImplementation::PyPy => format!("pypy{}-c", config.version.major),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(format!(
|
|
||||||
"cargo:rustc-link-lib={link_model}{link_name}",
|
|
||||||
link_model = if config.shared { "" } else { "static=" },
|
|
||||||
link_name = link_name
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rustc_minor_version() -> Option<u32> {
|
fn rustc_minor_version() -> Option<u32> {
|
||||||
let rustc = env::var_os("RUSTC")?;
|
let rustc = env::var_os("RUSTC")?;
|
||||||
let output = Command::new(rustc).arg("--version").output().ok()?;
|
let output = Command::new(rustc).arg("--version").output().ok()?;
|
||||||
|
@ -108,28 +55,29 @@ fn rustc_minor_version() -> Option<u32> {
|
||||||
fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()> {
|
fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()> {
|
||||||
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
|
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
|
||||||
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
|
let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some();
|
||||||
match (is_extension_module, target_os.as_str()) {
|
if target_os == "windows" || target_os == "android" || !is_extension_module {
|
||||||
(_, "windows") => {
|
// windows and android - always link
|
||||||
// always link on windows, even with extension module
|
// other systems - only link if not extension module
|
||||||
println!("{}", get_rustc_link_lib(interpreter_config)?);
|
println!(
|
||||||
// Set during cross-compiling.
|
"cargo:rustc-link-lib={link_model}{alias}{lib_name}",
|
||||||
if let Some(libdir) = &interpreter_config.libdir {
|
link_model = if interpreter_config.shared {
|
||||||
println!("cargo:rustc-link-search=native={}", libdir);
|
""
|
||||||
}
|
} else {
|
||||||
// Set if we have an interpreter to use.
|
"static="
|
||||||
if let Some(base_prefix) = &interpreter_config.base_prefix {
|
},
|
||||||
println!("cargo:rustc-link-search=native={}\\libs", base_prefix);
|
alias = if target_os == "windows" {
|
||||||
}
|
"pythonXY:"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
lib_name = interpreter_config
|
||||||
|
.lib_name
|
||||||
|
.as_ref()
|
||||||
|
.ok_or("config does not contain lib_name")?,
|
||||||
|
);
|
||||||
|
if let Some(lib_dir) = &interpreter_config.lib_dir {
|
||||||
|
println!("cargo:rustc-link-search=native={}", lib_dir);
|
||||||
}
|
}
|
||||||
(false, _) | (_, "android") => {
|
|
||||||
// other systems, only link libs if not extension module
|
|
||||||
// android always link.
|
|
||||||
println!("{}", get_rustc_link_lib(interpreter_config)?);
|
|
||||||
if let Some(libdir) = &interpreter_config.libdir {
|
|
||||||
println!("cargo:rustc-link-search=native={}", libdir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
|
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
|
||||||
|
@ -152,9 +100,9 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
|
||||||
|
|
||||||
// TODO: PYO3_CI env is a hack to workaround CI with PyPy, where the `dev-dependencies`
|
// TODO: PYO3_CI env is a hack to workaround CI with PyPy, where the `dev-dependencies`
|
||||||
// currently cause `auto-initialize` to be enabled in CI.
|
// currently cause `auto-initialize` to be enabled in CI.
|
||||||
// Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
|
// Once MSRV is 1.51 or higher, use cargo's `resolver = "2"` instead.
|
||||||
if interpreter_config.is_pypy() && env::var_os("PYO3_CI").is_none() {
|
if interpreter_config.is_pypy() && env::var_os("PYO3_CI").is_none() {
|
||||||
bail!("The `auto-initialize` feature is not supported with PyPy.");
|
bail!("the `auto-initialize` feature is not supported with PyPy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,21 +114,56 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
|
||||||
/// The result is written to pyo3_build_config::PATH, which downstream scripts can read from
|
/// The result is written to pyo3_build_config::PATH, which downstream scripts can read from
|
||||||
/// (including `pyo3-macros-backend` during macro expansion).
|
/// (including `pyo3-macros-backend` during macro expansion).
|
||||||
fn configure_pyo3() -> Result<()> {
|
fn configure_pyo3() -> Result<()> {
|
||||||
let interpreter_config = pyo3_build_config::make_interpreter_config()?;
|
let write_config_file = env_var("PYO3_WRITE_CONFIG_FILE").map_or(false, |os_str| os_str == "1");
|
||||||
|
let custom_config_file_path = env_var("PYO3_CONFIG_FILE");
|
||||||
|
if let Some(path) = &custom_config_file_path {
|
||||||
|
ensure!(
|
||||||
|
Path::new(path).is_absolute(),
|
||||||
|
"PYO3_CONFIG_FILE must be absolute"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let (interpreter_config, path_to_write) = match (write_config_file, custom_config_file_path) {
|
||||||
|
(true, Some(path)) => {
|
||||||
|
// Create new interpreter config and write it to config file
|
||||||
|
(pyo3_build_config::make_interpreter_config()?, Some(path))
|
||||||
|
}
|
||||||
|
(true, None) => bail!("PYO3_CONFIG_FILE must be set when PYO3_WRITE_CONFIG_FILE is set"),
|
||||||
|
(false, Some(path)) => {
|
||||||
|
// Read custom config file
|
||||||
|
let path = Path::new(&path);
|
||||||
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
|
let config_file = std::fs::File::open(path)
|
||||||
|
.with_context(|| format!("failed to read config file at {}", path.display()))?;
|
||||||
|
let reader = std::io::BufReader::new(config_file);
|
||||||
|
(
|
||||||
|
pyo3_build_config::InterpreterConfig::from_reader(reader)?,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(false, None) => (
|
||||||
|
// Create new interpreter config and write it to the default location
|
||||||
|
pyo3_build_config::make_interpreter_config()?,
|
||||||
|
Some(OsString::from(pyo3_build_config::DEFAULT_CONFIG_PATH)),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(path) = path_to_write {
|
||||||
|
interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to create config file at {}",
|
||||||
|
Path::new(&path).display()
|
||||||
|
)
|
||||||
|
})?)?;
|
||||||
|
}
|
||||||
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
|
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
|
||||||
print_config_and_exit(&interpreter_config);
|
print_config_and_exit(&interpreter_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_python_version(&interpreter_config)?;
|
ensure_python_version(&interpreter_config)?;
|
||||||
ensure_target_architecture(&interpreter_config)?;
|
if let Some(pointer_width) = interpreter_config.pointer_width {
|
||||||
|
ensure_target_pointer_width(pointer_width)?;
|
||||||
|
}
|
||||||
emit_cargo_configuration(&interpreter_config)?;
|
emit_cargo_configuration(&interpreter_config)?;
|
||||||
interpreter_config.to_writer(
|
|
||||||
&mut std::fs::File::create(pyo3_build_config::PATH).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to create config file at {}",
|
|
||||||
pyo3_build_config::PATH
|
|
||||||
)
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
interpreter_config.emit_pyo3_cfgs();
|
interpreter_config.emit_pyo3_cfgs();
|
||||||
|
|
||||||
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
|
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
|
||||||
|
@ -200,15 +183,9 @@ fn configure_pyo3() -> Result<()> {
|
||||||
|
|
||||||
fn print_config_and_exit(config: &InterpreterConfig) {
|
fn print_config_and_exit(config: &InterpreterConfig) {
|
||||||
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
|
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
|
||||||
println!("implementation: {}", config.implementation);
|
config
|
||||||
println!("interpreter version: {}", config.version);
|
.to_writer(&mut std::io::stdout())
|
||||||
println!("interpreter path: {:?}", config.executable);
|
.expect("failed to print config to stdout");
|
||||||
println!("libdir: {:?}", config.libdir);
|
|
||||||
println!("shared: {}", config.shared);
|
|
||||||
println!("base prefix: {:?}", config.base_prefix);
|
|
||||||
println!("ld_version: {:?}", config.ld_version);
|
|
||||||
println!("pointer width: {:?}", config.calcsize_pointer);
|
|
||||||
|
|
||||||
std::process::exit(101);
|
std::process::exit(101);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,15 +43,14 @@ pub fn env_var(var: &str) -> Option<OsString> {
|
||||||
/// this type.
|
/// this type.
|
||||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
pub struct InterpreterConfig {
|
pub struct InterpreterConfig {
|
||||||
|
pub implementation: PythonImplementation,
|
||||||
pub version: PythonVersion,
|
pub version: PythonVersion,
|
||||||
pub libdir: Option<String>,
|
|
||||||
pub shared: bool,
|
pub shared: bool,
|
||||||
pub abi3: bool,
|
pub abi3: bool,
|
||||||
pub ld_version: Option<String>,
|
pub lib_name: Option<String>,
|
||||||
pub base_prefix: Option<String>,
|
pub lib_dir: Option<String>,
|
||||||
pub executable: Option<String>,
|
pub executable: Option<String>,
|
||||||
pub calcsize_pointer: Option<u32>,
|
pub pointer_width: Option<u32>,
|
||||||
pub implementation: PythonImplementation,
|
|
||||||
pub build_flags: BuildFlags,
|
pub build_flags: BuildFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,66 +88,85 @@ impl InterpreterConfig {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn from_reader(reader: impl Read) -> Result<Self> {
|
pub fn from_reader(reader: impl Read) -> Result<Self> {
|
||||||
let reader = BufReader::new(reader);
|
let reader = BufReader::new(reader);
|
||||||
let mut lines = reader.lines();
|
let lines = reader.lines();
|
||||||
|
|
||||||
macro_rules! parse_line {
|
macro_rules! parse_value {
|
||||||
($value:literal) => {
|
($variable:ident, $value:ident) => {
|
||||||
lines
|
$variable = Some($value.parse().context(format!(
|
||||||
|
concat!(
|
||||||
|
"failed to parse ",
|
||||||
|
stringify!($variable),
|
||||||
|
" from config value '{}'"
|
||||||
|
),
|
||||||
|
$value
|
||||||
|
))?)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut implementation = None;
|
||||||
|
let mut version = None;
|
||||||
|
let mut shared = None;
|
||||||
|
let mut abi3 = None;
|
||||||
|
let mut lib_name = None;
|
||||||
|
let mut lib_dir = None;
|
||||||
|
let mut executable = None;
|
||||||
|
let mut pointer_width = None;
|
||||||
|
let mut build_flags = None;
|
||||||
|
|
||||||
|
for (i, line) in lines.enumerate() {
|
||||||
|
let line = line.context("failed to read line from config")?;
|
||||||
|
let mut split = line.splitn(2, '=');
|
||||||
|
let (key, value) = (
|
||||||
|
split
|
||||||
.next()
|
.next()
|
||||||
.ok_or(concat!("reached end of config when reading ", $value))?
|
.expect("first splitn value should always be present"),
|
||||||
.context(concat!("failed to read ", $value, " from config"))?
|
split
|
||||||
.parse()
|
.next()
|
||||||
.context(concat!("failed to parse ", $value, " from config"))
|
.ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?,
|
||||||
};
|
);
|
||||||
|
match key {
|
||||||
|
"implementation" => parse_value!(implementation, value),
|
||||||
|
"version" => parse_value!(version, value),
|
||||||
|
"shared" => parse_value!(shared, value),
|
||||||
|
"abi3" => parse_value!(abi3, value),
|
||||||
|
"lib_name" => parse_value!(lib_name, value),
|
||||||
|
"lib_dir" => parse_value!(lib_dir, value),
|
||||||
|
"executable" => parse_value!(executable, value),
|
||||||
|
"pointer_width" => parse_value!(pointer_width, value),
|
||||||
|
"build_flags" => parse_value!(build_flags, value),
|
||||||
|
unknown => bail!("unknown config key `{}`", unknown),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! parse_option_line {
|
let version = version.ok_or("missing value for version")?;
|
||||||
($value:literal) => {
|
let implementation = implementation.unwrap_or(PythonImplementation::CPython);
|
||||||
parse_option_string(
|
let abi3 = abi3.unwrap_or(false);
|
||||||
lines
|
|
||||||
.next()
|
|
||||||
.ok_or(concat!("reached end of config when reading ", $value))?
|
|
||||||
.context(concat!("failed to read ", $value, " from config"))?,
|
|
||||||
)
|
|
||||||
.context(concat!("failed to parse ", $value, "from config"))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let major = parse_line!("major version")?;
|
|
||||||
let minor = parse_line!("minor version")?;
|
|
||||||
let libdir = parse_option_line!("libdir")?;
|
|
||||||
let shared = parse_line!("shared")?;
|
|
||||||
let abi3 = parse_line!("abi3")?;
|
|
||||||
let ld_version = parse_option_line!("ld_version")?;
|
|
||||||
let base_prefix = parse_option_line!("base_prefix")?;
|
|
||||||
let executable = parse_option_line!("executable")?;
|
|
||||||
let calcsize_pointer = parse_option_line!("calcsize_pointer")?;
|
|
||||||
let implementation = parse_line!("implementation")?;
|
|
||||||
let mut build_flags = BuildFlags(HashSet::new());
|
|
||||||
for line in lines {
|
|
||||||
build_flags
|
|
||||||
.0
|
|
||||||
.insert(line.context("failed to read flag from config")?.parse()?);
|
|
||||||
}
|
|
||||||
Ok(InterpreterConfig {
|
Ok(InterpreterConfig {
|
||||||
version: PythonVersion { major, minor },
|
|
||||||
libdir,
|
|
||||||
shared,
|
|
||||||
abi3,
|
|
||||||
ld_version,
|
|
||||||
base_prefix,
|
|
||||||
executable,
|
|
||||||
calcsize_pointer,
|
|
||||||
implementation,
|
implementation,
|
||||||
build_flags,
|
version,
|
||||||
|
shared: shared.unwrap_or(true),
|
||||||
|
abi3,
|
||||||
|
lib_name,
|
||||||
|
lib_dir,
|
||||||
|
executable,
|
||||||
|
pointer_width,
|
||||||
|
build_flags: build_flags.unwrap_or_else(|| {
|
||||||
|
if abi3 {
|
||||||
|
BuildFlags::abi3()
|
||||||
|
} else {
|
||||||
|
BuildFlags(HashSet::new())
|
||||||
|
}
|
||||||
|
.fixup(version, implementation)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
|
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
|
||||||
macro_rules! write_line {
|
macro_rules! write_line {
|
||||||
($value:expr) => {
|
($value:ident) => {
|
||||||
writeln!(writer, "{}", $value).context(concat!(
|
writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!(
|
||||||
"failed to write ",
|
"failed to write ",
|
||||||
stringify!($value),
|
stringify!($value),
|
||||||
" to config"
|
" to config"
|
||||||
|
@ -157,44 +175,32 @@ impl InterpreterConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! write_option_line {
|
macro_rules! write_option_line {
|
||||||
($opt:expr) => {
|
($value:ident) => {
|
||||||
match &$opt {
|
if let Some(value) = &self.$value {
|
||||||
Some(value) => writeln!(writer, "{}", value),
|
writeln!(writer, "{}={}", stringify!($value), value).context(concat!(
|
||||||
None => writeln!(writer, "null"),
|
"failed to write ",
|
||||||
|
stringify!($value),
|
||||||
|
" to config"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
.context(concat!(
|
|
||||||
"failed to write ",
|
|
||||||
stringify!($value),
|
|
||||||
" to config"
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
write_line!(self.version.major)?;
|
write_line!(implementation)?;
|
||||||
write_line!(self.version.minor)?;
|
write_line!(version)?;
|
||||||
write_option_line!(self.libdir)?;
|
write_line!(shared)?;
|
||||||
write_line!(self.shared)?;
|
write_line!(abi3)?;
|
||||||
write_line!(self.abi3)?;
|
write_option_line!(lib_name)?;
|
||||||
write_option_line!(self.ld_version)?;
|
write_option_line!(lib_dir)?;
|
||||||
write_option_line!(self.base_prefix)?;
|
write_option_line!(executable)?;
|
||||||
write_option_line!(self.executable)?;
|
write_option_line!(pointer_width)?;
|
||||||
write_option_line!(self.calcsize_pointer)?;
|
write_line!(build_flags)?;
|
||||||
write_line!(self.implementation)?;
|
|
||||||
for flag in &self.build_flags.0 {
|
|
||||||
write_line!(flag)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_option_string<T: FromStr>(string: String) -> Result<Option<T>, <T as FromStr>::Err> {
|
|
||||||
if string == "null" {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
string.parse().map(Some)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct PythonVersion {
|
pub struct PythonVersion {
|
||||||
pub major: u8,
|
pub major: u8,
|
||||||
|
@ -211,6 +217,24 @@ impl Display for PythonVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for PythonVersion {
|
||||||
|
type Err = crate::errors::Error;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut split = value.splitn(2, '.');
|
||||||
|
let (major, minor) = (
|
||||||
|
split
|
||||||
|
.next()
|
||||||
|
.expect("first splitn value should always be present"),
|
||||||
|
split.next().ok_or("expected major.minor version")?,
|
||||||
|
);
|
||||||
|
Ok(Self {
|
||||||
|
major: major.parse().context("failed to parse major version")?,
|
||||||
|
minor: minor.parse().context("failed to parse minor version")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum PythonImplementation {
|
pub enum PythonImplementation {
|
||||||
CPython,
|
CPython,
|
||||||
|
@ -453,14 +477,6 @@ impl BuildFlags {
|
||||||
// query the interpreter directly for its build flags.
|
// query the interpreter directly for its build flags.
|
||||||
let mut flags = HashSet::new();
|
let mut flags = HashSet::new();
|
||||||
flags.insert(BuildFlag::WITH_THREAD);
|
flags.insert(BuildFlag::WITH_THREAD);
|
||||||
|
|
||||||
// Uncomment these manually if your python was built with these and you want
|
|
||||||
// the cfg flags to be set in rust.
|
|
||||||
//
|
|
||||||
// flags.insert(BuildFlag::Py_DEBUG);
|
|
||||||
// flags.insert(BuildFlag::Py_REF_DEBUG);
|
|
||||||
// flags.insert(BuildFlag::Py_TRACE_REFS);
|
|
||||||
// flags.insert(BuildFlag::COUNT_ALLOCS;
|
|
||||||
Self(flags)
|
Self(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,6 +504,33 @@ impl BuildFlags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for BuildFlags {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut first = true;
|
||||||
|
for flag in &self.0 {
|
||||||
|
if !first {
|
||||||
|
write!(f, ",")?;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
write!(f, "{}", flag)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for BuildFlags {
|
||||||
|
type Err = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut flags = HashSet::new();
|
||||||
|
for flag in value.split(',') {
|
||||||
|
flags.insert(flag.parse().unwrap());
|
||||||
|
}
|
||||||
|
Ok(BuildFlags(flags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_script_output(output: &str) -> HashMap<String, String> {
|
fn parse_script_output(output: &str) -> HashMap<String, String> {
|
||||||
output
|
output
|
||||||
.lines()
|
.lines()
|
||||||
|
@ -675,11 +718,10 @@ fn load_cross_compile_from_sysconfigdata(
|
||||||
|
|
||||||
let major = sysconfig_data.get_numeric("version_major")?;
|
let major = sysconfig_data.get_numeric("version_major")?;
|
||||||
let minor = sysconfig_data.get_numeric("version_minor")?;
|
let minor = sysconfig_data.get_numeric("version_minor")?;
|
||||||
let ld_version = match sysconfig_data.get("LDVERSION") {
|
let pointer_width = sysconfig_data
|
||||||
Some(s) => s.clone(),
|
.get_numeric("SIZEOF_VOID_P")
|
||||||
None => format!("{}.{}", major, minor),
|
.map(|bytes_width: u32| bytes_width * 8)
|
||||||
};
|
.ok();
|
||||||
let calcsize_pointer = sysconfig_data.get_numeric("SIZEOF_VOID_P").ok();
|
|
||||||
let soabi = match sysconfig_data.get("SOABI") {
|
let soabi = match sysconfig_data.get("SOABI") {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => bail!("sysconfigdata did not define SOABI"),
|
None => bail!("sysconfigdata did not define SOABI"),
|
||||||
|
@ -695,15 +737,18 @@ fn load_cross_compile_from_sysconfigdata(
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(InterpreterConfig {
|
Ok(InterpreterConfig {
|
||||||
|
implementation,
|
||||||
version,
|
version,
|
||||||
libdir: cross_compile_config.lib_dir.to_str().map(String::from),
|
|
||||||
shared: sysconfig_data.get_bool("Py_ENABLE_SHARED")?,
|
shared: sysconfig_data.get_bool("Py_ENABLE_SHARED")?,
|
||||||
abi3: is_abi3(),
|
abi3: is_abi3(),
|
||||||
ld_version: Some(ld_version),
|
lib_dir: cross_compile_config.lib_dir.to_str().map(String::from),
|
||||||
base_prefix: None,
|
lib_name: Some(default_lib_name_unix(
|
||||||
|
&version,
|
||||||
|
implementation,
|
||||||
|
sysconfig_data.get("LDVERSION").map(String::as_str),
|
||||||
|
)?),
|
||||||
executable: None,
|
executable: None,
|
||||||
calcsize_pointer,
|
pointer_width,
|
||||||
implementation,
|
|
||||||
build_flags: BuildFlags::from_config_map(&sysconfig_data).fixup(version, implementation),
|
build_flags: BuildFlags::from_config_map(&sysconfig_data).fixup(version, implementation),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -730,16 +775,17 @@ fn windows_hardcoded_cross_compile(
|
||||||
bail!("PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified when cross-compiling for Windows.")
|
bail!("PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified when cross-compiling for Windows.")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let version = PythonVersion { major, minor };
|
||||||
|
|
||||||
Ok(InterpreterConfig {
|
Ok(InterpreterConfig {
|
||||||
version: PythonVersion { major, minor },
|
implementation: PythonImplementation::CPython,
|
||||||
libdir: cross_compile_config.lib_dir.to_str().map(String::from),
|
version,
|
||||||
shared: true,
|
shared: true,
|
||||||
abi3: is_abi3(),
|
abi3: is_abi3(),
|
||||||
ld_version: None,
|
lib_name: Some(default_lib_name_windows(&version, false, "msvc")),
|
||||||
base_prefix: None,
|
lib_dir: cross_compile_config.lib_dir.to_str().map(String::from),
|
||||||
executable: None,
|
executable: None,
|
||||||
calcsize_pointer: None,
|
pointer_width: None,
|
||||||
implementation: PythonImplementation::CPython,
|
|
||||||
build_flags: BuildFlags::windows_hardcoded(),
|
build_flags: BuildFlags::windows_hardcoded(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -762,6 +808,37 @@ fn load_cross_compile_info(cross_compile_config: CrossCompileConfig) -> Result<I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link against python3.lib for the stable ABI on Windows.
|
||||||
|
// See https://www.python.org/dev/peps/pep-0384/#linkage
|
||||||
|
//
|
||||||
|
// This contains only the limited ABI symbols.
|
||||||
|
const WINDOWS_ABI3_LIB_NAME: &str = "python3";
|
||||||
|
|
||||||
|
fn default_lib_name_windows(version: &PythonVersion, abi3: bool, target_env: &str) -> String {
|
||||||
|
if abi3 {
|
||||||
|
WINDOWS_ABI3_LIB_NAME.to_owned()
|
||||||
|
} else if target_env == "gnu" {
|
||||||
|
// https://packages.msys2.org/base/mingw-w64-python
|
||||||
|
format!("python{}.{}", version.major, version.minor)
|
||||||
|
} else {
|
||||||
|
format!("python{}{}", version.major, version.minor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_lib_name_unix(
|
||||||
|
version: &PythonVersion,
|
||||||
|
implementation: PythonImplementation,
|
||||||
|
ld_version: Option<&str>,
|
||||||
|
) -> Result<String> {
|
||||||
|
match implementation {
|
||||||
|
PythonImplementation::CPython => match &ld_version {
|
||||||
|
Some(ld_version) => Ok(format!("python{}", ld_version)),
|
||||||
|
None => bail!("failed to configure `ld_version` when compiling for unix"),
|
||||||
|
},
|
||||||
|
PythonImplementation::PyPy => Ok(format!("pypy{}-c", version.major)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Run a python script using the specified interpreter binary.
|
/// Run a python script using the specified interpreter binary.
|
||||||
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
|
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
|
||||||
let out = Command::new(interpreter)
|
let out = Command::new(interpreter)
|
||||||
|
@ -878,13 +955,13 @@ FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
|
||||||
# unix-style shared library enabled
|
# unix-style shared library enabled
|
||||||
SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
|
SHARED = bool(get_config_var("Py_ENABLE_SHARED"))
|
||||||
|
|
||||||
|
print("implementation", platform.python_implementation())
|
||||||
print("version_major", sys.version_info[0])
|
print("version_major", sys.version_info[0])
|
||||||
print("version_minor", sys.version_info[1])
|
print("version_minor", sys.version_info[1])
|
||||||
print("implementation", platform.python_implementation())
|
|
||||||
print_if_set("libdir", get_config_var("LIBDIR"))
|
|
||||||
print_if_set("ld_version", get_config_var("LDVERSION"))
|
|
||||||
print_if_set("base_prefix", base_prefix)
|
|
||||||
print("shared", PYPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
|
print("shared", PYPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED)
|
||||||
|
print_if_set("ld_version", get_config_var("LDVERSION"))
|
||||||
|
print_if_set("libdir", get_config_var("LIBDIR"))
|
||||||
|
print_if_set("base_prefix", base_prefix)
|
||||||
print("executable", sys.executable)
|
print("executable", sys.executable)
|
||||||
print("calcsize_pointer", struct.calcsize("P"))
|
print("calcsize_pointer", struct.calcsize("P"))
|
||||||
"#;
|
"#;
|
||||||
|
@ -901,22 +978,48 @@ print("calcsize_pointer", struct.calcsize("P"))
|
||||||
.context("failed to parse minor version")?,
|
.context("failed to parse minor version")?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let abi3 = is_abi3();
|
||||||
let implementation = map["implementation"].parse()?;
|
let implementation = map["implementation"].parse()?;
|
||||||
|
|
||||||
|
let lib_name = if cfg!(windows) {
|
||||||
|
default_lib_name_windows(
|
||||||
|
&version,
|
||||||
|
abi3,
|
||||||
|
&cargo_env_var("CARGO_CFG_TARGET_ENV").unwrap(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
default_lib_name_unix(
|
||||||
|
&version,
|
||||||
|
implementation,
|
||||||
|
map.get("ld_version").map(String::as_str),
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let lib_dir = if cfg!(windows) {
|
||||||
|
map.get("base_prefix")
|
||||||
|
.map(|base_prefix| format!("cargo:rustc-link-search=native={}\\libs", base_prefix))
|
||||||
|
} else {
|
||||||
|
map.get("libdir").cloned()
|
||||||
|
};
|
||||||
|
|
||||||
|
// The reason we don't use platform.architecture() here is that it's not
|
||||||
|
// reliable on macOS. See https://stackoverflow.com/a/1405971/823869.
|
||||||
|
// Similarly, sys.maxsize is not reliable on Windows. See
|
||||||
|
// https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971
|
||||||
|
// and https://stackoverflow.com/a/3411134/823869.
|
||||||
|
let calcsize_pointer: u32 = map["calcsize_pointer"]
|
||||||
|
.parse()
|
||||||
|
.context("failed to parse calcsize_pointer")?;
|
||||||
|
|
||||||
Ok(InterpreterConfig {
|
Ok(InterpreterConfig {
|
||||||
version,
|
version,
|
||||||
implementation,
|
implementation,
|
||||||
libdir: map.get("libdir").cloned(),
|
|
||||||
shared,
|
shared,
|
||||||
abi3: is_abi3(),
|
abi3,
|
||||||
ld_version: map.get("ld_version").cloned(),
|
lib_name: Some(lib_name),
|
||||||
base_prefix: map.get("base_prefix").cloned(),
|
lib_dir,
|
||||||
executable: map.get("executable").cloned(),
|
executable: map.get("executable").cloned(),
|
||||||
calcsize_pointer: Some(
|
pointer_width: Some(calcsize_pointer * 8),
|
||||||
map["calcsize_pointer"]
|
|
||||||
.parse()
|
|
||||||
.context("failed to parse calcsize_pointer")?,
|
|
||||||
),
|
|
||||||
build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version, implementation),
|
build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version, implementation),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -932,19 +1035,25 @@ pub fn make_interpreter_config() -> Result<InterpreterConfig> {
|
||||||
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
|
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
|
||||||
if let Some(abi3_minor_version) = abi3_version {
|
if let Some(abi3_minor_version) = abi3_version {
|
||||||
if env_var("PYO3_NO_PYTHON").is_some() {
|
if env_var("PYO3_NO_PYTHON").is_some() {
|
||||||
|
let version = PythonVersion {
|
||||||
|
major: 3,
|
||||||
|
minor: abi3_minor_version,
|
||||||
|
};
|
||||||
|
let implementation = PythonImplementation::CPython;
|
||||||
|
let lib_name = if cfg!(windows) {
|
||||||
|
Some(WINDOWS_ABI3_LIB_NAME.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
return Ok(InterpreterConfig {
|
return Ok(InterpreterConfig {
|
||||||
version: PythonVersion {
|
version,
|
||||||
major: 3,
|
implementation,
|
||||||
minor: abi3_minor_version,
|
|
||||||
},
|
|
||||||
implementation: PythonImplementation::CPython,
|
|
||||||
abi3: true,
|
abi3: true,
|
||||||
libdir: None,
|
lib_name,
|
||||||
|
lib_dir: None,
|
||||||
build_flags: BuildFlags::abi3(),
|
build_flags: BuildFlags::abi3(),
|
||||||
base_prefix: None,
|
pointer_width: None,
|
||||||
calcsize_pointer: None,
|
|
||||||
executable: None,
|
executable: None,
|
||||||
ld_version: None,
|
|
||||||
shared: true,
|
shared: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -981,13 +1090,12 @@ mod tests {
|
||||||
fn test_read_write_roundtrip() {
|
fn test_read_write_roundtrip() {
|
||||||
let config = InterpreterConfig {
|
let config = InterpreterConfig {
|
||||||
abi3: true,
|
abi3: true,
|
||||||
base_prefix: Some("base_prefix".into()),
|
|
||||||
build_flags: BuildFlags::abi3(),
|
build_flags: BuildFlags::abi3(),
|
||||||
calcsize_pointer: Some(32),
|
pointer_width: Some(32),
|
||||||
executable: Some("executable".into()),
|
executable: Some("executable".into()),
|
||||||
implementation: PythonImplementation::CPython,
|
implementation: PythonImplementation::CPython,
|
||||||
ld_version: Some("ld_version".into()),
|
lib_name: Some("lib_name".into()),
|
||||||
libdir: Some("libdir".into()),
|
lib_dir: Some("lib_dir".into()),
|
||||||
shared: true,
|
shared: true,
|
||||||
version: MINIMUM_SUPPORTED_VERSION,
|
version: MINIMUM_SUPPORTED_VERSION,
|
||||||
};
|
};
|
||||||
|
@ -1003,18 +1111,17 @@ mod tests {
|
||||||
|
|
||||||
let config = InterpreterConfig {
|
let config = InterpreterConfig {
|
||||||
abi3: false,
|
abi3: false,
|
||||||
base_prefix: None,
|
|
||||||
build_flags: {
|
build_flags: {
|
||||||
let mut flags = HashSet::new();
|
let mut flags = HashSet::new();
|
||||||
flags.insert(BuildFlag::Py_DEBUG);
|
flags.insert(BuildFlag::Py_DEBUG);
|
||||||
flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
|
flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG")));
|
||||||
BuildFlags(flags)
|
BuildFlags(flags)
|
||||||
},
|
},
|
||||||
calcsize_pointer: None,
|
pointer_width: None,
|
||||||
executable: None,
|
executable: None,
|
||||||
implementation: PythonImplementation::PyPy,
|
implementation: PythonImplementation::PyPy,
|
||||||
ld_version: None,
|
lib_dir: None,
|
||||||
libdir: None,
|
lib_name: None,
|
||||||
shared: true,
|
shared: true,
|
||||||
version: PythonVersion {
|
version: PythonVersion {
|
||||||
major: 3,
|
major: 3,
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod impl_;
|
mod impl_;
|
||||||
|
|
||||||
|
use std::{ffi::OsString, path::Path};
|
||||||
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
// Used in PyO3's build.rs
|
// Used in PyO3's build.rs
|
||||||
|
@ -23,15 +25,20 @@ pub use impl_::{
|
||||||
pub fn get() -> &'static InterpreterConfig {
|
pub fn get() -> &'static InterpreterConfig {
|
||||||
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
|
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
|
||||||
CONFIG.get_or_init(|| {
|
CONFIG.get_or_init(|| {
|
||||||
let config_file = std::fs::File::open(PATH).expect("config file missing");
|
let config_path = std::env::var_os("PYO3_CONFIG_FILE")
|
||||||
|
.unwrap_or_else(|| OsString::from(DEFAULT_CONFIG_PATH));
|
||||||
|
let config_file = std::fs::File::open(DEFAULT_CONFIG_PATH).expect(&format!(
|
||||||
|
"failed to open PyO3 config file at {}",
|
||||||
|
Path::new(&config_path).display()
|
||||||
|
));
|
||||||
let reader = std::io::BufReader::new(config_file);
|
let reader = std::io::BufReader::new(config_file);
|
||||||
InterpreterConfig::from_reader(reader).expect("failed to parse config file")
|
InterpreterConfig::from_reader(reader).expect("failed to parse config file")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path where PyO3's build.rs will write configuration.
|
/// Path where PyO3's build.rs will write configuration by default.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub const PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-build-config.txt");
|
pub const DEFAULT_CONFIG_PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-build-config.txt");
|
||||||
|
|
||||||
/// Adds all the [`#[cfg]` flags](index.html) to the current compilation.
|
/// Adds all the [`#[cfg]` flags](index.html) to the current compilation.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue