pyo3-build-config: Create per-target cross config files

Rename `$OUT_DIR/pyo3-cross-compile-config.txt` to
`$OUT_DIR/<triple>/pyo3-build-config.txt` to exclude the possibility
of using stale build configuration data when the build target changes.

Use the presence of the corresponding build configuration file
in the `pyo3-build-config` build script output directory
to detect whether we are cross compiling or not.

This patch enables cross compilation without using
any of `PYO3_CROSS_*` env variables in many cases.
This commit is contained in:
Sergey Kvachonok 2022-03-24 14:37:03 +03:00
parent 328e7d69f6
commit ccda497e04
2 changed files with 33 additions and 13 deletions

View File

@ -759,7 +759,7 @@ pub(crate) struct CrossCompileEnvVars {
} }
impl CrossCompileEnvVars { impl CrossCompileEnvVars {
pub fn any(&self) -> bool { fn any(&self) -> bool {
self.pyo3_cross.is_some() self.pyo3_cross.is_some()
|| 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()

View File

@ -11,7 +11,11 @@ mod errors;
mod impl_; mod impl_;
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
use std::io::Cursor; use std::{
io::Cursor,
path::{Path, PathBuf},
};
use std::{env, process::Command}; use std::{env, process::Command};
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
@ -69,14 +73,21 @@ fn _add_extension_module_link_args(target_os: &str, mut writer: impl std::io::Wr
pub fn get() -> &'static InterpreterConfig { pub fn get() -> &'static InterpreterConfig {
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new(); static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
CONFIG.get_or_init(|| { CONFIG.get_or_init(|| {
// Check if we are in a build script and cross compiling to a different target.
let cross_compile_config_path = resolve_cross_compile_config_path();
let cross_compiling = cross_compile_config_path
.as_ref()
.map(|path| path.exists())
.unwrap_or(false);
if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() { if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
interpreter_config interpreter_config
} else if !CONFIG_FILE.is_empty() { } else if !CONFIG_FILE.is_empty() {
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 impl_::cross_compile_env_vars().any() { } else if cross_compiling {
InterpreterConfig::from_path(DEFAULT_CROSS_COMPILE_CONFIG_PATH) InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap())
} else { } else {
InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG)) InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
} }
@ -84,12 +95,6 @@ pub fn get() -> &'static InterpreterConfig {
}) })
} }
/// Path where PyO3's build.rs will write configuration by default.
#[doc(hidden)]
#[cfg(feature = "resolve-config")]
const DEFAULT_CROSS_COMPILE_CONFIG_PATH: &str =
concat!(env!("OUT_DIR"), "/pyo3-cross-compile-config.txt");
/// Build configuration provided by `PYO3_CONFIG_FILE`. May be empty if env var not set. /// Build configuration provided by `PYO3_CONFIG_FILE`. May be empty if env var not set.
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
@ -107,6 +112,22 @@ const ABI3_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-con
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt")); const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt"));
/// Returns the path where PyO3's build.rs writes its cross compile configuration.
///
/// The config file will be named `$OUT_DIR/<triple>/pyo3-build-config.txt`.
///
/// Must be called from a build script, returns `None` if not.
#[doc(hidden)]
#[cfg(feature = "resolve-config")]
fn resolve_cross_compile_config_path() -> Option<PathBuf> {
env::var_os("TARGET").map(|target| {
let mut path = PathBuf::from(env!("OUT_DIR"));
path.push(Path::new(&target));
path.push("pyo3-build-config.txt");
path
})
}
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
fn abi3_config() -> InterpreterConfig { fn abi3_config() -> InterpreterConfig {
let mut interpreter_config = InterpreterConfig::from_reader(Cursor::new(ABI3_CONFIG)) let mut interpreter_config = InterpreterConfig::from_reader(Cursor::new(ABI3_CONFIG))
@ -158,8 +179,6 @@ pub fn print_feature_cfgs() {
pub mod pyo3_build_script_impl { pub mod pyo3_build_script_impl {
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
use crate::errors::{Context, Result}; use crate::errors::{Context, Result};
#[cfg(feature = "resolve-config")]
use std::path::Path;
#[cfg(feature = "resolve-config")] #[cfg(feature = "resolve-config")]
use super::*; use super::*;
@ -185,7 +204,8 @@ pub mod pyo3_build_script_impl {
Ok(abi3_config()) Ok(abi3_config())
} else if let Some(interpreter_config) = 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 = resolve_cross_compile_config_path()
.expect("resolve_interpreter_config() must be called from a build script");
let parent_dir = path.parent().ok_or_else(|| { let parent_dir = path.parent().ok_or_else(|| {
format!( format!(
"failed to resolve parent directory of config file {}", "failed to resolve parent directory of config file {}",