From 29476b0d0dd98ffc45a817cc7bdfc1d28e356a63 Mon Sep 17 00:00:00 2001 From: Sergey Kvachonok Date: Tue, 5 Apr 2022 16:51:24 +0300 Subject: [PATCH] pyo3-build-config: Inline the `PYO3_NO_PYTHON` switch This patch folds the `PYO3_NO_PYTHON` + `abi3` special case into the existing native and cross compilation code paths. The cross compilation route is now guaranteed to behave the same whether `PYO3_NO_PYTHON` is set or not (except for sysconfigdata discovery for the Unix targets). The native compilation route now stores the hardcoded abi3 interpreter configuration in place of the discovered configuration blob. --- pyo3-build-config/build.rs | 36 +------- pyo3-build-config/src/impl_.rs | 153 ++++++++++++++++++++++++++++----- pyo3-build-config/src/lib.rs | 24 ------ 3 files changed, 134 insertions(+), 79 deletions(-) diff --git a/pyo3-build-config/build.rs b/pyo3-build-config/build.rs index 45a5bb71..309a78c8 100644 --- a/pyo3-build-config/build.rs +++ b/pyo3-build-config/build.rs @@ -12,10 +12,7 @@ mod errors; use std::{env, path::Path}; use errors::{Context, Result}; -use impl_::{ - env_var, get_abi3_version, make_interpreter_config, BuildFlags, InterpreterConfig, - PythonImplementation, -}; +use impl_::{env_var, make_interpreter_config, InterpreterConfig}; fn configure(interpreter_config: Option, name: &str) -> Result { let target = Path::new(&env::var_os("OUT_DIR").unwrap()).join(name); @@ -52,37 +49,12 @@ fn config_file() -> Result> { } } -/// If PYO3_NO_PYTHON is set with abi3, use standard abi3 settings. -pub fn abi3_config() -> Option { - if let Some(version) = get_abi3_version() { - if env_var("PYO3_NO_PYTHON").is_some() { - return Some(InterpreterConfig { - version, - // NB PyPy doesn't support abi3 yet - implementation: PythonImplementation::CPython, - abi3: true, - lib_name: None, - lib_dir: None, - build_flags: BuildFlags::default(), - pointer_width: None, - executable: None, - shared: true, - suppress_build_script_link_lines: false, - extra_build_script_lines: vec![], - }); - } - } - None -} - fn generate_build_configs() -> Result<()> { - let mut configured = false; - configured |= configure(config_file()?, "pyo3-build-config-file.txt")?; - configured |= configure(abi3_config(), "pyo3-build-config-abi3.txt")?; + let configured = configure(config_file()?, "pyo3-build-config-file.txt")?; if configured { - // Don't bother trying to find an interpreter on the host system if at least one of the - // config file or abi3 settings are present + // Don't bother trying to find an interpreter on the host system + // if the user-provided config file is present. configure(None, "pyo3-build-config.txt")?; } else { configure(Some(make_interpreter_config()?), "pyo3-build-config.txt")?; diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 51065f35..afcb6494 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -641,6 +641,17 @@ impl FromStr for PythonImplementation { } } +/// Checks if we should look for a Python interpreter installation +/// to get the target interpreter configuration. +/// +/// Returns `false` if `PYO3_NO_PYTHON` environment variable is set. +fn have_python_interpreter() -> bool { + env_var("PYO3_NO_PYTHON").is_none() +} + +/// Checks if `abi3` or any of the `abi3-py3*` features is enabled for the PyO3 crate. +/// +/// Must be called from a PyO3 crate build script. fn is_abi3() -> bool { cargo_env_var("CARGO_FEATURE_ABI3").is_some() } @@ -1387,6 +1398,46 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result InterpreterConfig { + // FIXME: PyPy does not support the Stable ABI yet. + let implementation = PythonImplementation::CPython; + let abi3 = true; + + let lib_name = if host.operating_system == OperatingSystem::Windows { + Some(default_lib_name_windows( + version, + implementation, + abi3, + false, + )) + } else { + None + }; + + InterpreterConfig { + implementation, + version, + shared: true, + abi3, + lib_name, + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } +} + /// Detects the cross compilation target interpreter configuration from all /// available sources (PyO3 environment variables, Python sysconfigdata, etc.). /// @@ -1397,30 +1448,31 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result Result { - // Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set - // since it has no sysconfigdata files in it. - if cross_compile_config.target.operating_system == OperatingSystem::Windows { - return default_cross_compile(&cross_compile_config); - } + let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows; - // Try to find and parse sysconfigdata files on other targets - // and fall back to the defaults when none are found. - if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? { - Ok(config) + let config = if windows || !have_python_interpreter() { + // Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set + // since it has no sysconfigdata files in it. + // Also, do not try to look for sysconfigdata when `PYO3_NO_PYTHON` variable is set. + default_cross_compile(&cross_compile_config)? + } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? { + // Try to find and parse sysconfigdata files on other targets. + config } else { - let config = default_cross_compile(&cross_compile_config)?; + // Fall back to the defaults when nothing else can be done. + default_cross_compile(&cross_compile_config)? + }; - if config.lib_name.is_some() && config.lib_dir.is_none() { - warn!( - "The output binary will link to libpython, \ - but PYO3_CROSS_LIB_DIR environment variable is not set. \ - Ensure that the target Python library directory is \ - in the rustc native library search path." - ); - } - - Ok(config) + if config.lib_name.is_some() && config.lib_dir.is_none() { + warn!( + "The output binary will link to libpython, \ + but PYO3_CROSS_LIB_DIR environment variable is not set. \ + Ensure that the target Python library directory is \ + in the rustc native library search path." + ); } + + Ok(config) } // Link against python3.lib for the stable ABI on Windows. @@ -1591,9 +1643,18 @@ pub fn make_cross_compile_config() -> Result> { /// Only used by `pyo3-build-config` build script. #[allow(dead_code)] pub fn make_interpreter_config() -> Result { - let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?; - interpreter_config.fixup_for_abi3_version(get_abi3_version())?; - Ok(interpreter_config) + let abi3_version = get_abi3_version(); + + if have_python_interpreter() { + let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?; + interpreter_config.fixup_for_abi3_version(abi3_version)?; + Ok(interpreter_config) + } else if let Some(version) = abi3_version { + let host = Triple::host(); + Ok(default_abi3_config(&host, version)) + } else { + bail!("An abi3-py3* feature must be specified when compiling without a Python interpreter.") + } } #[cfg(test)] @@ -1844,6 +1905,52 @@ mod tests { ); } + #[test] + fn windows_hardcoded_abi3_compile() { + let host = triple!("x86_64-pc-windows-msvc"); + let min_version = "3.7".parse().unwrap(); + + assert_eq!( + default_abi3_config(&host, min_version), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 7 }, + shared: true, + abi3: true, + lib_name: Some("python3".into()), + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + + #[test] + fn unix_hardcoded_abi3_compile() { + let host = triple!("x86_64-unknown-linux-gnu"); + let min_version = "3.9".parse().unwrap(); + + assert_eq!( + default_abi3_config(&host, min_version), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 9 }, + shared: true, + abi3: true, + lib_name: None, + lib_dir: None, + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + #[test] fn windows_hardcoded_cross_compile() { let env_vars = CrossCompileEnvVars { diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index 062e2da3..c227d6a3 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -84,8 +84,6 @@ pub fn get() -> &'static InterpreterConfig { interpreter_config } else if !CONFIG_FILE.is_empty() { InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) - } else if !ABI3_CONFIG.is_empty() { - Ok(abi3_config()) } else if cross_compiling { InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap()) } else { @@ -100,12 +98,6 @@ pub fn get() -> &'static InterpreterConfig { #[cfg(feature = "resolve-config")] const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt")); -/// Build configuration set if abi3 features enabled and `PYO3_NO_PYTHON` env var present. Empty if -/// not both present. -#[doc(hidden)] -#[cfg(feature = "resolve-config")] -const ABI3_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-abi3.txt")); - /// Build configuration discovered by `pyo3-build-config` build script. Not aware of /// cross-compilation settings. #[doc(hidden)] @@ -128,20 +120,6 @@ fn resolve_cross_compile_config_path() -> Option { }) } -#[cfg(feature = "resolve-config")] -fn abi3_config() -> InterpreterConfig { - let mut interpreter_config = InterpreterConfig::from_reader(Cursor::new(ABI3_CONFIG)) - .expect("failed to parse hardcoded PyO3 abi3 config"); - // If running from a build script on Windows, tweak the hardcoded abi3 config to contain - // the standard lib name (this is necessary so that abi3 extension modules using - // PYO3_NO_PYTHON on Windows can link) - if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |target_os| target_os == "windows") { - assert_eq!(interpreter_config.lib_name, None); - interpreter_config.lib_name = Some("python3".to_owned()) - } - interpreter_config -} - /// Use certain features if we detect the compiler being used supports them. /// /// Features may be removed or added as MSRV gets bumped or new features become available, @@ -200,8 +178,6 @@ pub mod pyo3_build_script_impl { pub fn resolve_interpreter_config() -> Result { if !CONFIG_FILE.is_empty() { InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) - } else if !ABI3_CONFIG.is_empty() { - Ok(abi3_config()) } else if let Some(interpreter_config) = make_cross_compile_config()? { // This is a cross compile and need to write the config file. let path = resolve_cross_compile_config_path()