pyo3-build-config: improve config file format

This commit is contained in:
David Hewitt 2021-08-03 21:17:17 +01:00
parent fcd35c1c3b
commit ed994ca44f
3 changed files with 333 additions and 242 deletions

183
build.rs
View File

@ -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);
} }

View File

@ -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,

View File

@ -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.
/// ///