Skip to content

Commit 09d641a

Browse files
committed
build_rust: warn if target mismatch
1 parent 03ad856 commit 09d641a

File tree

1 file changed

+92
-38
lines changed

1 file changed

+92
-38
lines changed

setuptools_rust/build.py

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from distutils.sysconfig import get_config_var
1616
from subprocess import check_output
17+
from typing import NamedTuple, Optional
1718

1819
from setuptools.command.build_ext import get_abi3_suffix
1920

@@ -27,15 +28,6 @@
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-
3931
class 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

Comments
 (0)