@@ -13,6 +13,7 @@ use std::{
1313 path:: { Path , PathBuf } ,
1414 process:: { Command , Stdio } ,
1515 str:: { self , FromStr } ,
16+ sync:: LazyLock ,
1617} ;
1718
1819pub use target_lexicon:: Triple ;
@@ -51,26 +52,146 @@ thread_local! {
5152 static READ_ENV_VARS : RefCell <Vec <String >> = const { RefCell :: new( Vec :: new( ) ) } ;
5253}
5354
54- /// Gets an environment variable owned by cargo.
55- ///
56- /// Environment variables set by cargo are expected to be valid UTF8.
57- pub fn cargo_env_var ( var : & str ) -> Option < String > {
58- env :: var_os ( var ) . map ( |os_string| os_string . to_str ( ) . unwrap ( ) . into ( ) )
55+ pub static BUILD_CTX : LazyLock < BuildScriptContext > = LazyLock :: new ( BuildScriptContext :: new ) ;
56+
57+ pub struct BuildScriptContext {
58+ pub ext : ExtEnv ,
59+ pub cargo : CargoEnv ,
5960}
6061
61- /// Gets an external environment variable, and registers the build script to rerun if
62- /// the variable changes.
63- pub fn env_var ( var : & str ) -> Option < OsString > {
64- if cfg ! ( feature = "resolve-config" ) {
65- println ! ( "cargo:rerun-if-env-changed={var}" ) ;
62+ pub struct ExtEnv {
63+ pub is_print_config : LazyLock < bool > ,
64+ pub use_abi13_forward_compatibility : LazyLock < bool > ,
65+ pub pyo3_config_file : LazyLock < Option < PathBuf > > ,
66+ pub pyo3_python : LazyLock < Option < OsString > > ,
67+ pub pyo3_no_python : LazyLock < bool > ,
68+ pub pyo3_build_extension_module : LazyLock < bool > ,
69+ pub pyo3_cross : LazyLock < Option < OsString > > ,
70+ pub pyo3_cross_lib_dir : LazyLock < Option < OsString > > ,
71+ pub pyo3_cross_python_version : LazyLock < Option < OsString > > ,
72+ pub pyo3_cross_python_implementation : LazyLock < Option < OsString > > ,
73+ pub python_sysconfigdata_name : LazyLock < Option < OsString > > ,
74+ pub virtual_env : LazyLock < Option < OsString > > ,
75+ pub conda_prefix : LazyLock < Option < OsString > > ,
76+ }
77+
78+ pub struct CargoEnv {
79+ pub dep_python_pyo3_config : LazyLock < Option < String > > ,
80+ pub cargo_feature_abi3 : LazyLock < bool > ,
81+ pub cargo_feature_extension_module : LazyLock < bool > ,
82+
83+ /// The minimum supported Python version from PyO3 `abi3-py*` features.
84+ /// Must be called from a PyO3 crate build script.
85+ pub abi3_version : LazyLock < Option < PythonVersion > > ,
86+ pub cargo_cfg_target_pointer_width :
87+ LazyLock < Result < u32 , Box < dyn std:: error:: Error + Send + Sync > > > ,
88+ pub cargo_cfg_target_os : LazyLock < String > ,
89+ pub cargo_feature_auto_initialize : LazyLock < bool > ,
90+ }
91+
92+ impl BuildScriptContext {
93+ pub fn new ( ) -> Self {
94+ Self {
95+ ext : ExtEnv :: new ( ) ,
96+ cargo : CargoEnv :: new ( ) ,
97+ }
6698 }
67- #[ cfg( test) ]
68- {
69- READ_ENV_VARS . with ( |env_vars| {
70- env_vars. borrow_mut ( ) . push ( var. to_owned ( ) ) ;
71- } ) ;
99+ }
100+
101+ impl Default for BuildScriptContext {
102+ fn default ( ) -> Self {
103+ Self :: new ( )
104+ }
105+ }
106+
107+ impl ExtEnv {
108+ pub fn new ( ) -> Self {
109+ Self {
110+ is_print_config : LazyLock :: new ( || {
111+ Self :: env_var ( "PYO3_PRINT_CONFIG" ) . is_some_and ( |os_str| os_str == "1" )
112+ } ) ,
113+ use_abi13_forward_compatibility : LazyLock :: new ( || {
114+ Self :: env_var ( "PYO3_USE_ABI3_FORWARD_COMPATIBILITY" )
115+ . is_some_and ( & |os_str| os_str == "1" )
116+ } ) ,
117+ pyo3_config_file : LazyLock :: new ( || {
118+ Self :: env_var ( "PYO3_CONFIG_FILE" ) . map ( PathBuf :: from)
119+ } ) ,
120+ pyo3_python : LazyLock :: new ( || Self :: env_var ( "PYO3_PYTHON" ) ) ,
121+ pyo3_no_python : LazyLock :: new ( || Self :: env_var ( "PYO3_NO_PYTHON" ) . is_none ( ) ) ,
122+ pyo3_build_extension_module : LazyLock :: new ( || {
123+ Self :: env_var ( "PYO3_BUILD_EXTENSION_MODULE" ) . is_some ( )
124+ } ) ,
125+ pyo3_cross : LazyLock :: new ( || Self :: env_var ( "PYO3_CROSS" ) ) ,
126+ pyo3_cross_lib_dir : LazyLock :: new ( || Self :: env_var ( "PYO3_CROSS_LIB_DIR" ) ) ,
127+ pyo3_cross_python_version : LazyLock :: new ( || Self :: env_var ( "PYO3_CROSS_PYTHON_VERSION" ) ) ,
128+ pyo3_cross_python_implementation : LazyLock :: new ( || {
129+ Self :: env_var ( "PYO3_CROSS_PYTHON_IMPLEMENTATION" )
130+ } ) ,
131+ python_sysconfigdata_name : LazyLock :: new ( || {
132+ Self :: env_var ( "_PYTHON_SYSCONFIGDATA_NAME" )
133+ } ) ,
134+ virtual_env : LazyLock :: new ( || Self :: env_var ( "VIRTUAL_ENV" ) ) ,
135+ conda_prefix : LazyLock :: new ( || Self :: env_var ( "CONDA_PREFIX" ) ) ,
136+ }
137+ }
138+
139+ /// Gets an external environment variable, and registers the build script to rerun if
140+ /// the variable changes.
141+ pub fn env_var ( var : & str ) -> Option < OsString > {
142+ if cfg ! ( feature = "resolve-config" ) {
143+ println ! ( "cargo:rerun-if-env-changed={var}" ) ;
144+ }
145+ #[ cfg( test) ]
146+ {
147+ READ_ENV_VARS . with ( |env_vars| {
148+ env_vars. borrow_mut ( ) . push ( var. to_owned ( ) ) ;
149+ } ) ;
150+ }
151+ env:: var_os ( var)
152+ }
153+ }
154+
155+ impl CargoEnv {
156+ fn new ( ) -> Self {
157+ Self {
158+ dep_python_pyo3_config : LazyLock :: new ( || Self :: cargo_env_var ( "DEP_PYTHON_PYO3_CONFIG" ) ) ,
159+ cargo_feature_abi3 : LazyLock :: new ( || {
160+ Self :: cargo_env_var ( "CARGO_FEATURE_ABI3" ) . is_some ( )
161+ } ) ,
162+ cargo_feature_extension_module : LazyLock :: new ( || {
163+ Self :: cargo_env_var ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( )
164+ } ) ,
165+ abi3_version : LazyLock :: new ( || {
166+ let minor_version = ( MINIMUM_SUPPORTED_VERSION . minor ..=ABI3_MAX_MINOR )
167+ . find ( |i| Self :: cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{i}" ) ) . is_some ( ) ) ;
168+ minor_version. map ( |minor| PythonVersion { major : 3 , minor } )
169+ } ) ,
170+ cargo_cfg_target_pointer_width : LazyLock :: new ( || {
171+ Ok (
172+ match Self :: cargo_env_var ( "CARGO_CFG_TARGET_POINTER_WIDTH" ) . as_deref ( ) {
173+ Some ( "64" ) => 64 ,
174+ Some ( "32" ) => 32 ,
175+ Some ( x) => bail ! ( "unexpected Rust target pointer width: {}" , x) ,
176+ None => bail ! ( "CARGO_CFG_TARGET_POINTER_WIDTH is unset" ) ,
177+ } ,
178+ )
179+ } ) ,
180+ cargo_cfg_target_os : LazyLock :: new ( || {
181+ Self :: cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( )
182+ } ) ,
183+ cargo_feature_auto_initialize : LazyLock :: new ( || {
184+ Self :: cargo_env_var ( "CARGO_FEATURE_AUTO_INITIALIZE" ) . is_some ( )
185+ } ) ,
186+ }
187+ }
188+
189+ /// Gets an environment variable owned by cargo.
190+ ///
191+ /// Environment variables set by cargo are expected to be valid UTF8.
192+ pub fn cargo_env_var ( var : & str ) -> Option < String > {
193+ env:: var_os ( var) . map ( |os_string| os_string. to_str ( ) . unwrap ( ) . into ( ) )
72194 }
73- env:: var_os ( var)
74195}
75196
76197/// Gets the compilation target triple from environment variables set by Cargo.
@@ -455,8 +576,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
455576 /// The `abi3` features, if set, may apply an `abi3` constraint to the Python version.
456577 #[ allow( dead_code) ] // only used in build.rs
457578 pub ( super ) fn from_pyo3_config_file_env ( ) -> Option < Result < Self > > {
458- env_var ( "PYO3_CONFIG_FILE" ) . map ( |path| {
459- let path = Path :: new ( & path) ;
579+ BUILD_CTX . ext . pyo3_config_file . as_ref ( ) . map ( |path| {
460580 println ! ( "cargo:rerun-if-changed={}" , path. display( ) ) ;
461581 // Absolute path is necessary because this build script is run with a cwd different to the
462582 // original `cargo build` instruction.
@@ -473,7 +593,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
473593 // TODO: abi3 is a property of the build mode, not the interpreter. Should this be
474594 // removed from `InterpreterConfig`?
475595 config. abi3 |= is_abi3 ( ) ;
476- config. fixup_for_abi3_version ( get_abi3_version ( ) ) ?;
596+ config. fixup_for_abi3_version ( * BUILD_CTX . cargo . abi3_version ) ?;
477597
478598 Ok ( config)
479599 } )
@@ -490,8 +610,11 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED"))
490610
491611 #[ doc( hidden) ]
492612 pub fn from_cargo_dep_env ( ) -> Option < Result < Self > > {
493- cargo_env_var ( "DEP_PYTHON_PYO3_CONFIG" )
494- . map ( |buf| InterpreterConfig :: from_reader ( & * unescape ( & buf) ) )
613+ BUILD_CTX
614+ . cargo
615+ . dep_python_pyo3_config
616+ . as_ref ( )
617+ . map ( |buf| InterpreterConfig :: from_reader ( & * unescape ( buf) ) )
495618 }
496619
497620 #[ doc( hidden) ]
@@ -834,24 +957,14 @@ impl FromStr for PythonImplementation {
834957///
835958/// Returns `false` if `PYO3_NO_PYTHON` environment variable is set.
836959fn have_python_interpreter ( ) -> bool {
837- env_var ( "PYO3_NO_PYTHON" ) . is_none ( )
960+ * BUILD_CTX . ext . pyo3_no_python
838961}
839962
840963/// Checks if `abi3` or any of the `abi3-py3*` features is enabled for the PyO3 crate.
841964///
842965/// Must be called from a PyO3 crate build script.
843966fn is_abi3 ( ) -> bool {
844- cargo_env_var ( "CARGO_FEATURE_ABI3" ) . is_some ( )
845- || env_var ( "PYO3_USE_ABI3_FORWARD_COMPATIBILITY" ) . is_some_and ( |os_str| os_str == "1" )
846- }
847-
848- /// Gets the minimum supported Python version from PyO3 `abi3-py*` features.
849- ///
850- /// Must be called from a PyO3 crate build script.
851- pub fn get_abi3_version ( ) -> Option < PythonVersion > {
852- let minor_version = ( MINIMUM_SUPPORTED_VERSION . minor ..=ABI3_MAX_MINOR )
853- . find ( |i| cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{i}" ) ) . is_some ( ) ) ;
854- minor_version. map ( |minor| PythonVersion { major : 3 , minor } )
967+ * BUILD_CTX . cargo . cargo_feature_abi3 || * BUILD_CTX . ext . use_abi13_forward_compatibility
855968}
856969
857970/// Checks if the `extension-module` feature is enabled for the PyO3 crate.
@@ -862,8 +975,7 @@ pub fn get_abi3_version() -> Option<PythonVersion> {
862975///
863976/// Must be called from a PyO3 crate build script.
864977pub fn is_extension_module ( ) -> bool {
865- cargo_env_var ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( )
866- || env_var ( "PYO3_BUILD_EXTENSION_MODULE" ) . is_some ( )
978+ * BUILD_CTX . cargo . cargo_feature_extension_module || * BUILD_CTX . ext . pyo3_build_extension_module
867979}
868980
869981/// Checks if we need to link to `libpython` for the target.
@@ -1002,10 +1114,13 @@ impl CrossCompileEnvVars {
10021114 /// Registers the build script to rerun if any of the variables changes.
10031115 fn from_env ( ) -> Self {
10041116 CrossCompileEnvVars {
1005- pyo3_cross : env_var ( "PYO3_CROSS" ) ,
1006- pyo3_cross_lib_dir : env_var ( "PYO3_CROSS_LIB_DIR" ) ,
1007- pyo3_cross_python_version : env_var ( "PYO3_CROSS_PYTHON_VERSION" ) ,
1008- pyo3_cross_python_implementation : env_var ( "PYO3_CROSS_PYTHON_IMPLEMENTATION" ) ,
1117+ pyo3_cross : BUILD_CTX . ext . pyo3_cross . clone ( ) ,
1118+ pyo3_cross_lib_dir : BUILD_CTX . ext . pyo3_cross_lib_dir . clone ( ) ,
1119+ pyo3_cross_python_version : BUILD_CTX . ext . pyo3_cross_python_version . clone ( ) ,
1120+ pyo3_cross_python_implementation : BUILD_CTX
1121+ . ext
1122+ . pyo3_cross_python_implementation
1123+ . clone ( ) ,
10091124 }
10101125 }
10111126
@@ -1412,13 +1527,13 @@ pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result<Vec<PathBuf>
14121527 return Ok ( Vec :: new ( ) ) ;
14131528 } ;
14141529
1415- let sysconfig_name = env_var ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
1530+ let sysconfig_name = BUILD_CTX . ext . python_sysconfigdata_name . as_deref ( ) ;
14161531 let mut sysconfig_paths = sysconfig_paths
14171532 . iter ( )
14181533 . filter_map ( |p| {
14191534 let canonical = fs:: canonicalize ( p) . ok ( ) ;
14201535 match & sysconfig_name {
1421- Some ( _) => canonical. filter ( |p| p. file_stem ( ) == sysconfig_name. as_deref ( ) ) ,
1536+ Some ( _) => canonical. filter ( |p| p. file_stem ( ) == sysconfig_name) ,
14221537 None => canonical,
14231538 }
14241539 } )
@@ -1554,7 +1669,7 @@ fn cross_compile_from_sysconfigdata(
15541669fn default_cross_compile ( cross_compile_config : & CrossCompileConfig ) -> Result < InterpreterConfig > {
15551670 let version = cross_compile_config
15561671 . version
1557- . or_else ( get_abi3_version )
1672+ . or_else ( || * BUILD_CTX . cargo . abi3_version )
15581673 . ok_or_else ( ||
15591674 format ! (
15601675 "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \
@@ -1829,11 +1944,14 @@ fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf {
18291944}
18301945
18311946fn get_env_interpreter ( ) -> Option < PathBuf > {
1832- match ( env_var ( "VIRTUAL_ENV" ) , env_var ( "CONDA_PREFIX" ) ) {
1947+ match (
1948+ BUILD_CTX . ext . virtual_env . as_ref ( ) ,
1949+ BUILD_CTX . ext . conda_prefix . as_ref ( ) ,
1950+ ) {
18331951 // Use cfg rather than CARGO_CFG_TARGET_OS because this affects where files are located on the
18341952 // build host
1835- ( Some ( dir) , None ) => Some ( venv_interpreter ( & dir, cfg ! ( windows) ) ) ,
1836- ( None , Some ( dir) ) => Some ( conda_env_interpreter ( & dir, cfg ! ( windows) ) ) ,
1953+ ( Some ( dir) , None ) => Some ( venv_interpreter ( dir, cfg ! ( windows) ) ) ,
1954+ ( None , Some ( dir) ) => Some ( conda_env_interpreter ( dir, cfg ! ( windows) ) ) ,
18371955 ( Some ( _) , Some ( _) ) => {
18381956 warn ! (
18391957 "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
@@ -1857,7 +1975,7 @@ pub fn find_interpreter() -> Result<PathBuf> {
18571975 // See https://github.com/PyO3/pyo3/issues/2724
18581976 println ! ( "cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE" ) ;
18591977
1860- if let Some ( exe) = env_var ( "PYO3_PYTHON" ) {
1978+ if let Some ( exe) = BUILD_CTX . ext . pyo3_python . as_ref ( ) {
18611979 Ok ( exe. into ( ) )
18621980 } else if let Some ( env_interpreter) = get_env_interpreter ( ) {
18631981 Ok ( env_interpreter)
@@ -1900,7 +2018,7 @@ fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<Interpret
19002018pub fn make_cross_compile_config ( ) -> Result < Option < InterpreterConfig > > {
19012019 let interpreter_config = if let Some ( cross_config) = cross_compiling_from_cargo_env ( ) ? {
19022020 let mut interpreter_config = load_cross_compile_config ( cross_config) ?;
1903- interpreter_config. fixup_for_abi3_version ( get_abi3_version ( ) ) ?;
2021+ interpreter_config. fixup_for_abi3_version ( * BUILD_CTX . cargo . abi3_version ) ?;
19042022 Some ( interpreter_config)
19052023 } else {
19062024 None
@@ -1914,7 +2032,7 @@ pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
19142032#[ allow( dead_code, unused_mut) ]
19152033pub fn make_interpreter_config ( ) -> Result < InterpreterConfig > {
19162034 let host = Triple :: host ( ) ;
1917- let abi3_version = get_abi3_version ( ) ;
2035+ let abi3_version = * BUILD_CTX . cargo . abi3_version ;
19182036
19192037 // See if we can safely skip the Python interpreter configuration detection.
19202038 // Unix "abi3" extension modules can usually be built without any interpreter.
0 commit comments