#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
mod errors;
mod impl_;
use std::{env, process::Command, str::FromStr, sync::LazyLock};
pub use impl_::{
cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags,
CrossCompileConfig, GilUsed, InterpreterConfig, InterpreterConfigBuilder, PythonAbi,
PythonAbiBuilder, PythonAbiKind, PythonImplementation, PythonVersion, StableAbi, Triple,
};
use target_lexicon::{Architecture, OperatingSystem};
#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple-python-versions.html)")]
pub fn use_pyo3_cfgs() {
print_expected_cfgs();
for cargo_command in get().build_script_outputs() {
println!("{cargo_command}")
}
}
pub fn add_extension_module_link_args() {
_add_extension_module_link_args(
&impl_::target_triple_from_env(),
std::io::stdout(),
rustc_minor_version(),
)
}
fn _add_extension_module_link_args(
triple: &Triple,
mut writer: impl std::io::Write,
rustc_minor_version: Option<u32>,
) {
if matches!(triple.operating_system, OperatingSystem::Darwin(_)) {
writeln!(writer, "cargo:rustc-cdylib-link-arg=-undefined").unwrap();
writeln!(writer, "cargo:rustc-cdylib-link-arg=dynamic_lookup").unwrap();
} else if triple == &Triple::from_str("wasm32-unknown-emscripten").unwrap()
&& rustc_minor_version.is_some_and(|version| version < 95)
{
writeln!(writer, "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2").unwrap();
writeln!(writer, "cargo:rustc-cdylib-link-arg=-sWASM_BIGINT").unwrap();
}
}
#[doc = concat!("[See PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution#dynamically-embedding-the-python-interpreter)")]
pub fn add_libpython_rpath_link_args() {
let target = impl_::target_triple_from_env();
pyo3_build_script_impl::print_libpython_rpath_link_args(&target, get());
}
pub fn add_python_framework_link_args() {
let target = impl_::target_triple_from_env();
_add_python_framework_link_args(
get(),
&target,
impl_::is_linking_libpython_for_target(&target),
std::io::stdout(),
)
}
fn _add_python_framework_link_args(
interpreter_config: &InterpreterConfig,
triple: &Triple,
link_libpython: bool,
mut writer: impl std::io::Write,
) {
if matches!(triple.operating_system, OperatingSystem::Darwin(_)) && link_libpython {
if let Some(framework_prefix) = interpreter_config.python_framework_prefix() {
writeln!(writer, "cargo:rustc-link-arg=-Wl,-rpath,{framework_prefix}").unwrap();
}
}
}
pub fn get() -> &'static InterpreterConfig {
static CONFIG: LazyLock<InterpreterConfig> = LazyLock::new(get_inner);
&CONFIG
}
#[track_caller]
fn get_inner() -> InterpreterConfig {
let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() else {
panic!("`pyo3_build_config::get()` requires a direct dependency on `pyo3` or `pyo3-ffi`")
};
interpreter_config.expect("failed to parse PyO3 config")
}
#[doc(hidden)]
pub fn print_expected_cfgs() {
println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)");
println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)");
println!("cargo:rustc-check-cfg=cfg(PyPy)");
println!("cargo:rustc-check-cfg=cfg(GraalPy)");
println!("cargo:rustc-check-cfg=cfg(RustPython)");
println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))");
for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::STABLE_ABI_MAX_MINOR + 1 {
println!("cargo:rustc-check-cfg=cfg(Py_3_{i})");
}
}
#[doc(hidden)]
pub mod pyo3_build_script_impl {
use crate::{
errors::Result,
impl_::{make_cross_compile_config, make_interpreter_config},
};
use super::*;
pub mod errors {
pub use crate::errors::*;
}
pub use crate::impl_::{
cargo_env_var, env_var, is_linking_libpython_for_target, target_triple_from_env,
InterpreterConfig, PythonAbi, PythonAbiKind, PythonVersion, StableAbi,
};
pub enum BuildConfigSource {
ConfigFile,
Host,
CrossCompile,
}
pub struct BuildConfig {
pub interpreter_config: InterpreterConfig,
pub source: BuildConfigSource,
}
pub fn resolve_build_config(target: &Triple) -> Result<BuildConfig> {
#[allow(
clippy::const_is_empty,
reason = "CONFIG_FILE is generated in build.rs, content can vary"
)]
if let Some(interpreter_config) =
InterpreterConfig::from_pyo3_config_file_env(target).transpose()?
{
Ok(BuildConfig {
interpreter_config,
source: BuildConfigSource::ConfigFile,
})
} else if let Some(interpreter_config) = make_cross_compile_config(target)? {
Ok(BuildConfig {
interpreter_config,
source: BuildConfigSource::CrossCompile,
})
} else {
let host_config = make_interpreter_config();
Ok(BuildConfig {
interpreter_config: host_config?,
source: BuildConfigSource::Host,
})
}
}
pub struct MaximumVersionExceeded {
message: String,
}
impl MaximumVersionExceeded {
pub fn new(
interpreter_config: &InterpreterConfig,
supported_version: PythonVersion,
) -> Self {
let implementation = match interpreter_config.target_abi().implementation() {
PythonImplementation::CPython => "Python",
PythonImplementation::PyPy => "PyPy",
PythonImplementation::GraalPy => "GraalPy",
PythonImplementation::RustPython => "RustPython",
};
let version = &interpreter_config.target_abi().version();
let message = format!(
"the configured {implementation} version ({version}) is newer than PyO3's maximum supported version ({supported_version})\n\
= help: this package is being built with PyO3 version {current_version}\n\
= help: check https://crates.io/crates/pyo3 for the latest PyO3 version available\n\
= help: updating this package to the latest version of PyO3 may provide compatibility with this {implementation} version",
current_version = env!("CARGO_PKG_VERSION")
);
Self { message }
}
pub fn add_help(&mut self, help: &str) {
self.message.push_str("\n= help: ");
self.message.push_str(help);
}
pub fn finish(self) -> String {
self.message
}
}
pub fn print_feature_cfgs() {
print_feature_cfg(84, "const_is_null");
print_feature_cfg(85, "fn_ptr_eq");
print_feature_cfg(86, "from_bytes_with_nul_error");
print_feature_cfg(95, "cfg_select");
}
fn print_feature_cfg(minor_version_required: u32, cfg: &str) {
println!("cargo:rustc-check-cfg=cfg({cfg})");
let minor_version = rustc_minor_version().unwrap_or(0);
if minor_version >= minor_version_required {
println!("cargo:rustc-cfg={cfg}");
}
}
pub fn print_libpython_rpath_link_args(
target: &Triple,
interpreter_config: &InterpreterConfig,
) {
let is_linking_libpython = is_linking_libpython_for_target(target);
let is_wasm = matches!(
target.architecture,
Architecture::Wasm32 | Architecture::Wasm64
);
let is_emscripten = target.operating_system == OperatingSystem::Emscripten;
let is_cygwin = target.operating_system == OperatingSystem::Cygwin;
let is_windows = target.operating_system == OperatingSystem::Windows;
if is_linking_libpython && !is_windows && !is_cygwin && (!is_wasm || is_emscripten) {
if let Some(lib_dir) = interpreter_config.lib_dir() {
println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}");
}
}
}
}
fn rustc_minor_version() -> Option<u32> {
static RUSTC_MINOR_VERSION: LazyLock<Option<u32>> = LazyLock::new(|| {
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()
});
*RUSTC_MINOR_VERSION
}
#[cfg(test)]
#[expect(deprecated, reason = "accessing config directly")]
mod tests {
use crate::impl_::escape;
use super::*;
#[test]
fn extension_module_link_args() {
let mut buf = Vec::new();
_add_extension_module_link_args(
&Triple::from_str("x86_64-pc-windows-msvc").unwrap(),
&mut buf,
None,
);
assert_eq!(buf, Vec::new());
_add_extension_module_link_args(
&Triple::from_str("x86_64-apple-darwin").unwrap(),
&mut buf,
None,
);
assert_eq!(
std::str::from_utf8(&buf).unwrap(),
"cargo:rustc-cdylib-link-arg=-undefined\n\
cargo:rustc-cdylib-link-arg=dynamic_lookup\n"
);
buf.clear();
_add_extension_module_link_args(
&Triple::from_str("wasm32-unknown-emscripten").unwrap(),
&mut buf,
Some(94),
);
assert_eq!(
std::str::from_utf8(&buf).unwrap(),
"cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2\n\
cargo:rustc-cdylib-link-arg=-sWASM_BIGINT\n"
);
buf.clear();
_add_extension_module_link_args(
&Triple::from_str("wasm32-unknown-emscripten").unwrap(),
&mut buf,
Some(95),
);
assert_eq!(std::str::from_utf8(&buf).unwrap(), "");
}
#[test]
fn python_framework_link_args() {
let mut buf = Vec::new();
let implementation = PythonImplementation::CPython;
let version = PythonVersion::PY313;
let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
.python_framework_prefix(
"/Applications/Xcode.app/Contents/Developer/Library/Frameworks".to_string(),
)
.finalize()
.unwrap();
_add_python_framework_link_args(
&interpreter_config,
&Triple::from_str("x86_64-pc-windows-msvc").unwrap(),
true,
&mut buf,
);
assert_eq!(buf, Vec::new());
_add_python_framework_link_args(
&interpreter_config,
&Triple::from_str("x86_64-apple-darwin").unwrap(),
true,
&mut buf,
);
assert_eq!(
std::str::from_utf8(&buf).unwrap(),
"cargo:rustc-link-arg=-Wl,-rpath,/Applications/Xcode.app/Contents/Developer/Library/Frameworks\n"
);
}
#[test]
fn test_maximum_version_exceeded_formatting() {
let implementation = PythonImplementation::CPython;
let version = PythonVersion::PY313;
let interpreter_config = InterpreterConfigBuilder::new(implementation, version)
.finalize()
.unwrap();
let mut error = pyo3_build_script_impl::MaximumVersionExceeded::new(
&interpreter_config,
PythonVersion::PY312,
);
error.add_help("this is a help message");
let error = error.finish();
let expected = concat!("\
the configured Python version (3.13) is newer than PyO3's maximum supported version (3.12)\n\
= help: this package is being built with PyO3 version ", env!("CARGO_PKG_VERSION"), "\n\
= help: check https://crates.io/crates/pyo3 for the latest PyO3 version available\n\
= help: updating this package to the latest version of PyO3 may provide compatibility with this Python version\n\
= help: this is a help message"
);
assert_eq!(error, expected);
}
#[test]
fn test_interpreter_config_from_cargo_env() {
assert!(InterpreterConfig::from_cargo_dep_env().is_none());
let interpreter_config =
InterpreterConfigBuilder::new(PythonImplementation::CPython, PythonVersion::PY313)
.finalize()
.unwrap();
let mut buf = Vec::new();
interpreter_config.to_writer(&mut buf).unwrap();
let config_string = escape(&buf);
unsafe { std::env::set_var(InterpreterConfig::PYO3_FFI_CONFIG_ENV_VAR, &config_string) };
assert_eq!(get_inner(), interpreter_config);
unsafe {
std::env::remove_var(InterpreterConfig::PYO3_FFI_CONFIG_ENV_VAR);
std::env::set_var(InterpreterConfig::PYO3_CONFIG_ENV_VAR, &config_string)
}
assert_eq!(get_inner(), interpreter_config);
}
}