1414)
1515from distutils .sysconfig import get_config_var
1616from subprocess import check_output
17+ from typing import NamedTuple , Optional
1718
1819from setuptools .command .build_ext import get_abi3_suffix
1920
2728)
2829
2930
30- class _TargetInfo :
31- def __init__ (self , triple = None , cross_lib = None , linker = None , link_args = None ):
32- self .triple = triple
33- self .cross_lib = cross_lib
34- self .linker = linker
35- self .link_args = link_args
36-
37-
3831class build_rust (RustCommand ):
3932 """Command for building Rust crates via cargo."""
4033
@@ -83,24 +76,46 @@ def finalize_options(self):
8376 ("inplace" , "inplace" ),
8477 )
8578
86- def get_target_info (self ):
79+ def get_target_info (self ) -> "_TargetInfo" :
8780 # If we are on a 64-bit machine, but running a 32-bit Python, then
8881 # we'll target a 32-bit Rust build.
8982 # Automatic target detection can be overridden via the CARGO_BUILD_TARGET
9083 # environment variable or --target command line option
91- if self .target :
92- return _TargetInfo (self .target )
93- elif self .plat_name == "win32" :
84+ if self .plat_name == "win32" :
9485 return _TargetInfo ("i686-pc-windows-msvc" )
9586 elif self .plat_name == "win-amd64" :
9687 return _TargetInfo ("x86_64-pc-windows-msvc" )
9788 elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
9889 # x86_64 or arm64 macOS targeting x86_64
9990 return _TargetInfo ("x86_64-apple-darwin" )
100- else :
101- return self .get_nix_target_info ()
10291
103- def get_nix_target_info (self ):
92+ cross_compile_info = self .get_nix_cross_compile_info ()
93+ if cross_compile_info is not None :
94+ target_info = cross_compile_info .to_target_info ()
95+ if target_info is not None :
96+ if self .target is not None :
97+ if not target_info .is_compatible_with (self .target ):
98+ self .warn (
99+ f"Forced Rust target `{ self .target } ` is not "
100+ f"compatible with deduced Rust target "
101+ f"`{ target_info .triple } ` - the built package may "
102+ f"not import successfully once installed."
103+ )
104+ else :
105+ return target_info
106+
107+ if self .target :
108+ return _TargetInfo (self .target , cross_compile_info .cross_lib )
109+
110+ raise DistutilsPlatformError (
111+ "Don't know the correct rust target for system type %s. Please "
112+ "set the CARGO_BUILD_TARGET environment variable."
113+ % cross_compile_info .host_type
114+ )
115+
116+ return _TargetInfo (self .target )
117+
118+ def get_nix_cross_compile_info (self ) -> Optional ["_CrossCompileInfo" ]:
104119 # See https://github.com/PyO3/setuptools-rust/issues/138
105120 # This is to support cross compiling on *NIX, where plat_name isn't
106121 # necessarily the same as the system we are running on. *NIX systems
@@ -111,7 +126,7 @@ def get_nix_target_info(self):
111126
112127 if not host_type or host_type == build_type :
113128 # not *NIX, or not cross compiling
114- return _TargetInfo ()
129+ return None
115130
116131 stdlib = sysconfig .get_path ("stdlib" )
117132 cross_lib = os .path .dirname (stdlib )
@@ -125,24 +140,7 @@ def get_nix_target_info(self):
125140 linker = bldshared [0 ]
126141 linker_args = bldshared [1 :]
127142
128- # hopefully an exact match
129- targets = get_rust_target_list ()
130- if host_type in targets :
131- return _TargetInfo (host_type , cross_lib , linker , linker_args )
132-
133- # the vendor field can be ignored, so x86_64-pc-linux-gnu is compatible
134- # with x86_64-unknown-linux-gnu
135- components = host_type .split ("-" )
136- if len (components ) == 4 :
137- components [1 ] = "unknown"
138- host_type2 = "-" .join (components )
139- if host_type2 in targets :
140- return _TargetInfo (host_type2 , cross_lib , linker , linker_args )
141-
142- raise DistutilsPlatformError (
143- "Don't know the correct rust target for system type %s. Please "
144- "set the CARGO_BUILD_TARGET environment variable." % host_type
145- )
143+ return _CrossCompileInfo (host_type , cross_lib , linker , linker_args )
146144
147145 def run_for_extension (self , ext : RustExtension ):
148146 arch_flags = os .getenv ("ARCHFLAGS" )
@@ -270,7 +268,7 @@ def build_extension(self, ext: RustExtension, target_triple=None):
270268
271269 if target_info .linker is not None :
272270 args .extend (["-C" , "linker=" + target_info .linker ])
273- # We're ignoring target_info.link_args for now because we're not
271+ # We're ignoring target_info.linker_args for now because we're not
274272 # sure if they will always do the right thing. Might help with some
275273 # of the OS-specific logic below if it does.
276274
@@ -477,3 +475,63 @@ def _py_limited_api(self) -> PyLimitedApi:
477475 else :
478476 bdist_wheel .ensure_finalized ()
479477 return bdist_wheel .py_limited_api
478+
479+
480+ class _TargetInfo (NamedTuple ):
481+ triple : str
482+ cross_lib : Optional [str ] = None
483+ linker : Optional [str ] = None
484+ linker_args : Optional [str ] = None
485+
486+ def is_compatible_with (self , target : str ) -> bool :
487+ if self .triple == target :
488+ return True
489+
490+ # the vendor field can be ignored, so x86_64-pc-linux-gnu is compatible
491+ # with x86_64-unknown-linux-gnu
492+ if _replace_vendor_with_unknown (self .triple ) == target :
493+ return True
494+
495+ return False
496+
497+
498+ class _CrossCompileInfo (NamedTuple ):
499+ host_type : str
500+ cross_lib : Optional [str ] = None
501+ linker : Optional [str ] = None
502+ linker_args : Optional [str ] = None
503+
504+ def to_target_info (self ) -> Optional [_TargetInfo ]:
505+ """Maps this cross compile info to target info.
506+
507+ Returns None if the corresponding target information could not be
508+ deduced.
509+ """
510+ # hopefully an exact match
511+ targets = get_rust_target_list ()
512+ if self .host_type in targets :
513+ return _TargetInfo (
514+ self .host_type , self .cross_lib , self .linker , self .linker_args
515+ )
516+
517+ # the vendor field can be ignored, so x86_64-pc-linux-gnu is compatible
518+ # with x86_64-unknown-linux-gnu
519+ without_vendor = _replace_vendor_with_unknown (self .host_type )
520+ if without_vendor in targets :
521+ return _TargetInfo (
522+ without_vendor , self .cross_lib , self .linker , self .linker_args
523+ )
524+
525+ return None
526+
527+
528+ def _replace_vendor_with_unknown (target : str ) -> Optional [str ]:
529+ """Replaces vendor in the target triple with unknown.
530+
531+ Returns None if the target is not made of 4 parts.
532+ """
533+ components = target .split ("-" )
534+ if len (components ) != 4 :
535+ return None
536+ components [1 ] = "unknown"
537+ return "-" .join (components )
0 commit comments