pyo3/pyo3-build-config/build.rs
Gregory Szorc 1e951d5d8b pyo3-build-config: add a crate feature to control build script
I have a use case in PyOxidizer where I want to use the
pyo3-build-config crate as a library crate so I can access the
`InterpreterConfig` struct so I can read/write config files without
reinventing the wheel.

This is doable before this commit. But it requires that the
build environment have a Python interpreter. This is undesirable
for library usage.

This commit introduces a cargo feature flag to control whether the
build script does anything. The feature flag must be present for
the build script to resolve a config. The feature flag is enabled
by default for backwards compatibility. The pyo3 and pyo3-macros-backend
crates use this feature by default, for backwards compatibility and
because it is the reasonable default.

This is probably room to conditionalize some APIs and other behavior
based on this feature flag. But we stop short of doing that for
the time being.
2021-09-01 19:44:54 -07:00

103 lines
3.4 KiB
Rust

// 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::{Context, Result};
use impl_::{
env_var, get_abi3_version, make_interpreter_config, BuildFlags, InterpreterConfig,
PythonImplementation,
};
fn configure(interpreter_config: Option<InterpreterConfig>, name: &str) -> Result<bool> {
let target = Path::new(&env::var_os("OUT_DIR").unwrap()).join(name);
if let Some(config) = interpreter_config {
config
.to_writer(&mut std::fs::File::create(&target).with_context(|| {
format!("failed to write config file at {}", target.display())
})?)?;
Ok(true)
} else {
std::fs::File::create(&target)
.with_context(|| format!("failed to create new file at {}", target.display()))?;
Ok(false)
}
}
/// If PYO3_CONFIG_FILE is set, copy it into the crate.
fn config_file() -> Result<Option<InterpreterConfig>> {
if let Some(path) = env_var("PYO3_CONFIG_FILE") {
let path = Path::new(&path);
println!("cargo:rerun-if-changed={}", path.display());
// Absolute path is necessary because this build script is run with a cwd different to the
// original `cargo build` instruction.
ensure!(
path.is_absolute(),
"PYO3_CONFIG_FILE must be an absolute path"
);
let interpreter_config = InterpreterConfig::from_path(path)
.context("failed to parse contents of PYO3_CONFIG_FILE")?;
Ok(Some(interpreter_config))
} else {
Ok(None)
}
}
/// If PYO3_NO_PYTHON is set with abi3, use standard abi3 settings.
pub fn abi3_config() -> Option<InterpreterConfig> {
if let Some(version) = get_abi3_version() {
if env_var("PYO3_NO_PYTHON").is_some() {
return Some(InterpreterConfig {
version,
// NB PyPy doesn't support abi3 yet
implementation: PythonImplementation::CPython,
abi3: true,
lib_name: None,
lib_dir: None,
build_flags: BuildFlags::abi3(),
pointer_width: None,
executable: None,
shared: true,
suppress_build_script_link_lines: false,
extra_build_script_lines: vec![],
});
}
}
None
}
fn generate_build_configs() -> Result<()> {
let mut configured = false;
configured |= configure(config_file()?, "pyo3-build-config-file.txt")?;
configured |= configure(abi3_config(), "pyo3-build-config-abi3.txt")?;
if configured {
// Don't bother trying to find an interpreter on the host system if at least one of the
// config file or abi3 settings are present
configure(None, "pyo3-build-config.txt")?;
} else {
configure(Some(make_interpreter_config()?), "pyo3-build-config.txt")?;
}
Ok(())
}
fn main() {
if std::env::var("CARGO_FEATURE_RESOLVE_CONFIG").is_ok() {
if let Err(e) = generate_build_configs() {
eprintln!("error: {}", e.report());
std::process::exit(1)
}
} else {
eprintln!("resolve-config feature not enabled; build script in no-op mode");
}
}