pyo3-build-config: inline config when not cross compiling

This commit is contained in:
David Hewitt 2021-08-04 12:54:45 +01:00
parent 20b34a5528
commit 9507979d93
No known key found for this signature in database
GPG Key ID: 90D281038A647CAF
5 changed files with 136 additions and 68 deletions

View File

@ -1,9 +1,9 @@
use std::{env, ffi::OsString, path::Path, process::Command}; use std::{env, io::Cursor, path::Path, process::Command};
use pyo3_build_config::{ use pyo3_build_config::{
bail, cargo_env_var, ensure, env_var, bail, cargo_env_var, ensure, env_var,
errors::{Context, Result}, errors::{Context, Result},
InterpreterConfig, PythonVersion, make_cross_compile_config, InterpreterConfig, PythonVersion,
}; };
/// Minimum Python version PyO3 supports. /// Minimum Python version PyO3 supports.
@ -114,41 +114,16 @@ fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()
/// The result is written to pyo3_build_config::PATH, which downstream scripts can read from /// The result is written to pyo3_build_config::PATH, which downstream scripts can read from
/// (including `pyo3-macros-backend` during macro expansion). /// (including `pyo3-macros-backend` during macro expansion).
fn configure_pyo3() -> Result<()> { fn configure_pyo3() -> Result<()> {
let write_config_file = env_var("PYO3_WRITE_CONFIG_FILE").map_or(false, |os_str| os_str == "1"); let interpreter_config = if let Some(path) = env_var("PYO3_CONFIG_FILE") {
let custom_config_file_path = env_var("PYO3_CONFIG_FILE");
if let Some(path) = &custom_config_file_path {
ensure!(
Path::new(path).is_absolute(),
"PYO3_CONFIG_FILE must be absolute"
);
}
let (interpreter_config, path_to_write) = match (write_config_file, custom_config_file_path) {
(true, Some(path)) => {
// Create new interpreter config and write it to config file
(pyo3_build_config::make_interpreter_config()?, Some(path))
}
(true, None) => bail!("PYO3_CONFIG_FILE must be set when PYO3_WRITE_CONFIG_FILE is set"),
(false, Some(path)) => {
// Read custom config file
let path = Path::new(&path); let path = Path::new(&path);
// This is necessary because the compilations that access PYO3_CONFIG_FILE (build scripts,
// proc macros) have many different working directories, so a relative path is no good.
ensure!(path.is_absolute(), "PYO3_CONFIG_FILE must be an absolute path");
println!("cargo:rerun-if-changed={}", path.display()); println!("cargo:rerun-if-changed={}", path.display());
let config_file = std::fs::File::open(path) InterpreterConfig::from_path(path)?
.with_context(|| format!("failed to read config file at {}", path.display()))?; } else if let Some(interpreter_config) = make_cross_compile_config()? {
let reader = std::io::BufReader::new(config_file); // This is a cross compile, need to write the config file.
( let path = Path::new(&pyo3_build_config::DEFAULT_CROSS_COMPILE_CONFIG_PATH);
pyo3_build_config::InterpreterConfig::from_reader(reader)?,
None,
)
}
(false, None) => (
// Create new interpreter config and write it to the default location
pyo3_build_config::make_interpreter_config()?,
Some(OsString::from(pyo3_build_config::DEFAULT_CONFIG_PATH)),
),
};
if let Some(path) = path_to_write {
let path = Path::new(&path);
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 {}",
@ -165,7 +140,11 @@ fn configure_pyo3() -> Result<()> {
.to_writer(&mut std::fs::File::create(&path).with_context(|| { .to_writer(&mut std::fs::File::create(&path).with_context(|| {
format!("failed to create config file at {}", path.display()) format!("failed to create config file at {}", path.display())
})?)?; })?)?;
} interpreter_config
} else {
InterpreterConfig::from_reader(Cursor::new(pyo3_build_config::HOST_CONFIG))?
};
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") { if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
print_config_and_exit(&interpreter_config); print_config_and_exit(&interpreter_config);
} }
@ -201,20 +180,8 @@ fn print_config_and_exit(config: &InterpreterConfig) {
} }
fn main() { fn main() {
// Print out error messages using display, to get nicer formatting.
if let Err(e) = configure_pyo3() { if let Err(e) = configure_pyo3() {
use std::error::Error; eprintln!("error: {}", e.report());
eprintln!("error: {}", e);
let mut source = e.source();
if source.is_some() {
eprintln!("caused by:");
let mut index = 0;
while let Some(some_source) = source {
eprintln!(" - {}: {}", index, some_source);
source = some_source.source();
index += 1;
}
}
std::process::exit(1) std::process::exit(1)
} }
} }

View File

@ -1,3 +1,32 @@
fn main() { // Import some modules from this crate inline to generate the build config.
// Empty build script to force cargo to produce the "OUT_DIR" environment variable. // Allow dead code because not all code in the modules is used in this build script.
#[path = "src/impl_.rs"]
#[allow(dead_code)]
mod impl_;
#[path = "src/errors.rs"]
#[allow(dead_code)]
mod errors;
use std::{env, path::Path};
use errors::{Result, Context};
fn generate_build_config() -> Result<()> {
// Create new interpreter config and write it to the default location
let interpreter_config = impl_::make_interpreter_config()?;
let path = Path::new(&env::var_os("OUT_DIR").unwrap()).join("pyo3-build-config.txt");
interpreter_config
.to_writer(&mut std::fs::File::create(&path).with_context(|| {
format!("failed to create config file at {}", path.display())
})?)
}
fn main() {
if let Err(e) = generate_build_config() {
eprintln!("error: {}", e.report());
std::process::exit(1)
}
} }

View File

@ -26,6 +26,16 @@ pub struct Error {
source: Option<Box<dyn std::error::Error>>, source: Option<Box<dyn std::error::Error>>,
} }
/// Error report inspired by
/// https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html#2-error-reporter
pub struct ErrorReport<'a>(&'a Error);
impl Error {
pub fn report(&self) -> ErrorReport<'_> {
ErrorReport(self)
}
}
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value) write!(f, "{}", self.value)
@ -38,6 +48,24 @@ impl std::error::Error for Error {
} }
} }
impl std::fmt::Display for ErrorReport<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::error::Error;
self.0.fmt(f)?;
let mut source = self.0.source();
if source.is_some() {
writeln!(f, "\ncaused by:")?;
let mut index = 0;
while let Some(some_source) = source {
writeln!(f, " - {}: {}", index, some_source)?;
source = some_source.source();
index += 1;
}
}
Ok(())
}
}
impl From<String> for Error { impl From<String> for Error {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self { Self {

View File

@ -85,6 +85,20 @@ impl InterpreterConfig {
self.implementation == PythonImplementation::PyPy self.implementation == PythonImplementation::PyPy
} }
#[doc(hidden)]
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let config_file =
std::fs::File::open(path).with_context(|| {
format!(
"failed to open PyO3 config file at {}",
path.display()
)
})?;
let reader = std::io::BufReader::new(config_file);
InterpreterConfig::from_reader(reader)
}
#[doc(hidden)] #[doc(hidden)]
pub fn from_reader(reader: impl Read) -> Result<Self> { pub fn from_reader(reader: impl Read) -> Result<Self> {
let reader = BufReader::new(reader); let reader = BufReader::new(reader);
@ -303,6 +317,12 @@ struct CrossCompileConfig {
arch: String, arch: String,
} }
pub fn any_cross_compiling_env_vars_set() -> bool {
env::var_os("PYO3_CROSS").is_some()
|| env::var_os("PYO3_CROSS_LIB_DIR").is_some()
|| env::var_os("PYO3_CROSS_PYTHON_VERSION").is_some()
}
fn cross_compiling() -> Result<Option<CrossCompileConfig>> { fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
let cross = env_var("PYO3_CROSS"); let cross = env_var("PYO3_CROSS");
let cross_lib_dir = env_var("PYO3_CROSS_LIB_DIR"); let cross_lib_dir = env_var("PYO3_CROSS_LIB_DIR");
@ -1029,6 +1049,30 @@ fn get_abi3_minor_version() -> Option<u8> {
.find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some()) .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some())
} }
pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
let abi3_version = get_abi3_minor_version();
let mut interpreter_config = if let Some(paths) = cross_compiling()? {
load_cross_compile_info(paths)?
} else {
return Ok(None);
};
// Fixup minor version if abi3-pyXX feature set
if let Some(abi3_minor_version) = abi3_version {
ensure!(
abi3_minor_version <= interpreter_config.version.minor,
"You cannot set a mininimum Python version 3.{} higher than the interpreter version 3.{}",
abi3_minor_version,
interpreter_config.version.minor
);
interpreter_config.version.minor = abi3_minor_version;
}
Ok(Some(interpreter_config))
}
pub fn make_interpreter_config() -> Result<InterpreterConfig> { pub fn make_interpreter_config() -> Result<InterpreterConfig> {
let abi3_version = get_abi3_minor_version(); let abi3_version = get_abi3_minor_version();
@ -1059,11 +1103,7 @@ pub fn make_interpreter_config() -> Result<InterpreterConfig> {
} }
} }
let mut interpreter_config = if let Some(paths) = cross_compiling()? { let mut interpreter_config = get_config_from_interpreter(&find_interpreter()?)?;
load_cross_compile_info(paths)?
} else {
get_config_from_interpreter(&find_interpreter()?)?
};
// Fixup minor version if abi3-pyXX feature set // Fixup minor version if abi3-pyXX feature set
if let Some(abi3_minor_version) = abi3_version { if let Some(abi3_minor_version) = abi3_version {

View File

@ -7,14 +7,14 @@
pub mod errors; pub mod errors;
mod impl_; mod impl_;
use std::{ffi::OsString, path::Path}; use std::io::Cursor;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
// Used in PyO3's build.rs // Used in PyO3's build.rs
#[doc(hidden)] #[doc(hidden)]
pub use impl_::{ pub use impl_::{
cargo_env_var, env_var, find_interpreter, get_config_from_interpreter, make_interpreter_config, cargo_env_var, env_var, find_interpreter, get_config_from_interpreter, make_interpreter_config, make_cross_compile_config,
InterpreterConfig, PythonImplementation, PythonVersion, InterpreterConfig, PythonImplementation, PythonVersion,
}; };
@ -25,20 +25,24 @@ pub use impl_::{
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(|| {
let config_path = std::env::var_os("PYO3_CONFIG_FILE") if let Some(path) = std::env::var_os("PYO3_CONFIG_FILE") {
.unwrap_or_else(|| OsString::from(DEFAULT_CONFIG_PATH)); // Config file set - use that
let config_file = std::fs::File::open(DEFAULT_CONFIG_PATH).expect(&format!( InterpreterConfig::from_path(path)
"failed to open PyO3 config file at {}", } else if impl_::any_cross_compiling_env_vars_set() {
Path::new(&config_path).display() InterpreterConfig::from_path(DEFAULT_CROSS_COMPILE_CONFIG_PATH)
)); } else {
let reader = std::io::BufReader::new(config_file); InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
InterpreterConfig::from_reader(reader).expect("failed to parse config file") }.expect("failed to parse PyO3 config file")
}) })
} }
/// Path where PyO3's build.rs will write configuration by default. /// Path where PyO3's build.rs will write configuration by default.
#[doc(hidden)] #[doc(hidden)]
pub const DEFAULT_CONFIG_PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-build-config.txt"); pub const DEFAULT_CROSS_COMPILE_CONFIG_PATH: &str = concat!(env!("OUT_DIR"), "/pyo3-cross-compile-config.txt");
/// Build configuration discovered by `pyo3-build-config` build script. Not aware of
/// cross-compilation settings.
pub const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt"));
/// Adds all the [`#[cfg]` flags](index.html) to the current compilation. /// Adds all the [`#[cfg]` flags](index.html) to the current compilation.
/// ///