Skip to content

Commit

Permalink
pyo3-build-config: Replace TargetInfo with target_lexicon::Triple
Browse files Browse the repository at this point in the history
WORK IN PROGRESS
  • Loading branch information
ravenexp committed Mar 25, 2022
1 parent 29baf4d commit 65b382e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 147 deletions.
4 changes: 4 additions & 0 deletions pyo3-build-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ edition = "2018"

[dependencies]
once_cell = "1"
target-lexicon = "0.12"

[build-dependencies]
target-lexicon = "0.12"

[features]
default = []
Expand Down
235 changes: 89 additions & 146 deletions pyo3-build-config/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use std::{
str::FromStr,
};

pub use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};

use crate::{
bail, ensure,
errors::{Context, Error, Result},
Expand Down Expand Up @@ -40,6 +42,16 @@ pub fn env_var(var: &str) -> Option<OsString> {
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
Expand Down Expand Up @@ -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<String>,
}

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<Self> {
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.
Expand All @@ -770,27 +691,28 @@ pub struct CrossCompileConfig {
/// The version of the Python library to link against.
version: Option<PythonVersion>,

/// The target information
target_info: TargetInfo,
/// The compile target triple (e.g. aarch64-unknown-linux-gnu)
target: Triple,
}

impl CrossCompileConfig {
/// 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(
fn try_from_env_vars_host_target(
env_vars: CrossCompileEnvVars,
host: &str,
target_info: TargetInfo,
host: &Triple,
target: &Triple,
) -> Result<Option<Self>> {
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 {
Expand All @@ -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
Expand Down Expand Up @@ -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<Option<CrossCompileConfig>> {
pub fn cross_compiling(host: &Triple, target: &Triple) -> Result<Option<CrossCompileConfig>> {
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
Expand All @@ -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<Option<CrossCompileConfig>> {
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)]
Expand Down Expand Up @@ -1245,15 +1179,11 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
search_lib_dir(f.path(), cross)
} else if file_name.starts_with("lib.") {
// check if right target os
if !file_name.contains(if cross.target_info.os == "android" {
"linux"
} else {
&cross.target_info.os
}) {
if !file_name.contains(&cross.target.operating_system.to_string()) {
continue;
}
// Check if right arch
if !file_name.contains(&cross.target_info.arch) {
if !file_name.contains(&cross.target.architecture.to_string()) {
continue;
}
search_lib_dir(f.path(), cross)
Expand All @@ -1278,7 +1208,10 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
if sysconfig_paths.len() > 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::<Vec<PathBuf>>();
if !temp.is_empty() {
Expand Down Expand Up @@ -1324,12 +1257,12 @@ fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result<In
when cross-compiling and PYO3_CROSS_LIB_DIR is not set.",
)?;

let target = &cross_compile_config.target_info;
let target = &cross_compile_config.target;

let abi3 = is_abi3();
let implementation = PythonImplementation::CPython;
let lib_name = if target.is_windows() {
let mingw = target.is_windows_mingw();
let lib_name = if target.operating_system == OperatingSystem::Windows {
let mingw = target.environment == Environment::Gnu;

Some(default_lib_name_windows(
version,
Expand Down Expand Up @@ -1372,7 +1305,7 @@ fn load_cross_compile_config(
) -> Result<InterpreterConfig> {
// 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);
}

Expand Down Expand Up @@ -1572,6 +1505,7 @@ pub fn make_interpreter_config() -> Result<InterpreterConfig> {
#[cfg(test)]
mod tests {
use std::{io::Cursor, iter::FromIterator};
use target_lexicon::triple;

use super::*;

Expand Down Expand Up @@ -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!(
Expand All @@ -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!(
Expand All @@ -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!(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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]
Expand Down
Loading

0 comments on commit 65b382e

Please sign in to comment.