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 4051f6ae1da..95a1cc3bee3 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -12,6 +12,8 @@ use std::{ str::FromStr, }; +pub use target_lexicon::{Architecture, Environment, OperatingSystem, Triple}; + use crate::{ bail, ensure, errors::{Context, Error, Result}, @@ -40,6 +42,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 @@ -654,108 +666,17 @@ pub fn is_extension_module() -> bool { /// /// Must be called from a crate PyO3 build script. pub fn is_linking_libpython() -> bool { - let target = TargetInfo::from_cargo_env().expect("must be called from a build script"); - - is_linking_libpython_for_target(&target) + 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. -fn is_linking_libpython_for_target(target: &TargetInfo) -> bool { - target.os == "windows" || target.os == "android" || !is_extension_module() -} - -#[derive(Debug, PartialEq)] -struct TargetInfo { - /// The `arch` component of the compilation target triple. - /// - /// e.g. x86_64, aarch64, x86, arm, thumb, mips, etc. - arch: String, - - /// The `vendor` component of the compilation target triple. - /// - /// e.g. apple, pc, unknown, etc. - vendor: String, - - /// The `os` component of the compilation target triple. - /// - /// e.g. darwin, freebsd, linux, windows, etc. - os: String, - - /// The optional `env` component of the compilation target triple. - /// - /// e.g. gnu, musl, msvc, etc. - env: Option, -} - -impl TargetInfo { - fn from_triple(arch: &str, vendor: &str, os: &str, env: Option<&str>) -> Self { - TargetInfo { - arch: arch.to_owned(), - vendor: vendor.to_owned(), - os: os.to_owned(), - env: env.map(|s| s.to_owned()), - } - } - - /// Gets the compile target from the Cargo environment. - /// - /// Must be called from a build script. - 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")?, - env: cargo_env_var("CARGO_CFG_TARGET_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 - } - ) - } - - fn is_cross_compiling_from(&self, host: &str) -> bool { - let target_triple = self.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 - let host_target_compatible = 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"); - - // Cross compiling if not compiler compatible - !host_target_compatible - } - - fn is_windows(&self) -> bool { - self.os == "windows" - } - - fn is_windows_mingw(&self) -> bool { - self.is_windows() && self.env.as_ref().map_or(false, |env| env == "gnu") - } +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. @@ -770,8 +691,8 @@ 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 { @@ -779,18 +700,19 @@ impl CrossCompileConfig { /// and the build environment when cross compilation mode is detected. /// /// Returns `None` when not cross compiling. - fn try_from_env_vars( + fn try_from_env_vars_host_target( env_vars: CrossCompileEnvVars, - host: &str, - target_info: TargetInfo, + host: &Triple, + target: &Triple, ) -> Result> { - let maybe_config = if env_vars.any() || target_info.is_cross_compiling_from(host) { + let maybe_config = 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(); Some(CrossCompileConfig { lib_dir, - target_info, + target, version, }) } else { @@ -800,6 +722,25 @@ impl CrossCompileConfig { Ok(maybe_config) } + 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 @@ -880,16 +821,9 @@ 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. -pub fn cross_compiling( - host: &str, - target_arch: &str, - target_vendor: &str, - target_os: &str, -) -> Result> { +pub fn cross_compiling(host: &Triple, target: &Triple) -> Result> { let env_vars = cross_compile_env_vars(); - let target_info = TargetInfo::from_triple(target_arch, target_vendor, target_os, None); - - CrossCompileConfig::try_from_env_vars(env_vars, host, target_info) + CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target) } /// Detect whether we are cross compiling from Cargo and `PYO3_CROSS_*` environment @@ -899,10 +833,10 @@ pub fn cross_compiling( /// 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 = cargo_env_var("HOST").ok_or("expected HOST env var")?; - let target_info = TargetInfo::from_cargo_env()?; + let host = Triple::host(); + let target = target_triple_from_env(); - CrossCompileConfig::try_from_env_vars(env_vars, &host, target_info) + CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) } #[allow(non_camel_case_types)] @@ -1245,15 +1179,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() { @@ -1324,12 +1257,12 @@ 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_info.is_windows() { + if cross_compile_config.target.operating_system == OperatingSystem::Windows { return default_cross_compile(&cross_compile_config); } @@ -1572,6 +1505,7 @@ pub fn make_interpreter_config() -> Result { #[cfg(test)] mod tests { use std::{io::Cursor, iter::FromIterator}; + use target_lexicon::triple; use super::*; @@ -1821,7 +1755,7 @@ mod tests { let cross_config = CrossCompileConfig { lib_dir: Some("C:\\some\\path".into()), version: Some(PythonVersion { major: 3, minor: 7 }), - target_info: TargetInfo::from_triple("x86", "pc", "windows", Some("msvc")), + target: triple!("i686-pc-windows-msvc"), }; assert_eq!( @@ -1847,7 +1781,7 @@ mod tests { let cross_config = CrossCompileConfig { lib_dir: Some("/usr/lib/mingw".into()), version: Some(PythonVersion { major: 3, minor: 8 }), - target_info: TargetInfo::from_triple("x86", "pc", "windows", Some("gnu")), + target: triple!("i686-pc-windows-gnu"), }; assert_eq!( @@ -1873,7 +1807,7 @@ mod tests { let cross_config = CrossCompileConfig { lib_dir: Some("/usr/arm64/lib".into()), version: Some(PythonVersion { major: 3, minor: 9 }), - target_info: TargetInfo::from_triple("aarch64", "unknown", "linux", Some("gnu")), + target: triple!("aarch64-unknown-linux-gnu"), }; assert_eq!( @@ -2086,7 +2020,7 @@ mod tests { let cross = CrossCompileConfig { lib_dir: Some(lib_dir.into()), version: Some(interpreter_config.version), - target_info: TargetInfo::from_triple("x86_64", "unknown", "linux", Some("gnu")), + target: triple!("x86_64-unknown-linux-gnu"), }; let sysconfigdata_path = match find_sysconfigdata(&cross) { @@ -2143,21 +2077,30 @@ mod tests { #[test] fn test_not_cross_compiling() { - assert!( - cross_compiling("aarch64-apple-darwin", "x86_64", "apple", "darwin") - .unwrap() - .is_none() - ); - assert!( - cross_compiling("x86_64-apple-darwin", "aarch64", "apple", "darwin") - .unwrap() - .is_none() - ); - assert!( - cross_compiling("x86_64-unknown-linux-gnu", "x86_64", "unknown", "linux") - .unwrap() - .is_none() - ); + assert!(cross_compiling( + &triple!("aarch64-apple-darwin"), + &triple!("x86_64-apple-darwin") + ) + .unwrap() + .is_none()); + assert!(cross_compiling( + &triple!("x86_64-apple-darwin"), + &triple!("aarch64-apple-darwin") + ) + .unwrap() + .is_none()); + assert!(cross_compiling( + &triple!("x86_64-pc-windows-msvc"), + &triple!("i686-pc-windows-msvc") + ) + .unwrap() + .is_none()); + assert!(cross_compiling( + &triple!("x86_64-unknown-linux-gnu"), + &triple!("x86_64-unknown-linux-musl") + ) + .unwrap() + .is_none()); } #[test] diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index 68ce2e6991c..c22bf4e85cd 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -21,7 +21,7 @@ use once_cell::sync::OnceCell; pub use impl_::{ cross_compiling, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, - CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, + CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, Triple }; /// Adds all the [`#[cfg]` flags](index.html) to the current compilation.