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-
31- class _TargetInfo :
32- def __init__ (self , triple = None , cross_lib = None , linker = None , link_args = None ):
33- self .triple = triple
34- self .cross_lib = cross_lib
35- self .linker = linker
36- self .link_args = link_args
37-
38-
3931class build_rust (RustCommand ):
4032 """Command for building Rust crates via cargo."""
4133
@@ -84,7 +76,7 @@ def finalize_options(self):
8476 ("inplace" , "inplace" ),
8577 )
8678
87- def get_target_info (self ):
79+ def get_target_info (self ) -> "_TargetInfo" :
8880 # If we are on a 64-bit machine, but running a 32-bit Python, then
8981 # we'll target a 32-bit Rust build.
9082 # Automatic target detection can be overridden via the CARGO_BUILD_TARGET
@@ -96,10 +88,34 @@ def get_target_info(self):
9688 elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
9789 # x86_64 or arm64 macOS targeting x86_64
9890 return _TargetInfo ("x86_64-apple-darwin" )
99- else :
100- return self .get_nix_target_info ()
10191
102- 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" ]:
103119 # See https://github.com/PyO3/setuptools-rust/issues/138
104120 # This is to support cross compiling on *NIX, where plat_name isn't
105121 # necessarily the same as the system we are running on. *NIX systems
@@ -110,7 +126,7 @@ def get_nix_target_info(self):
110126
111127 if not host_type or host_type == build_type :
112128 # not *NIX, or not cross compiling
113- return _TargetInfo ( self . target )
129+ return None
114130
115131 stdlib = sysconfig .get_path ("stdlib" )
116132 cross_lib = os .path .dirname (stdlib )
@@ -124,29 +140,7 @@ def get_nix_target_info(self):
124140 linker = bldshared [0 ]
125141 linker_args = bldshared [1 :]
126142
127- # hopefully an exact match
128- targets = get_rust_target_list ()
129- if host_type in targets :
130- # FIXME: what if self.target != host_type
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- # FIXME: what if self.target != host_type2
141- return _TargetInfo (host_type2 , cross_lib , linker , linker_args )
142-
143- if self .target :
144- return _TargetInfo (self .target , cross_lib )
145-
146- raise DistutilsPlatformError (
147- "Don't know the correct rust target for system type %s. Please "
148- "set the CARGO_BUILD_TARGET environment variable." % host_type
149- )
143+ return _CrossCompileInfo (host_type , cross_lib , linker , linker_args )
150144
151145 def run_for_extension (self , ext : RustExtension ):
152146 arch_flags = os .getenv ("ARCHFLAGS" )
@@ -274,7 +268,7 @@ def build_extension(self, ext: RustExtension, target_triple=None):
274268
275269 if target_info .linker is not None :
276270 args .extend (["-C" , "linker=" + target_info .linker ])
277- # 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
278272 # sure if they will always do the right thing. Might help with some
279273 # of the OS-specific logic below if it does.
280274
@@ -481,3 +475,63 @@ def _py_limited_api(self) -> PyLimitedApi:
481475 else :
482476 bdist_wheel .ensure_finalized ()
483477 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