use pyo3_build_config::{ bail, ensure, print_feature_cfgs, pyo3_build_script_impl::{ cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, InterpreterConfig, PythonVersion, }, PythonImplementation, }; /// Minimum Python version PyO3 supports. struct SupportedVersions { min: PythonVersion, max: PythonVersion, } const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 7 }, max: PythonVersion { major: 3, minor: 13, }, }; const SUPPORTED_VERSIONS_PYPY: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 7 }, max: PythonVersion { major: 3, minor: 10, }, }; const SUPPORTED_VERSIONS_GRAALPY: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 10, }, max: PythonVersion { major: 3, minor: 11, }, }; fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { // This is an undocumented env var which is only really intended to be used in CI / for testing // and development. if std::env::var("UNSAFE_PYO3_SKIP_VERSION_CHECK").as_deref() == Ok("1") { return Ok(()); } match interpreter_config.implementation { PythonImplementation::CPython => { let versions = SUPPORTED_VERSIONS_CPYTHON; ensure!( interpreter_config.version >= versions.min, "the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})", interpreter_config.version, versions.min, ); ensure!( interpreter_config.version <= versions.max || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"), "the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ = help: please check if an updated version of PyO3 is available. Current version: {}\n\ = help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI", interpreter_config.version, versions.max, std::env::var("CARGO_PKG_VERSION").unwrap(), ); } PythonImplementation::PyPy => { let versions = SUPPORTED_VERSIONS_PYPY; ensure!( interpreter_config.version >= versions.min, "the configured PyPy interpreter version ({}) is lower than PyO3's minimum supported version ({})", interpreter_config.version, versions.min, ); // PyO3 does not support abi3, so we cannot offer forward compatibility ensure!( interpreter_config.version <= versions.max, "the configured PyPy interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ = help: please check if an updated version of PyO3 is available. Current version: {}", interpreter_config.version, versions.max, std::env::var("CARGO_PKG_VERSION").unwrap() ); } PythonImplementation::GraalPy => { let versions = SUPPORTED_VERSIONS_GRAALPY; ensure!( interpreter_config.version >= versions.min, "the configured GraalPy interpreter version ({}) is lower than PyO3's minimum supported version ({})", interpreter_config.version, versions.min, ); // GraalPy does not support abi3, so we cannot offer forward compatibility ensure!( interpreter_config.version <= versions.max, "the configured GraalPy interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ = help: please check if an updated version of PyO3 is available. Current version: {}", interpreter_config.version, versions.max, std::env::var("CARGO_PKG_VERSION").unwrap() ); } } Ok(()) } fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { if let Some(pointer_width) = interpreter_config.pointer_width { // 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 emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); 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( "attempted to link to Python shared library but config does not contain lib_name" )?, ); if let Some(lib_dir) = &interpreter_config.lib_dir { println!("cargo:rustc-link-search=native={}", lib_dir); } Ok(()) } /// Prepares the PyO3 crate for compilation. /// /// This loads the config from pyo3-build-config and then makes some additional checks to improve UX /// for users. /// /// Emits the cargo configuration based on this config as well as a few checks of the Rust compiler /// version to enable features which aren't supported on MSRV. fn configure_pyo3() -> Result<()> { let interpreter_config = resolve_interpreter_config()?; 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)?; ensure_target_pointer_width(&interpreter_config)?; // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. interpreter_config.to_cargo_dep_env()?; if is_linking_libpython() && !interpreter_config.suppress_build_script_link_lines { emit_link_config(&interpreter_config)?; } for cfg in interpreter_config.build_script_outputs() { println!("{}", cfg) } // Extra lines come last, to support last write wins. for line in &interpreter_config.extra_build_script_lines { println!("{}", line); } // Emit cfgs like `invalid_from_utf8_lint` print_feature_cfgs(); Ok(()) } fn print_config_and_exit(config: &InterpreterConfig) { println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --"); config .to_writer(std::io::stdout()) .expect("failed to print config to stdout"); println!("\nnote: unset the PYO3_PRINT_CONFIG environment variable and retry to compile with the above config"); std::process::exit(101); } fn main() { pyo3_build_config::print_expected_cfgs(); if let Err(e) = configure_pyo3() { eprintln!("error: {}", e.report()); std::process::exit(1) } }