@@ -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,21 @@ 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
- } )
130
- }
131
-
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
- } ;
137
+ fn cross_compiling ( ) -> Result < Option < CrossCompileConfig > > {
138
+ let cross = env_var ( "PYO3_CROSS" ) ;
139
+ let cross_lib_dir = env_var ( "PYO3_CROSS_LIB_DIR" ) ;
140
+ let cross_python_version = env_var ( "PYO3_CROSS_PYTHON_VERSION" ) ;
140
141
141
- if fs :: metadata ( & path ) . is_err ( ) {
142
- bail ! ( "{} value of {:?} does not exist" , var , path )
143
- }
142
+ let target_arch = cargo_env_var ( "CARGO_CFG_TARGET_ARCH" ) ;
143
+ let target_vendor = cargo_env_var ( "CARGO_CFG_TARGET_VENDOR" ) ;
144
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) ;
144
145
145
- Ok ( path . into ( ) )
146
- }
147
- }
146
+ if cross . is_none ( ) && cross_lib_dir . is_none ( ) && cross_python_version . is_none ( ) {
147
+ // No cross-compiling environment variables set; try to determine if this is a known case
148
+ // which is not cross-compilation.
148
149
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
- {
154
- let target = env:: var ( "TARGET" ) ?;
155
- let host = env:: var ( "HOST" ) ?;
150
+ let target = cargo_env_var ( "TARGET" ) . unwrap ( ) ;
151
+ let host = cargo_env_var ( "HOST" ) . unwrap ( ) ;
156
152
if target == host {
157
153
// Not cross-compiling
158
154
return Ok ( None ) ;
@@ -173,20 +169,32 @@ fn cross_compiling() -> Result<Option<CrossCompileConfig>> {
173
169
return Ok ( None ) ;
174
170
}
175
171
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 ) ;
172
+ if let ( Some ( arch) , Some ( vendor) , Some ( os) ) = ( & target_arch, & target_vendor, & target_os) {
173
+ if host. starts_with ( & format ! ( "{}-{}-{}" , arch, vendor, os) ) {
174
+ // Not cross-compiling if arch-vendor-os is all the same
175
+ // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
176
+ return Ok ( None ) ;
177
+ }
185
178
}
186
179
}
187
180
188
- // Cross-compiling on any other platform
189
- Ok ( Some ( CrossCompileConfig :: new ( ) ?) )
181
+ // At this point we assume that we are cross compiling.
182
+
183
+ Ok ( Some ( CrossCompileConfig {
184
+ lib_dir : cross_lib_dir
185
+ . ok_or ( "The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling" ) ?
186
+ . into ( ) ,
187
+ os : target_os. unwrap ( ) ,
188
+ arch : target_arch. unwrap ( ) ,
189
+ version : cross_python_version
190
+ . map ( |os_string| {
191
+ os_string
192
+ . to_str ( )
193
+ . ok_or ( "PYO3_CROSS_PYTHON_VERSION is not valid utf-8." )
194
+ . map ( str:: to_owned)
195
+ } )
196
+ . transpose ( ) ?,
197
+ } ) )
190
198
}
191
199
192
200
/// A list of python interpreter compile-time preprocessor defines that
@@ -226,7 +234,7 @@ impl BuildFlags {
226
234
/// the interpreter and printing variables of interest from
227
235
/// sysconfig.get_config_vars.
228
236
fn from_interpreter ( python_path : & Path ) -> Result < Self > {
229
- if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
237
+ if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
230
238
return Ok ( Self :: windows_hardcoded ( ) ) ;
231
239
}
232
240
@@ -377,7 +385,7 @@ fn ends_with(entry: &DirEntry, pat: &str) -> bool {
377
385
/// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389
378
386
fn find_sysconfigdata ( cross : & CrossCompileConfig ) -> Result < PathBuf > {
379
387
let sysconfig_paths = search_lib_dir ( & cross. lib_dir , & cross) ;
380
- let sysconfig_name = env :: var_os ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
388
+ let sysconfig_name = env_var ( "_PYTHON_SYSCONFIGDATA_NAME" ) ;
381
389
let mut sysconfig_paths = sysconfig_paths
382
390
. iter ( )
383
391
. filter_map ( |p| {
@@ -525,7 +533,7 @@ fn windows_hardcoded_cross_compile(
525
533
fn load_cross_compile_info (
526
534
cross_compile_config : CrossCompileConfig ,
527
535
) -> Result < ( InterpreterConfig , BuildFlags ) > {
528
- match env :: var_os ( "CARGO_CFG_TARGET_FAMILY" ) {
536
+ match cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) {
529
537
// Configure for unix platforms using the sysconfigdata file
530
538
Some ( os) if os == "unix" => load_cross_compile_from_sysconfigdata ( cross_compile_config) ,
531
539
// Use hardcoded interpreter config when targeting Windows
@@ -580,14 +588,14 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
580
588
}
581
589
582
590
fn get_rustc_link_lib ( config : & InterpreterConfig ) -> String {
583
- let link_name = if env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) == "windows" {
591
+ let link_name = if cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) == "windows" {
584
592
if is_abi3 ( ) {
585
593
// Link against python3.lib for the stable ABI on Windows.
586
594
// See https://www.python.org/dev/peps/pep-0384/#linkage
587
595
//
588
596
// This contains only the limited ABI symbols.
589
597
"pythonXY:python3" . to_owned ( )
590
- } else if env :: var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) . as_str ( ) == "gnu" {
598
+ } else if cargo_env_var ( "CARGO_CFG_TARGET_ENV" ) . unwrap ( ) == "gnu" {
591
599
// https://packages.msys2.org/base/mingw-w64-python
592
600
format ! (
593
601
"pythonXY:python{}.{}" ,
@@ -613,13 +621,31 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> String {
613
621
)
614
622
}
615
623
624
+ fn get_venv_path ( ) -> Option < PathBuf > {
625
+ match ( env_var ( "VIRTUAL_ENV" ) , env_var ( "CONDA_PREFIX" ) ) {
626
+ ( Some ( dir) , None ) => Some ( PathBuf :: from ( dir) ) ,
627
+ ( None , Some ( dir) ) => Some ( PathBuf :: from ( dir) ) ,
628
+ ( Some ( _) , Some ( _) ) => {
629
+ warn ! (
630
+ "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \
631
+ locating the Python interpreter until you unset one of them."
632
+ ) ;
633
+ None
634
+ }
635
+ ( None , None ) => None ,
636
+ }
637
+ }
638
+
616
639
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.
640
+ if let Some ( exe) = env_var ( "PYO3_PYTHON" ) {
621
641
Ok ( exe. into ( ) )
642
+ } else if let Some ( venv_path) = get_venv_path ( ) {
643
+ match cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) {
644
+ "windows" => Ok ( venv_path. join ( "Scripts\\ python" ) ) ,
645
+ _ => Ok ( venv_path. join ( "bin/python" ) ) ,
646
+ }
622
647
} else {
648
+ println ! ( "cargo:rerun-if-env-changed=PATH" ) ;
623
649
[ "python" , "python3" ]
624
650
. iter ( )
625
651
. find ( |bin| {
@@ -692,7 +718,7 @@ print("calcsize_pointer", struct.calcsize("P"))
692
718
let output = run_python_script ( interpreter, script) ?;
693
719
let map: HashMap < String , String > = parse_script_output ( & output) ;
694
720
let shared = match (
695
- env :: var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
721
+ cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) . as_str ( ) ,
696
722
map[ "framework" ] . as_str ( ) ,
697
723
map[ "shared" ] . as_str ( ) ,
698
724
) {
@@ -735,8 +761,8 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
735
761
736
762
check_target_architecture ( interpreter_config) ?;
737
763
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 ( ) ;
764
+ let target_os = cargo_env_var ( "CARGO_CFG_TARGET_OS" ) . unwrap ( ) ;
765
+ let is_extension_module = cargo_env_var ( "CARGO_FEATURE_EXTENSION_MODULE" ) . is_some ( ) ;
740
766
match ( is_extension_module, target_os. as_str ( ) ) {
741
767
( _, "windows" ) => {
742
768
// always link on windows, even with extension module
@@ -833,7 +859,10 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result<()> {
833
859
834
860
fn check_target_architecture ( interpreter_config : & InterpreterConfig ) -> Result < ( ) > {
835
861
// 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 ( ) {
862
+ let rust_target = match cargo_env_var ( "CARGO_CFG_TARGET_POINTER_WIDTH" )
863
+ . unwrap ( )
864
+ . as_str ( )
865
+ {
837
866
"64" => "64-bit" ,
838
867
"32" => "32-bit" ,
839
868
x => bail ! ( "unexpected Rust target pointer width: {}" , x) ,
@@ -868,10 +897,10 @@ fn check_target_architecture(interpreter_config: &InterpreterConfig) -> Result<(
868
897
869
898
fn get_abi3_minor_version ( ) -> Option < u8 > {
870
899
( PY3_MIN_MINOR ..=ABI3_MAX_MINOR )
871
- . find ( |i| env :: var_os ( format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
900
+ . find ( |i| cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , i) ) . is_some ( ) )
872
901
}
873
902
874
- fn abi3_without_interpreter ( ) -> Result < ( ) > {
903
+ fn configure_abi3_without_interpreter ( ) {
875
904
println ! ( "cargo:rustc-cfg=Py_LIMITED_API" ) ;
876
905
let mut flags = "FLAG_WITH_THREAD=1" . to_string ( ) ;
877
906
let abi_version = get_abi3_minor_version ( ) . unwrap_or ( ABI3_MAX_MINOR ) ;
@@ -882,26 +911,25 @@ fn abi3_without_interpreter() -> Result<()> {
882
911
println ! ( "cargo:rustc-cfg=py_sys_config=\" WITH_THREAD\" " ) ;
883
912
println ! ( "cargo:python_flags={}" , flags) ;
884
913
885
- if env :: var ( "CARGO_CFG_TARGET_FAMILY" ) ? == "windows" {
914
+ if cargo_env_var ( "CARGO_CFG_TARGET_FAMILY" ) . unwrap ( ) == "windows" {
886
915
// Unfortunately, on windows we can't build without at least providing
887
916
// python.lib to the linker. While maturin tells the linker the location
888
917
// of python.lib, we need to do the renaming here, otherwise cargo
889
918
// complains that the crate using pyo3 does not contains a `#[link(...)]`
890
919
// attribute with pythonXY.
891
920
println ! ( "cargo:rustc-link-lib=pythonXY:python3" ) ;
892
921
}
893
-
894
- Ok ( ( ) )
895
922
}
896
923
897
924
fn main_impl ( ) -> Result < ( ) > {
898
925
// If PYO3_NO_PYTHON is set with abi3, we can build PyO3 without calling Python.
899
926
// 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 ( )
927
+ if cargo_env_var ( "PYO3_NO_PYTHON" ) . is_some ( )
928
+ && cargo_env_var ( & format ! ( "CARGO_FEATURE_ABI3_PY3{}" , ABI3_MAX_MINOR ) ) . is_some ( )
902
929
{
903
930
println ! ( "cargo:rerun-if-env-changed=PYO3_NO_PYTHON" ) ;
904
- return abi3_without_interpreter ( ) ;
931
+ configure_abi3_without_interpreter ( ) ;
932
+ return Ok ( ( ) ) ;
905
933
}
906
934
// 1. Setup cfg variables so we can do conditional compilation in this library based on the
907
935
// python interpeter's compilation flags. This is necessary for e.g. matching the right unicode
@@ -929,28 +957,6 @@ fn main_impl() -> Result<()> {
929
957
println ! ( "cargo:rustc-cfg={}=\" {}\" " , CFG_KEY , flag)
930
958
}
931
959
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
960
Ok ( ( ) )
955
961
}
956
962
0 commit comments