1
- use regex:: Regex ;
2
- use serde:: Deserialize ;
3
- use std:: io:: { self , BufRead , BufReader } ;
4
- use std:: process:: { Command , Stdio } ;
5
- use std:: { collections:: HashMap , convert:: AsRef , env, fmt, fs:: File , path:: Path } ;
1
+ use std:: {
2
+ collections:: HashMap ,
3
+ convert:: AsRef ,
4
+ env, fmt,
5
+ fs:: File ,
6
+ io:: { self , BufRead , BufReader } ,
7
+ path:: { Path , PathBuf } ,
8
+ process:: { Command , Stdio } ,
9
+ str:: FromStr ,
10
+ } ;
6
11
use version_check:: { Channel , Date , Version } ;
7
12
8
13
/// Specifies the minimum nightly version needed to compile pyo3.
@@ -23,25 +28,25 @@ macro_rules! bail {
23
28
}
24
29
25
30
/// Information returned from python interpreter
26
- #[ derive( Deserialize , Debug ) ]
31
+ #[ derive( Debug ) ]
27
32
struct InterpreterConfig {
28
33
version : PythonVersion ,
29
34
libdir : Option < String > ,
30
35
shared : bool ,
31
36
ld_version : String ,
32
37
/// Prefix used for determining the directory of libpython
33
38
base_prefix : String ,
34
- executable : String ,
39
+ executable : PathBuf ,
35
40
calcsize_pointer : Option < u32 > ,
36
41
}
37
42
38
- #[ derive( Deserialize , Debug , Clone , PartialEq ) ]
43
+ #[ derive( Debug , Clone , PartialEq ) ]
39
44
pub enum PythonInterpreterKind {
40
45
CPython ,
41
46
PyPy ,
42
47
}
43
48
44
- #[ derive( Deserialize , Debug , Clone ) ]
49
+ #[ derive( Debug , Clone ) ]
45
50
struct PythonVersion {
46
51
major : u8 ,
47
52
// minor == None means any minor version will do
@@ -67,6 +72,17 @@ impl fmt::Display for PythonVersion {
67
72
}
68
73
}
69
74
75
+ impl FromStr for PythonInterpreterKind {
76
+ type Err = Box < dyn std:: error:: Error > ;
77
+ fn from_str ( s : & str ) -> Result < Self > {
78
+ match s {
79
+ "CPython" => Ok ( Self :: CPython ) ,
80
+ "PyPy" => Ok ( Self :: PyPy ) ,
81
+ _ => Err ( format ! ( "Invalid interpreter: {}" , s) . into ( ) ) ,
82
+ }
83
+ }
84
+ }
85
+
70
86
/// A list of python interpreter compile-time preprocessor defines that
71
87
/// we will pick up and pass to rustc via --cfg=py_sys_config={varname};
72
88
/// this allows using them conditional cfg attributes in the .rs files, so
@@ -101,22 +117,15 @@ static SYSCONFIG_VALUES: [&str; 1] = [
101
117
/// Attempts to parse the header at the given path, returning a map of definitions to their values.
102
118
/// Each entry in the map directly corresponds to a `#define` in the given header.
103
119
fn parse_header_defines ( header_path : impl AsRef < Path > ) -> Result < HashMap < String , String > > {
104
- // This regex picks apart a C style, single line `#define` statement into an identifier and a
105
- // value. e.g. for the line `#define Py_DEBUG 1`, this regex will capture `Py_DEBUG` into
106
- // `ident` and `1` into `value`.
107
- let define_regex = Regex :: new ( r"^\s*#define\s+(?P<ident>[a-zA-Z0-9_]+)\s+(?P<value>.+)\s*$" ) ?;
108
-
109
- let header_file = File :: open ( header_path. as_ref ( ) ) ?;
110
- let header_reader = BufReader :: new ( & header_file) ;
111
-
120
+ let header_reader = BufReader :: new ( File :: open ( header_path. as_ref ( ) ) ?) ;
112
121
let mut definitions = HashMap :: new ( ) ;
113
- let tostr = |r : regex:: Match < ' _ > | r. as_str ( ) . to_string ( ) ;
114
122
for maybe_line in header_reader. lines ( ) {
115
- if let Some ( captures) = define_regex. captures ( & maybe_line?) {
116
- match ( captures. name ( "ident" ) , captures. name ( "value" ) ) {
117
- ( Some ( key) , Some ( val) ) => definitions. insert ( tostr ( key) , tostr ( val) ) ,
118
- _ => None ,
119
- } ;
123
+ let line = maybe_line?;
124
+ let mut i = line. trim ( ) . split_whitespace ( ) ;
125
+ if i. next ( ) == Some ( "#define" ) {
126
+ if let ( Some ( key) , Some ( value) , None ) = ( i. next ( ) , i. next ( ) , i. next ( ) ) {
127
+ definitions. insert ( key. into ( ) , value. into ( ) ) ;
128
+ }
120
129
}
121
130
}
122
131
Ok ( definitions)
@@ -179,7 +188,7 @@ fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, Strin
179
188
shared,
180
189
ld_version : "" . to_string ( ) ,
181
190
base_prefix : "" . to_string ( ) ,
182
- executable : "" . to_string ( ) ,
191
+ executable : PathBuf :: new ( ) ,
183
192
calcsize_pointer : None ,
184
193
} ;
185
194
@@ -190,10 +199,7 @@ fn load_cross_compile_info() -> Result<(InterpreterConfig, HashMap<String, Strin
190
199
/// the interpreter and printing variables of interest from
191
200
/// sysconfig.get_config_vars.
192
201
#[ cfg( not( target_os = "windows" ) ) ]
193
- fn get_config_vars ( python_path : & str ) -> Result < HashMap < String , String > > {
194
- // FIXME: We can do much better here using serde:
195
- // import json, sysconfig; print(json.dumps({k:str(v) for k, v in sysconfig.get_config_vars().items()}))
196
-
202
+ fn get_config_vars ( python_path : & Path ) -> Result < HashMap < String , String > > {
197
203
let mut script = "import sysconfig; \
198
204
config = sysconfig.get_config_vars();"
199
205
. to_owned ( ) ;
@@ -228,7 +234,7 @@ fn get_config_vars(python_path: &str) -> Result<HashMap<String, String>> {
228
234
}
229
235
230
236
#[ cfg( target_os = "windows" ) ]
231
- fn get_config_vars ( _: & str ) -> Result < HashMap < String , String > > {
237
+ fn get_config_vars ( _: & Path ) -> Result < HashMap < String , String > > {
232
238
// sysconfig is missing all the flags on windows, so we can't actually
233
239
// query the interpreter directly for its build flags.
234
240
//
@@ -274,7 +280,7 @@ fn cfg_line_for_var(key: &str, val: &str) -> Option<String> {
274
280
}
275
281
276
282
/// Run a python script using the specified interpreter binary.
277
- fn run_python_script ( interpreter : & str , script : & str ) -> Result < String > {
283
+ fn run_python_script ( interpreter : & Path , script : & str ) -> Result < String > {
278
284
let out = Command :: new ( interpreter)
279
285
. args ( & [ "-c" , script] )
280
286
. stderr ( Stdio :: inherit ( ) )
@@ -286,12 +292,12 @@ fn run_python_script(interpreter: &str, script: &str) -> Result<String> {
286
292
bail ! (
287
293
"Could not find any interpreter at {}, \
288
294
are you sure you have Python installed on your PATH?",
289
- interpreter
295
+ interpreter. display ( )
290
296
) ;
291
297
} else {
292
298
bail ! (
293
299
"Failed to run the Python interpreter at {}: {}" ,
294
- interpreter,
300
+ interpreter. display ( ) ,
295
301
err
296
302
) ;
297
303
}
@@ -395,33 +401,24 @@ fn get_rustc_link_lib(config: &InterpreterConfig) -> Result<String> {
395
401
///
396
402
/// If none of the above works, an error is returned
397
403
fn find_interpreter_and_get_config ( ) -> Result < ( InterpreterConfig , HashMap < String , String > ) > {
398
- if let Some ( sys_executable) = env:: var_os ( "PYTHON_SYS_EXECUTABLE" ) {
399
- let interpreter_path = sys_executable
400
- . to_str ( )
401
- . ok_or ( "Unable to get PYTHON_SYS_EXECUTABLE value" ) ?;
402
- let interpreter_config = get_config_from_interpreter ( interpreter_path) ?;
403
-
404
- return Ok ( ( interpreter_config, get_config_vars ( interpreter_path) ?) ) ;
404
+ let python_interpreter = if let Some ( exe) = env:: var_os ( "PYTHON_SYS_EXECUTABLE" ) {
405
+ exe. into ( )
406
+ } else {
407
+ PathBuf :: from (
408
+ [ "python" , "python3" ]
409
+ . iter ( )
410
+ . find ( |bin| {
411
+ if let Ok ( out) = Command :: new ( bin) . arg ( "--version" ) . output ( ) {
412
+ // begin with `Python 3.X.X :: additional info`
413
+ out. stdout . starts_with ( b"Python 3" ) || out. stderr . starts_with ( b"Python 3" )
414
+ } else {
415
+ false
416
+ }
417
+ } )
418
+ . ok_or ( "Python 3.x interpreter not found" ) ?,
419
+ )
405
420
} ;
406
421
407
- let python_interpreter = [ "python" , "python3" ]
408
- . iter ( )
409
- . find ( |bin| {
410
- if let Ok ( out) = Command :: new ( bin) . arg ( "--version" ) . output ( ) {
411
- // begin with `Python 3.X.X :: additional info`
412
- out. stdout . starts_with ( b"Python 3" ) || out. stderr . starts_with ( b"Python 3" )
413
- } else {
414
- false
415
- }
416
- } )
417
- . ok_or ( "Python 3.x interpreter not found" ) ?;
418
-
419
- // check default python
420
- let interpreter_config = get_config_from_interpreter ( & python_interpreter) ?;
421
- if interpreter_config. version . major == 3 {
422
- return Ok ( ( interpreter_config, get_config_vars ( & python_interpreter) ?) ) ;
423
- }
424
-
425
422
let interpreter_config = get_config_from_interpreter ( & python_interpreter) ?;
426
423
if interpreter_config. version . major == 3 {
427
424
return Ok ( ( interpreter_config, get_config_vars ( & python_interpreter) ?) ) ;
@@ -431,7 +428,7 @@ fn find_interpreter_and_get_config() -> Result<(InterpreterConfig, HashMap<Strin
431
428
}
432
429
433
430
/// Extract compilation vars from the specified interpreter.
434
- fn get_config_from_interpreter ( interpreter : & str ) -> Result < InterpreterConfig > {
431
+ fn get_config_from_interpreter ( interpreter : & Path ) -> Result < InterpreterConfig > {
435
432
let script = r#"
436
433
import json
437
434
import platform
@@ -446,23 +443,40 @@ try:
446
443
except AttributeError:
447
444
base_prefix = sys.exec_prefix
448
445
449
- print(json.dumps({
450
- "version": {
451
- "major": sys.version_info[0],
452
- "minor": sys.version_info[1],
453
- "implementation": platform.python_implementation()
454
- },
455
- "libdir": sysconfig.get_config_var('LIBDIR'),
456
- "ld_version": sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'),
457
- "base_prefix": base_prefix,
458
- "shared": PYPY or bool(sysconfig.get_config_var('Py_ENABLE_SHARED')),
459
- "executable": sys.executable,
460
- "calcsize_pointer": struct.calcsize("P"),
461
- }))
446
+ libdir = sysconfig.get_config_var('LIBDIR')
447
+
448
+ print("version_major", sys.version_info[0])
449
+ print("version_minor", sys.version_info[1])
450
+ print("implementation", platform.python_implementation())
451
+ if libdir is not None:
452
+ print("libdir", libdir)
453
+ print("ld_version", sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'))
454
+ print("base_prefix", base_prefix)
455
+ print("shared", PYPY or bool(sysconfig.get_config_var('Py_ENABLE_SHARED')))
456
+ print("executable", sys.executable)
457
+ print("calcsize_pointer", struct.calcsize("P"))
462
458
"# ;
463
- let json = run_python_script ( interpreter, script) ?;
464
- Ok ( serde_json:: from_str ( & json)
465
- . map_err ( |e| format ! ( "Failed to get InterPreterConfig: {}" , e) ) ?)
459
+ let output = run_python_script ( interpreter, script) ?;
460
+ let map: HashMap < String , String > = output
461
+ . lines ( )
462
+ . filter_map ( |line| {
463
+ let mut i = line. splitn ( 2 , ' ' ) ;
464
+ Some ( ( i. next ( ) ?. into ( ) , i. next ( ) ?. into ( ) ) )
465
+ } )
466
+ . collect ( ) ;
467
+ Ok ( InterpreterConfig {
468
+ version : PythonVersion {
469
+ major : map[ "version_major" ] . parse ( ) ?,
470
+ minor : Some ( map[ "version_minor" ] . parse ( ) ?) ,
471
+ implementation : map[ "implementation" ] . parse ( ) ?,
472
+ } ,
473
+ libdir : map. get ( "libdir" ) . cloned ( ) ,
474
+ shared : map[ "shared" ] == "True" ,
475
+ ld_version : map[ "ld_version" ] . clone ( ) ,
476
+ base_prefix : map[ "base_prefix" ] . clone ( ) ,
477
+ executable : map[ "executable" ] . clone ( ) . into ( ) ,
478
+ calcsize_pointer : Some ( map[ "calcsize_pointer" ] . parse ( ) ?) ,
479
+ } )
466
480
}
467
481
468
482
fn configure ( interpreter_config : & InterpreterConfig ) -> Result < String > {
0 commit comments