From 401b0c7e3268a506641a49dc2c79da9716ed5f83 Mon Sep 17 00:00:00 2001 From: Sergey Kvachonok Date: Mon, 11 Apr 2022 14:45:16 +0300 Subject: [PATCH] pyo3-build-config: Build "abi3" extensions without an interpreter Support compiling portable "abi3" extension modules even when the build host Python interpreter configuration is not available or the discovered Python interpreter version is not supported. Maturin already implements this by building "abi3" extension wheels with `PYO3_NO_PYTHON` environment veriable set for cargo when an `abi3-py3*` feature is detected. Closes #2292 --- pyo3-build-config/src/impl_.rs | 70 ++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index d34cc7a1ac6..2cb124fea92 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -682,14 +682,14 @@ pub fn is_extension_module() -> bool { /// Checks if we need to link to `libpython` for the current build target. /// -/// Must be called from a crate PyO3 build script. +/// Must be called from a PyO3 crate build script. pub fn is_linking_libpython() -> bool { is_linking_libpython_for_target(&target_triple_from_env()) } /// Checks if we need to link to `libpython` for the target. /// -/// Must be called from a crate PyO3 build script. +/// Must be called from a PyO3 crate build script. fn is_linking_libpython_for_target(target: &Triple) -> bool { target.operating_system == OperatingSystem::Windows || target.environment == Environment::Android @@ -697,6 +697,18 @@ fn is_linking_libpython_for_target(target: &Triple) -> bool { || !is_extension_module() } +/// Checks if we need to discover the Python library directory +/// to link the extension module binary. +/// +/// Must be called from a PyO3 crate build script. +fn require_libdir_for_target(target: &Triple) -> bool { + let is_generating_libpython = cfg!(feature = "python3-dll-a") + && target.operating_system == OperatingSystem::Windows + && is_abi3(); + + is_linking_libpython_for_target(target) && !is_generating_libpython +} + /// Configuration needed by PyO3 to cross-compile for a target platform. /// /// Usually this is collected from the environment (i.e. `PYO3_CROSS_*` and `CARGO_CFG_TARGET_*`) @@ -1646,6 +1658,18 @@ pub fn find_interpreter() -> Result { } } +/// Locates and extracts the build host Python interpreter configuration. +/// +/// Lowers the configured Python version to `abi3_version` if required. +fn get_host_interpreter(abi3_version: Option) -> Result { + let interpreter_path = find_interpreter()?; + + let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?; + interpreter_config.fixup_for_abi3_version(abi3_version)?; + + Ok(interpreter_config) +} + /// Generates an interpreter config suitable for cross-compilation. /// /// This must be called from PyO3's build script, because it relies on environment variables such as @@ -1666,26 +1690,40 @@ pub fn make_cross_compile_config() -> Result> { /// Only used by `pyo3-build-config` build script. #[allow(dead_code, unused_mut)] pub fn make_interpreter_config() -> Result { + let host = Triple::host(); let abi3_version = get_abi3_version(); + // See if we can safely skip the Python interpreter configuration detection. + // Unix "abi3" extension modules can usually be built without any interpreter. + let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host); + 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(); - let mut interpreter_config = default_abi3_config(&host, version); - - // Auto generate python3.dll import libraries for Windows targets. - #[cfg(feature = "python3-dll-a")] - { - interpreter_config.lib_dir = self::abi3_import_lib::generate_abi3_import_lib(&host)?; + match get_host_interpreter(abi3_version) { + Ok(interpreter_config) => return Ok(interpreter_config), + // Bail if the interpreter configuration is required to build. + Err(e) if need_interpreter => return Err(e), + _ => { + // Fall back to the "abi3" defaults just as if `PYO3_NO_PYTHON` + // environment variable was set. + warn!("Compiling without a working Python interpreter."); + } } - - Ok(interpreter_config) } else { - bail!("An abi3-py3* feature must be specified when compiling without a Python interpreter.") + ensure!( + abi3_version.is_some(), + "An abi3-py3* feature must be specified when compiling without a Python interpreter." + ); + }; + + let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap()); + + // Auto generate python3.dll import libraries for Windows targets. + #[cfg(feature = "python3-dll-a")] + { + interpreter_config.lib_dir = self::abi3_import_lib::generate_abi3_import_lib(&host)?; } + + Ok(interpreter_config) } #[cfg(test)]