@@ -2,6 +2,7 @@ use std::{
2
2
collections:: { HashMap , HashSet } ,
3
3
convert:: AsRef ,
4
4
env,
5
+ ffi:: OsString ,
5
6
fs:: { self , DirEntry } ,
6
7
io,
7
8
path:: { Path , PathBuf } ,
@@ -30,6 +31,20 @@ macro_rules! warn {
30
31
} ;
31
32
}
32
33
34
+ /// Gets an environment variable owned by cargo.
35
+ ///
36
+ /// Environment variables set by cargo are expected to be valid UTF8.
37
+ fn cargo_env_var ( var : & str ) -> Option < String > {
38
+ env:: var_os ( var) . map ( |os_string| os_string. to_str ( ) . unwrap ( ) . into ( ) )
39
+ }
40
+
41
+ /// Gets an external environment variable, and registers the build script to rerun if
42
+ /// the variable changes.
43
+ fn env_var ( var : & str ) -> Option < OsString > {
44
+ println ! ( "cargo:rerun-if-env-changed={}" , var) ;
45
+ env:: var_os ( var)
46
+ }
47
+
33
48
/// Information returned from python interpreter
34
49
#[ derive( Debug ) ]
35
50
struct InterpreterConfig {
@@ -78,7 +93,7 @@ impl FromStr for PythonInterpreterKind {
78
93
}
79
94
80
95
fn is_abi3 ( ) -> bool {
81
- env :: var_os ( "CARGO_FEATURE_ABI3" ) . is_some ( )
96
+ cargo_env_var ( "CARGO_FEATURE_ABI3" ) . is_some ( )
82
97
}
83
98
84
99
trait GetPrimitive {
@@ -114,40 +129,29 @@ struct CrossCompileConfig {
114
129
arch : String ,
115
130
}
116
131
117
- impl CrossCompileConfig {
118
- fn new ( ) -> Result < Self > {
119
- Ok ( CrossCompileConfig {
120
- lib_dir : CrossCompileConfig :: validate_variable ( "PYO3_CROSS_LIB_DIR" ) ?,
121
- os : env:: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ,
122
- arch : env:: var ( "CARGO_CFG_TARGET_ARCH" ) . unwrap ( ) ,
123
- version : env:: var_os ( "PYO3_CROSS_PYTHON_VERSION" ) . map ( |s| s. into_string ( ) . unwrap ( ) ) ,
124
- } )
132
+ fn cross_compiling ( ) -> Result < Option < CrossCompileConfig > > {
133
+ for var in & [
134
+ "PYO3_CROSS" ,
135
+ "PYO3_CROSS_LIB_DIR" ,
136
+ "PYO3_CROSS_PYTHON_VERSION" ,
137
+ ] {
138
+ println ! ( "cargo:rerun-if-env-changed={}" , var) ;
125
139
}
126
140
127
- fn validate_variable ( var : & str ) -> Result < PathBuf > {
128
- let path = match env:: var_os ( var) {
129
- Some ( v) => v,
130
- None => bail ! (
131
- "Must provide {} environment variable when cross-compiling" ,
132
- var
133
- ) ,
134
- } ;
141
+ let cross_lib_dir = env_var ( "PYO3_CROSS_LIB_DIR" ) ;
142
+ let cross_python_version = env_var ( "PYO3_CROSS_PYTHON_VERSION" ) ;
135
143
136
- if fs :: metadata ( & path ) . is_err ( ) {
137
- bail ! ( "{} value of {:?} does not exist" , var , path )
138
- }
144
+ let target_arch = cargo_env_var ( "CARGO_CFG_TARGET_ARCH" ) ;
145
+ let target_vendor = cargo_env_var ( "CARGO_CFG_TARGET_VENDOR" ) ;
146
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) ;
139
147
140
- Ok ( path. into ( ) )
141
- }
142
- }
143
-
144
- fn cross_compiling ( ) -> Result < Option < CrossCompileConfig > > {
145
- if env:: var_os ( "PYO3_CROSS" ) . is_none ( )
146
- && env:: var_os ( "PYO3_CROSS_LIB_DIR" ) . is_none ( )
147
- && env:: var_os ( "PYO3_CROSS_PYTHON_VERSION" ) . is_none ( )
148
+ if env_var ( "PYO3_CROSS" ) . is_none ( ) && cross_lib_dir. is_none ( ) && cross_python_version. is_none ( )
148
149
{
149
- let target = env:: var ( "TARGET" ) ?;
150
- let host = env:: var ( "HOST" ) ?;
150
+ // No cross-compiling environment variables set; try to determine if this is a known case
151
+ // which is not cross-compilation.
152
+
153
+ let target = cargo_env_var ( "TARGET" ) . unwrap ( ) ;
154
+ let host = cargo_env_var ( "HOST" ) . unwrap ( ) ;
151
155
if target == host {
152
156
// Not cross-compiling
153
157
return Ok ( None ) ;
@@ -168,20 +172,32 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
168
172
return Ok ( None ) ;
169
173
}
170
174
171
- if host. starts_with ( & format ! (
172
- "{}-{}-{}" ,
173
- env:: var( "CARGO_CFG_TARGET_ARCH" ) ?,
174
- env:: var( "CARGO_CFG_TARGET_VENDOR" ) ?,
175
- env:: var( "CARGO_CFG_TARGET_OS" ) ?
176
- ) ) {
177
- // Not cross-compiling if arch-vendor-os is all the same
178
- // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
179
- return Ok ( None ) ;
175
+ if let ( Some ( arch) , Some ( vendor) , Some ( os) ) = ( & target_arch, & target_vendor, & target_os) {
176
+ if host. starts_with ( & format ! ( "{}-{}-{}" , arch, vendor, os) ) {
177
+ // Not cross-compiling if arch-vendor-os is all the same
178
+ // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
179
+ return Ok ( None ) ;
180
+ }
180
181
}
181
182
}
182
183
183
- // Cross-compiling on any other platform
184
- Ok ( Some ( CrossCompileConfig :: new ( ) ?) )
184
+ // At this point we assume that we are cross compiling.
185
+
186
+ Ok ( Some ( CrossCompileConfig {
187
+ lib_dir : cross_lib_dir
188
+ . ok_or ( "The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling" ) ?
189
+ . into ( ) ,
190
+ os : target_os. unwrap ( ) ,
191
+ arch : target_arch. unwrap ( ) ,
192
+ version : cross_python_version
193
+ . map ( |os_string| {
194
+ os_string
195
+ . to_str ( )
196
+ . ok_or ( "PYO3_CROSS_PYTHON_VERSION is not valid utf-8." )
197
+ . map ( str:: to_owned)
198
+ } )
199
+ . transpose ( ) ?,
200
+ } ) )
185
201
}
186
202
187
203
/// A list of python interpreter compile-time preprocessor defines that
@@ -221,7 +237,7 @@ impl BuildFlags {
221
237
/// the interpreter and printing variables of interest from
222
238
/// sysconfig.get_config_vars.
223
239
fn from_interpreter ( python_path : & Path ) -> Result < Self > {
224
- if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
240
+ if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
225
241
return Ok ( Self :: windows_hardcoded ( ) ) ;
226
242
}
227
243
@@ -372,7 +388,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
372
388
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
373
389
fn find_sysconfigdata ( cross : & CrossCompileConfig ) -> Result < PathBuf > {
374
390
let sysconfig_paths = search_lib_dir ( & cross. lib_dir , & cross) ;
375
- let sysconfig_name = env :: var_os ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
391
+ let sysconfig_name = env_var ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
376
392
let mut sysconfig_paths = sysconfig_paths
377
393
. iter ( )
378
394
. filter_map ( |p| {
@@ -520,7 +536,7 @@ fn windows_hardcoded_cross_compile(
520
536
fn load_cross_compile_info (
521
537
cross_compile_config : CrossCompileConfig ,
522
538
) -> Result < ( InterpreterConfig , BuildFlags ) > {
523
- match env :: var_os ( "CARGO_CFG_TARGET_FAMILY" ) {
539
+ match cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) {
524
540
// Configure for unix platforms using the sysconfigdata file
525
541
Some ( os) if os == "unix" => load_cross_compile_from_sysconfigdata ( cross_compile_config) ,
526
542
// Use hardcoded interpreter config when targeting Windows
@@ -575,14 +591,14 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
575
591
}
576
592
577
593
fn get_rustc_link_lib ( config : & InterpreterConfig ) -> String {
578
- let link_name = if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) == "windows" {
594
+ let link_name = if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
579
595
if is_abi3 ( ) {
580
596
// Link against python3.lib for the stable ABI on Windows.
581
597
// See https://www.python.org/dev/peps/pep-0384/#linkage
582
598
//
583
599
// This contains only the limited ABI symbols.
584
600
"pythonXY:python3" . to_owned ( )
585
- } else if env :: var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) . as_str ( ) == "gnu" {
601
+ } else if cargo_env_var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) == "gnu" {
586
602
// https://packages.msys2.org/base/mingw-w64-python
587
603
format ! (
588
604
"pythonXY:python{}.{}" ,
@@ -608,13 +624,31 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
608
624
)
609
625
}
610
626
627
+ fn get_venv_path ( ) -> Option < PathBuf > {
628
+ match ( env_var ( "VIRTUAL_ENV" ) , env_var ( "CONDA_PREFIX" ) ) {
629
+ ( Some ( dir) , None ) => Some ( PathBuf :: from ( dir) ) ,
630
+ ( None , Some ( dir) ) => Some ( PathBuf :: from ( dir) ) ,
631
+ ( Some ( _) , Some ( _) ) => {
632
+ warn ! (
633
+ "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
634
+ locating the Python interpreter until you unset one of them."
635
+ ) ;
636
+ None
637
+ }
638
+ ( None , None ) => None ,
639
+ }
640
+ }
641
+
611
642
fn find_interpreter ( ) -> Result < PathBuf > {
612
- if let Some ( exe) = env:: var_os ( "PYO3_PYTHON" ) {
613
- Ok ( exe. into ( ) )
614
- } else if let Some ( exe) = env:: var_os ( "PYTHON_SYS_EXECUTABLE" ) {
615
- // Backwards-compatible name for PYO3_PYTHON; this may be removed at some point in the future.
643
+ if let Some ( exe) = env_var ( "PYO3_PYTHON" ) {
616
644
Ok ( exe. into ( ) )
645
+ } else if let Some ( venv_path) = get_venv_path ( ) {
646
+ match cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) {
647
+ "windows" => Ok ( venv_path. join ( "Scripts\\ python" ) ) ,
648
+ _ => Ok ( venv_path. join ( "bin/python" ) ) ,
649
+ }
617
650
} else {
651
+ println ! ( "cargo:rerun-if-env-changed=PATH" ) ;
618
652
[ "python" , "python3" ]
619
653
. iter ( )
620
654
. find ( |bin| {
@@ -687,7 +721,7 @@ print("calcsize_pointer", struct.calcsize("P"))
687
721
let output = run_python_script ( interpreter, script) ?;
688
722
let map: HashMap < String , String > = parse_script_output ( & output) ;
689
723
let shared = match (
690
- env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
724
+ cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
691
725
map[ "framework" ] . as_str ( ) ,
692
726
map[ "shared" ] . as_str ( ) ,
693
727
) {
@@ -730,8 +764,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
730
764
731
765
check_target_architecture ( interpreter_config) ?;
732
766
733
- let target_os = env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ;
734
- let is_extension_module = env :: var_os ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( ) ;
767
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ;
768
+ let is_extension_module = cargo_env_var ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( ) ;
735
769
match ( is_extension_module, target_os. as_str ( ) ) {
736
770
( _, "windows" ) => {
737
771
// always link on windows, even with extension module
@@ -798,7 +832,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
798
832
799
833
fn check_target_architecture ( interpreter_config : & InterpreterConfig ) -> Result < ( ) > {
800
834
// Try to check whether the target architecture matches the python library
801
- let rust_target = match env:: var ( "CARGO_CFG_TARGET_POINTER_WIDTH" ) ?. as_str ( ) {
835
+ let rust_target = match cargo_env_var ( "CARGO_CFG_TARGET_POINTER_WIDTH" )
836
+ . unwrap ( )
837
+ . as_str ( )
838
+ {
802
839
"64" => "64-bit" ,
803
840
"32" => "32-bit" ,
804
841
x => bail ! ( "unexpected Rust target pointer width: {}" , x) ,
@@ -833,10 +870,10 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
833
870
834
871
fn get_abi3_minor_version ( ) -> Option < u8 > {
835
872
( PY3_MIN_MINOR ..=ABI3_MAX_MINOR )
836
- . find ( |i| env :: var_os ( format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
873
+ . find ( |i| cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
837
874
}
838
875
839
- fn abi3_without_interpreter ( ) -> Result < ( ) > {
876
+ fn configure_abi3_without_interpreter ( ) {
840
877
println ! ( "cargo:rustc-cfg=Py_LIMITED_API" ) ;
841
878
let mut flags = "FLAG_WITH_THREAD=1" . to_string ( ) ;
842
879
let abi_version = get_abi3_minor_version ( ) . unwrap_or ( ABI3_MAX_MINOR ) ;
@@ -847,7 +884,7 @@ fn abi3_without_interpreter() -> Result<()> {
847
884
println ! ( "cargo:rustc-cfg=py_sys_config=\" WITH_THREAD\" " ) ;
848
885
println ! ( "cargo:python_flags={}" , flags) ;
849
886
850
- if env :: var ( "CARGO_CFG_TARGET_FAMILY" ) ? == "windows" {
887
+ if cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) . unwrap ( ) == "windows" {
851
888
// Unfortunately, on windows we can't build without at least providing
852
889
// python.lib to the linker. While maturin tells the linker the location
853
890
// of python.lib, we need to do the renaming here, otherwise cargo
@@ -859,18 +896,17 @@ fn abi3_without_interpreter() -> Result<()> {
859
896
// assume "Py_ENABLE_SHARED" to be set on Windows.
860
897
println ! ( "cargo:rustc-cfg=Py_SHARED" ) ;
861
898
}
862
-
863
- Ok ( ( ) )
864
899
}
865
900
866
901
fn main_impl ( ) -> Result < ( ) > {
867
902
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
868
903
// We only check for the abi3-py3{ABI3_MAX_MINOR} because lower versions depend on it.
869
- if env :: var_os ( "PYO3_NO_PYTHON" ) . is_some ( )
870
- && env :: var_os ( format ! ( "CARGO_FEATURE_ABI3_PY3{}" , ABI3_MAX_MINOR ) ) . is_some ( )
904
+ if cargo_env_var ( "PYO3_NO_PYTHON" ) . is_some ( )
905
+ && cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , ABI3_MAX_MINOR ) ) . is_some ( )
871
906
{
872
907
println ! ( "cargo:rerun-if-env-changed=PYO3_NO_PYTHON" ) ;
873
- return abi3_without_interpreter ( ) ;
908
+ configure_abi3_without_interpreter ( ) ;
909
+ return Ok ( ( ) ) ;
874
910
}
875
911
// 1. Setup cfg variables so we can do conditional compilation in this library based on the
876
912
// python interpeter's compilation flags. This is necessary for e.g. matching the right unicode
@@ -898,28 +934,6 @@ fn main_impl() -> Result<()> {
898
934
println ! ( "cargo:rustc-cfg={}=\" {}\" " , CFG_KEY , flag)
899
935
}
900
936
901
- for var in & [
902
- "LIB" ,
903
- "LD_LIBRARY_PATH" ,
904
- "PYO3_PYTHON" ,
905
- "PYO3_CROSS" ,
906
- "PYO3_CROSS_LIB_DIR" ,
907
- "PYO3_CROSS_PYTHON_VERSION" ,
908
- ] {
909
- println ! ( "cargo:rerun-if-env-changed={}" , var) ;
910
- }
911
-
912
- if env:: var_os ( "PYO3_PYTHON" ) . is_none ( ) {
913
- // When PYO3_PYTHON is not used, PYTHON_SYS_EXECUTABLE has the highest priority.
914
- // Let's watch it.
915
- println ! ( "cargo:rerun-if-env-changed=PYTHON_SYS_EXECUTABLE" ) ;
916
- if env:: var_os ( "PYTHON_SYS_EXECUTABLE" ) . is_none ( ) {
917
- // When PYTHON_SYS_EXECUTABLE is also not used, then we use PATH.
918
- // Let's watch this, too.
919
- println ! ( "cargo:rerun-if-env-changed=PATH" ) ;
920
- }
921
- }
922
-
923
937
// TODO: this is a hack to workaround compile_error! warnings about auto-initialize on PyPy
924
938
// Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this.
925
939
if env:: var_os ( "PYO3_CI" ) . is_some ( ) {
0 commit comments