1#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
9
10mod errors;
11mod impl_;
12
13#[cfg(feature = "resolve-config")]
14use std::{
15 io::Cursor,
16 path::{Path, PathBuf},
17};
18
19use std::{env, process::Command, str::FromStr, sync::OnceLock};
20
21pub use impl_::{
22 cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags,
23 CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, Triple,
24};
25
26use target_lexicon::OperatingSystem;
27
28#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")]
43#[cfg(feature = "resolve-config")]
45pub fn use_pyo3_cfgs() {
46 print_expected_cfgs();
47 for cargo_command in get().build_script_outputs() {
48 println!("{cargo_command}")
49 }
50}
51
52pub fn add_extension_module_link_args() {
63 _add_extension_module_link_args(&impl_::target_triple_from_env(), std::io::stdout())
64}
65
66fn _add_extension_module_link_args(triple: &Triple, mut writer: impl std::io::Write) {
67 if matches!(triple.operating_system, OperatingSystem::Darwin(_)) {
68 writeln!(writer, "cargo:rustc-cdylib-link-arg=-undefined").unwrap();
69 writeln!(writer, "cargo:rustc-cdylib-link-arg=dynamic_lookup").unwrap();
70 } else if triple == &Triple::from_str("wasm32-unknown-emscripten").unwrap() {
71 writeln!(writer, "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2").unwrap();
72 writeln!(writer, "cargo:rustc-cdylib-link-arg=-sWASM_BIGINT").unwrap();
73 }
74}
75
76#[cfg(feature = "resolve-config")]
85pub fn add_python_framework_link_args() {
86 let target = impl_::target_triple_from_env();
87 _add_python_framework_link_args(
88 get(),
89 &target,
90 impl_::is_linking_libpython_for_target(&target),
91 std::io::stdout(),
92 )
93}
94
95#[cfg(feature = "resolve-config")]
96fn _add_python_framework_link_args(
97 interpreter_config: &InterpreterConfig,
98 triple: &Triple,
99 link_libpython: bool,
100 mut writer: impl std::io::Write,
101) {
102 if matches!(triple.operating_system, OperatingSystem::Darwin(_)) && link_libpython {
103 if let Some(framework_prefix) = interpreter_config.python_framework_prefix.as_ref() {
104 writeln!(writer, "cargo:rustc-link-arg=-Wl,-rpath,{framework_prefix}").unwrap();
105 }
106 }
107}
108
109#[cfg(feature = "resolve-config")]
113pub fn get() -> &'static InterpreterConfig {
114 static CONFIG: OnceLock<InterpreterConfig> = OnceLock::new();
115 CONFIG.get_or_init(|| {
116 let cross_compile_config_path = resolve_cross_compile_config_path();
118 let cross_compiling = cross_compile_config_path
119 .as_ref()
120 .map(|path| path.exists())
121 .unwrap_or(false);
122
123 #[allow(unknown_lints, clippy::const_is_empty)]
125 if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
126 interpreter_config
127 } else if let Some(interpreter_config) = config_from_pyo3_config_file_env() {
128 Ok(interpreter_config)
129 } else if cross_compiling {
130 InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap())
131 } else {
132 InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
133 }
134 .expect("failed to parse PyO3 config")
135 })
136}
137
138#[cfg(feature = "resolve-config")]
140fn config_from_pyo3_config_file_env() -> Option<InterpreterConfig> {
141 #[doc(hidden)]
142 const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt"));
143
144 #[allow(unknown_lints, clippy::const_is_empty)]
147 if !CONFIG_FILE.is_empty() {
148 let config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
149 .expect("contents of CONFIG_FILE should always be valid (generated by pyo3-build-config's build.rs)");
150 Some(config)
151 } else {
152 None
153 }
154}
155
156#[doc(hidden)]
159#[cfg(feature = "resolve-config")]
160const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt"));
161
162#[doc(hidden)]
168#[cfg(feature = "resolve-config")]
169fn resolve_cross_compile_config_path() -> Option<PathBuf> {
170 env::var_os("TARGET").map(|target| {
171 let mut path = PathBuf::from(env!("OUT_DIR"));
172 path.push(Path::new(&target));
173 path.push("pyo3-build-config.txt");
174 path
175 })
176}
177
178fn print_feature_cfg(minor_version_required: u32, cfg: &str) {
180 let minor_version = rustc_minor_version().unwrap_or(0);
181
182 if minor_version >= minor_version_required {
183 println!("cargo:rustc-cfg={cfg}");
184 }
185
186 if minor_version >= 80 {
188 println!("cargo:rustc-check-cfg=cfg({cfg})");
189 }
190}
191
192#[doc(hidden)]
197pub fn print_feature_cfgs() {
198 print_feature_cfg(75, "return_position_impl_trait_in_traits");
199 print_feature_cfg(79, "c_str_lit");
200 print_feature_cfg(79, "diagnostic_namespace");
203 print_feature_cfg(83, "io_error_more");
204 print_feature_cfg(83, "mut_ref_in_const_fn");
205 print_feature_cfg(85, "fn_ptr_eq");
206 print_feature_cfg(86, "from_bytes_with_nul_error");
207}
208
209#[doc(hidden)]
214pub fn print_expected_cfgs() {
215 if rustc_minor_version().is_some_and(|version| version < 80) {
216 return;
218 }
219
220 println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)");
221 println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)");
222 println!("cargo:rustc-check-cfg=cfg(PyPy)");
223 println!("cargo:rustc-check-cfg=cfg(GraalPy)");
224 println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))");
225 println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)");
226 println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)");
227
228 for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::ABI3_MAX_MINOR + 1 {
231 println!("cargo:rustc-check-cfg=cfg(Py_3_{i})");
232 }
233}
234
235#[doc(hidden)]
239#[cfg(feature = "resolve-config")]
240pub mod pyo3_build_script_impl {
241 use crate::errors::{Context, Result};
242
243 use super::*;
244
245 pub mod errors {
246 pub use crate::errors::*;
247 }
248 pub use crate::impl_::{
249 cargo_env_var, env_var, is_linking_libpython_for_target, make_cross_compile_config,
250 target_triple_from_env, InterpreterConfig, PythonVersion,
251 };
252 pub enum BuildConfigSource {
253 ConfigFile,
255 Host,
257 CrossCompile,
259 }
260
261 pub struct BuildConfig {
262 pub interpreter_config: InterpreterConfig,
263 pub source: BuildConfigSource,
264 }
265
266 pub fn resolve_build_config(target: &Triple) -> Result<BuildConfig> {
276 #[allow(unknown_lints, clippy::const_is_empty)]
278 if let Some(mut interpreter_config) = config_from_pyo3_config_file_env() {
279 interpreter_config.apply_default_lib_name_to_config_file(target);
280 interpreter_config.generate_import_libs()?;
281 Ok(BuildConfig {
282 interpreter_config,
283 source: BuildConfigSource::ConfigFile,
284 })
285 } else if let Some(interpreter_config) = make_cross_compile_config()? {
286 let path = resolve_cross_compile_config_path()
288 .expect("resolve_build_config() must be called from a build script");
289 let parent_dir = path.parent().ok_or_else(|| {
290 format!(
291 "failed to resolve parent directory of config file {}",
292 path.display()
293 )
294 })?;
295 std::fs::create_dir_all(parent_dir).with_context(|| {
296 format!(
297 "failed to create config file directory {}",
298 parent_dir.display()
299 )
300 })?;
301 interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context(
302 || format!("failed to create config file at {}", path.display()),
303 )?)?;
304 Ok(BuildConfig {
305 interpreter_config,
306 source: BuildConfigSource::CrossCompile,
307 })
308 } else {
309 let interpreter_config = InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))?;
310 Ok(BuildConfig {
311 interpreter_config,
312 source: BuildConfigSource::Host,
313 })
314 }
315 }
316
317 pub struct MaximumVersionExceeded {
320 message: String,
321 }
322
323 impl MaximumVersionExceeded {
324 pub fn new(
325 interpreter_config: &InterpreterConfig,
326 supported_version: PythonVersion,
327 ) -> Self {
328 let implementation = match interpreter_config.implementation {
329 PythonImplementation::CPython => "Python",
330 PythonImplementation::PyPy => "PyPy",
331 PythonImplementation::GraalPy => "GraalPy",
332 };
333 let version = &interpreter_config.version;
334 let message = format!(
335 "the configured {implementation} version ({version}) is newer than PyO3's maximum supported version ({supported_version})\n\
336 = help: this package is being built with PyO3 version {current_version}\n\
337 = help: check https://crates.io/crates/pyo3 for the latest PyO3 version available\n\
338 = help: updating this package to the latest version of PyO3 may provide compatibility with this {implementation} version",
339 current_version = env!("CARGO_PKG_VERSION")
340 );
341 Self { message }
342 }
343
344 pub fn add_help(&mut self, help: &str) {
345 self.message.push_str("\n= help: ");
346 self.message.push_str(help);
347 }
348
349 pub fn finish(self) -> String {
350 self.message
351 }
352 }
353}
354
355fn rustc_minor_version() -> Option<u32> {
356 static RUSTC_MINOR_VERSION: OnceLock<Option<u32>> = OnceLock::new();
357 *RUSTC_MINOR_VERSION.get_or_init(|| {
358 let rustc = env::var_os("RUSTC")?;
359 let output = Command::new(rustc).arg("--version").output().ok()?;
360 let version = core::str::from_utf8(&output.stdout).ok()?;
361 let mut pieces = version.split('.');
362 if pieces.next() != Some("rustc 1") {
363 return None;
364 }
365 pieces.next()?.parse().ok()
366 })
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn extension_module_link_args() {
375 let mut buf = Vec::new();
376
377 _add_extension_module_link_args(
379 &Triple::from_str("x86_64-pc-windows-msvc").unwrap(),
380 &mut buf,
381 );
382 assert_eq!(buf, Vec::new());
383
384 _add_extension_module_link_args(
385 &Triple::from_str("x86_64-apple-darwin").unwrap(),
386 &mut buf,
387 );
388 assert_eq!(
389 std::str::from_utf8(&buf).unwrap(),
390 "cargo:rustc-cdylib-link-arg=-undefined\n\
391 cargo:rustc-cdylib-link-arg=dynamic_lookup\n"
392 );
393
394 buf.clear();
395 _add_extension_module_link_args(
396 &Triple::from_str("wasm32-unknown-emscripten").unwrap(),
397 &mut buf,
398 );
399 assert_eq!(
400 std::str::from_utf8(&buf).unwrap(),
401 "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2\n\
402 cargo:rustc-cdylib-link-arg=-sWASM_BIGINT\n"
403 );
404 }
405
406 #[cfg(feature = "resolve-config")]
407 #[test]
408 fn python_framework_link_args() {
409 let mut buf = Vec::new();
410
411 let interpreter_config = InterpreterConfig {
412 implementation: PythonImplementation::CPython,
413 version: PythonVersion {
414 major: 3,
415 minor: 13,
416 },
417 shared: true,
418 abi3: false,
419 lib_name: None,
420 lib_dir: None,
421 executable: None,
422 pointer_width: None,
423 build_flags: BuildFlags::default(),
424 suppress_build_script_link_lines: false,
425 extra_build_script_lines: vec![],
426 python_framework_prefix: Some(
427 "/Applications/Xcode.app/Contents/Developer/Library/Frameworks".to_string(),
428 ),
429 };
430 _add_python_framework_link_args(
432 &interpreter_config,
433 &Triple::from_str("x86_64-pc-windows-msvc").unwrap(),
434 true,
435 &mut buf,
436 );
437 assert_eq!(buf, Vec::new());
438
439 _add_python_framework_link_args(
440 &interpreter_config,
441 &Triple::from_str("x86_64-apple-darwin").unwrap(),
442 true,
443 &mut buf,
444 );
445 assert_eq!(
446 std::str::from_utf8(&buf).unwrap(),
447 "cargo:rustc-link-arg=-Wl,-rpath,/Applications/Xcode.app/Contents/Developer/Library/Frameworks\n"
448 );
449 }
450
451 #[test]
452 #[cfg(feature = "resolve-config")]
453 fn test_maximum_version_exceeded_formatting() {
454 let interpreter_config = InterpreterConfig {
455 implementation: PythonImplementation::CPython,
456 version: PythonVersion {
457 major: 3,
458 minor: 13,
459 },
460 shared: true,
461 abi3: false,
462 lib_name: None,
463 lib_dir: None,
464 executable: None,
465 pointer_width: None,
466 build_flags: BuildFlags::default(),
467 suppress_build_script_link_lines: false,
468 extra_build_script_lines: vec![],
469 python_framework_prefix: None,
470 };
471 let mut error = pyo3_build_script_impl::MaximumVersionExceeded::new(
472 &interpreter_config,
473 PythonVersion {
474 major: 3,
475 minor: 12,
476 },
477 );
478 error.add_help("this is a help message");
479 let error = error.finish();
480 let expected = concat!("\
481 the configured Python version (3.13) is newer than PyO3's maximum supported version (3.12)\n\
482 = help: this package is being built with PyO3 version ", env!("CARGO_PKG_VERSION"), "\n\
483 = help: check https://crates.io/crates/pyo3 for the latest PyO3 version available\n\
484 = help: updating this package to the latest version of PyO3 may provide compatibility with this Python version\n\
485 = help: this is a help message"
486 );
487 assert_eq!(error, expected);
488 }
489}