1+ from __future__ import annotations
2+
13import glob
24import os
35import platform
1315 DistutilsPlatformError ,
1416)
1517from distutils .sysconfig import get_config_var
16- from typing import Dict , List , NamedTuple , Optional , cast
18+ from typing import Dict , List , NamedTuple , Optional , Set , Tuple , cast
1719
1820from setuptools .command .build import build as CommandBuild # type: ignore[import]
1921from setuptools .command .build_ext import build_ext as CommandBuildExt
2022from setuptools .command .build_ext import get_abi3_suffix
2123from typing_extensions import Literal
2224
25+ from ._utils import format_called_process_error
2326from .command import RustCommand
24- from .extension import RustBin , RustExtension , Strip
25- from .private import format_called_process_error
26- from .utils import (
27- PyLimitedApi ,
28- binding_features ,
29- get_rust_target_info ,
30- get_rust_target_list ,
31- split_platform_and_extension ,
32- )
27+ from .extension import Binding , RustBin , RustExtension , Strip
28+ from .rustc_info import get_rust_host , get_rust_target_list , get_rustc_cfgs
3329
3430
3531class build_rust (RustCommand ):
@@ -131,7 +127,7 @@ def build_extension(
131127 cross_lib = None
132128 linker = None
133129
134- rustc_cfgs = _get_rustc_cfgs (target_triple )
130+ rustc_cfgs = get_rustc_cfgs (target_triple )
135131
136132 env = _prepare_build_environment (cross_lib )
137133
@@ -213,9 +209,7 @@ def build_extension(
213209 # Execute cargo
214210 try :
215211 stderr = subprocess .PIPE if quiet else None
216- output = subprocess .check_output (
217- command , env = env , encoding = "latin-1" , stderr = stderr
218- )
212+ output = subprocess .check_output (command , env = env , stderr = stderr , text = True )
219213 except subprocess .CalledProcessError as e :
220214 raise CompileError (format_called_process_error (e ))
221215
@@ -310,7 +304,7 @@ def install_extension(
310304 if ext ._uses_exec_binding ():
311305 ext_path = build_ext .get_ext_fullpath (module_name )
312306 # remove extensions
313- ext_path , _ , _ = split_platform_and_extension (ext_path )
307+ ext_path , _ , _ = _split_platform_and_extension (ext_path )
314308
315309 # Add expected extension
316310 exe = sysconfig .get_config_var ("EXE" )
@@ -393,12 +387,12 @@ def get_dylib_ext_path(self, ext: RustExtension, target_fname: str) -> str:
393387 host_arch = host_platform .rsplit ("-" , 1 )[1 ]
394388 # Remove incorrect platform tag if we are cross compiling
395389 if target_arch and host_arch != target_arch :
396- ext_path , _ , extension = split_platform_and_extension (ext_path )
390+ ext_path , _ , extension = _split_platform_and_extension (ext_path )
397391 # rust.so, removed platform tag
398392 ext_path += extension
399393 return ext_path
400394
401- def _py_limited_api (self ) -> PyLimitedApi :
395+ def _py_limited_api (self ) -> _PyLimitedApi :
402396 bdist_wheel = self .distribution .get_command_obj ("bdist_wheel" , create = False )
403397
404398 if bdist_wheel is None :
@@ -409,11 +403,12 @@ def _py_limited_api(self) -> PyLimitedApi:
409403
410404 bdist_wheel_command = cast (CommandBdistWheel , bdist_wheel ) # type: ignore[no-any-unimported]
411405 bdist_wheel_command .ensure_finalized ()
412- return cast (PyLimitedApi , bdist_wheel_command .py_limited_api )
406+ return cast (_PyLimitedApi , bdist_wheel_command .py_limited_api )
413407
414408 def _detect_rust_target (
415409 self , forced_target_triple : Optional [str ] = None
416410 ) -> Optional ["_TargetInfo" ]:
411+ assert self .plat_name is not None
417412 cross_compile_info = _detect_unix_cross_compile_info ()
418413 if cross_compile_info is not None :
419414 cross_target_info = cross_compile_info .to_target_info ()
@@ -448,33 +443,23 @@ def _detect_rust_target(
448443 )
449444
450445 elif forced_target_triple is not None :
451- return _TargetInfo .for_triple (forced_target_triple )
452-
453- else :
454446 # Automatic target detection can be overridden via the CARGO_BUILD_TARGET
455447 # environment variable or --target command line option
456- return self . _detect_local_rust_target ( )
448+ return _TargetInfo . for_triple ( forced_target_triple )
457449
458- def _detect_local_rust_target (self ) -> Optional ["_TargetInfo" ]:
459- """Attempts to infer the correct Rust target from build environment for
460- some edge cases."""
461- assert self .plat_name is not None
450+ # Determine local rust target which needs to be "forced" if necessary
451+ local_rust_target = _adjusted_local_rust_target (self .plat_name )
462452
463- # If we are on a 64-bit machine, but running a 32-bit Python, then
464- # we'll target a 32-bit Rust build.
465- if self .plat_name == "win32" :
466- if _get_rustc_cfgs (None ).get ("target_env" ) == "gnu" :
467- return _TargetInfo .for_triple ("i686-pc-windows-gnu" )
468- return _TargetInfo .for_triple ("i686-pc-windows-msvc" )
469- elif self .plat_name == "win-amd64" :
470- if _get_rustc_cfgs (None ).get ("target_env" ) == "gnu" :
471- return _TargetInfo .for_triple ("x86_64-pc-windows-gnu" )
472- return _TargetInfo .for_triple ("x86_64-pc-windows-msvc" )
473- elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
474- # x86_64 or arm64 macOS targeting x86_64
475- return _TargetInfo .for_triple ("x86_64-apple-darwin" )
476- else :
477- return None
453+ # Match cargo's behaviour of not using an explicit target if the
454+ # target we're compiling for is the host
455+ if (
456+ local_rust_target is not None
457+ # check for None first to avoid calling to rustc if not needed
458+ and local_rust_target != get_rust_host ()
459+ ):
460+ return _TargetInfo .for_triple (local_rust_target )
461+
462+ return None
478463
479464 def _is_debug_build (self , ext : RustExtension ) -> bool :
480465 if self .release :
@@ -512,7 +497,7 @@ def _cargo_args(
512497
513498 features = {
514499 * ext .features ,
515- * binding_features (ext , py_limited_api = self ._py_limited_api ()),
500+ * _binding_features (ext , py_limited_api = self ._py_limited_api ()),
516501 }
517502
518503 if features :
@@ -531,11 +516,9 @@ def create_universal2_binary(output_path: str, input_paths: List[str]) -> None:
531516 # Try lipo first
532517 command = ["lipo" , "-create" , "-output" , output_path , * input_paths ]
533518 try :
534- subprocess .check_output (command )
519+ subprocess .check_output (command , text = True )
535520 except subprocess .CalledProcessError as e :
536521 output = e .output
537- if isinstance (output , bytes ):
538- output = e .output .decode ("latin-1" ).strip ()
539522 raise CompileError ("lipo failed with code: %d\n %s" % (e .returncode , output ))
540523 except OSError :
541524 # lipo not found, try using the fat-macho library
@@ -649,21 +632,6 @@ def _detect_unix_cross_compile_info() -> Optional["_CrossCompileInfo"]:
649632 return _CrossCompileInfo (host_type , cross_lib , linker , linker_args )
650633
651634
652- _RustcCfgs = Dict [str , Optional [str ]]
653-
654-
655- def _get_rustc_cfgs (target_triple : Optional [str ]) -> _RustcCfgs :
656- cfgs : _RustcCfgs = {}
657- for entry in get_rust_target_info (target_triple ):
658- maybe_split = entry .split ("=" , maxsplit = 1 )
659- if len (maybe_split ) == 2 :
660- cfgs [maybe_split [0 ]] = maybe_split [1 ].strip ('"' )
661- else :
662- assert len (maybe_split ) == 1
663- cfgs [maybe_split [0 ]] = None
664- return cfgs
665-
666-
667635def _replace_vendor_with_unknown (target : str ) -> Optional [str ]:
668636 """Replaces vendor in the target triple with unknown.
669637
@@ -719,7 +687,7 @@ def _base_cargo_target_dir(ext: RustExtension, *, quiet: bool) -> str:
719687
720688def _is_py_limited_api (
721689 ext_setting : Literal ["auto" , True , False ],
722- wheel_setting : Optional [PyLimitedApi ],
690+ wheel_setting : Optional [_PyLimitedApi ],
723691) -> bool :
724692 """Returns whether this extension is being built for the limited api.
725693
@@ -742,3 +710,64 @@ def _is_py_limited_api(
742710
743711 # "auto" setting - use whether the bdist_wheel option is truthy.
744712 return bool (wheel_setting )
713+
714+
715+ def _binding_features (
716+ ext : RustExtension ,
717+ py_limited_api : _PyLimitedApi ,
718+ ) -> Set [str ]:
719+ if ext .binding in (Binding .NoBinding , Binding .Exec ):
720+ return set ()
721+ elif ext .binding is Binding .PyO3 :
722+ features = {"pyo3/extension-module" }
723+ if ext .py_limited_api == "auto" :
724+ if isinstance (py_limited_api , str ):
725+ python_version = py_limited_api [2 :]
726+ features .add (f"pyo3/abi3-py{ python_version } " )
727+ elif py_limited_api :
728+ features .add (f"pyo3/abi3" )
729+ return features
730+ elif ext .binding is Binding .RustCPython :
731+ return {"cpython/python3-sys" , "cpython/extension-module" }
732+ else :
733+ raise DistutilsPlatformError (f"unknown Rust binding: '{ ext .binding } '" )
734+
735+
736+ _PyLimitedApi = Literal ["cp37" , "cp38" , "cp39" , "cp310" , "cp311" , "cp312" , True , False ]
737+
738+
739+ def _adjusted_local_rust_target (plat_name : str ) -> Optional [str ]:
740+ """Returns the local rust target for the given `plat_name`, if it is
741+ necessary to 'force' a specific target for correctness."""
742+
743+ # If we are on a 64-bit machine, but running a 32-bit Python, then
744+ # we'll target a 32-bit Rust build.
745+ if plat_name == "win32" :
746+ if get_rustc_cfgs (None ).get ("target_env" ) == "gnu" :
747+ return "i686-pc-windows-gnu"
748+ else :
749+ return "i686-pc-windows-msvc"
750+ elif plat_name == "win-amd64" :
751+ if get_rustc_cfgs (None ).get ("target_env" ) == "gnu" :
752+ return "x86_64-pc-windows-gnu"
753+ else :
754+ return "x86_64-pc-windows-msvc"
755+ elif plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
756+ # x86_64 or arm64 macOS targeting x86_64
757+ return "x86_64-apple-darwin"
758+
759+ return None
760+
761+
762+ def _split_platform_and_extension (ext_path : str ) -> Tuple [str , str , str ]:
763+ """Splits an extension path into a tuple (ext_path, plat_tag, extension).
764+
765+ >>> _split_platform_and_extension("foo/bar.platform.so")
766+ ('foo/bar', '.platform', '.so')
767+ """
768+
769+ # rust.cpython-38-x86_64-linux-gnu.so to (rust.cpython-38-x86_64-linux-gnu, .so)
770+ ext_path , extension = os .path .splitext (ext_path )
771+ # rust.cpython-38-x86_64-linux-gnu to (rust, .cpython-38-x86_64-linux-gnu)
772+ ext_path , platform_tag = os .path .splitext (ext_path )
773+ return (ext_path , platform_tag , extension )
0 commit comments