diff --git a/build.rs b/build.rs index c4ac6bf9..c7f8ed05 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,9 @@ -use std::{env, process::Command}; +use std::{env, ffi::OsString, path::Path, process::Command}; use pyo3_build_config::{ bail, cargo_env_var, ensure, env_var, errors::{Context, Result}, - InterpreterConfig, PythonImplementation, PythonVersion, + InterpreterConfig, PythonVersion, }; /// Minimum Python version PyO3 supports. @@ -20,80 +20,27 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { 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 let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH") .unwrap() .as_str() { - "64" => "64-bit", - "32" => "32-bit", + "64" => 64, + "32" => 32, 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!( - rust_target == python_target, - "Your Rust target architecture ({}) does not match your python interpreter ({})", + rust_target == pointer_width, + "your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)", rust_target, - python_target + pointer_width ); Ok(()) } -fn get_rustc_link_lib(config: &InterpreterConfig) -> Result { - 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 { let rustc = env::var_os("RUSTC")?; let output = Command::new(rustc).arg("--version").output().ok()?; @@ -108,28 +55,29 @@ fn rustc_minor_version() -> Option { fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()> { let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); let is_extension_module = cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some(); - match (is_extension_module, target_os.as_str()) { - (_, "windows") => { - // always link on windows, even with extension module - println!("{}", get_rustc_link_lib(interpreter_config)?); - // Set during cross-compiling. - if let Some(libdir) = &interpreter_config.libdir { - println!("cargo:rustc-link-search=native={}", libdir); - } - // Set if we have an interpreter to use. - if let Some(base_prefix) = &interpreter_config.base_prefix { - println!("cargo:rustc-link-search=native={}\\libs", base_prefix); - } + if target_os == "windows" || target_os == "android" || !is_extension_module { + // windows and android - always link + // other systems - only link if not extension module + println!( + "cargo:rustc-link-lib={link_model}{alias}{lib_name}", + link_model = if interpreter_config.shared { + "" + } else { + "static=" + }, + 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() { @@ -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` // 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() { - 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 /// (including `pyo3-macros-backend` during macro expansion). 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") { print_config_and_exit(&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)?; - 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(); 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) { println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --"); - println!("implementation: {}", config.implementation); - println!("interpreter version: {}", config.version); - println!("interpreter path: {:?}", config.executable); - 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); - + config + .to_writer(&mut std::io::stdout()) + .expect("failed to print config to stdout"); std::process::exit(101); } diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index a8ac9972..7b6594d8 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -43,15 +43,14 @@ pub fn env_var(var: &str) -> Option { /// this type. #[cfg_attr(test, derive(Debug, PartialEq))] pub struct InterpreterConfig { + pub implementation: PythonImplementation, pub version: PythonVersion, - pub libdir: Option, pub shared: bool, pub abi3: bool, - pub ld_version: Option, - pub base_prefix: Option, + pub lib_name: Option, + pub lib_dir: Option, pub executable: Option, - pub calcsize_pointer: Option, - pub implementation: PythonImplementation, + pub pointer_width: Option, pub build_flags: BuildFlags, } @@ -89,66 +88,85 @@ impl InterpreterConfig { #[doc(hidden)] pub fn from_reader(reader: impl Read) -> Result { let reader = BufReader::new(reader); - let mut lines = reader.lines(); + let lines = reader.lines(); - macro_rules! parse_line { - ($value:literal) => { - lines + macro_rules! parse_value { + ($variable:ident, $value:ident) => { + $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() - .ok_or(concat!("reached end of config when reading ", $value))? - .context(concat!("failed to read ", $value, " from config"))? - .parse() - .context(concat!("failed to parse ", $value, " from config")) - }; + .expect("first splitn value should always be present"), + split + .next() + .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 { - ($value:literal) => { - parse_option_string( - 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 version = version.ok_or("missing value for version")?; + let implementation = implementation.unwrap_or(PythonImplementation::CPython); + let abi3 = abi3.unwrap_or(false); - 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 { - version: PythonVersion { major, minor }, - libdir, - shared, - abi3, - ld_version, - base_prefix, - executable, - calcsize_pointer, 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)] pub fn to_writer(&self, mut writer: impl Write) -> Result<()> { macro_rules! write_line { - ($value:expr) => { - writeln!(writer, "{}", $value).context(concat!( + ($value:ident) => { + writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!( "failed to write ", stringify!($value), " to config" @@ -157,44 +175,32 @@ impl InterpreterConfig { } macro_rules! write_option_line { - ($opt:expr) => { - match &$opt { - Some(value) => writeln!(writer, "{}", value), - None => writeln!(writer, "null"), + ($value:ident) => { + if let Some(value) = &self.$value { + writeln!(writer, "{}={}", stringify!($value), value).context(concat!( + "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!(self.version.minor)?; - write_option_line!(self.libdir)?; - write_line!(self.shared)?; - write_line!(self.abi3)?; - write_option_line!(self.ld_version)?; - write_option_line!(self.base_prefix)?; - write_option_line!(self.executable)?; - write_option_line!(self.calcsize_pointer)?; - write_line!(self.implementation)?; - for flag in &self.build_flags.0 { - write_line!(flag)?; - } + write_line!(implementation)?; + write_line!(version)?; + write_line!(shared)?; + write_line!(abi3)?; + write_option_line!(lib_name)?; + write_option_line!(lib_dir)?; + write_option_line!(executable)?; + write_option_line!(pointer_width)?; + write_line!(build_flags)?; Ok(()) } } -fn parse_option_string(string: String) -> Result, ::Err> { - if string == "null" { - Ok(None) - } else { - string.parse().map(Some) - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PythonVersion { 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 { + 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)] pub enum PythonImplementation { CPython, @@ -453,14 +477,6 @@ impl BuildFlags { // query the interpreter directly for its build flags. let mut flags = HashSet::new(); 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) } @@ -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 { + 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 { output .lines() @@ -675,11 +718,10 @@ fn load_cross_compile_from_sysconfigdata( let major = sysconfig_data.get_numeric("version_major")?; let minor = sysconfig_data.get_numeric("version_minor")?; - let ld_version = match sysconfig_data.get("LDVERSION") { - Some(s) => s.clone(), - None => format!("{}.{}", major, minor), - }; - let calcsize_pointer = sysconfig_data.get_numeric("SIZEOF_VOID_P").ok(); + let pointer_width = sysconfig_data + .get_numeric("SIZEOF_VOID_P") + .map(|bytes_width: u32| bytes_width * 8) + .ok(); let soabi = match sysconfig_data.get("SOABI") { Some(s) => s, None => bail!("sysconfigdata did not define SOABI"), @@ -695,15 +737,18 @@ fn load_cross_compile_from_sysconfigdata( }; Ok(InterpreterConfig { + implementation, version, - libdir: cross_compile_config.lib_dir.to_str().map(String::from), shared: sysconfig_data.get_bool("Py_ENABLE_SHARED")?, abi3: is_abi3(), - ld_version: Some(ld_version), - base_prefix: None, + lib_dir: cross_compile_config.lib_dir.to_str().map(String::from), + lib_name: Some(default_lib_name_unix( + &version, + implementation, + sysconfig_data.get("LDVERSION").map(String::as_str), + )?), executable: None, - calcsize_pointer, - implementation, + pointer_width, 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.") }; + let version = PythonVersion { major, minor }; + Ok(InterpreterConfig { - version: PythonVersion { major, minor }, - libdir: cross_compile_config.lib_dir.to_str().map(String::from), + implementation: PythonImplementation::CPython, + version, shared: true, abi3: is_abi3(), - ld_version: None, - base_prefix: None, + lib_name: Some(default_lib_name_windows(&version, false, "msvc")), + lib_dir: cross_compile_config.lib_dir.to_str().map(String::from), executable: None, - calcsize_pointer: None, - implementation: PythonImplementation::CPython, + pointer_width: None, build_flags: BuildFlags::windows_hardcoded(), }) } @@ -762,6 +808,37 @@ fn load_cross_compile_info(cross_compile_config: CrossCompileConfig) -> Result 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 { + 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. fn run_python_script(interpreter: &Path, script: &str) -> Result { let out = Command::new(interpreter) @@ -878,13 +955,13 @@ FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK")) # unix-style shared library enabled SHARED = bool(get_config_var("Py_ENABLE_SHARED")) +print("implementation", platform.python_implementation()) print("version_major", sys.version_info[0]) 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_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("calcsize_pointer", struct.calcsize("P")) "#; @@ -901,22 +978,48 @@ print("calcsize_pointer", struct.calcsize("P")) .context("failed to parse minor version")?, }; + let abi3 = is_abi3(); 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 { version, implementation, - libdir: map.get("libdir").cloned(), shared, - abi3: is_abi3(), - ld_version: map.get("ld_version").cloned(), - base_prefix: map.get("base_prefix").cloned(), + abi3, + lib_name: Some(lib_name), + lib_dir, executable: map.get("executable").cloned(), - calcsize_pointer: Some( - map["calcsize_pointer"] - .parse() - .context("failed to parse calcsize_pointer")?, - ), + pointer_width: Some(calcsize_pointer * 8), build_flags: BuildFlags::from_interpreter(interpreter)?.fixup(version, implementation), }) } @@ -932,19 +1035,25 @@ pub fn make_interpreter_config() -> Result { // If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python. if let Some(abi3_minor_version) = abi3_version { 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 { - version: PythonVersion { - major: 3, - minor: abi3_minor_version, - }, - implementation: PythonImplementation::CPython, + version, + implementation, abi3: true, - libdir: None, + lib_name, + lib_dir: None, build_flags: BuildFlags::abi3(), - base_prefix: None, - calcsize_pointer: None, + pointer_width: None, executable: None, - ld_version: None, shared: true, }); } @@ -981,13 +1090,12 @@ mod tests { fn test_read_write_roundtrip() { let config = InterpreterConfig { abi3: true, - base_prefix: Some("base_prefix".into()), build_flags: BuildFlags::abi3(), - calcsize_pointer: Some(32), + pointer_width: Some(32), executable: Some("executable".into()), implementation: PythonImplementation::CPython, - ld_version: Some("ld_version".into()), - libdir: Some("libdir".into()), + lib_name: Some("lib_name".into()), + lib_dir: Some("lib_dir".into()), shared: true, version: MINIMUM_SUPPORTED_VERSION, }; @@ -1003,18 +1111,17 @@ mod tests { let config = InterpreterConfig { abi3: false, - base_prefix: None, build_flags: { let mut flags = HashSet::new(); flags.insert(BuildFlag::Py_DEBUG); flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG"))); BuildFlags(flags) }, - calcsize_pointer: None, + pointer_width: None, executable: None, implementation: PythonImplementation::PyPy, - ld_version: None, - libdir: None, + lib_dir: None, + lib_name: None, shared: true, version: PythonVersion { major: 3, diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index 0bc947aa..5a974923 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -7,6 +7,8 @@ pub mod errors; mod impl_; +use std::{ffi::OsString, path::Path}; + use once_cell::sync::OnceCell; // Used in PyO3's build.rs @@ -23,15 +25,20 @@ pub use impl_::{ pub fn get() -> &'static InterpreterConfig { static CONFIG: OnceCell = OnceCell::new(); 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); 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)] -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. ///