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
- 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
-
38
31
class build_rust (RustCommand ):
39
32
"""Command for building Rust crates via cargo."""
40
33
@@ -83,24 +76,46 @@ def finalize_options(self):
83
76
("inplace" , "inplace" ),
84
77
)
85
78
86
- def get_target_info (self ):
79
+ def get_target_info (self ) -> "_TargetInfo" :
87
80
# If we are on a 64-bit machine, but running a 32-bit Python, then
88
81
# we'll target a 32-bit Rust build.
89
82
# Automatic target detection can be overridden via the CARGO_BUILD_TARGET
90
83
# 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" :
94
85
return _TargetInfo ("i686-pc-windows-msvc" )
95
86
elif self .plat_name == "win-amd64" :
96
87
return _TargetInfo ("x86_64-pc-windows-msvc" )
97
88
elif self .plat_name .startswith ("macosx-" ) and platform .machine () == "x86_64" :
98
89
# x86_64 or arm64 macOS targeting x86_64
99
90
return _TargetInfo ("x86_64-apple-darwin" )
100
- else :
101
- return self .get_nix_target_info ()
102
91
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" ]:
104
119
# See https://github.com/PyO3/setuptools-rust/issues/138
105
120
# This is to support cross compiling on *NIX, where plat_name isn't
106
121
# necessarily the same as the system we are running on. *NIX systems
@@ -111,7 +126,7 @@ def get_nix_target_info(self):
111
126
112
127
if not host_type or host_type == build_type :
113
128
# not *NIX, or not cross compiling
114
- return _TargetInfo ()
129
+ return None
115
130
116
131
stdlib = sysconfig .get_path ("stdlib" )
117
132
cross_lib = os .path .dirname (stdlib )
@@ -125,24 +140,7 @@ def get_nix_target_info(self):
125
140
linker = bldshared [0 ]
126
141
linker_args = bldshared [1 :]
127
142
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 )
146
144
147
145
def run_for_extension (self , ext : RustExtension ):
148
146
arch_flags = os .getenv ("ARCHFLAGS" )
@@ -270,7 +268,7 @@ def build_extension(self, ext: RustExtension, target_triple=None):
270
268
271
269
if target_info .linker is not None :
272
270
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
274
272
# sure if they will always do the right thing. Might help with some
275
273
# of the OS-specific logic below if it does.
276
274
@@ -477,3 +475,63 @@ def _py_limited_api(self) -> PyLimitedApi:
477
475
else :
478
476
bdist_wheel .ensure_finalized ()
479
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