14
14
)
15
15
from distutils .sysconfig import get_config_var
16
16
from subprocess import check_output
17
+ from typing import NamedTuple , Optional
17
18
18
19
from setuptools .command .build_ext import get_abi3_suffix
19
20
27
28
)
28
29
29
30
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
-
39
31
class build_rust (RustCommand ):
40
32
"""Command for building Rust crates via cargo."""
41
33
@@ -84,7 +76,7 @@ def finalize_options(self):
84
76
("inplace" , "inplace" ),
85
77
)
86
78
87
- def get_target_info (self ):
79
+ def get_target_info (self ) -> "_TargetInfo" :
88
80
# If we are on a 64-bit machine, but running a 32-bit Python, then
89
81
# we'll target a 32-bit Rust build.
90
82
# Automatic target detection can be overridden via the CARGO_BUILD_TARGET
@@ -96,10 +88,34 @@ def get_target_info(self):
96
88
elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
97
89
# x86_64 or arm64 macOS targeting x86_64
98
90
return _TargetInfo ("x86_64-apple-darwin" )
99
- else :
100
- return self .get_nix_target_info ()
101
91
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" ]:
103
119
# See https://github.com/PyO3/setuptools-rust/issues/138
104
120
# This is to support cross compiling on *NIX, where plat_name isn't
105
121
# necessarily the same as the system we are running on. *NIX systems
@@ -110,7 +126,7 @@ def get_nix_target_info(self):
110
126
111
127
if not host_type or host_type == build_type :
112
128
# not *NIX, or not cross compiling
113
- return _TargetInfo ( self . target )
129
+ return None
114
130
115
131
stdlib = sysconfig .get_path ("stdlib" )
116
132
cross_lib = os .path .dirname (stdlib )
@@ -124,29 +140,7 @@ def get_nix_target_info(self):
124
140
linker = bldshared [0 ]
125
141
linker_args = bldshared [1 :]
126
142
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 )
150
144
151
145
def run_for_extension (self , ext : RustExtension ):
152
146
arch_flags = os .getenv ("ARCHFLAGS" )
@@ -274,7 +268,7 @@ def build_extension(self, ext: RustExtension, target_triple=None):
274
268
275
269
if target_info .linker is not None :
276
270
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
278
272
# sure if they will always do the right thing. Might help with some
279
273
# of the OS-specific logic below if it does.
280
274
@@ -481,3 +475,63 @@ def _py_limited_api(self) -> PyLimitedApi:
481
475
else :
482
476
bdist_wheel .ensure_finalized ()
483
477
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