Skip to content

Commit db77904

Browse files
authored
Merge pull request #141 from messense/crossenv
Add cross compilation test with crossenv
2 parents ad05f1c + 09d641a commit db77904

File tree

2 files changed

+138
-35
lines changed

2 files changed

+138
-35
lines changed

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,48 @@ jobs:
169169
pip install rust_with_cffi/dist/rust_with_cffi*.whl
170170
python -c "from rust_with_cffi import rust; assert rust.rust_func() == 14"
171171
python -c "from rust_with_cffi.cffi import lib; assert lib.cffi_func() == 15"
172+
173+
test-cross-compile:
174+
runs-on: ubuntu-latest
175+
strategy:
176+
matrix:
177+
platform: [
178+
{ target: "aarch64-unknown-linux-gnu", arch: "aarch64" },
179+
{ target: "armv7-unknown-linux-gnueabihf", arch: "armv7" },
180+
]
181+
steps:
182+
- uses: actions/checkout@master
183+
- name: Build Wheels
184+
run: |
185+
echo 'curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
186+
source ~/.cargo/env
187+
rustup target add ${{ matrix.platform.target }}
188+
cd examples/rust_with_cffi/
189+
python3.6 -m pip install crossenv
190+
python3.6 -m crossenv "/opt/python/cp36-cp36m/bin/python3" --cc $TARGET_CC --cxx $TARGET_CXX --sysroot $TARGET_SYSROOT --env LIBRARY_PATH= --manylinux manylinux1 venv
191+
. venv/bin/activate
192+
build-pip install cffi wheel
193+
cross-expose cffi
194+
pip install wheel
195+
pip install -e ../../
196+
python setup.py bdist_wheel --py-limited-api=cp36
197+
ls -la dist/
198+
' > build-wheels.sh
199+
200+
docker run --rm -v "$PWD":/io -w /io messense/manylinux2014-cross:${{ matrix.platform.arch }} bash build-wheels.sh
201+
- name: Install abi3 wheel and run tests
202+
uses: uraimo/[email protected]
203+
with:
204+
arch: ${{ matrix.platform.arch }}
205+
distro: ubuntu20.04
206+
dockerRunArgs: |
207+
--volume "${PWD}/examples:/examples"
208+
install: |
209+
apt-get update
210+
apt-get install -y --no-install-recommends python3 python3-dev python3-pip build-essential libffi-dev
211+
run: |
212+
cd /examples
213+
python3 --version
214+
pip3 install rust_with_cffi/dist/rust_with_cffi*.whl
215+
python3 -c "from rust_with_cffi import rust; assert rust.rust_func() == 14"
216+
python3 -c "from rust_with_cffi.cffi import lib; assert lib.cffi_func() == 15"

setuptools_rust/build.py

Lines changed: 93 additions & 35 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,14 +28,6 @@
2728
)
2829

2930

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-
3831
class build_rust(RustCommand):
3932
"""Command for building Rust crates via cargo."""
4033

@@ -83,24 +76,46 @@ def finalize_options(self):
8376
("inplace", "inplace"),
8477
)
8578

86-
def get_target_info(self):
79+
def get_target_info(self) -> "_TargetInfo":
8780
# If we are on a 64-bit machine, but running a 32-bit Python, then
8881
# we'll target a 32-bit Rust build.
8982
# Automatic target detection can be overridden via the CARGO_BUILD_TARGET
9083
# 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":
9485
return _TargetInfo("i686-pc-windows-msvc")
9586
elif self.plat_name == "win-amd64":
9687
return _TargetInfo("x86_64-pc-windows-msvc")
9788
elif self.plat_name.startswith("macosx-") and platform.machine() == "x86_64":
9889
# x86_64 or arm64 macOS targeting x86_64
9990
return _TargetInfo("x86_64-apple-darwin")
100-
else:
101-
return self.get_nix_target_info()
10291

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"]:
104119
# See https://github.com/PyO3/setuptools-rust/issues/138
105120
# This is to support cross compiling on *NIX, where plat_name isn't
106121
# necessarily the same as the system we are running on. *NIX systems
@@ -111,7 +126,7 @@ def get_nix_target_info(self):
111126

112127
if not host_type or host_type == build_type:
113128
# not *NIX, or not cross compiling
114-
return _TargetInfo()
129+
return None
115130

116131
stdlib = sysconfig.get_path("stdlib")
117132
cross_lib = os.path.dirname(stdlib)
@@ -125,24 +140,7 @@ def get_nix_target_info(self):
125140
linker = bldshared[0]
126141
linker_args = bldshared[1:]
127142

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)
146144

147145
def run_for_extension(self, ext: RustExtension):
148146
arch_flags = os.getenv("ARCHFLAGS")
@@ -270,7 +268,7 @@ def build_extension(self, ext: RustExtension, target_triple=None):
270268

271269
if target_info.linker is not None:
272270
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
274272
# sure if they will always do the right thing. Might help with some
275273
# of the OS-specific logic below if it does.
276274

@@ -477,3 +475,63 @@ def _py_limited_api(self) -> PyLimitedApi:
477475
else:
478476
bdist_wheel.ensure_finalized()
479477
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)