@@ -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,28 @@ 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
- }
148
+ if cross_lib_dir . is_none ( ) && cross_python_version . is_none ( ) {
149
+ // No cross-compiling environment variables set; try to determine if this is a known case
150
+ // which is not cross-compilation.
143
151
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
- {
149
- let target = env:: var ( "TARGET" ) ?;
150
- let host = env:: var ( "HOST" ) ?;
152
+ let target = cargo_env_var ( "TARGET" ) . unwrap ( ) ;
153
+ let host = cargo_env_var ( "HOST" ) . unwrap ( ) ;
151
154
if target == host {
152
155
// Not cross-compiling
153
156
return Ok ( None ) ;
@@ -168,20 +171,32 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
168
171
return Ok ( None ) ;
169
172
}
170
173
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 ) ;
174
+ if let ( Some ( arch) , Some ( vendor) , Some ( os) ) = ( & target_arch, & target_vendor, & target_os) {
175
+ if host. starts_with ( & format ! ( "{}-{}-{}" , arch, vendor, os) ) {
176
+ // Not cross-compiling if arch-vendor-os is all the same
177
+ // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
178
+ return Ok ( None ) ;
179
+ }
180
180
}
181
181
}
182
182
183
- // Cross-compiling on any other platform
184
- Ok ( Some ( CrossCompileConfig :: new ( ) ?) )
183
+ // At this point we assume that we are cross compiling.
184
+
185
+ Ok ( Some ( CrossCompileConfig {
186
+ lib_dir : cross_lib_dir
187
+ . ok_or ( "The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling" ) ?
188
+ . into ( ) ,
189
+ os : target_os. unwrap ( ) ,
190
+ arch : target_arch. unwrap ( ) ,
191
+ version : cross_python_version
192
+ . map ( |os_string| {
193
+ os_string
194
+ . to_str ( )
195
+ . ok_or ( "PYO3_CROSS_PYTHON_VERSION is not valid utf-8." )
196
+ . map ( str:: to_owned)
197
+ } )
198
+ . transpose ( ) ?,
199
+ } ) )
185
200
}
186
201
187
202
/// A list of python interpreter compile-time preprocessor defines that
@@ -221,7 +236,7 @@ impl BuildFlags {
221
236
/// the interpreter and printing variables of interest from
222
237
/// sysconfig.get_config_vars.
223
238
fn from_interpreter ( python_path : & Path ) -> Result < Self > {
224
- if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
239
+ if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
225
240
return Ok ( Self :: windows_hardcoded ( ) ) ;
226
241
}
227
242
@@ -372,7 +387,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
372
387
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
373
388
fn find_sysconfigdata ( cross : & CrossCompileConfig ) -> Result < PathBuf > {
374
389
let sysconfig_paths = search_lib_dir ( & cross. lib_dir , & cross) ;
375
- let sysconfig_name = env :: var_os ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
390
+ let sysconfig_name = env_var ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
376
391
let mut sysconfig_paths = sysconfig_paths
377
392
. iter ( )
378
393
. filter_map ( |p| {
@@ -520,7 +535,7 @@ fn windows_hardcoded_cross_compile(
520
535
fn load_cross_compile_info (
521
536
cross_compile_config : CrossCompileConfig ,
522
537
) -> Result < ( InterpreterConfig , BuildFlags ) > {
523
- match env :: var_os ( "CARGO_CFG_TARGET_FAMILY" ) {
538
+ match cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) {
524
539
// Configure for unix platforms using the sysconfigdata file
525
540
Some ( os) if os == "unix" => load_cross_compile_from_sysconfigdata ( cross_compile_config) ,
526
541
// Use hardcoded interpreter config when targeting Windows
@@ -575,14 +590,14 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
575
590
}
576
591
577
592
fn get_rustc_link_lib ( config : & InterpreterConfig ) -> String {
578
- let link_name = if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) == "windows" {
593
+ let link_name = if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
579
594
if is_abi3 ( ) {
580
595
// Link against python3.lib for the stable ABI on Windows.
581
596
// See https://www.python.org/dev/peps/pep-0384/#linkage
582
597
//
583
598
// This contains only the limited ABI symbols.
584
599
"pythonXY:python3" . to_owned ( )
585
- } else if env :: var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) . as_str ( ) == "gnu" {
600
+ } else if cargo_env_var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) == "gnu" {
586
601
// https://packages.msys2.org/base/mingw-w64-python
587
602
format ! (
588
603
"pythonXY:python{}.{}" ,
@@ -608,13 +623,31 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
608
623
)
609
624
}
610
625
626
+ fn get_venv_path ( ) -> Option < PathBuf > {
627
+ match ( env_var ( "VIRTUAL_ENV" ) , env_var ( "CONDA_PREFIX" ) ) {
628
+ ( Some ( dir) , None ) => Some ( PathBuf :: from ( dir) ) ,
629
+ ( None , Some ( dir) ) => Some ( PathBuf :: from ( dir) ) ,
630
+ ( Some ( _) , Some ( _) ) => {
631
+ warn ! (
632
+ "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
633
+ locating the Python interpreter until you unset one of them."
634
+ ) ;
635
+ None
636
+ }
637
+ ( None , None ) => None ,
638
+ }
639
+ }
640
+
611
641
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.
642
+ if let Some ( exe) = env_var ( "PYO3_PYTHON" ) {
616
643
Ok ( exe. into ( ) )
644
+ } else if let Some ( venv_path) = get_venv_path ( ) {
645
+ match cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) {
646
+ "windows" => Ok ( venv_path. join ( "Scripts\\ python" ) ) ,
647
+ _ => Ok ( venv_path. join ( "bin/python" ) ) ,
648
+ }
617
649
} else {
650
+ println ! ( "cargo:rerun-if-env-changed=PATH" ) ;
618
651
[ "python" , "python3" ]
619
652
. iter ( )
620
653
. find ( |bin| {
@@ -687,7 +720,7 @@ print("calcsize_pointer", struct.calcsize("P"))
687
720
let output = run_python_script ( interpreter, script) ?;
688
721
let map: HashMap < String , String > = parse_script_output ( & output) ;
689
722
let shared = match (
690
- env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
723
+ cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
691
724
map[ "framework" ] . as_str ( ) ,
692
725
map[ "shared" ] . as_str ( ) ,
693
726
) {
@@ -730,8 +763,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
730
763
731
764
check_target_architecture ( interpreter_config) ?;
732
765
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 ( ) ;
766
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ;
767
+ let is_extension_module = cargo_env_var ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( ) ;
735
768
match ( is_extension_module, target_os. as_str ( ) ) {
736
769
( _, "windows" ) => {
737
770
// always link on windows, even with extension module
@@ -798,7 +831,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
798
831
799
832
fn check_target_architecture ( interpreter_config : & InterpreterConfig ) -> Result < ( ) > {
800
833
// 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 ( ) {
834
+ let rust_target = match cargo_env_var ( "CARGO_CFG_TARGET_POINTER_WIDTH" )
835
+ . unwrap ( )
836
+ . as_str ( )
837
+ {
802
838
"64" => "64-bit" ,
803
839
"32" => "32-bit" ,
804
840
x => bail ! ( "unexpected Rust target pointer width: {}" , x) ,
@@ -833,7 +869,7 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
833
869
834
870
fn get_abi3_minor_version ( ) -> Option < u8 > {
835
871
( PY3_MIN_MINOR ..=ABI3_MAX_MINOR )
836
- . find ( |i| env :: var_os ( format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
872
+ . find ( |i| cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
837
873
}
838
874
839
875
fn abi3_without_interpreter ( ) -> Result < ( ) > {
@@ -847,7 +883,7 @@ fn abi3_without_interpreter() -> Result<()> {
847
883
println ! ( "cargo:rustc-cfg=py_sys_config=\" WITH_THREAD\" " ) ;
848
884
println ! ( "cargo:python_flags={}" , flags) ;
849
885
850
- if env :: var ( "CARGO_CFG_TARGET_FAMILY" ) ? == "windows" {
886
+ if cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) . unwrap ( ) == "windows" {
851
887
// Unfortunately, on windows we can't build without at least providing
852
888
// python.lib to the linker. While maturin tells the linker the location
853
889
// of python.lib, we need to do the renaming here, otherwise cargo
@@ -866,8 +902,8 @@ fn abi3_without_interpreter() -> Result<()> {
866
902
fn main_impl ( ) -> Result < ( ) > {
867
903
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
868
904
// 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 ( )
905
+ if cargo_env_var ( "PYO3_NO_PYTHON" ) . is_some ( )
906
+ && cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , ABI3_MAX_MINOR ) ) . is_some ( )
871
907
{
872
908
println ! ( "cargo:rerun-if-env-changed=PYO3_NO_PYTHON" ) ;
873
909
return abi3_without_interpreter ( ) ;
@@ -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