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};
20
21use once_cell::sync::OnceCell;
22
23pub use impl_::{
24 cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags,
25 CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, Triple,
26};
27use target_lexicon::OperatingSystem;
28
29#[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")]
44#[cfg(feature = "resolve-config")]
46pub fn use_pyo3_cfgs() {
47 print_expected_cfgs();
48 for cargo_command in get().build_script_outputs() {
49 println!("{}", cargo_command)
50 }
51}
52
53pub fn add_extension_module_link_args() {
64 _add_extension_module_link_args(&impl_::target_triple_from_env(), std::io::stdout())
65}
66
67fn _add_extension_module_link_args(triple: &Triple, mut writer: impl std::io::Write) {
68 if matches!(triple.operating_system, OperatingSystem::Darwin(_)) {
69 writeln!(writer, "cargo:rustc-cdylib-link-arg=-undefined").unwrap();
70 writeln!(writer, "cargo:rustc-cdylib-link-arg=dynamic_lookup").unwrap();
71 } else if triple == &Triple::from_str("wasm32-unknown-emscripten").unwrap() {
72 writeln!(writer, "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2").unwrap();
73 writeln!(writer, "cargo:rustc-cdylib-link-arg=-sWASM_BIGINT").unwrap();
74 }
75}
76
77#[cfg(feature = "resolve-config")]
86pub fn add_python_framework_link_args() {
87 let interpreter_config = pyo3_build_script_impl::resolve_interpreter_config().unwrap();
88 _add_python_framework_link_args(
89 &interpreter_config,
90 &impl_::target_triple_from_env(),
91 impl_::is_linking_libpython(),
92 std::io::stdout(),
93 )
94}
95
96#[cfg(feature = "resolve-config")]
97fn _add_python_framework_link_args(
98 interpreter_config: &InterpreterConfig,
99 triple: &Triple,
100 link_libpython: bool,
101 mut writer: impl std::io::Write,
102) {
103 if matches!(triple.operating_system, OperatingSystem::Darwin(_)) && link_libpython {
104 if let Some(framework_prefix) = interpreter_config.python_framework_prefix.as_ref() {
105 writeln!(
106 writer,
107 "cargo:rustc-link-arg=-Wl,-rpath,{}",
108 framework_prefix
109 )
110 .unwrap();
111 }
112 }
113}
114
115#[cfg(feature = "resolve-config")]
119pub fn get() -> &'static InterpreterConfig {
120 static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
121 CONFIG.get_or_init(|| {
122 let cross_compile_config_path = resolve_cross_compile_config_path();
124 let cross_compiling = cross_compile_config_path
125 .as_ref()
126 .map(|path| path.exists())
127 .unwrap_or(false);
128
129 #[allow(unknown_lints, clippy::const_is_empty)]
131 if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
132 interpreter_config
133 } else if !CONFIG_FILE.is_empty() {
134 InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
135 } else if cross_compiling {
136 InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap())
137 } else {
138 InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
139 }
140 .expect("failed to parse PyO3 config")
141 })
142}
143
144#[doc(hidden)]
146#[cfg(feature = "resolve-config")]
147const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt"));
148
149#[doc(hidden)]
152#[cfg(feature = "resolve-config")]
153const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt"));
154
155#[doc(hidden)]
161#[cfg(feature = "resolve-config")]
162fn resolve_cross_compile_config_path() -> Option<PathBuf> {
163 env::var_os("TARGET").map(|target| {
164 let mut path = PathBuf::from(env!("OUT_DIR"));
165 path.push(Path::new(&target));
166 path.push("pyo3-build-config.txt");
167 path
168 })
169}
170
171#[doc(hidden)]
176pub fn print_feature_cfgs() {
177 let rustc_minor_version = rustc_minor_version().unwrap_or(0);
178
179 if rustc_minor_version >= 70 {
180 println!("cargo:rustc-cfg=rustc_has_once_lock");
181 }
182
183 if rustc_minor_version >= 71 {
184 println!("cargo:rustc-cfg=rustc_has_extern_c_unwind");
185 }
186
187 if rustc_minor_version >= 74 {
189 println!("cargo:rustc-cfg=invalid_from_utf8_lint");
190 }
191
192 if rustc_minor_version >= 79 {
193 println!("cargo:rustc-cfg=c_str_lit");
194 }
195
196 if rustc_minor_version >= 79 {
199 println!("cargo:rustc-cfg=diagnostic_namespace");
200 }
201
202 if rustc_minor_version >= 83 {
203 println!("cargo:rustc-cfg=io_error_more");
204 }
205
206 if rustc_minor_version >= 85 {
207 println!("cargo:rustc-cfg=fn_ptr_eq");
208 }
209}
210
211#[doc(hidden)]
216pub fn print_expected_cfgs() {
217 if rustc_minor_version().map_or(false, |version| version < 80) {
218 return;
220 }
221
222 println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)");
223 println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)");
224 println!("cargo:rustc-check-cfg=cfg(PyPy)");
225 println!("cargo:rustc-check-cfg=cfg(GraalPy)");
226 println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))");
227 println!("cargo:rustc-check-cfg=cfg(invalid_from_utf8_lint)");
228 println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)");
229 println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)");
230 println!("cargo:rustc-check-cfg=cfg(diagnostic_namespace)");
231 println!("cargo:rustc-check-cfg=cfg(c_str_lit)");
232 println!("cargo:rustc-check-cfg=cfg(rustc_has_once_lock)");
233 println!("cargo:rustc-check-cfg=cfg(rustc_has_extern_c_unwind)");
234 println!("cargo:rustc-check-cfg=cfg(io_error_more)");
235 println!("cargo:rustc-check-cfg=cfg(fn_ptr_eq)");
236
237 for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=std::cmp::max(14, impl_::ABI3_MAX_MINOR + 1) {
241 println!("cargo:rustc-check-cfg=cfg(Py_3_{i})");
242 }
243}
244
245#[doc(hidden)]
249pub mod pyo3_build_script_impl {
250 #[cfg(feature = "resolve-config")]
251 use crate::errors::{Context, Result};
252
253 #[cfg(feature = "resolve-config")]
254 use super::*;
255
256 pub mod errors {
257 pub use crate::errors::*;
258 }
259 pub use crate::impl_::{
260 cargo_env_var, env_var, is_linking_libpython, make_cross_compile_config, InterpreterConfig,
261 PythonVersion,
262 };
263
264 #[cfg(feature = "resolve-config")]
270 pub fn resolve_interpreter_config() -> Result<InterpreterConfig> {
271 #[allow(unknown_lints, clippy::const_is_empty)]
273 if !CONFIG_FILE.is_empty() {
274 let mut interperter_config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))?;
275 interperter_config.generate_import_libs()?;
276 Ok(interperter_config)
277 } else if let Some(interpreter_config) = make_cross_compile_config()? {
278 let path = resolve_cross_compile_config_path()
280 .expect("resolve_interpreter_config() must be called from a build script");
281 let parent_dir = path.parent().ok_or_else(|| {
282 format!(
283 "failed to resolve parent directory of config file {}",
284 path.display()
285 )
286 })?;
287 std::fs::create_dir_all(parent_dir).with_context(|| {
288 format!(
289 "failed to create config file directory {}",
290 parent_dir.display()
291 )
292 })?;
293 interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context(
294 || format!("failed to create config file at {}", path.display()),
295 )?)?;
296 Ok(interpreter_config)
297 } else {
298 InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
299 }
300 }
301}
302
303fn rustc_minor_version() -> Option<u32> {
304 static RUSTC_MINOR_VERSION: OnceCell<Option<u32>> = OnceCell::new();
305 *RUSTC_MINOR_VERSION.get_or_init(|| {
306 let rustc = env::var_os("RUSTC")?;
307 let output = Command::new(rustc).arg("--version").output().ok()?;
308 let version = core::str::from_utf8(&output.stdout).ok()?;
309 let mut pieces = version.split('.');
310 if pieces.next() != Some("rustc 1") {
311 return None;
312 }
313 pieces.next()?.parse().ok()
314 })
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn extension_module_link_args() {
323 let mut buf = Vec::new();
324
325 _add_extension_module_link_args(
327 &Triple::from_str("x86_64-pc-windows-msvc").unwrap(),
328 &mut buf,
329 );
330 assert_eq!(buf, Vec::new());
331
332 _add_extension_module_link_args(
333 &Triple::from_str("x86_64-apple-darwin").unwrap(),
334 &mut buf,
335 );
336 assert_eq!(
337 std::str::from_utf8(&buf).unwrap(),
338 "cargo:rustc-cdylib-link-arg=-undefined\n\
339 cargo:rustc-cdylib-link-arg=dynamic_lookup\n"
340 );
341
342 buf.clear();
343 _add_extension_module_link_args(
344 &Triple::from_str("wasm32-unknown-emscripten").unwrap(),
345 &mut buf,
346 );
347 assert_eq!(
348 std::str::from_utf8(&buf).unwrap(),
349 "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2\n\
350 cargo:rustc-cdylib-link-arg=-sWASM_BIGINT\n"
351 );
352 }
353
354 #[cfg(feature = "resolve-config")]
355 #[test]
356 fn python_framework_link_args() {
357 let mut buf = Vec::new();
358
359 let interpreter_config = InterpreterConfig {
360 implementation: PythonImplementation::CPython,
361 version: PythonVersion {
362 major: 3,
363 minor: 13,
364 },
365 shared: true,
366 abi3: false,
367 lib_name: None,
368 lib_dir: None,
369 executable: None,
370 pointer_width: None,
371 build_flags: BuildFlags::default(),
372 suppress_build_script_link_lines: false,
373 extra_build_script_lines: vec![],
374 python_framework_prefix: Some(
375 "/Applications/Xcode.app/Contents/Developer/Library/Frameworks".to_string(),
376 ),
377 };
378 _add_python_framework_link_args(
380 &interpreter_config,
381 &Triple::from_str("x86_64-pc-windows-msvc").unwrap(),
382 true,
383 &mut buf,
384 );
385 assert_eq!(buf, Vec::new());
386
387 _add_python_framework_link_args(
388 &interpreter_config,
389 &Triple::from_str("x86_64-apple-darwin").unwrap(),
390 true,
391 &mut buf,
392 );
393 assert_eq!(
394 std::str::from_utf8(&buf).unwrap(),
395 "cargo:rustc-link-arg=-Wl,-rpath,/Applications/Xcode.app/Contents/Developer/Library/Frameworks\n"
396 );
397 }
398}