1 | //! Configuration used by PyO3 for conditional support of varying Python versions. |
2 | //! |
3 | //! This crate exposes functionality to be called from build scripts to simplify building crates |
4 | //! which depend on PyO3. |
5 | //! |
6 | //! It used internally by the PyO3 crate's build script to apply the same configuration. |
7 | |
8 | #![warn (elided_lifetimes_in_paths, unused_lifetimes)] |
9 | |
10 | mod errors; |
11 | mod impl_; |
12 | |
13 | #[cfg (feature = "resolve-config" )] |
14 | use std::{ |
15 | io::Cursor, |
16 | path::{Path, PathBuf}, |
17 | }; |
18 | |
19 | use std::{env, process::Command, str::FromStr}; |
20 | |
21 | #[cfg (feature = "resolve-config" )] |
22 | use once_cell::sync::OnceCell; |
23 | |
24 | pub use impl_::{ |
25 | cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, |
26 | CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, Triple, |
27 | }; |
28 | use target_lexicon::OperatingSystem; |
29 | |
30 | /// Adds all the [`#[cfg]` flags](index.html) to the current compilation. |
31 | /// |
32 | /// This should be called from a build script. |
33 | /// |
34 | /// The full list of attributes added are the following: |
35 | /// |
36 | /// | Flag | Description | |
37 | /// | ---- | ----------- | |
38 | /// | `#[cfg(Py_3_7)]`, `#[cfg(Py_3_8)]`, `#[cfg(Py_3_9)]`, `#[cfg(Py_3_10)]` | These attributes mark code only for a given Python version and up. For example, `#[cfg(Py_3_7)]` marks code which can run on Python 3.7 **and newer**. | |
39 | /// | `#[cfg(Py_LIMITED_API)]` | This marks code which is run when compiling with PyO3's `abi3` feature enabled. | |
40 | /// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. | |
41 | /// |
42 | /// For examples of how to use these attributes, [see PyO3's guide](https://pyo3.rs/latest/building_and_distribution/multiple_python_versions.html). |
43 | #[cfg (feature = "resolve-config" )] |
44 | pub fn use_pyo3_cfgs() { |
45 | for cargo_command: String in get().build_script_outputs() { |
46 | println!(" {}" , cargo_command) |
47 | } |
48 | } |
49 | |
50 | /// Adds linker arguments suitable for PyO3's `extension-module` feature. |
51 | /// |
52 | /// This should be called from a build script. |
53 | /// |
54 | /// The following link flags are added: |
55 | /// - macOS: `-undefined dynamic_lookup` |
56 | /// - wasm32-unknown-emscripten: `-sSIDE_MODULE=2 -sWASM_BIGINT` |
57 | /// |
58 | /// All other platforms currently are no-ops, however this may change as necessary |
59 | /// in future. |
60 | pub fn add_extension_module_link_args() { |
61 | _add_extension_module_link_args(&impl_::target_triple_from_env(), writer:std::io::stdout()) |
62 | } |
63 | |
64 | fn _add_extension_module_link_args(triple: &Triple, mut writer: impl std::io::Write) { |
65 | if triple.operating_system == OperatingSystem::Darwin { |
66 | writeln!(writer, "cargo:rustc-cdylib-link-arg=-undefined" ).unwrap(); |
67 | writeln!(writer, "cargo:rustc-cdylib-link-arg=dynamic_lookup" ).unwrap(); |
68 | } else if triple == &Triple::from_str("wasm32-unknown-emscripten" ).unwrap() { |
69 | writeln!(writer, "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2" ).unwrap(); |
70 | writeln!(writer, "cargo:rustc-cdylib-link-arg=-sWASM_BIGINT" ).unwrap(); |
71 | } |
72 | } |
73 | |
74 | /// Loads the configuration determined from the build environment. |
75 | /// |
76 | /// Because this will never change in a given compilation run, this is cached in a `once_cell`. |
77 | #[cfg (feature = "resolve-config" )] |
78 | pub fn get() -> &'static InterpreterConfig { |
79 | static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new(); |
80 | CONFIG.get_or_init(|| { |
81 | // Check if we are in a build script and cross compiling to a different target. |
82 | let cross_compile_config_path: Option = resolve_cross_compile_config_path(); |
83 | let cross_compiling: bool = cross_compile_config_path |
84 | .as_ref() |
85 | .map(|path| path.exists()) |
86 | .unwrap_or(default:false); |
87 | |
88 | if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() { |
89 | interpreter_config |
90 | } else if !CONFIG_FILE.is_empty() { |
91 | InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) |
92 | } else if cross_compiling { |
93 | InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap()) |
94 | } else { |
95 | InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG)) |
96 | } |
97 | .expect(msg:"failed to parse PyO3 config" ) |
98 | }) |
99 | } |
100 | |
101 | /// Build configuration provided by `PYO3_CONFIG_FILE`. May be empty if env var not set. |
102 | #[doc (hidden)] |
103 | #[cfg (feature = "resolve-config" )] |
104 | const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR" ), "/pyo3-build-config-file.txt" )); |
105 | |
106 | /// Build configuration discovered by `pyo3-build-config` build script. Not aware of |
107 | /// cross-compilation settings. |
108 | #[doc (hidden)] |
109 | #[cfg (feature = "resolve-config" )] |
110 | const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR" ), "/pyo3-build-config.txt" )); |
111 | |
112 | /// Returns the path where PyO3's build.rs writes its cross compile configuration. |
113 | /// |
114 | /// The config file will be named `$OUT_DIR/<triple>/pyo3-build-config.txt`. |
115 | /// |
116 | /// Must be called from a build script, returns `None` if not. |
117 | #[doc (hidden)] |
118 | #[cfg (feature = "resolve-config" )] |
119 | fn resolve_cross_compile_config_path() -> Option<PathBuf> { |
120 | env::var_os(key:"TARGET" ).map(|target: OsString| { |
121 | let mut path: PathBuf = PathBuf::from(env!("OUT_DIR" )); |
122 | path.push(Path::new(&target)); |
123 | path.push(path:"pyo3-build-config.txt" ); |
124 | path |
125 | }) |
126 | } |
127 | |
128 | /// Use certain features if we detect the compiler being used supports them. |
129 | /// |
130 | /// Features may be removed or added as MSRV gets bumped or new features become available, |
131 | /// so this function is unstable. |
132 | #[doc (hidden)] |
133 | pub fn print_feature_cfgs() { |
134 | fn rustc_minor_version() -> Option<u32> { |
135 | let rustc: OsString = env::var_os(key:"RUSTC" )?; |
136 | let output: Output = Command::new(program:rustc).arg("--version" ).output().ok()?; |
137 | let version: &str = core::str::from_utf8(&output.stdout).ok()?; |
138 | let mut pieces: Split<'_, char> = version.split('.' ); |
139 | if pieces.next() != Some("rustc 1" ) { |
140 | return None; |
141 | } |
142 | pieces.next()?.parse().ok() |
143 | } |
144 | |
145 | let rustc_minor_version: u32 = rustc_minor_version().unwrap_or(default:0); |
146 | |
147 | // Enable use of const initializer for thread_local! on Rust 1.59 and greater |
148 | if rustc_minor_version >= 59 { |
149 | println!("cargo:rustc-cfg=thread_local_const_init" ); |
150 | } |
151 | |
152 | // invalid_from_utf8 lint was added in Rust 1.74 |
153 | if rustc_minor_version >= 74 { |
154 | println!("cargo:rustc-cfg=invalid_from_utf8_lint" ); |
155 | } |
156 | } |
157 | |
158 | /// Private exports used in PyO3's build.rs |
159 | /// |
160 | /// Please don't use these - they could change at any time. |
161 | #[doc (hidden)] |
162 | pub mod pyo3_build_script_impl { |
163 | #[cfg (feature = "resolve-config" )] |
164 | use crate::errors::{Context, Result}; |
165 | |
166 | #[cfg (feature = "resolve-config" )] |
167 | use super::*; |
168 | |
169 | pub mod errors { |
170 | pub use crate::errors::*; |
171 | } |
172 | pub use crate::impl_::{ |
173 | cargo_env_var, env_var, is_linking_libpython, make_cross_compile_config, InterpreterConfig, |
174 | PythonVersion, |
175 | }; |
176 | |
177 | /// Gets the configuration for use from PyO3's build script. |
178 | /// |
179 | /// Differs from .get() above only in the cross-compile case, where PyO3's build script is |
180 | /// required to generate a new config (as it's the first build script which has access to the |
181 | /// correct value for CARGO_CFG_TARGET_OS). |
182 | #[cfg (feature = "resolve-config" )] |
183 | pub fn resolve_interpreter_config() -> Result<InterpreterConfig> { |
184 | if !CONFIG_FILE.is_empty() { |
185 | let mut interperter_config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))?; |
186 | interperter_config.generate_import_libs()?; |
187 | Ok(interperter_config) |
188 | } else if let Some(interpreter_config) = make_cross_compile_config()? { |
189 | // This is a cross compile and need to write the config file. |
190 | let path = resolve_cross_compile_config_path() |
191 | .expect("resolve_interpreter_config() must be called from a build script" ); |
192 | let parent_dir = path.parent().ok_or_else(|| { |
193 | format!( |
194 | "failed to resolve parent directory of config file {}" , |
195 | path.display() |
196 | ) |
197 | })?; |
198 | std::fs::create_dir_all(parent_dir).with_context(|| { |
199 | format!( |
200 | "failed to create config file directory {}" , |
201 | parent_dir.display() |
202 | ) |
203 | })?; |
204 | interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context( |
205 | || format!("failed to create config file at {}" , path.display()), |
206 | )?)?; |
207 | Ok(interpreter_config) |
208 | } else { |
209 | InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG)) |
210 | } |
211 | } |
212 | } |
213 | |
214 | #[cfg (test)] |
215 | mod tests { |
216 | use super::*; |
217 | |
218 | #[test ] |
219 | fn extension_module_link_args() { |
220 | let mut buf = Vec::new(); |
221 | |
222 | // Does nothing on non-mac |
223 | _add_extension_module_link_args( |
224 | &Triple::from_str("x86_64-pc-windows-msvc" ).unwrap(), |
225 | &mut buf, |
226 | ); |
227 | assert_eq!(buf, Vec::new()); |
228 | |
229 | _add_extension_module_link_args( |
230 | &Triple::from_str("x86_64-apple-darwin" ).unwrap(), |
231 | &mut buf, |
232 | ); |
233 | assert_eq!( |
234 | std::str::from_utf8(&buf).unwrap(), |
235 | "cargo:rustc-cdylib-link-arg=-undefined \n\ |
236 | cargo:rustc-cdylib-link-arg=dynamic_lookup \n" |
237 | ); |
238 | |
239 | buf.clear(); |
240 | _add_extension_module_link_args( |
241 | &Triple::from_str("wasm32-unknown-emscripten" ).unwrap(), |
242 | &mut buf, |
243 | ); |
244 | assert_eq!( |
245 | std::str::from_utf8(&buf).unwrap(), |
246 | "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2 \n\ |
247 | cargo:rustc-cdylib-link-arg=-sWASM_BIGINT \n" |
248 | ); |
249 | } |
250 | } |
251 | |