@@ -18,11 +18,18 @@ use version_check::{is_min_date, is_min_version, supports_features};
18
18
const MIN_DATE : & ' static str = "2019-02-06" ;
19
19
const MIN_VERSION : & ' static str = "1.34.0-nightly" ;
20
20
21
+ #[ derive( Debug , Clone , PartialEq ) ]
22
+ pub enum PythonInterpreterKind {
23
+ CPython ,
24
+ PyPy ,
25
+ }
26
+
21
27
#[ derive( Debug ) ]
22
28
struct PythonVersion {
23
29
major : u8 ,
24
30
// minor == None means any minor version will do
25
31
minor : Option < u8 > ,
32
+ implementation : PythonInterpreterKind ,
26
33
}
27
34
28
35
impl PartialEq for PythonVersion {
@@ -142,6 +149,7 @@ fn load_cross_compile_info() -> Result<(PythonVersion, HashMap<String, String>,
142
149
let python_version = PythonVersion {
143
150
major,
144
151
minor : Some ( minor) ,
152
+ implementation : PythonInterpreterKind :: CPython ,
145
153
} ;
146
154
147
155
let config_map = parse_header_defines ( python_include_dir. join ( "pyconfig.h" ) ) ?;
@@ -280,17 +288,43 @@ fn run_python_script(interpreter: &str, script: &str) -> Result<String, String>
280
288
Ok ( String :: from_utf8 ( out. stdout ) . unwrap ( ) )
281
289
}
282
290
291
+ fn get_library_link_name ( version : & PythonVersion , ld_version : & str ) -> String {
292
+ if cfg ! ( target_os = "windows" ) {
293
+ let minor_or_empty_string = match version. minor {
294
+ Some ( minor) => format ! ( "{}" , minor) ,
295
+ None => String :: new ( ) ,
296
+ } ;
297
+ match version. implementation {
298
+ PythonInterpreterKind :: CPython => {
299
+ format ! ( "python{}{}" , version. major, minor_or_empty_string)
300
+ }
301
+ PythonInterpreterKind :: PyPy => format ! ( "pypy{}-c" , version. major) ,
302
+ }
303
+ } else {
304
+ match version. implementation {
305
+ PythonInterpreterKind :: CPython => format ! ( "python{}" , ld_version) ,
306
+ PythonInterpreterKind :: PyPy => format ! ( "pypy{}-c" , version. major) ,
307
+ }
308
+ }
309
+ }
310
+
283
311
#[ cfg( not( target_os = "macos" ) ) ]
284
312
#[ cfg( not( target_os = "windows" ) ) ]
285
313
fn get_rustc_link_lib (
286
- _ : & PythonVersion ,
314
+ version : & PythonVersion ,
287
315
ld_version : & str ,
288
316
enable_shared : bool ,
289
317
) -> Result < String , String > {
290
318
if enable_shared {
291
- Ok ( format ! ( "cargo:rustc-link-lib=python{}" , ld_version) )
319
+ Ok ( format ! (
320
+ "cargo:rustc-link-lib={}" ,
321
+ get_library_link_name( & version, ld_version)
322
+ ) )
292
323
} else {
293
- Ok ( format ! ( "cargo:rustc-link-lib=static=python{}" , ld_version) )
324
+ Ok ( format ! (
325
+ "cargo:rustc-link-lib=static={}" ,
326
+ get_library_link_name( & version, ld_version)
327
+ ) )
294
328
}
295
329
}
296
330
@@ -311,42 +345,63 @@ else:
311
345
}
312
346
313
347
#[ cfg( target_os = "macos" ) ]
314
- fn get_rustc_link_lib ( _: & PythonVersion , ld_version : & str , _: bool ) -> Result < String , String > {
348
+ fn get_rustc_link_lib (
349
+ version : & PythonVersion ,
350
+ ld_version : & str ,
351
+ _: bool ,
352
+ ) -> Result < String , String > {
315
353
// os x can be linked to a framework or static or dynamic, and
316
354
// Py_ENABLE_SHARED is wrong; framework means shared library
317
355
match get_macos_linkmodel ( ) . unwrap ( ) . as_ref ( ) {
318
- "static" => Ok ( format ! ( "cargo:rustc-link-lib=static=python{}" , ld_version) ) ,
319
- "shared" => Ok ( format ! ( "cargo:rustc-link-lib=python{}" , ld_version) ) ,
320
- "framework" => Ok ( format ! ( "cargo:rustc-link-lib=python{}" , ld_version) ) ,
356
+ "static" => Ok ( format ! (
357
+ "cargo:rustc-link-lib=static={}" ,
358
+ get_library_link_name( & version, ld_version)
359
+ ) ) ,
360
+ "shared" => Ok ( format ! (
361
+ "cargo:rustc-link-lib={}" ,
362
+ get_library_link_name( & version, ld_version)
363
+ ) ) ,
364
+ "framework" => Ok ( format ! (
365
+ "cargo:rustc-link-lib={}" ,
366
+ get_library_link_name( & version, ld_version)
367
+ ) ) ,
321
368
other => Err ( format ! ( "unknown linkmodel {}" , other) ) ,
322
369
}
323
370
}
324
371
372
+ #[ cfg( target_os = "windows" ) ]
373
+ fn get_rustc_link_lib (
374
+ version : & PythonVersion ,
375
+ ld_version : & str ,
376
+ _: bool ,
377
+ ) -> Result < String , String > {
378
+ // Py_ENABLE_SHARED doesn't seem to be present on windows.
379
+ Ok ( format ! (
380
+ "cargo:rustc-link-lib=pythonXY:{}" ,
381
+ get_library_link_name( & version, ld_version)
382
+ ) )
383
+ }
384
+
325
385
/// Parse string as interpreter version.
326
- fn get_interpreter_version ( line : & str ) -> Result < PythonVersion , String > {
386
+ fn get_interpreter_version ( line : & str , implementation : & str ) -> Result < PythonVersion , String > {
327
387
let version_re = Regex :: new ( r"\((\d+), (\d+)\)" ) . unwrap ( ) ;
328
388
match version_re. captures ( & line) {
329
389
Some ( cap) => Ok ( PythonVersion {
330
390
major : cap. get ( 1 ) . unwrap ( ) . as_str ( ) . parse ( ) . unwrap ( ) ,
331
391
minor : Some ( cap. get ( 2 ) . unwrap ( ) . as_str ( ) . parse ( ) . unwrap ( ) ) ,
392
+ implementation : match implementation {
393
+ "CPython" => PythonInterpreterKind :: CPython ,
394
+ "PyPy" => PythonInterpreterKind :: PyPy ,
395
+ _ => panic ! ( format!(
396
+ "Unsupported python implementation `{}`" ,
397
+ implementation
398
+ ) ) ,
399
+ } ,
332
400
} ) ,
333
401
None => Err ( format ! ( "Unexpected response to version query {}" , line) ) ,
334
402
}
335
403
}
336
404
337
- #[ cfg( target_os = "windows" ) ]
338
- fn get_rustc_link_lib ( version : & PythonVersion , _: & str , _: bool ) -> Result < String , String > {
339
- // Py_ENABLE_SHARED doesn't seem to be present on windows.
340
- Ok ( format ! (
341
- "cargo:rustc-link-lib=pythonXY:python{}{}" ,
342
- version. major,
343
- match version. minor {
344
- Some ( minor) => minor. to_string( ) ,
345
- None => "" . to_owned( ) ,
346
- }
347
- ) )
348
- }
349
-
350
405
/// Locate a suitable python interpreter and extract config from it.
351
406
///
352
407
/// The following locations are checked in the order listed:
@@ -387,10 +442,17 @@ fn find_interpreter_and_get_config(
387
442
let expected_version = version. unwrap_or ( PythonVersion {
388
443
major : 3 ,
389
444
minor : None ,
445
+ implementation : PythonInterpreterKind :: CPython ,
390
446
} ) ;
391
447
448
+ let binary_name = match expected_version. implementation {
449
+ PythonInterpreterKind :: CPython => "python" ,
450
+ PythonInterpreterKind :: PyPy => "pypy" ,
451
+ } ;
452
+
392
453
// check default python
393
- let interpreter_path = "python" ;
454
+ let interpreter_path = binary_name;
455
+
394
456
let ( interpreter_version, lines) = get_config_from_interpreter ( interpreter_path) ?;
395
457
if expected_version == interpreter_version {
396
458
return Ok ( (
@@ -430,20 +492,45 @@ fn get_config_from_interpreter(interpreter: &str) -> Result<(PythonVersion, Vec<
430
492
let script = r#"
431
493
import sys
432
494
import sysconfig
495
+ import platform
496
+
497
+ PYPY = platform.python_implementation() == "PyPy"
433
498
434
499
print(sys.version_info[0:2])
435
500
print(sysconfig.get_config_var('LIBDIR'))
436
- print(sysconfig.get_config_var('Py_ENABLE_SHARED'))
501
+ if PYPY:
502
+ print("1")
503
+ else:
504
+ print(sysconfig.get_config_var('Py_ENABLE_SHARED'))
437
505
print(sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'))
438
506
print(sys.exec_prefix)
507
+ print(platform.python_implementation())
439
508
"# ;
440
509
let out = run_python_script ( interpreter, script) ?;
441
510
let lines: Vec < String > = out. lines ( ) . map ( |line| line. to_owned ( ) ) . collect ( ) ;
442
- let interpreter_version = get_interpreter_version ( & lines[ 0 ] ) ?;
511
+ let interpreter_version = get_interpreter_version ( & lines[ 0 ] , & lines [ 5 ] ) ?;
443
512
Ok ( ( interpreter_version, lines) )
444
513
}
445
514
515
+ fn ensure_python_version_is_supported ( version : & PythonVersion ) -> Result < ( ) , String > {
516
+ match ( & version. implementation , version. major , version. minor ) {
517
+ ( PythonInterpreterKind :: PyPy , 2 , _) => {
518
+ Err ( "PyPy cpyext bindings is only supported for Python3" . to_string ( ) )
519
+ }
520
+ ( _, 3 , Some ( minor) ) if minor < PY3_MIN_MINOR => Err ( format ! (
521
+ "Python 3 required version is 3.{}, current version is 3.{}" ,
522
+ PY3_MIN_MINOR , minor
523
+ ) ) ,
524
+ _ => Ok ( ( ) ) ,
525
+ }
526
+ }
527
+
446
528
fn configure ( interpreter_version : & PythonVersion , lines : Vec < String > ) -> Result < ( String ) , String > {
529
+ ensure_python_version_is_supported ( & interpreter_version) . expect ( & format ! (
530
+ "Unsupported interpreter {:?}" ,
531
+ interpreter_version
532
+ ) ) ;
533
+
447
534
let libpath: & str = & lines[ 1 ] ;
448
535
let enable_shared: & str = & lines[ 2 ] ;
449
536
let ld_version: & str = & lines[ 3 ] ;
@@ -464,26 +551,28 @@ fn configure(interpreter_version: &PythonVersion, lines: Vec<String>) -> Result<
464
551
465
552
let mut flags = String :: new ( ) ;
466
553
554
+ if interpreter_version. implementation == PythonInterpreterKind :: PyPy {
555
+ println ! ( "cargo:rustc-cfg=PyPy" ) ;
556
+ flags += format ! ( "CFG_PyPy" ) . as_ref ( ) ;
557
+ } ;
558
+
467
559
if let PythonVersion {
468
560
major : 3 ,
469
561
minor : some_minor,
562
+ implementation : _,
470
563
} = interpreter_version
471
564
{
472
565
if env:: var_os ( "CARGO_FEATURE_ABI3" ) . is_some ( ) {
473
566
println ! ( "cargo:rustc-cfg=Py_LIMITED_API" ) ;
474
567
}
568
+
475
569
if let Some ( minor) = some_minor {
476
- if minor < & PY3_MIN_MINOR {
477
- return Err ( format ! (
478
- "Python 3 required version is 3.{}, current version is 3.{}" ,
479
- PY3_MIN_MINOR , minor
480
- ) ) ;
481
- }
482
570
for i in 5 ..( minor + 1 ) {
483
571
println ! ( "cargo:rustc-cfg=Py_3_{}" , i) ;
484
572
flags += format ! ( "CFG_Py_3_{}," , i) . as_ref ( ) ;
485
573
}
486
574
}
575
+ println ! ( "cargo:rustc-cfg=Py_3" ) ;
487
576
} else {
488
577
// fail PYTHON_SYS_EXECUTABLE=python2 cargo ...
489
578
return Err ( "Python 2 is not supported" . to_string ( ) ) ;
@@ -512,6 +601,7 @@ fn version_from_env() -> Option<PythonVersion> {
512
601
Some ( s) => Some ( s. as_str ( ) . parse ( ) . unwrap ( ) ) ,
513
602
None => None ,
514
603
} ,
604
+ implementation : PythonInterpreterKind :: CPython ,
515
605
} ) ;
516
606
}
517
607
None => ( ) ,
@@ -589,6 +679,15 @@ fn main() -> Result<(), String> {
589
679
}
590
680
}
591
681
682
+ // These flags need to be enabled manually for PyPy, because it does not expose
683
+ // them in `sysconfig.get_config_vars()`
684
+ if interpreter_version. implementation == PythonInterpreterKind :: PyPy {
685
+ config_map. insert ( "WITH_THREAD" . to_owned ( ) , "1" . to_owned ( ) ) ;
686
+ config_map. insert ( "Py_USING_UNICODE" . to_owned ( ) , "1" . to_owned ( ) ) ;
687
+ config_map. insert ( "Py_UNICODE_SIZE" . to_owned ( ) , "4" . to_owned ( ) ) ;
688
+ config_map. insert ( "Py_UNICODE_WIDE" . to_owned ( ) , "1" . to_owned ( ) ) ;
689
+ }
690
+
592
691
// WITH_THREAD is always on for 3.7
593
692
if interpreter_version. major == 3 && interpreter_version. minor . unwrap_or ( 0 ) >= 7 {
594
693
config_map. insert ( "WITH_THREAD" . to_owned ( ) , "1" . to_owned ( ) ) ;
0 commit comments