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