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