From 9b45a19161cd20fac1c83c25897aa9d213244a15 Mon Sep 17 00:00:00 2001 From: Sergey Kvachonok Date: Fri, 25 Mar 2022 12:12:05 +0300 Subject: [PATCH] pyo3-build-config: Replace `TargetInfo` with `target_lexicon::Triple` Add a new public crate function `cross_compile_from_to()` using `target_lexicon::Triple` arguments instead of plain strings used in `cross_compile()`. Deprecate `pyo3_build_config::cross_compile()` since v0.17. Attempt to extract common code patterns into methods and standalone helper functions. Add docstrings to the new private items. Make some of the new helper functions public within the PyO3 crate and reuse them in the build scripts. Add PYO3_CROSS_PYTHON_VERSION parsing unit test. Add a ChangeLog entry mentioning the new `pyo3-build-config` API. --- CHANGELOG.md | 1 + pyo3-build-config/Cargo.toml | 4 + pyo3-build-config/src/impl_.rs | 515 ++++++++++++++++++++++----------- pyo3-build-config/src/lib.rs | 11 +- pyo3-ffi/build.rs | 55 ++-- 5 files changed, 389 insertions(+), 197 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bba62ff9c..fda04d1dd12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add new public `pyo3-build-config` API using the types from `target_lexicon` crate. Deprecate `cross_compiling()`. [#2253](https://github.com/PyO3/pyo3/pull/2253) - Allow dependent crates to access config values from `pyo3-build-config` via cargo link dep env vars. [#2092](https://github.com/PyO3/pyo3/pull/2092) - Added methods on `InterpreterConfig` to run Python scripts using the configured executable. [#2092](https://github.com/PyO3/pyo3/pull/2092) - Added FFI definitions for `PyType_FromModuleAndSpec`, `PyType_GetModule`, `PyType_GetModuleState` and `PyModule_AddType`. [#2250](https://github.com/PyO3/pyo3/pull/2250) diff --git a/pyo3-build-config/Cargo.toml b/pyo3-build-config/Cargo.toml index 2233dd78a4e..96d9c3717ee 100644 --- a/pyo3-build-config/Cargo.toml +++ b/pyo3-build-config/Cargo.toml @@ -12,6 +12,10 @@ edition = "2018" [dependencies] once_cell = "1" +target-lexicon = "0.12" + +[build-dependencies] +target-lexicon = "0.12" [features] default = [] diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index c3dcff32d97..0439951df4c 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -12,6 +12,10 @@ use std::{ str::FromStr, }; +pub use target_lexicon::Triple; + +use target_lexicon::{Architecture, BinaryFormat, Environment, OperatingSystem, Vendor}; + use crate::{ bail, ensure, errors::{Context, Error, Result}, @@ -40,6 +44,16 @@ pub fn env_var(var: &str) -> Option { env::var_os(var) } +/// Gets the compilation target triple from environment variables set by Cargo. +/// +/// Must be called from a crate build script. +pub fn target_triple_from_env() -> Triple { + env::var("TARGET") + .expect("target_triple_from_env() must be called from a build script") + .parse() + .expect("Unrecognized TARGET environment variable value") +} + /// Configuration needed by PyO3 to build for the correct Python implementation. /// /// Usually this is queried directly from the Python interpreter, or overridden using the @@ -524,6 +538,29 @@ print("mingw", get_platform().startswith("mingw")) envs, ) } + + /// Lowers the configured version to the abi3 version, if set. + fn fixup_for_abi3_version(&mut self, abi3_version: Option) -> Result<()> { + // PyPy doesn't support abi3; don't adjust the version + if self.implementation.is_pypy() { + return Ok(()); + } + + if let Some(version) = abi3_version { + ensure!( + version <= self.version, + "cannot set a minimum Python version {} higher than the interpreter version {} \ + (the minimum Python version is implied by the abi3-py3{} feature)", + version, + self.version, + version.minor, + ); + + self.version = version; + } + + Ok(()) + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -608,52 +645,37 @@ fn is_abi3() -> bool { cargo_env_var("CARGO_FEATURE_ABI3").is_some() } -#[derive(Debug, PartialEq)] -struct TargetInfo { - /// The `arch` component of the compilation target triple. - /// - /// e.g. x86_64, i386, arm, thumb, mips, etc. - arch: String, - - /// The `vendor` component of the compilation target triple. - /// - /// e.g. apple, pc, unknown, etc. - vendor: String, +/// Gets the minimum supported Python version from PyO3 `abi3-py*` features. +/// +/// Must be called from a PyO3 crate build script. +pub fn get_abi3_version() -> Option { + let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR) + .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some()); + minor_version.map(|minor| PythonVersion { major: 3, minor }) +} - /// The `os` component of the compilation target triple. - /// - /// e.g. darwin, freebsd, linux, windows, etc. - os: String, +/// Checks if the `extension-module` feature is enabled for the PyO3 crate. +/// +/// Must be called from a PyO3 crate build script. +pub fn is_extension_module() -> bool { + cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some() } -impl TargetInfo { - fn from_cargo_env() -> Result { - Ok(Self { - arch: cargo_env_var("CARGO_CFG_TARGET_ARCH") - .ok_or("expected CARGO_CFG_TARGET_ARCH env var")?, - vendor: cargo_env_var("CARGO_CFG_TARGET_VENDOR") - .ok_or("expected CARGO_CFG_TARGET_VENDOR env var")?, - os: cargo_env_var("CARGO_CFG_TARGET_OS") - .ok_or("expected CARGO_CFG_TARGET_OS env var")?, - }) - } +/// Checks if we need to link to `libpython` for the current build target. +/// +/// Must be called from a crate PyO3 build script. +pub fn is_linking_libpython() -> bool { + is_linking_libpython_for_target(&target_triple_from_env()) +} - fn to_target_triple(&self) -> String { - format!( - "{}-{}-{}", - if self.arch == "x86" { - "i686" - } else { - &self.arch - }, - self.vendor, - if self.os == "macos" { - "darwin" - } else { - &self.os - } - ) - } +/// Checks if we need to link to `libpython` for the target. +/// +/// Must be called from a crate PyO3 build script. +fn is_linking_libpython_for_target(target: &Triple) -> bool { + target.operating_system == OperatingSystem::Windows + || target.environment == Environment::Android + || target.environment == Environment::Androideabi + || !is_extension_module() } /// Configuration needed by PyO3 to cross-compile for a target platform. @@ -668,32 +690,63 @@ pub struct CrossCompileConfig { /// The version of the Python library to link against. version: Option, - /// The target information - target_info: TargetInfo, + /// The compile target triple (e.g. aarch64-unknown-linux-gnu) + target: Triple, } impl CrossCompileConfig { - fn from_env_vars(env_vars: CrossCompileEnvVars, target_info: TargetInfo) -> Result { - Ok(CrossCompileConfig { - lib_dir: env_vars - .pyo3_cross_lib_dir - .ok_or( - "The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling", - )? - .into(), - target_info, - version: env_vars - .pyo3_cross_python_version - .map(|os_string| { - let utf8_str = os_string - .to_str() - .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid utf-8.")?; - utf8_str - .parse() - .context("failed to parse PYO3_CROSS_PYTHON_VERSION") - }) - .transpose()?, - }) + /// Creates a new cross compile config struct from PyO3 environment variables + /// and the build environment when cross compilation mode is detected. + /// + /// Returns `None` when not cross compiling. + fn try_from_env_vars_host_target( + env_vars: CrossCompileEnvVars, + host: &Triple, + target: &Triple, + ) -> Result> { + if env_vars.any() || Self::is_cross_compiling_from_to(host, target) { + let lib_dir = env_vars.lib_dir_path()?; + let version = env_vars.parse_version()?; + let target = target.clone(); + + Ok(Some(CrossCompileConfig { + lib_dir, + target, + version, + })) + } else { + Ok(None) + } + } + + /// Checks if compiling on `host` for `target` required "real" cross compilation. + /// + /// Returns `false` if the target Python interpreter can run on the host. + fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool { + // Not cross-compiling if arch-vendor-os is all the same + // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host + // x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host + let mut compatible = host.architecture == target.architecture + && host.vendor == target.vendor + && host.operating_system == target.operating_system; + + // Not cross-compiling to compile for 32-bit Python from windows 64-bit + compatible |= target.operating_system == OperatingSystem::Windows + && host.operating_system == OperatingSystem::Windows; + + // Not cross-compiling to compile for x86-64 Python from macOS arm64 and vice versa + compatible |= target.operating_system == OperatingSystem::Darwin + && host.operating_system == OperatingSystem::Darwin; + + !compatible + } + + /// Converts `lib_dir` member field to an UTF-8 string. + /// + /// The conversion can not fail because `PYO3_CROSS_LIB_DIR` variable + /// is ensured contain a valid UTF-8 string. + fn lib_dir_string(&self) -> String { + self.lib_dir.to_str().unwrap().to_owned() } } @@ -709,6 +762,44 @@ impl CrossCompileEnvVars { || self.pyo3_cross_lib_dir.is_some() || self.pyo3_cross_python_version.is_some() } + + /// Parses `PYO3_CROSS_PYTHON_VERSION` environment variable value + /// into `PythonVersion`. + fn parse_version(&self) -> Result> { + let version = self + .pyo3_cross_python_version + .as_ref() + .map(|os_string| { + let utf8_str = os_string + .to_str() + .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?; + utf8_str + .parse() + .context("failed to parse PYO3_CROSS_PYTHON_VERSION") + }) + .transpose()?; + + Ok(version) + } + + /// Converts the stored `PYO3_CROSS_LIB_DIR` variable value (if any) + /// into a `PathBuf` instance. + /// + /// Ensures that the path is a valid UTF-8 string. + fn lib_dir_path(&self) -> Result { + let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from); + + if let Some(dir) = lib_dir.as_ref() { + ensure!( + dir.to_str().is_some(), + "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string" + ); + Ok(dir.clone()) + } else { + // FIXME: Relax this restriction in the future. + bail!("The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling") + } + } } pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars { @@ -733,39 +824,76 @@ pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars { /// `PYO3_CROSS_LIB_DIR`. /// /// See the [PyO3 User Guide](https://pyo3.rs/) for more info on cross-compiling. +#[deprecated( + since = "0.17.0", + note = "please use cross_compiling_from_to() instead" +)] pub fn cross_compiling( host: &str, target_arch: &str, target_vendor: &str, target_os: &str, ) -> Result> { - let env_vars = cross_compile_env_vars(); + let host: Triple = host.parse().map_err(|_| "bad host triple")?; + + let architecture: Architecture = target_arch.parse().map_err(|_| "bad target arch")?; + let vendor: Vendor = target_vendor.parse().map_err(|_| "bad target vendor")?; + let operating_system: OperatingSystem = target_os.parse().map_err(|_| "bad target os")?; - let target_info = TargetInfo { - arch: target_arch.to_owned(), - vendor: target_vendor.to_owned(), - os: target_os.to_owned(), + // FIXME: This is a very bad approximation that only works + // for the current `CrossCompileConfig` implementation. + let environment = match operating_system { + OperatingSystem::Windows => Environment::Msvc, + _ => Environment::Gnu, }; - if !env_vars.any() && is_not_cross_compiling(host, &target_info) { - return Ok(None); - } + // FIXME: This field is currently unused. + let binary_format = BinaryFormat::Elf; + + let target = Triple { + architecture, + vendor, + operating_system, + environment, + binary_format, + }; - CrossCompileConfig::from_env_vars(env_vars, target_info).map(Some) + cross_compiling_from_to(&host, &target) } -fn is_not_cross_compiling(host: &str, target_info: &TargetInfo) -> bool { - let target_triple = target_info.to_target_triple(); - // Not cross-compiling if arch-vendor-os is all the same - // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host - // x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host - host.starts_with(&target_triple) - // Not cross-compiling to compile for 32-bit Python from windows 64-bit - || (target_triple == "i686-pc-windows" && host.starts_with("x86_64-pc-windows")) - // Not cross-compiling to compile for x86-64 Python from macOS arm64 - || (target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin") - // Not cross-compiling to compile for arm64 Python from macOS x86_64 - || (target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin") +/// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so. +/// +/// This function relies on PyO3 cross-compiling environment variables: +/// +/// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation. +/// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing +/// the target's libpython DSO and the associated `_sysconfigdata*.py` file for +/// Unix-like targets, or the Python DLL import libraries for the Windows target. +/// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python +/// installation. This variable is only needed if PyO3 cannnot determine the version to target +/// from `abi3-py3*` features, or if there are multiple versions of Python present in +/// `PYO3_CROSS_LIB_DIR`. +/// +/// See the [PyO3 User Guide](https://pyo3.rs/) for more info on cross-compiling. +pub fn cross_compiling_from_to( + host: &Triple, + target: &Triple, +) -> Result> { + let env_vars = cross_compile_env_vars(); + CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target) +} + +/// Detect whether we are cross compiling from Cargo and `PYO3_CROSS_*` environment +/// variables and return an assembled `CrossCompileConfig` if so. +/// +/// This must be called from PyO3's build script, because it relies on environment +/// variables such as `CARGO_CFG_TARGET_OS` which aren't available at any other time. +pub fn cross_compiling_from_cargo_env() -> Result> { + let env_vars = cross_compile_env_vars(); + let host = Triple::host(); + let target = target_triple_from_env(); + + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) } #[allow(non_camel_case_types)] @@ -1091,15 +1219,11 @@ fn search_lib_dir(path: impl AsRef, cross: &CrossCompileConfig) -> Vec, cross: &CrossCompileConfig) -> Vec 1 { let temp = sysconfig_paths .iter() - .filter(|p| p.to_string_lossy().contains(&cross.target_info.arch)) + .filter(|p| { + p.to_string_lossy() + .contains(&cross.target.architecture.to_string()) + }) .cloned() .collect::>(); if !temp.is_empty() { @@ -1150,11 +1277,19 @@ fn cross_compile_from_sysconfigdata( fn windows_hardcoded_cross_compile( cross_compile_config: CrossCompileConfig, ) -> Result { - let version = cross_compile_config.version.or_else(get_abi3_version) - .ok_or("PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified when cross-compiling for Windows.")?; + let version = cross_compile_config + .version + .or_else(get_abi3_version) + .ok_or( + "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \ + when cross-compiling and PYO3_CROSS_LIB_DIR is not set.", + )?; let abi3 = is_abi3(); let implementation = PythonImplementation::CPython; + let mingw = cross_compile_config.target.environment == Environment::Gnu; + + let lib_dir = Some(cross_compile_config.lib_dir_string()); Ok(InterpreterConfig { implementation, @@ -1165,9 +1300,9 @@ fn windows_hardcoded_cross_compile( version, PythonImplementation::CPython, abi3, - false, + mingw, )), - lib_dir: cross_compile_config.lib_dir.to_str().map(String::from), + lib_dir, executable: None, pointer_width: None, build_flags: BuildFlags::default(), @@ -1303,7 +1438,7 @@ fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf { fn get_env_interpreter() -> Option { match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) { - // Use cfg rather can CARGO_TARGET_OS because this affects where files are located on the + // Use cfg rather than CARGO_CFG_TARGET_OS because this affects where files are located on the // build host (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))), (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))), @@ -1347,62 +1482,16 @@ pub fn find_interpreter() -> Result { } } -pub fn get_abi3_version() -> Option { - let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR) - .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some()); - minor_version.map(|minor| PythonVersion { major: 3, minor }) -} - -/// Lowers the configured version to the abi3 version, if set. -fn fixup_config_for_abi3( - config: &mut InterpreterConfig, - abi3_version: Option, -) -> Result<()> { - // PyPy doesn't support abi3; don't adjust the version - if config.implementation.is_pypy() { - return Ok(()); - } - - if let Some(version) = abi3_version { - ensure!( - version <= config.version, - "cannot set a minimum Python version {} higher than the interpreter version {} \ - (the minimum Python version is implied by the abi3-py3{} feature)", - version, - config.version, - version.minor, - ); - - config.version = version; - } - Ok(()) -} - /// 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 /// CARGO_CFG_TARGET_OS which aren't available at any other time. pub fn make_cross_compile_config() -> Result> { - let env_vars = cross_compile_env_vars(); - - let host = cargo_env_var("HOST").ok_or("expected HOST env var")?; - let target = cargo_env_var("TARGET").ok_or("expected TARGET env var")?; - - let target_info = TargetInfo::from_cargo_env()?; - - let interpreter_config = if env_vars.any() { - let cross_config = CrossCompileConfig::from_env_vars(env_vars, target_info)?; + let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? { let mut interpreter_config = load_cross_compile_config(cross_config)?; - fixup_config_for_abi3(&mut interpreter_config, get_abi3_version())?; + interpreter_config.fixup_for_abi3_version(get_abi3_version())?; Some(interpreter_config) } else { - ensure!( - host == target || is_not_cross_compiling(&host, &target_info), - "PyO3 detected compile host {host} and build target {target}, but none of PYO3_CROSS, PYO3_CROSS_LIB_DIR \ - or PYO3_CROSS_PYTHON_VERSION environment variables are set.", - host=host, - target=target, - ); None }; @@ -1414,13 +1503,14 @@ pub fn make_cross_compile_config() -> Result> { #[allow(dead_code)] pub fn make_interpreter_config() -> Result { let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?; - fixup_config_for_abi3(&mut interpreter_config, get_abi3_version())?; + interpreter_config.fixup_for_abi3_version(get_abi3_version())?; Ok(interpreter_config) } #[cfg(test)] mod tests { use std::{io::Cursor, iter::FromIterator}; + use target_lexicon::triple; use super::*; @@ -1670,11 +1760,7 @@ mod tests { let cross_config = CrossCompileConfig { lib_dir: "C:\\some\\path".into(), version: Some(PythonVersion { major: 3, minor: 7 }), - target_info: TargetInfo { - os: "os".into(), - arch: "arch".into(), - vendor: "vendor".into(), - }, + target: triple!("i686-pc-windows-msvc"), }; assert_eq!( @@ -1695,6 +1781,32 @@ mod tests { ); } + #[test] + fn mingw_hardcoded_cross_compile() { + let cross_config = CrossCompileConfig { + lib_dir: "/usr/lib/mingw".into(), + version: Some(PythonVersion { major: 3, minor: 8 }), + target: triple!("i686-pc-windows-gnu"), + }; + + assert_eq!( + super::windows_hardcoded_cross_compile(cross_config).unwrap(), + InterpreterConfig { + implementation: PythonImplementation::CPython, + version: PythonVersion { major: 3, minor: 8 }, + shared: true, + abi3: false, + lib_name: Some("python3.8".into()), + lib_dir: Some("/usr/lib/mingw".into()), + executable: None, + pointer_width: None, + build_flags: BuildFlags::default(), + suppress_build_script_link_lines: false, + extra_build_script_lines: vec![], + } + ); + } + #[test] fn default_lib_name_windows() { use PythonImplementation::*; @@ -1780,6 +1892,36 @@ mod tests { ); } + #[test] + fn parse_cross_python_version() { + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_version: Some("3.9".into()), + }; + + assert_eq!( + env_vars.parse_version().unwrap(), + Some(PythonVersion { major: 3, minor: 9 }) + ); + + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_version: None, + }; + + assert_eq!(env_vars.parse_version().unwrap(), None); + + let env_vars = CrossCompileEnvVars { + pyo3_cross: None, + pyo3_cross_lib_dir: None, + pyo3_cross_python_version: Some("100".into()), + }; + + assert!(env_vars.parse_version().is_err()); + } + #[test] fn interpreter_version_reduced_to_abi3() { let mut config = InterpreterConfig { @@ -1796,7 +1938,9 @@ mod tests { extra_build_script_lines: vec![], }; - fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 7 })).unwrap(); + config + .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 })) + .unwrap(); assert_eq!(config.version, PythonVersion { major: 3, minor: 7 }); } @@ -1816,12 +1960,13 @@ mod tests { extra_build_script_lines: vec![], }; - assert!( - fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 8 })) - .unwrap_err() - .to_string() - .contains("cannot set a minimum Python version 3.8 higher than the interpreter version 3.7") - ); + assert!(config + .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 })) + .unwrap_err() + .to_string() + .contains( + "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7" + )); } #[test] @@ -1846,11 +1991,7 @@ mod tests { let cross = CrossCompileConfig { lib_dir: lib_dir.into(), version: Some(interpreter_config.version), - target_info: TargetInfo { - arch: "x86_64".into(), - vendor: "unknown".into(), - os: "linux".into(), - }, + target: triple!("x86_64-unknown-linux-gnu"), }; let sysconfigdata_path = match find_sysconfigdata(&cross) { @@ -1906,6 +2047,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_not_cross_compiling() { assert!( cross_compiling("aarch64-apple-darwin", "x86_64", "apple", "darwin") @@ -1924,6 +2066,51 @@ mod tests { ); } + #[test] + fn test_not_cross_compiling_from_to() { + assert!(cross_compiling_from_to( + &triple!("x86_64-unknown-linux-gnu"), + &triple!("x86_64-unknown-linux-gnu"), + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-apple-darwin"), + &triple!("x86_64-apple-darwin") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("aarch64-apple-darwin"), + &triple!("x86_64-apple-darwin") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-apple-darwin"), + &triple!("aarch64-apple-darwin") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-pc-windows-msvc"), + &triple!("i686-pc-windows-msvc") + ) + .unwrap() + .is_none()); + + assert!(cross_compiling_from_to( + &triple!("x86_64-unknown-linux-gnu"), + &triple!("x86_64-unknown-linux-musl") + ) + .unwrap() + .is_none()); + } + #[test] fn test_run_python_script() { // as above, this should be okay in CI where Python is presumed installed diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index 4cf49e3b49b..bd86e30db4e 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -17,9 +17,11 @@ use std::{env, process::Command}; #[cfg(feature = "resolve-config")] use once_cell::sync::OnceCell; +#[allow(deprecated)] pub use impl_::{ - cross_compiling, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, - CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, + cross_compiling, cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, + BuildFlag, BuildFlags, CrossCompileConfig, InterpreterConfig, PythonImplementation, + PythonVersion, Triple, }; /// Adds all the [`#[cfg]` flags](index.html) to the current compilation. @@ -166,7 +168,8 @@ pub mod pyo3_build_script_impl { pub use crate::errors::*; } pub use crate::impl_::{ - cargo_env_var, env_var, make_cross_compile_config, InterpreterConfig, PythonVersion, + cargo_env_var, env_var, is_linking_libpython, make_cross_compile_config, InterpreterConfig, + PythonVersion, }; /// Gets the configuration for use from PyO3's build script. @@ -180,7 +183,7 @@ pub mod pyo3_build_script_impl { InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) } else if !ABI3_CONFIG.is_empty() { Ok(abi3_config()) - } else if let Some(interpreter_config) = impl_::make_cross_compile_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 = Path::new(DEFAULT_CROSS_COMPILE_CONFIG_PATH); let parent_dir = path.parent().ok_or_else(|| { diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index ddc60e62e5e..f46c13b50bd 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -1,8 +1,8 @@ use pyo3_build_config::{ bail, ensure, print_feature_cfgs, pyo3_build_script_impl::{ - cargo_env_var, env_var, errors::Result, resolve_interpreter_config, InterpreterConfig, - PythonVersion, + cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, + InterpreterConfig, PythonVersion, }, }; @@ -44,33 +44,27 @@ fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result fn emit_link_config(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(); - 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( - "attempted to link to Python shared library but config does not contain lib_name" - )?, - ); - if let Some(lib_dir) = &interpreter_config.lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); - } - } - // serialize the whole interpreter config in DEP_PYTHON_PYO3_CONFIG - interpreter_config.to_cargo_dep_env()?; + 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( + "attempted to link to Python shared library but config does not contain lib_name" + )?, + ); + + if let Some(lib_dir) = &interpreter_config.lib_dir { + println!("cargo:rustc-link-search=native={}", lib_dir); + } Ok(()) } @@ -92,7 +86,10 @@ fn configure_pyo3() -> Result<()> { ensure_python_version(&interpreter_config)?; ensure_target_pointer_width(&interpreter_config)?; - if !interpreter_config.suppress_build_script_link_lines { + // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. + interpreter_config.to_cargo_dep_env()?; + + if is_linking_libpython() && !interpreter_config.suppress_build_script_link_lines { emit_link_config(&interpreter_config)?; }