@@ -18,11 +18,18 @@ use version_check::{is_min_date, is_min_version, supports_features};
1818const MIN_DATE : & ' static str = "2019-02-06" ;
1919const MIN_VERSION : & ' static str = "1.34.0-nightly" ;
2020
21+ #[ derive( Debug , Clone , PartialEq ) ]
22+ pub enum PythonInterpreterKind {
23+ CPython ,
24+ PyPy ,
25+ }
26+
2127#[ derive( Debug ) ]
2228struct PythonVersion {
2329 major : u8 ,
2430 // minor == None means any minor version will do
2531 minor : Option < u8 > ,
32+ implementation : PythonInterpreterKind ,
2633}
2734
2835impl PartialEq for PythonVersion {
@@ -142,6 +149,7 @@ fn load_cross_compile_info() -> Result<(PythonVersion, HashMap<String, String>,
142149 let python_version = PythonVersion {
143150 major,
144151 minor : Some ( minor) ,
152+ implementation : PythonInterpreterKind :: CPython ,
145153 } ;
146154
147155 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>
280288 Ok ( String :: from_utf8 ( out. stdout ) . unwrap ( ) )
281289}
282290
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+
283311#[ cfg( not( target_os = "macos" ) ) ]
284312#[ cfg( not( target_os = "windows" ) ) ]
285313fn get_rustc_link_lib (
286- _ : & PythonVersion ,
314+ version : & PythonVersion ,
287315 ld_version : & str ,
288316 enable_shared : bool ,
289317) -> Result < String , String > {
290318 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+ ) )
292323 } 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+ ) )
294328 }
295329}
296330
@@ -311,42 +345,63 @@ else:
311345}
312346
313347#[ 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 > {
315353 // os x can be linked to a framework or static or dynamic, and
316354 // Py_ENABLE_SHARED is wrong; framework means shared library
317355 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+ ) ) ,
321368 other => Err ( format ! ( "unknown linkmodel {}" , other) ) ,
322369 }
323370}
324371
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+
325385/// 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 > {
327387 let version_re = Regex :: new ( r"\((\d+), (\d+)\)" ) . unwrap ( ) ;
328388 match version_re. captures ( & line) {
329389 Some ( cap) => Ok ( PythonVersion {
330390 major : cap. get ( 1 ) . unwrap ( ) . as_str ( ) . parse ( ) . unwrap ( ) ,
331391 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+ } ,
332400 } ) ,
333401 None => Err ( format ! ( "Unexpected response to version query {}" , line) ) ,
334402 }
335403}
336404
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-
350405/// Locate a suitable python interpreter and extract config from it.
351406///
352407/// The following locations are checked in the order listed:
@@ -387,10 +442,17 @@ fn find_interpreter_and_get_config(
387442 let expected_version = version. unwrap_or ( PythonVersion {
388443 major : 3 ,
389444 minor : None ,
445+ implementation : PythonInterpreterKind :: CPython ,
390446 } ) ;
391447
448+ let binary_name = match expected_version. implementation {
449+ PythonInterpreterKind :: CPython => "python" ,
450+ PythonInterpreterKind :: PyPy => "pypy" ,
451+ } ;
452+
392453 // check default python
393- let interpreter_path = "python" ;
454+ let interpreter_path = binary_name;
455+
394456 let ( interpreter_version, lines) = get_config_from_interpreter ( interpreter_path) ?;
395457 if expected_version == interpreter_version {
396458 return Ok ( (
@@ -430,20 +492,45 @@ fn get_config_from_interpreter(interpreter: &str) -> Result<(PythonVersion, Vec<
430492 let script = r#"
431493import sys
432494import sysconfig
495+ import platform
496+
497+ PYPY = platform.python_implementation() == "PyPy"
433498
434499print(sys.version_info[0:2])
435500print(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'))
437505print(sysconfig.get_config_var('LDVERSION') or sysconfig.get_config_var('py_version_short'))
438506print(sys.exec_prefix)
507+ print(platform.python_implementation())
439508"# ;
440509 let out = run_python_script ( interpreter, script) ?;
441510 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 ] ) ?;
443512 Ok ( ( interpreter_version, lines) )
444513}
445514
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+
446528fn 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+
447534 let libpath: & str = & lines[ 1 ] ;
448535 let enable_shared: & str = & lines[ 2 ] ;
449536 let ld_version: & str = & lines[ 3 ] ;
@@ -464,26 +551,28 @@ fn configure(interpreter_version: &PythonVersion, lines: Vec<String>) -> Result<
464551
465552 let mut flags = String :: new ( ) ;
466553
554+ if interpreter_version. implementation == PythonInterpreterKind :: PyPy {
555+ println ! ( "cargo:rustc-cfg=PyPy" ) ;
556+ flags += format ! ( "CFG_PyPy" ) . as_ref ( ) ;
557+ } ;
558+
467559 if let PythonVersion {
468560 major : 3 ,
469561 minor : some_minor,
562+ implementation : _,
470563 } = interpreter_version
471564 {
472565 if env:: var_os ( "CARGO_FEATURE_ABI3" ) . is_some ( ) {
473566 println ! ( "cargo:rustc-cfg=Py_LIMITED_API" ) ;
474567 }
568+
475569 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- }
482570 for i in 5 ..( minor + 1 ) {
483571 println ! ( "cargo:rustc-cfg=Py_3_{}" , i) ;
484572 flags += format ! ( "CFG_Py_3_{}," , i) . as_ref ( ) ;
485573 }
486574 }
575+ println ! ( "cargo:rustc-cfg=Py_3" ) ;
487576 } else {
488577 // fail PYTHON_SYS_EXECUTABLE=python2 cargo ...
489578 return Err ( "Python 2 is not supported" . to_string ( ) ) ;
@@ -512,6 +601,7 @@ fn version_from_env() -> Option<PythonVersion> {
512601 Some ( s) => Some ( s. as_str ( ) . parse ( ) . unwrap ( ) ) ,
513602 None => None ,
514603 } ,
604+ implementation : PythonInterpreterKind :: CPython ,
515605 } ) ;
516606 }
517607 None => ( ) ,
@@ -589,6 +679,15 @@ fn main() -> Result<(), String> {
589679 }
590680 }
591681
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+
592691 // WITH_THREAD is always on for 3.7
593692 if interpreter_version. major == 3 && interpreter_version. minor . unwrap_or ( 0 ) >= 7 {
594693 config_map. insert ( "WITH_THREAD" . to_owned ( ) , "1" . to_owned ( ) ) ;
0 commit comments