Skip to content

Commit 3f97179

Browse files
authored
Merge pull request #258 from davidhewitt/default-target
build: don't pass --target if not needed
2 parents 55211d9 + a09c530 commit 3f97179

File tree

9 files changed

+182
-140
lines changed

9 files changed

+182
-140
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- `Exec` binding `RustExtension` with `script=True` is deprecated in favor of `RustBin`. [#248](https://github.com/PyO3/setuptools-rust/pull/248)
1414
- Errors while calling `cargo metadata` are now reported back to the user [#254](https://github.com/PyO3/setuptools-rust/pull/254)
1515
- `quiet` option will now suppress output of `cargo metadata`. [#256](https://github.com/PyO3/setuptools-rust/pull/256)
16+
- `setuptools-rust` will now match `cargo` behavior of not setting `--target` when the selected target is the rust host. [#258](https://github.com/PyO3/setuptools-rust/pull/258)
1617

1718
### Fixed
1819
- If the sysconfig for `BLDSHARED` has no flags, `setuptools-rust` won't crash anymore. [#241](https://github.com/PyO3/setuptools-rust/pull/241)

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ def mypy(session: nox.Session):
3434
@nox.session()
3535
def test(session: nox.Session):
3636
session.install("pytest", ".")
37-
session.run("pytest", "setuptools_rust", *session.posargs)
37+
session.run("pytest", "setuptools_rust", "tests", *session.posargs)
File renamed without changes.

setuptools_rust/build.py

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import glob
24
import os
35
import platform
@@ -13,23 +15,17 @@
1315
DistutilsPlatformError,
1416
)
1517
from distutils.sysconfig import get_config_var
16-
from typing import Dict, List, NamedTuple, Optional, cast
18+
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, cast
1719

1820
from setuptools.command.build import build as CommandBuild # type: ignore[import]
1921
from setuptools.command.build_ext import build_ext as CommandBuildExt
2022
from setuptools.command.build_ext import get_abi3_suffix
2123
from typing_extensions import Literal
2224

25+
from ._utils import format_called_process_error
2326
from .command import RustCommand
24-
from .extension import RustBin, RustExtension, Strip
25-
from .private import format_called_process_error
26-
from .utils import (
27-
PyLimitedApi,
28-
binding_features,
29-
get_rust_target_info,
30-
get_rust_target_list,
31-
split_platform_and_extension,
32-
)
27+
from .extension import Binding, RustBin, RustExtension, Strip
28+
from .rustc_info import get_rust_host, get_rust_target_list, get_rustc_cfgs
3329

3430

3531
class build_rust(RustCommand):
@@ -131,7 +127,7 @@ def build_extension(
131127
cross_lib = None
132128
linker = None
133129

134-
rustc_cfgs = _get_rustc_cfgs(target_triple)
130+
rustc_cfgs = get_rustc_cfgs(target_triple)
135131

136132
env = _prepare_build_environment(cross_lib)
137133

@@ -213,9 +209,7 @@ def build_extension(
213209
# Execute cargo
214210
try:
215211
stderr = subprocess.PIPE if quiet else None
216-
output = subprocess.check_output(
217-
command, env=env, encoding="latin-1", stderr=stderr
218-
)
212+
output = subprocess.check_output(command, env=env, stderr=stderr, text=True)
219213
except subprocess.CalledProcessError as e:
220214
raise CompileError(format_called_process_error(e))
221215

@@ -310,7 +304,7 @@ def install_extension(
310304
if ext._uses_exec_binding():
311305
ext_path = build_ext.get_ext_fullpath(module_name)
312306
# remove extensions
313-
ext_path, _, _ = split_platform_and_extension(ext_path)
307+
ext_path, _, _ = _split_platform_and_extension(ext_path)
314308

315309
# Add expected extension
316310
exe = sysconfig.get_config_var("EXE")
@@ -393,12 +387,12 @@ def get_dylib_ext_path(self, ext: RustExtension, target_fname: str) -> str:
393387
host_arch = host_platform.rsplit("-", 1)[1]
394388
# Remove incorrect platform tag if we are cross compiling
395389
if target_arch and host_arch != target_arch:
396-
ext_path, _, extension = split_platform_and_extension(ext_path)
390+
ext_path, _, extension = _split_platform_and_extension(ext_path)
397391
# rust.so, removed platform tag
398392
ext_path += extension
399393
return ext_path
400394

401-
def _py_limited_api(self) -> PyLimitedApi:
395+
def _py_limited_api(self) -> _PyLimitedApi:
402396
bdist_wheel = self.distribution.get_command_obj("bdist_wheel", create=False)
403397

404398
if bdist_wheel is None:
@@ -409,11 +403,12 @@ def _py_limited_api(self) -> PyLimitedApi:
409403

410404
bdist_wheel_command = cast(CommandBdistWheel, bdist_wheel) # type: ignore[no-any-unimported]
411405
bdist_wheel_command.ensure_finalized()
412-
return cast(PyLimitedApi, bdist_wheel_command.py_limited_api)
406+
return cast(_PyLimitedApi, bdist_wheel_command.py_limited_api)
413407

414408
def _detect_rust_target(
415409
self, forced_target_triple: Optional[str] = None
416410
) -> Optional["_TargetInfo"]:
411+
assert self.plat_name is not None
417412
cross_compile_info = _detect_unix_cross_compile_info()
418413
if cross_compile_info is not None:
419414
cross_target_info = cross_compile_info.to_target_info()
@@ -448,33 +443,23 @@ def _detect_rust_target(
448443
)
449444

450445
elif forced_target_triple is not None:
451-
return _TargetInfo.for_triple(forced_target_triple)
452-
453-
else:
454446
# Automatic target detection can be overridden via the CARGO_BUILD_TARGET
455447
# environment variable or --target command line option
456-
return self._detect_local_rust_target()
448+
return _TargetInfo.for_triple(forced_target_triple)
457449

458-
def _detect_local_rust_target(self) -> Optional["_TargetInfo"]:
459-
"""Attempts to infer the correct Rust target from build environment for
460-
some edge cases."""
461-
assert self.plat_name is not None
450+
# Determine local rust target which needs to be "forced" if necessary
451+
local_rust_target = _adjusted_local_rust_target(self.plat_name)
462452

463-
# If we are on a 64-bit machine, but running a 32-bit Python, then
464-
# we'll target a 32-bit Rust build.
465-
if self.plat_name == "win32":
466-
if _get_rustc_cfgs(None).get("target_env") == "gnu":
467-
return _TargetInfo.for_triple("i686-pc-windows-gnu")
468-
return _TargetInfo.for_triple("i686-pc-windows-msvc")
469-
elif self.plat_name == "win-amd64":
470-
if _get_rustc_cfgs(None).get("target_env") == "gnu":
471-
return _TargetInfo.for_triple("x86_64-pc-windows-gnu")
472-
return _TargetInfo.for_triple("x86_64-pc-windows-msvc")
473-
elif self.plat_name.startswith("macosx-") and platform.machine() == "x86_64":
474-
# x86_64 or arm64 macOS targeting x86_64
475-
return _TargetInfo.for_triple("x86_64-apple-darwin")
476-
else:
477-
return None
453+
# Match cargo's behaviour of not using an explicit target if the
454+
# target we're compiling for is the host
455+
if (
456+
local_rust_target is not None
457+
# check for None first to avoid calling to rustc if not needed
458+
and local_rust_target != get_rust_host()
459+
):
460+
return _TargetInfo.for_triple(local_rust_target)
461+
462+
return None
478463

479464
def _is_debug_build(self, ext: RustExtension) -> bool:
480465
if self.release:
@@ -512,7 +497,7 @@ def _cargo_args(
512497

513498
features = {
514499
*ext.features,
515-
*binding_features(ext, py_limited_api=self._py_limited_api()),
500+
*_binding_features(ext, py_limited_api=self._py_limited_api()),
516501
}
517502

518503
if features:
@@ -531,11 +516,9 @@ def create_universal2_binary(output_path: str, input_paths: List[str]) -> None:
531516
# Try lipo first
532517
command = ["lipo", "-create", "-output", output_path, *input_paths]
533518
try:
534-
subprocess.check_output(command)
519+
subprocess.check_output(command, text=True)
535520
except subprocess.CalledProcessError as e:
536521
output = e.output
537-
if isinstance(output, bytes):
538-
output = e.output.decode("latin-1").strip()
539522
raise CompileError("lipo failed with code: %d\n%s" % (e.returncode, output))
540523
except OSError:
541524
# lipo not found, try using the fat-macho library
@@ -649,21 +632,6 @@ def _detect_unix_cross_compile_info() -> Optional["_CrossCompileInfo"]:
649632
return _CrossCompileInfo(host_type, cross_lib, linker, linker_args)
650633

651634

652-
_RustcCfgs = Dict[str, Optional[str]]
653-
654-
655-
def _get_rustc_cfgs(target_triple: Optional[str]) -> _RustcCfgs:
656-
cfgs: _RustcCfgs = {}
657-
for entry in get_rust_target_info(target_triple):
658-
maybe_split = entry.split("=", maxsplit=1)
659-
if len(maybe_split) == 2:
660-
cfgs[maybe_split[0]] = maybe_split[1].strip('"')
661-
else:
662-
assert len(maybe_split) == 1
663-
cfgs[maybe_split[0]] = None
664-
return cfgs
665-
666-
667635
def _replace_vendor_with_unknown(target: str) -> Optional[str]:
668636
"""Replaces vendor in the target triple with unknown.
669637
@@ -719,7 +687,7 @@ def _base_cargo_target_dir(ext: RustExtension, *, quiet: bool) -> str:
719687

720688
def _is_py_limited_api(
721689
ext_setting: Literal["auto", True, False],
722-
wheel_setting: Optional[PyLimitedApi],
690+
wheel_setting: Optional[_PyLimitedApi],
723691
) -> bool:
724692
"""Returns whether this extension is being built for the limited api.
725693
@@ -742,3 +710,64 @@ def _is_py_limited_api(
742710

743711
# "auto" setting - use whether the bdist_wheel option is truthy.
744712
return bool(wheel_setting)
713+
714+
715+
def _binding_features(
716+
ext: RustExtension,
717+
py_limited_api: _PyLimitedApi,
718+
) -> Set[str]:
719+
if ext.binding in (Binding.NoBinding, Binding.Exec):
720+
return set()
721+
elif ext.binding is Binding.PyO3:
722+
features = {"pyo3/extension-module"}
723+
if ext.py_limited_api == "auto":
724+
if isinstance(py_limited_api, str):
725+
python_version = py_limited_api[2:]
726+
features.add(f"pyo3/abi3-py{python_version}")
727+
elif py_limited_api:
728+
features.add(f"pyo3/abi3")
729+
return features
730+
elif ext.binding is Binding.RustCPython:
731+
return {"cpython/python3-sys", "cpython/extension-module"}
732+
else:
733+
raise DistutilsPlatformError(f"unknown Rust binding: '{ext.binding}'")
734+
735+
736+
_PyLimitedApi = Literal["cp37", "cp38", "cp39", "cp310", "cp311", "cp312", True, False]
737+
738+
739+
def _adjusted_local_rust_target(plat_name: str) -> Optional[str]:
740+
"""Returns the local rust target for the given `plat_name`, if it is
741+
necessary to 'force' a specific target for correctness."""
742+
743+
# If we are on a 64-bit machine, but running a 32-bit Python, then
744+
# we'll target a 32-bit Rust build.
745+
if plat_name == "win32":
746+
if get_rustc_cfgs(None).get("target_env") == "gnu":
747+
return "i686-pc-windows-gnu"
748+
else:
749+
return "i686-pc-windows-msvc"
750+
elif plat_name == "win-amd64":
751+
if get_rustc_cfgs(None).get("target_env") == "gnu":
752+
return "x86_64-pc-windows-gnu"
753+
else:
754+
return "x86_64-pc-windows-msvc"
755+
elif plat_name.startswith("macosx-") and platform.machine() == "x86_64":
756+
# x86_64 or arm64 macOS targeting x86_64
757+
return "x86_64-apple-darwin"
758+
759+
return None
760+
761+
762+
def _split_platform_and_extension(ext_path: str) -> Tuple[str, str, str]:
763+
"""Splits an extension path into a tuple (ext_path, plat_tag, extension).
764+
765+
>>> _split_platform_and_extension("foo/bar.platform.so")
766+
('foo/bar', '.platform', '.so')
767+
"""
768+
769+
# rust.cpython-38-x86_64-linux-gnu.so to (rust.cpython-38-x86_64-linux-gnu, .so)
770+
ext_path, extension = os.path.splitext(ext_path)
771+
# rust.cpython-38-x86_64-linux-gnu to (rust, .cpython-38-x86_64-linux-gnu)
772+
ext_path, platform_tag = os.path.splitext(ext_path)
773+
return (ext_path, platform_tag, extension)

setuptools_rust/command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from setuptools.dist import Distribution
88

99
from .extension import RustExtension
10-
from .utils import get_rust_version
10+
from .rustc_info import get_rust_version
1111

1212

1313
class RustCommand(Command, ABC):

setuptools_rust/extension.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from semantic_version import SimpleSpec
1111
from typing_extensions import Literal
1212

13-
from .private import format_called_process_error
13+
from ._utils import format_called_process_error
1414

1515

1616
class Binding(IntEnum):
@@ -246,7 +246,7 @@ def _metadata(self, *, quiet: bool) -> "_CargoMetadata":
246246
try:
247247
stderr = subprocess.PIPE if quiet else None
248248
payload = subprocess.check_output(
249-
metadata_command, encoding="latin-1", stderr=stderr
249+
metadata_command, stderr=stderr, encoding="latin-1"
250250
)
251251
except subprocess.CalledProcessError as e:
252252
raise DistutilsSetupError(format_called_process_error(e))

setuptools_rust/rustc_info.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import subprocess
2+
from distutils.errors import DistutilsPlatformError
3+
from functools import lru_cache
4+
from typing import Dict, List, NewType, Optional
5+
6+
from semantic_version import Version
7+
8+
9+
def get_rust_version() -> Optional[Version]: # type: ignore[no-any-unimported]
10+
try:
11+
# first line of rustc -Vv is something like
12+
# rustc 1.61.0 (fe5b13d68 2022-05-18)
13+
return Version(_rust_version_verbose().split(" ")[1])
14+
except (subprocess.CalledProcessError, OSError):
15+
return None
16+
17+
18+
_HOST_LINE_START = "host: "
19+
20+
21+
def get_rust_host() -> str:
22+
# rustc -Vv has a line denoting the host which cargo uses to decide the
23+
# default target, e.g.
24+
# host: aarch64-apple-darwin
25+
for line in _rust_version_verbose().splitlines():
26+
if line.startswith(_HOST_LINE_START):
27+
return line[len(_HOST_LINE_START) :].strip()
28+
raise DistutilsPlatformError("Could not determine rust host")
29+
30+
31+
RustCfgs = NewType("RustCfgs", Dict[str, Optional[str]])
32+
33+
34+
def get_rustc_cfgs(target_triple: Optional[str]) -> RustCfgs:
35+
cfgs = RustCfgs({})
36+
for entry in get_rust_target_info(target_triple):
37+
maybe_split = entry.split("=", maxsplit=1)
38+
if len(maybe_split) == 2:
39+
cfgs[maybe_split[0]] = maybe_split[1].strip('"')
40+
else:
41+
assert len(maybe_split) == 1
42+
cfgs[maybe_split[0]] = None
43+
return cfgs
44+
45+
46+
@lru_cache()
47+
def get_rust_target_info(target_triple: Optional[str] = None) -> List[str]:
48+
cmd = ["rustc", "--print", "cfg"]
49+
if target_triple:
50+
cmd.extend(["--target", target_triple])
51+
output = subprocess.check_output(cmd, text=True)
52+
return output.splitlines()
53+
54+
55+
@lru_cache()
56+
def get_rust_target_list() -> List[str]:
57+
output = subprocess.check_output(["rustc", "--print", "target-list"], text=True)
58+
return output.splitlines()
59+
60+
61+
@lru_cache()
62+
def _rust_version_verbose() -> str:
63+
return subprocess.check_output(["rustc", "-Vv"], text=True)

0 commit comments

Comments
 (0)