1
+ from __future__ import annotations
2
+
1
3
import glob
2
4
import os
3
5
import platform
13
15
DistutilsPlatformError ,
14
16
)
15
17
from 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
17
19
18
20
from setuptools .command .build import build as CommandBuild # type: ignore[import]
19
21
from setuptools .command .build_ext import build_ext as CommandBuildExt
20
22
from setuptools .command .build_ext import get_abi3_suffix
21
23
from typing_extensions import Literal
22
24
25
+ from ._utils import format_called_process_error
23
26
from .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
33
29
34
30
35
31
class build_rust (RustCommand ):
@@ -131,7 +127,7 @@ def build_extension(
131
127
cross_lib = None
132
128
linker = None
133
129
134
- rustc_cfgs = _get_rustc_cfgs (target_triple )
130
+ rustc_cfgs = get_rustc_cfgs (target_triple )
135
131
136
132
env = _prepare_build_environment (cross_lib )
137
133
@@ -213,9 +209,7 @@ def build_extension(
213
209
# Execute cargo
214
210
try :
215
211
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 )
219
213
except subprocess .CalledProcessError as e :
220
214
raise CompileError (format_called_process_error (e ))
221
215
@@ -310,7 +304,7 @@ def install_extension(
310
304
if ext ._uses_exec_binding ():
311
305
ext_path = build_ext .get_ext_fullpath (module_name )
312
306
# remove extensions
313
- ext_path , _ , _ = split_platform_and_extension (ext_path )
307
+ ext_path , _ , _ = _split_platform_and_extension (ext_path )
314
308
315
309
# Add expected extension
316
310
exe = sysconfig .get_config_var ("EXE" )
@@ -393,12 +387,12 @@ def get_dylib_ext_path(self, ext: RustExtension, target_fname: str) -> str:
393
387
host_arch = host_platform .rsplit ("-" , 1 )[1 ]
394
388
# Remove incorrect platform tag if we are cross compiling
395
389
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 )
397
391
# rust.so, removed platform tag
398
392
ext_path += extension
399
393
return ext_path
400
394
401
- def _py_limited_api (self ) -> PyLimitedApi :
395
+ def _py_limited_api (self ) -> _PyLimitedApi :
402
396
bdist_wheel = self .distribution .get_command_obj ("bdist_wheel" , create = False )
403
397
404
398
if bdist_wheel is None :
@@ -409,11 +403,12 @@ def _py_limited_api(self) -> PyLimitedApi:
409
403
410
404
bdist_wheel_command = cast (CommandBdistWheel , bdist_wheel ) # type: ignore[no-any-unimported]
411
405
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 )
413
407
414
408
def _detect_rust_target (
415
409
self , forced_target_triple : Optional [str ] = None
416
410
) -> Optional ["_TargetInfo" ]:
411
+ assert self .plat_name is not None
417
412
cross_compile_info = _detect_unix_cross_compile_info ()
418
413
if cross_compile_info is not None :
419
414
cross_target_info = cross_compile_info .to_target_info ()
@@ -448,33 +443,23 @@ def _detect_rust_target(
448
443
)
449
444
450
445
elif forced_target_triple is not None :
451
- return _TargetInfo .for_triple (forced_target_triple )
452
-
453
- else :
454
446
# Automatic target detection can be overridden via the CARGO_BUILD_TARGET
455
447
# environment variable or --target command line option
456
- return self . _detect_local_rust_target ( )
448
+ return _TargetInfo . for_triple ( forced_target_triple )
457
449
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 )
462
452
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
478
463
479
464
def _is_debug_build (self , ext : RustExtension ) -> bool :
480
465
if self .release :
@@ -512,7 +497,7 @@ def _cargo_args(
512
497
513
498
features = {
514
499
* ext .features ,
515
- * binding_features (ext , py_limited_api = self ._py_limited_api ()),
500
+ * _binding_features (ext , py_limited_api = self ._py_limited_api ()),
516
501
}
517
502
518
503
if features :
@@ -531,11 +516,9 @@ def create_universal2_binary(output_path: str, input_paths: List[str]) -> None:
531
516
# Try lipo first
532
517
command = ["lipo" , "-create" , "-output" , output_path , * input_paths ]
533
518
try :
534
- subprocess .check_output (command )
519
+ subprocess .check_output (command , text = True )
535
520
except subprocess .CalledProcessError as e :
536
521
output = e .output
537
- if isinstance (output , bytes ):
538
- output = e .output .decode ("latin-1" ).strip ()
539
522
raise CompileError ("lipo failed with code: %d\n %s" % (e .returncode , output ))
540
523
except OSError :
541
524
# lipo not found, try using the fat-macho library
@@ -649,21 +632,6 @@ def _detect_unix_cross_compile_info() -> Optional["_CrossCompileInfo"]:
649
632
return _CrossCompileInfo (host_type , cross_lib , linker , linker_args )
650
633
651
634
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
-
667
635
def _replace_vendor_with_unknown (target : str ) -> Optional [str ]:
668
636
"""Replaces vendor in the target triple with unknown.
669
637
@@ -719,7 +687,7 @@ def _base_cargo_target_dir(ext: RustExtension, *, quiet: bool) -> str:
719
687
720
688
def _is_py_limited_api (
721
689
ext_setting : Literal ["auto" , True , False ],
722
- wheel_setting : Optional [PyLimitedApi ],
690
+ wheel_setting : Optional [_PyLimitedApi ],
723
691
) -> bool :
724
692
"""Returns whether this extension is being built for the limited api.
725
693
@@ -742,3 +710,64 @@ def _is_py_limited_api(
742
710
743
711
# "auto" setting - use whether the bdist_wheel option is truthy.
744
712
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