@@ -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 } ,
@@ -35,6 +36,20 @@ macro_rules! warn {
35
36
} ;
36
37
}
37
38
39
+ /// Gets an environment variable owned by cargo.
40
+ ///
41
+ /// Environment variables set by cargo are expected to be valid UTF8.
42
+ fn cargo_env_var ( var : & str ) -> Option < String > {
43
+ env:: var_os ( var) . map ( |os_string| os_string. to_str ( ) . unwrap ( ) . into ( ) )
44
+ }
45
+
46
+ /// Gets an external environment variable, and registers the build script to rerun if
47
+ /// the variable changes.
48
+ fn env_var ( var : & str ) -> Option < OsString > {
49
+ println ! ( "cargo:rerun-if-env-changed={}" , var) ;
50
+ env:: var_os ( var)
51
+ }
52
+
38
53
/// Information returned from python interpreter
39
54
#[ derive( Debug ) ]
40
55
struct InterpreterConfig {
@@ -83,7 +98,7 @@ impl FromStr for PythonInterpreterKind {
83
98
}
84
99
85
100
fn is_abi3 ( ) -> bool {
86
- env :: var_os ( "CARGO_FEATURE_ABI3" ) . is_some ( )
101
+ cargo_env_var ( "CARGO_FEATURE_ABI3" ) . is_some ( )
87
102
}
88
103
89
104
trait GetPrimitive {
@@ -119,40 +134,29 @@ struct CrossCompileConfig {
119
134
arch : String ,
120
135
}
121
136
122
- impl CrossCompileConfig {
123
- fn new ( ) -> Result < Self > {
124
- Ok ( CrossCompileConfig {
125
- lib_dir : CrossCompileConfig :: validate_variable ( "PYO3_CROSS_LIB_DIR" ) ?,
126
- os : env:: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ,
127
- arch : env:: var ( "CARGO_CFG_TARGET_ARCH" ) . unwrap ( ) ,
128
- version : env:: var_os ( "PYO3_CROSS_PYTHON_VERSION" ) . map ( |s| s. into_string ( ) . unwrap ( ) ) ,
129
- } )
137
+ fn cross_compiling ( ) -> Result < Option < CrossCompileConfig > > {
138
+ for var in & [
139
+ "PYO3_CROSS" ,
140
+ "PYO3_CROSS_LIB_DIR" ,
141
+ "PYO3_CROSS_PYTHON_VERSION" ,
142
+ ] {
143
+ println ! ( "cargo:rerun-if-env-changed={}" , var) ;
130
144
}
131
145
132
- fn validate_variable ( var : & str ) -> Result < PathBuf > {
133
- let path = match env:: var_os ( var) {
134
- Some ( v) => v,
135
- None => bail ! (
136
- "Must provide {} environment variable when cross-compiling" ,
137
- var
138
- ) ,
139
- } ;
146
+ let cross_lib_dir = env_var ( "PYO3_CROSS_LIB_DIR" ) ;
147
+ let cross_python_version = env_var ( "PYO3_CROSS_PYTHON_VERSION" ) ;
140
148
141
- if fs :: metadata ( & path ) . is_err ( ) {
142
- bail ! ( "{} value of {:?} does not exist" , var , path )
143
- }
149
+ let target_arch = cargo_env_var ( "CARGO_CFG_TARGET_ARCH" ) ;
150
+ let target_vendor = cargo_env_var ( "CARGO_CFG_TARGET_VENDOR" ) ;
151
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) ;
144
152
145
- Ok ( path. into ( ) )
146
- }
147
- }
148
-
149
- fn cross_compiling ( ) -> Result < Option < CrossCompileConfig > > {
150
- if env:: var_os ( "PYO3_CROSS" ) . is_none ( )
151
- && env:: var_os ( "PYO3_CROSS_LIB_DIR" ) . is_none ( )
152
- && env:: var_os ( "PYO3_CROSS_PYTHON_VERSION" ) . is_none ( )
153
+ if env_var ( "PYO3_CROSS" ) . is_none ( ) && cross_lib_dir. is_none ( ) && cross_python_version. is_none ( )
153
154
{
154
- let target = env:: var ( "TARGET" ) ?;
155
- let host = env:: var ( "HOST" ) ?;
155
+ // No cross-compiling environment variables set; try to determine if this is a known case
156
+ // which is not cross-compilation.
157
+
158
+ let target = cargo_env_var ( "TARGET" ) . unwrap ( ) ;
159
+ let host = cargo_env_var ( "HOST" ) . unwrap ( ) ;
156
160
if target == host {
157
161
// Not cross-compiling
158
162
return Ok ( None ) ;
@@ -173,20 +177,32 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
173
177
return Ok ( None ) ;
174
178
}
175
179
176
- if host. starts_with ( & format ! (
177
- "{}-{}-{}" ,
178
- env:: var( "CARGO_CFG_TARGET_ARCH" ) ?,
179
- env:: var( "CARGO_CFG_TARGET_VENDOR" ) ?,
180
- env:: var( "CARGO_CFG_TARGET_OS" ) ?
181
- ) ) {
182
- // Not cross-compiling if arch-vendor-os is all the same
183
- // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
184
- return Ok ( None ) ;
180
+ if let ( Some ( arch) , Some ( vendor) , Some ( os) ) = ( & target_arch, & target_vendor, & target_os) {
181
+ if host. starts_with ( & format ! ( "{}-{}-{}" , arch, vendor, os) ) {
182
+ // Not cross-compiling if arch-vendor-os is all the same
183
+ // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
184
+ return Ok ( None ) ;
185
+ }
185
186
}
186
187
}
187
188
188
- // Cross-compiling on any other platform
189
- Ok ( Some ( CrossCompileConfig :: new ( ) ?) )
189
+ // At this point we assume that we are cross compiling.
190
+
191
+ Ok ( Some ( CrossCompileConfig {
192
+ lib_dir : cross_lib_dir
193
+ . ok_or ( "The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling" ) ?
194
+ . into ( ) ,
195
+ os : target_os. unwrap ( ) ,
196
+ arch : target_arch. unwrap ( ) ,
197
+ version : cross_python_version
198
+ . map ( |os_string| {
199
+ os_string
200
+ . to_str ( )
201
+ . ok_or ( "PYO3_CROSS_PYTHON_VERSION is not valid utf-8." )
202
+ . map ( str:: to_owned)
203
+ } )
204
+ . transpose ( ) ?,
205
+ } ) )
190
206
}
191
207
192
208
/// A list of python interpreter compile-time preprocessor defines that
@@ -226,7 +242,7 @@ impl BuildFlags {
226
242
/// the interpreter and printing variables of interest from
227
243
/// sysconfig.get_config_vars.
228
244
fn from_interpreter ( python_path : & Path ) -> Result < Self > {
229
- if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
245
+ if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
230
246
return Ok ( Self :: windows_hardcoded ( ) ) ;
231
247
}
232
248
@@ -377,7 +393,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
377
393
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
378
394
fn find_sysconfigdata ( cross : & CrossCompileConfig ) -> Result < PathBuf > {
379
395
let sysconfig_paths = search_lib_dir ( & cross. lib_dir , & cross) ;
380
- let sysconfig_name = env :: var_os ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
396
+ let sysconfig_name = env_var ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
381
397
let mut sysconfig_paths = sysconfig_paths
382
398
. iter ( )
383
399
. filter_map ( |p| {
@@ -525,7 +541,7 @@ fn windows_hardcoded_cross_compile(
525
541
fn load_cross_compile_info (
526
542
cross_compile_config : CrossCompileConfig ,
527
543
) -> Result < ( InterpreterConfig , BuildFlags ) > {
528
- match env :: var_os ( "CARGO_CFG_TARGET_FAMILY" ) {
544
+ match cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) {
529
545
// Configure for unix platforms using the sysconfigdata file
530
546
Some ( os) if os == "unix" => load_cross_compile_from_sysconfigdata ( cross_compile_config) ,
531
547
// Use hardcoded interpreter config when targeting Windows
@@ -580,14 +596,14 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
580
596
}
581
597
582
598
fn get_rustc_link_lib ( config : & InterpreterConfig ) -> String {
583
- let link_name = if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) == "windows" {
599
+ let link_name = if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
584
600
if is_abi3 ( ) {
585
601
// Link against python3.lib for the stable ABI on Windows.
586
602
// See https://www.python.org/dev/peps/pep-0384/#linkage
587
603
//
588
604
// This contains only the limited ABI symbols.
589
605
"pythonXY:python3" . to_owned ( )
590
- } else if env :: var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) . as_str ( ) == "gnu" {
606
+ } else if cargo_env_var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) == "gnu" {
591
607
// https://packages.msys2.org/base/mingw-w64-python
592
608
format ! (
593
609
"pythonXY:python{}.{}" ,
@@ -613,13 +629,31 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
613
629
)
614
630
}
615
631
632
+ fn get_venv_path ( ) -> Option < PathBuf > {
633
+ match ( env_var ( "VIRTUAL_ENV" ) , env_var ( "CONDA_PREFIX" ) ) {
634
+ ( Some ( dir) , None ) => Some ( PathBuf :: from ( dir) ) ,
635
+ ( None , Some ( dir) ) => Some ( PathBuf :: from ( dir) ) ,
636
+ ( Some ( _) , Some ( _) ) => {
637
+ warn ! (
638
+ "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
639
+ locating the Python interpreter until you unset one of them."
640
+ ) ;
641
+ None
642
+ }
643
+ ( None , None ) => None ,
644
+ }
645
+ }
646
+
616
647
fn find_interpreter ( ) -> Result < PathBuf > {
617
- if let Some ( exe) = env:: var_os ( "PYO3_PYTHON" ) {
618
- Ok ( exe. into ( ) )
619
- } else if let Some ( exe) = env:: var_os ( "PYTHON_SYS_EXECUTABLE" ) {
620
- // Backwards-compatible name for PYO3_PYTHON; this may be removed at some point in the future.
648
+ if let Some ( exe) = env_var ( "PYO3_PYTHON" ) {
621
649
Ok ( exe. into ( ) )
650
+ } else if let Some ( venv_path) = get_venv_path ( ) {
651
+ match cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) {
652
+ "windows" => Ok ( venv_path. join ( "Scripts\\ python" ) ) ,
653
+ _ => Ok ( venv_path. join ( "bin/python" ) ) ,
654
+ }
622
655
} else {
656
+ println ! ( "cargo:rerun-if-env-changed=PATH" ) ;
623
657
[ "python" , "python3" ]
624
658
. iter ( )
625
659
. find ( |bin| {
@@ -692,7 +726,7 @@ print("calcsize_pointer", struct.calcsize("P"))
692
726
let output = run_python_script ( interpreter, script) ?;
693
727
let map: HashMap < String , String > = parse_script_output ( & output) ;
694
728
let shared = match (
695
- env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
729
+ cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
696
730
map[ "framework" ] . as_str ( ) ,
697
731
map[ "shared" ] . as_str ( ) ,
698
732
) {
@@ -735,8 +769,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
735
769
736
770
check_target_architecture ( interpreter_config) ?;
737
771
738
- let target_os = env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ;
739
- let is_extension_module = env :: var_os ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( ) ;
772
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ;
773
+ let is_extension_module = cargo_env_var ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( ) ;
740
774
match ( is_extension_module, target_os. as_str ( ) ) {
741
775
( _, "windows" ) => {
742
776
// always link on windows, even with extension module
@@ -833,7 +867,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
833
867
834
868
fn check_target_architecture ( interpreter_config : & InterpreterConfig ) -> Result < ( ) > {
835
869
// Try to check whether the target architecture matches the python library
836
- let rust_target = match env:: var ( "CARGO_CFG_TARGET_POINTER_WIDTH" ) ?. as_str ( ) {
870
+ let rust_target = match cargo_env_var ( "CARGO_CFG_TARGET_POINTER_WIDTH" )
871
+ . unwrap ( )
872
+ . as_str ( )
873
+ {
837
874
"64" => "64-bit" ,
838
875
"32" => "32-bit" ,
839
876
x => bail ! ( "unexpected Rust target pointer width: {}" , x) ,
@@ -868,10 +905,10 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
868
905
869
906
fn get_abi3_minor_version ( ) -> Option < u8 > {
870
907
( PY3_MIN_MINOR ..=ABI3_MAX_MINOR )
871
- . find ( |i| env :: var_os ( format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
908
+ . find ( |i| cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
872
909
}
873
910
874
- fn abi3_without_interpreter ( ) -> Result < ( ) > {
911
+ fn configure_abi3_without_interpreter ( ) {
875
912
println ! ( "cargo:rustc-cfg=Py_LIMITED_API" ) ;
876
913
let mut flags = "FLAG_WITH_THREAD=1" . to_string ( ) ;
877
914
let abi_version = get_abi3_minor_version ( ) . unwrap_or ( ABI3_MAX_MINOR ) ;
@@ -882,26 +919,25 @@ fn abi3_without_interpreter() -> Result<()> {
882
919
println ! ( "cargo:rustc-cfg=py_sys_config=\" WITH_THREAD\" " ) ;
883
920
println ! ( "cargo:python_flags={}" , flags) ;
884
921
885
- if env :: var ( "CARGO_CFG_TARGET_FAMILY" ) ? == "windows" {
922
+ if cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) . unwrap ( ) == "windows" {
886
923
// Unfortunately, on windows we can't build without at least providing
887
924
// python.lib to the linker. While maturin tells the linker the location
888
925
// of python.lib, we need to do the renaming here, otherwise cargo
889
926
// complains that the crate using pyo3 does not contains a `#[link(...)]`
890
927
// attribute with pythonXY.
891
928
println ! ( "cargo:rustc-link-lib=pythonXY:python3" ) ;
892
929
}
893
-
894
- Ok ( ( ) )
895
930
}
896
931
897
932
fn main_impl ( ) -> Result < ( ) > {
898
933
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
899
934
// We only check for the abi3-py3{ABI3_MAX_MINOR} because lower versions depend on it.
900
- if env :: var_os ( "PYO3_NO_PYTHON" ) . is_some ( )
901
- && env :: var_os ( format ! ( "CARGO_FEATURE_ABI3_PY3{}" , ABI3_MAX_MINOR ) ) . is_some ( )
935
+ if cargo_env_var ( "PYO3_NO_PYTHON" ) . is_some ( )
936
+ && cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , ABI3_MAX_MINOR ) ) . is_some ( )
902
937
{
903
938
println ! ( "cargo:rerun-if-env-changed=PYO3_NO_PYTHON" ) ;
904
- return abi3_without_interpreter ( ) ;
939
+ configure_abi3_without_interpreter ( ) ;
940
+ return Ok ( ( ) ) ;
905
941
}
906
942
// 1. Setup cfg variables so we can do conditional compilation in this library based on the
907
943
// python interpeter's compilation flags. This is necessary for e.g. matching the right unicode
@@ -929,28 +965,6 @@ fn main_impl() -> Result<()> {
929
965
println ! ( "cargo:rustc-cfg={}=\" {}\" " , CFG_KEY , flag)
930
966
}
931
967
932
- for var in & [
933
- "LIB" ,
934
- "LD_LIBRARY_PATH" ,
935
- "PYO3_PYTHON" ,
936
- "PYO3_CROSS" ,
937
- "PYO3_CROSS_LIB_DIR" ,
938
- "PYO3_CROSS_PYTHON_VERSION" ,
939
- ] {
940
- println ! ( "cargo:rerun-if-env-changed={}" , var) ;
941
- }
942
-
943
- if env:: var_os ( "PYO3_PYTHON" ) . is_none ( ) {
944
- // When PYO3_PYTHON is not used, PYTHON_SYS_EXECUTABLE has the highest priority.
945
- // Let's watch it.
946
- println ! ( "cargo:rerun-if-env-changed=PYTHON_SYS_EXECUTABLE" ) ;
947
- if env:: var_os ( "PYTHON_SYS_EXECUTABLE" ) . is_none ( ) {
948
- // When PYTHON_SYS_EXECUTABLE is also not used, then we use PATH.
949
- // Let's watch this, too.
950
- println ! ( "cargo:rerun-if-env-changed=PATH" ) ;
951
- }
952
- }
953
-
954
968
Ok ( ( ) )
955
969
}
956
970
0 commit comments