pyo3-build-config: inline config when not cross compiling
This commit is contained in:
parent
20b34a5528
commit
9507979d93
67
build.rs
67
build.rs
|
@ -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::{
|
||||
bail, cargo_env_var, ensure, env_var,
|
||||
errors::{Context, Result},
|
||||
InterpreterConfig, PythonVersion,
|
||||
make_cross_compile_config, InterpreterConfig, PythonVersion,
|
||||
};
|
||||
|
||||
/// 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
|
||||
/// (including `pyo3-macros-backend` during macro expansion).
|
||||
fn configure_pyo3() -> Result<()> {
|
||||
let write_config_file = env_var("PYO3_WRITE_CONFIG_FILE").map_or(false, |os_str| os_str == "1");
|
||||
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);
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
let config_file = std::fs::File::open(path)
|
||||
.with_context(|| format!("failed to read config file at {}", path.display()))?;
|
||||
let reader = std::io::BufReader::new(config_file);
|
||||
(
|
||||
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 interpreter_config = if let Some(path) = env_var("PYO3_CONFIG_FILE") {
|
||||
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());
|
||||
InterpreterConfig::from_path(path)?
|
||||
} else if let Some(interpreter_config) = make_cross_compile_config()? {
|
||||
// This is a cross compile, need to write the config file.
|
||||
let path = Path::new(&pyo3_build_config::DEFAULT_CROSS_COMPILE_CONFIG_PATH);
|
||||
let parent_dir = path.parent().ok_or_else(|| {
|
||||
format!(
|
||||
"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(|| {
|
||||
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") {
|
||||
print_config_and_exit(&interpreter_config);
|
||||
}
|
||||
|
@ -201,20 +180,8 @@ fn print_config_and_exit(config: &InterpreterConfig) {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
// Print out error messages using display, to get nicer formatting.
|
||||
if let Err(e) = configure_pyo3() {
|
||||
use std::error::Error;
|
||||
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;
|
||||
}
|
||||
}
|
||||
eprintln!("error: {}", e.report());
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,32 @@
|
|||
fn main() {
|
||||
// Empty build script to force cargo to produce the "OUT_DIR" environment variable.
|
||||
// Import some modules from this crate inline to generate the build config.
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,16 @@ pub struct 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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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 {
|
||||
fn from(value: String) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -85,6 +85,20 @@ impl InterpreterConfig {
|
|||
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)]
|
||||
pub fn from_reader(reader: impl Read) -> Result<Self> {
|
||||
let reader = BufReader::new(reader);
|
||||
|
@ -303,6 +317,12 @@ struct CrossCompileConfig {
|
|||
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>> {
|
||||
let cross = env_var("PYO3_CROSS");
|
||||
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())
|
||||
}
|
||||
|
||||
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> {
|
||||
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()? {
|
||||
load_cross_compile_info(paths)?
|
||||
} else {
|
||||
get_config_from_interpreter(&find_interpreter()?)?
|
||||
};
|
||||
let mut interpreter_config = get_config_from_interpreter(&find_interpreter()?)?;
|
||||
|
||||
// Fixup minor version if abi3-pyXX feature set
|
||||
if let Some(abi3_minor_version) = abi3_version {
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
pub mod errors;
|
||||
mod impl_;
|
||||
|
||||
use std::{ffi::OsString, path::Path};
|
||||
use std::io::Cursor;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
// Used in PyO3's build.rs
|
||||
#[doc(hidden)]
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -25,20 +25,24 @@ pub use impl_::{
|
|||
pub fn get() -> &'static InterpreterConfig {
|
||||
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
|
||||
CONFIG.get_or_init(|| {
|
||||
let config_path = std::env::var_os("PYO3_CONFIG_FILE")
|
||||
.unwrap_or_else(|| OsString::from(DEFAULT_CONFIG_PATH));
|
||||
let config_file = std::fs::File::open(DEFAULT_CONFIG_PATH).expect(&format!(
|
||||
"failed to open PyO3 config file at {}",
|
||||
Path::new(&config_path).display()
|
||||
));
|
||||
let reader = std::io::BufReader::new(config_file);
|
||||
InterpreterConfig::from_reader(reader).expect("failed to parse config file")
|
||||
if let Some(path) = std::env::var_os("PYO3_CONFIG_FILE") {
|
||||
// Config file set - use that
|
||||
InterpreterConfig::from_path(path)
|
||||
} else if impl_::any_cross_compiling_env_vars_set() {
|
||||
InterpreterConfig::from_path(DEFAULT_CROSS_COMPILE_CONFIG_PATH)
|
||||
} else {
|
||||
InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
|
||||
}.expect("failed to parse PyO3 config file")
|
||||
})
|
||||
}
|
||||
|
||||
/// Path where PyO3's build.rs will write configuration by default.
|
||||
#[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.
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue