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.
This commit is contained in:
Sergey Kvachonok 2022-03-25 12:12:05 +03:00
parent de060573d3
commit 9b45a19161
5 changed files with 390 additions and 198 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### 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) - 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 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) - Added FFI definitions for `PyType_FromModuleAndSpec`, `PyType_GetModule`, `PyType_GetModuleState` and `PyModule_AddType`. [#2250](https://github.com/PyO3/pyo3/pull/2250)

View File

@ -12,6 +12,10 @@ edition = "2018"
[dependencies] [dependencies]
once_cell = "1" once_cell = "1"
target-lexicon = "0.12"
[build-dependencies]
target-lexicon = "0.12"
[features] [features]
default = [] default = []

View File

@ -12,6 +12,10 @@ use std::{
str::FromStr, str::FromStr,
}; };
pub use target_lexicon::Triple;
use target_lexicon::{Architecture, BinaryFormat, Environment, OperatingSystem, Vendor};
use crate::{ use crate::{
bail, ensure, bail, ensure,
errors::{Context, Error, Result}, errors::{Context, Error, Result},
@ -40,6 +44,16 @@ pub fn env_var(var: &str) -> Option<OsString> {
env::var_os(var) 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. /// Configuration needed by PyO3 to build for the correct Python implementation.
/// ///
/// Usually this is queried directly from the Python interpreter, or overridden using the /// Usually this is queried directly from the Python interpreter, or overridden using the
@ -524,6 +538,29 @@ print("mingw", get_platform().startswith("mingw"))
envs, envs,
) )
} }
/// Lowers the configured version to the abi3 version, if set.
fn fixup_for_abi3_version(&mut self, abi3_version: Option<PythonVersion>) -> 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
@ -608,52 +645,37 @@ fn is_abi3() -> bool {
cargo_env_var("CARGO_FEATURE_ABI3").is_some() cargo_env_var("CARGO_FEATURE_ABI3").is_some()
} }
#[derive(Debug, PartialEq)] /// Gets the minimum supported Python version from PyO3 `abi3-py*` features.
struct TargetInfo { ///
/// The `arch` component of the compilation target triple. /// Must be called from a PyO3 crate build script.
/// pub fn get_abi3_version() -> Option<PythonVersion> {
/// e.g. x86_64, i386, arm, thumb, mips, etc. let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR)
arch: String, .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some());
minor_version.map(|minor| PythonVersion { major: 3, minor })
/// 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,
} }
impl TargetInfo { /// Checks if the `extension-module` feature is enabled for the PyO3 crate.
fn from_cargo_env() -> Result<Self> { ///
Ok(Self { /// Must be called from a PyO3 crate build script.
arch: cargo_env_var("CARGO_CFG_TARGET_ARCH") pub fn is_extension_module() -> bool {
.ok_or("expected CARGO_CFG_TARGET_ARCH env var")?, cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some()
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")?,
})
}
fn to_target_triple(&self) -> String { /// Checks if we need to link to `libpython` for the current build target.
format!( ///
"{}-{}-{}", /// Must be called from a crate PyO3 build script.
if self.arch == "x86" { pub fn is_linking_libpython() -> bool {
"i686" is_linking_libpython_for_target(&target_triple_from_env())
} else { }
&self.arch
}, /// Checks if we need to link to `libpython` for the target.
self.vendor, ///
if self.os == "macos" { /// Must be called from a crate PyO3 build script.
"darwin" fn is_linking_libpython_for_target(target: &Triple) -> bool {
} else { target.operating_system == OperatingSystem::Windows
&self.os || target.environment == Environment::Android
} || target.environment == Environment::Androideabi
) || !is_extension_module()
}
} }
/// Configuration needed by PyO3 to cross-compile for a target platform. /// 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. /// The version of the Python library to link against.
version: Option<PythonVersion>, version: Option<PythonVersion>,
/// The target information /// The compile target triple (e.g. aarch64-unknown-linux-gnu)
target_info: TargetInfo, target: Triple,
} }
impl CrossCompileConfig { impl CrossCompileConfig {
fn from_env_vars(env_vars: CrossCompileEnvVars, target_info: TargetInfo) -> Result<Self> { /// Creates a new cross compile config struct from PyO3 environment variables
Ok(CrossCompileConfig { /// and the build environment when cross compilation mode is detected.
lib_dir: env_vars ///
.pyo3_cross_lib_dir /// Returns `None` when not cross compiling.
.ok_or( fn try_from_env_vars_host_target(
"The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling", env_vars: CrossCompileEnvVars,
)? host: &Triple,
.into(), target: &Triple,
target_info, ) -> Result<Option<Self>> {
version: env_vars if env_vars.any() || Self::is_cross_compiling_from_to(host, target) {
.pyo3_cross_python_version let lib_dir = env_vars.lib_dir_path()?;
.map(|os_string| { let version = env_vars.parse_version()?;
let utf8_str = os_string let target = target.clone();
.to_str()
.ok_or("PYO3_CROSS_PYTHON_VERSION is not valid utf-8.")?; Ok(Some(CrossCompileConfig {
utf8_str lib_dir,
.parse() target,
.context("failed to parse PYO3_CROSS_PYTHON_VERSION") version,
}) }))
.transpose()?, } 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_lib_dir.is_some()
|| self.pyo3_cross_python_version.is_some() || self.pyo3_cross_python_version.is_some()
} }
/// Parses `PYO3_CROSS_PYTHON_VERSION` environment variable value
/// into `PythonVersion`.
fn parse_version(&self) -> Result<Option<PythonVersion>> {
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<PathBuf> {
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 { pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars {
@ -733,39 +824,76 @@ pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars {
/// `PYO3_CROSS_LIB_DIR`. /// `PYO3_CROSS_LIB_DIR`.
/// ///
/// See the [PyO3 User Guide](https://pyo3.rs/) for more info on cross-compiling. /// 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( pub fn cross_compiling(
host: &str, host: &str,
target_arch: &str, target_arch: &str,
target_vendor: &str, target_vendor: &str,
target_os: &str, target_os: &str,
) -> Result<Option<CrossCompileConfig>> { ) -> Result<Option<CrossCompileConfig>> {
let env_vars = cross_compile_env_vars(); let host: Triple = host.parse().map_err(|_| "bad host triple")?;
let target_info = TargetInfo { let architecture: Architecture = target_arch.parse().map_err(|_| "bad target arch")?;
arch: target_arch.to_owned(), let vendor: Vendor = target_vendor.parse().map_err(|_| "bad target vendor")?;
vendor: target_vendor.to_owned(), let operating_system: OperatingSystem = target_os.parse().map_err(|_| "bad target os")?;
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) { // FIXME: This field is currently unused.
return Ok(None); let binary_format = BinaryFormat::Elf;
}
CrossCompileConfig::from_env_vars(env_vars, target_info).map(Some) let target = Triple {
architecture,
vendor,
operating_system,
environment,
binary_format,
};
cross_compiling_from_to(&host, &target)
} }
fn is_not_cross_compiling(host: &str, target_info: &TargetInfo) -> bool { /// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so.
let target_triple = target_info.to_target_triple(); ///
// Not cross-compiling if arch-vendor-os is all the same /// This function relies on PyO3 cross-compiling environment variables:
// 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 /// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation.
host.starts_with(&target_triple) /// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing
// Not cross-compiling to compile for 32-bit Python from windows 64-bit /// the target's libpython DSO and the associated `_sysconfigdata*.py` file for
|| (target_triple == "i686-pc-windows" && host.starts_with("x86_64-pc-windows")) /// Unix-like targets, or the Python DLL import libraries for the Windows target.
// Not cross-compiling to compile for x86-64 Python from macOS arm64 /// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python
|| (target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin") /// installation. This variable is only needed if PyO3 cannnot determine the version to target
// Not cross-compiling to compile for arm64 Python from macOS x86_64 /// from `abi3-py3*` features, or if there are multiple versions of Python present in
|| (target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin") /// `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<Option<CrossCompileConfig>> {
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<Option<CrossCompileConfig>> {
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)] #[allow(non_camel_case_types)]
@ -1091,15 +1219,11 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
search_lib_dir(f.path(), cross) search_lib_dir(f.path(), cross)
} else if file_name.starts_with("lib.") { } else if file_name.starts_with("lib.") {
// check if right target os // check if right target os
if !file_name.contains(if cross.target_info.os == "android" { if !file_name.contains(&cross.target.operating_system.to_string()) {
"linux"
} else {
&cross.target_info.os
}) {
continue; continue;
} }
// Check if right arch // Check if right arch
if !file_name.contains(&cross.target_info.arch) { if !file_name.contains(&cross.target.architecture.to_string()) {
continue; continue;
} }
search_lib_dir(f.path(), cross) search_lib_dir(f.path(), cross)
@ -1124,7 +1248,10 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
if sysconfig_paths.len() > 1 { if sysconfig_paths.len() > 1 {
let temp = sysconfig_paths let temp = sysconfig_paths
.iter() .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() .cloned()
.collect::<Vec<PathBuf>>(); .collect::<Vec<PathBuf>>();
if !temp.is_empty() { if !temp.is_empty() {
@ -1150,11 +1277,19 @@ fn cross_compile_from_sysconfigdata(
fn windows_hardcoded_cross_compile( fn windows_hardcoded_cross_compile(
cross_compile_config: CrossCompileConfig, cross_compile_config: CrossCompileConfig,
) -> Result<InterpreterConfig> { ) -> Result<InterpreterConfig> {
let version = cross_compile_config.version.or_else(get_abi3_version) let version = cross_compile_config
.ok_or("PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified when cross-compiling for Windows.")?; .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 abi3 = is_abi3();
let implementation = PythonImplementation::CPython; 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 { Ok(InterpreterConfig {
implementation, implementation,
@ -1165,9 +1300,9 @@ fn windows_hardcoded_cross_compile(
version, version,
PythonImplementation::CPython, PythonImplementation::CPython,
abi3, abi3,
false, mingw,
)), )),
lib_dir: cross_compile_config.lib_dir.to_str().map(String::from), lib_dir,
executable: None, executable: None,
pointer_width: None, pointer_width: None,
build_flags: BuildFlags::default(), build_flags: BuildFlags::default(),
@ -1303,7 +1438,7 @@ fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
fn get_env_interpreter() -> Option<PathBuf> { fn get_env_interpreter() -> Option<PathBuf> {
match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) { 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 // build host
(Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))), (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))),
(None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))), (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))),
@ -1347,62 +1482,16 @@ pub fn find_interpreter() -> Result<PathBuf> {
} }
} }
pub fn get_abi3_version() -> Option<PythonVersion> {
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<PythonVersion>,
) -> 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. /// 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 /// 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. /// CARGO_CFG_TARGET_OS which aren't available at any other time.
pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> { pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
let env_vars = cross_compile_env_vars(); let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? {
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 mut interpreter_config = load_cross_compile_config(cross_config)?; 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) Some(interpreter_config)
} else { } 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 None
}; };
@ -1414,13 +1503,14 @@ pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
#[allow(dead_code)] #[allow(dead_code)]
pub fn make_interpreter_config() -> Result<InterpreterConfig> { pub fn make_interpreter_config() -> Result<InterpreterConfig> {
let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?; 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) Ok(interpreter_config)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{io::Cursor, iter::FromIterator}; use std::{io::Cursor, iter::FromIterator};
use target_lexicon::triple;
use super::*; use super::*;
@ -1670,11 +1760,7 @@ mod tests {
let cross_config = CrossCompileConfig { let cross_config = CrossCompileConfig {
lib_dir: "C:\\some\\path".into(), lib_dir: "C:\\some\\path".into(),
version: Some(PythonVersion { major: 3, minor: 7 }), version: Some(PythonVersion { major: 3, minor: 7 }),
target_info: TargetInfo { target: triple!("i686-pc-windows-msvc"),
os: "os".into(),
arch: "arch".into(),
vendor: "vendor".into(),
},
}; };
assert_eq!( 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] #[test]
fn default_lib_name_windows() { fn default_lib_name_windows() {
use PythonImplementation::*; 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] #[test]
fn interpreter_version_reduced_to_abi3() { fn interpreter_version_reduced_to_abi3() {
let mut config = InterpreterConfig { let mut config = InterpreterConfig {
@ -1796,7 +1938,9 @@ mod tests {
extra_build_script_lines: vec![], 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 }); assert_eq!(config.version, PythonVersion { major: 3, minor: 7 });
} }
@ -1816,12 +1960,13 @@ mod tests {
extra_build_script_lines: vec![], extra_build_script_lines: vec![],
}; };
assert!( assert!(config
fixup_config_for_abi3(&mut config, Some(PythonVersion { major: 3, minor: 8 })) .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 }))
.unwrap_err() .unwrap_err()
.to_string() .to_string()
.contains("cannot set a minimum Python version 3.8 higher than the interpreter version 3.7") .contains(
); "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7"
));
} }
#[test] #[test]
@ -1846,11 +1991,7 @@ mod tests {
let cross = CrossCompileConfig { let cross = CrossCompileConfig {
lib_dir: lib_dir.into(), lib_dir: lib_dir.into(),
version: Some(interpreter_config.version), version: Some(interpreter_config.version),
target_info: TargetInfo { target: triple!("x86_64-unknown-linux-gnu"),
arch: "x86_64".into(),
vendor: "unknown".into(),
os: "linux".into(),
},
}; };
let sysconfigdata_path = match find_sysconfigdata(&cross) { let sysconfigdata_path = match find_sysconfigdata(&cross) {
@ -1906,6 +2047,7 @@ mod tests {
} }
#[test] #[test]
#[allow(deprecated)]
fn test_not_cross_compiling() { fn test_not_cross_compiling() {
assert!( assert!(
cross_compiling("aarch64-apple-darwin", "x86_64", "apple", "darwin") 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] #[test]
fn test_run_python_script() { fn test_run_python_script() {
// as above, this should be okay in CI where Python is presumed installed // as above, this should be okay in CI where Python is presumed installed

View File

@ -17,9 +17,11 @@ use std::{env, process::Command};
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
#[allow(deprecated)]
pub use impl_::{ pub use impl_::{
cross_compiling, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, cross_compiling, cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata,
CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, BuildFlag, BuildFlags, CrossCompileConfig, InterpreterConfig, PythonImplementation,
PythonVersion, Triple,
}; };
/// Adds all the [`#[cfg]` flags](index.html) to the current compilation. /// 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::errors::*;
} }
pub use crate::impl_::{ 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. /// 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)) InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
} else if !ABI3_CONFIG.is_empty() { } else if !ABI3_CONFIG.is_empty() {
Ok(abi3_config()) 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. // This is a cross compile and need to write the config file.
let path = Path::new(DEFAULT_CROSS_COMPILE_CONFIG_PATH); let path = Path::new(DEFAULT_CROSS_COMPILE_CONFIG_PATH);
let parent_dir = path.parent().ok_or_else(|| { let parent_dir = path.parent().ok_or_else(|| {

View File

@ -1,8 +1,8 @@
use pyo3_build_config::{ use pyo3_build_config::{
bail, ensure, print_feature_cfgs, bail, ensure, print_feature_cfgs,
pyo3_build_script_impl::{ pyo3_build_script_impl::{
cargo_env_var, env_var, errors::Result, resolve_interpreter_config, InterpreterConfig, cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config,
PythonVersion, InterpreterConfig, PythonVersion,
}, },
}; };
@ -44,33 +44,27 @@ fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result
fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); 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 println!(
interpreter_config.to_cargo_dep_env()?; "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(()) Ok(())
} }
@ -92,7 +86,10 @@ fn configure_pyo3() -> Result<()> {
ensure_python_version(&interpreter_config)?; ensure_python_version(&interpreter_config)?;
ensure_target_pointer_width(&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)?; emit_link_config(&interpreter_config)?;
} }