221 lines
8.1 KiB
Rust
221 lines
8.1 KiB
Rust
use std::{env, ffi::OsString, path::Path, process::Command};
|
|
|
|
use pyo3_build_config::{
|
|
bail, cargo_env_var, ensure, env_var,
|
|
errors::{Context, Result},
|
|
InterpreterConfig, PythonVersion,
|
|
};
|
|
|
|
/// Minimum Python version PyO3 supports.
|
|
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 6 };
|
|
|
|
fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
|
|
ensure!(
|
|
interpreter_config.version >= MINIMUM_SUPPORTED_VERSION,
|
|
"the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})",
|
|
interpreter_config.version,
|
|
MINIMUM_SUPPORTED_VERSION,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn ensure_target_pointer_width(pointer_width: u32) -> Result<()> {
|
|
// Try to check whether the target architecture matches the python library
|
|
let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH")
|
|
.unwrap()
|
|
.as_str()
|
|
{
|
|
"64" => 64,
|
|
"32" => 32,
|
|
x => bail!("unexpected Rust target pointer width: {}", x),
|
|
};
|
|
|
|
ensure!(
|
|
rust_target == pointer_width,
|
|
"your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)",
|
|
rust_target,
|
|
pointer_width
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn rustc_minor_version() -> Option<u32> {
|
|
let rustc = env::var_os("RUSTC")?;
|
|
let output = Command::new(rustc).arg("--version").output().ok()?;
|
|
let version = core::str::from_utf8(&output.stdout).ok()?;
|
|
let mut pieces = version.split('.');
|
|
if pieces.next() != Some("rustc 1") {
|
|
return None;
|
|
}
|
|
pieces.next()?.parse().ok()
|
|
}
|
|
|
|
fn emit_cargo_configuration(interpreter_config: &InterpreterConfig) -> Result<()> {
|
|
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("config does not contain lib_name")?,
|
|
);
|
|
if let Some(lib_dir) = &interpreter_config.lib_dir {
|
|
println!("cargo:rustc-link-search=native={}", lib_dir);
|
|
}
|
|
}
|
|
|
|
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
|
|
if !interpreter_config.shared {
|
|
bail!(
|
|
"The `auto-initialize` feature is enabled, but your python installation only supports \
|
|
embedding the Python interpreter statically. If you are attempting to run tests, or a \
|
|
binary which is okay to link dynamically, install a Python distribution which ships \
|
|
with the Python shared library.\n\
|
|
\n\
|
|
Embedding the Python interpreter statically does not yet have first-class support in \
|
|
PyO3. If you are sure you intend to do this, disable the `auto-initialize` feature.\n\
|
|
\n\
|
|
For more information, see \
|
|
https://pyo3.rs/v{pyo3_version}/\
|
|
building_and_distribution.html#embedding-python-in-rust",
|
|
pyo3_version = env::var("CARGO_PKG_VERSION").unwrap()
|
|
);
|
|
}
|
|
|
|
// TODO: PYO3_CI env is a hack to workaround CI with PyPy, where the `dev-dependencies`
|
|
// currently cause `auto-initialize` to be enabled in CI.
|
|
// Once MSRV is 1.51 or higher, use cargo's `resolver = "2"` instead.
|
|
if interpreter_config.is_pypy() && env::var_os("PYO3_CI").is_none() {
|
|
bail!("the `auto-initialize` feature is not supported with PyPy");
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Generates the interpreter config suitable for the host / target / cross-compilation at hand.
|
|
///
|
|
/// 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 path = Path::new(&path);
|
|
let parent_dir = path.parent().ok_or_else(|| {
|
|
format!(
|
|
"failed to resolve parent directory of config file {}",
|
|
path.display()
|
|
)
|
|
})?;
|
|
std::fs::create_dir_all(&parent_dir).with_context(|| {
|
|
format!(
|
|
"failed to create config file directory {}",
|
|
parent_dir.display()
|
|
)
|
|
})?;
|
|
interpreter_config
|
|
.to_writer(&mut std::fs::File::create(&path).with_context(|| {
|
|
format!("failed to create config file at {}", path.display())
|
|
})?)?;
|
|
}
|
|
if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") {
|
|
print_config_and_exit(&interpreter_config);
|
|
}
|
|
|
|
ensure_python_version(&interpreter_config)?;
|
|
if let Some(pointer_width) = interpreter_config.pointer_width {
|
|
ensure_target_pointer_width(pointer_width)?;
|
|
}
|
|
emit_cargo_configuration(&interpreter_config)?;
|
|
interpreter_config.emit_pyo3_cfgs();
|
|
|
|
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
|
|
|
|
// Enable use of const generics on Rust 1.51 and greater
|
|
if rustc_minor_version >= 51 {
|
|
println!("cargo:rustc-cfg=min_const_generics");
|
|
}
|
|
|
|
// Enable use of std::ptr::addr_of! on Rust 1.51 and greater
|
|
if rustc_minor_version >= 51 {
|
|
println!("cargo:rustc-cfg=addr_of");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn print_config_and_exit(config: &InterpreterConfig) {
|
|
println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --");
|
|
config
|
|
.to_writer(&mut std::io::stdout())
|
|
.expect("failed to print config to stdout");
|
|
std::process::exit(101);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
std::process::exit(1)
|
|
}
|
|
}
|