Skip to content

Commit 6aa92b8

Browse files
committed
unix: initial support for building armv7-unknown-linux-gnueabihf
This commit teaches the build system to cross-compile for armv7-unknown-linux-gnueabihf. In order to get cross-compiles working, we introduce a new Docker environment based on Debian Stretch. This environment has cross toolchains readily available, unlike Jessie. I'm not keen on using Debian Stretch here or the built-in toolchains because I would prefer to compile with a modern toolchain we build ourselves. And I worry about things like glibc symbol versions from Stretch being too new. But this does seem to be the path of least resistance for getting ARM cross-compiles working. The way I see it, if we support creating the builds, others can run them and report issues. There are likely many problems with the builds. I don't believe I can run these binaries locally, so I have no way of testing. Although I could probably use an emulator. I may do that later.
1 parent 055ae96 commit 6aa92b8

File tree

9 files changed

+178
-8
lines changed

9 files changed

+178
-8
lines changed

cpython-unix/build-cpython.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ tar -xf pip-${PIP_VERSION}.tar.gz
2828
if [ "${BUILD_TRIPLE}" != "${TARGET_TRIPLE}" ]; then
2929
pushd "Python-${PYTHON_VERSION}"
3030

31+
OLD_CC=${CC}
32+
unset CC
33+
3134
# When cross-compiling, we need to build a host Python that has working zlib
3235
# and ctypes extensions, otherwise various things fail. (`make install` fails
3336
# without zlib and setuptools / pip used by target install fail due to missing
@@ -70,6 +73,8 @@ if [ "${BUILD_TRIPLE}" != "${TARGET_TRIPLE}" ]; then
7073
# at the front of PATH.
7174
export PATH="${TOOLS_PATH}/pyhost/bin:${PATH}"
7275

76+
export CC="${OLD_CC}"
77+
7378
popd
7479
# Nuke and re-pave the source directory out of paranoia.
7580
rm -rf "Python-${PYTHON_VERSION}"
@@ -587,6 +592,11 @@ if [ "${BUILD_TRIPLE}" != "${TARGET_TRIPLE}" ]; then
587592
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_file__dev_ptc=no"
588593
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_file__dev_ptmx=no"
589594
;;
595+
armv7-unknown-linux-gnueabihf)
596+
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_buggy_getaddrinfo=no"
597+
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_file__dev_ptc=no"
598+
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_file__dev_ptmx=no"
599+
;;
590600
x86_64-unknown-linux-musl)
591601
;;
592602
*)

cpython-unix/build-libX11.sh

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,30 @@ if [ "${BUILD_TRIPLE}" != "${TARGET_TRIPLE}" ]; then
4848
i686-unknown-linux-gnu)
4949
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
5050
;;
51-
51+
armv7-unknown-linux-gnueabihf)
52+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
53+
;;
5254
*)
5355
echo "cross-compiling but malloc(0) override not set; failures possible"
5456
;;
5557
esac
5658
fi
5759

60+
case "${TARGET_TRIPLE}" in
61+
armv7-unknown-linux-gnueabihf)
62+
CC_FOR_BUILD=gcc
63+
;;
64+
*)
65+
CC_FOR_BUILD=clang
66+
;;
67+
esac
68+
5869
# CC_FOR_BUILD is here because configure doesn't look for `clang` when
5970
# cross-compiling. So we force it.
6071
CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I/tools/deps/include" \
6172
CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I/tools/deps/include" \
6273
LDFLAGS="${EXTRA_TARGET_LDFLAGS}" \
63-
CC_FOR_BUILD=clang \
74+
CC_FOR_BUILD="${CC_FOR_BUILD}" \
6475
CFLAGS_FOR_BUILD="-I/tools/deps/include" \
6576
CPPFLAGS_FOR_BUILD="-I/tools/deps/include" \
6677
./configure \

cpython-unix/build.cross.Dockerfile

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Debian Stretch.
2+
FROM debian@sha256:d0b7b71db141cedc48e1c2807d12b199ffd7ffe75baf272a34c37480dc2159d1
3+
MAINTAINER Gregory Szorc <[email protected]>
4+
5+
RUN groupadd -g 1000 build && \
6+
useradd -u 1000 -g 1000 -d /build -s /bin/bash -m build && \
7+
mkdir /tools && \
8+
chown -R build:build /build /tools
9+
10+
ENV HOME=/build \
11+
SHELL=/bin/bash \
12+
USER=build \
13+
LOGNAME=build \
14+
HOSTNAME=builder \
15+
DEBIAN_FRONTEND=noninteractive
16+
17+
CMD ["/bin/bash", "--login"]
18+
WORKDIR '/build'
19+
20+
RUN for s in debian_stretch debian_stretch-updates debian-security_stretch/updates; do \
21+
echo "deb http://snapshot.debian.org/archive/${s%_*}/20210223T023121Z/ ${s#*_} main"; \
22+
done > /etc/apt/sources.list && \
23+
( echo 'quiet "true";'; \
24+
echo 'APT::Get::Assume-Yes "true";'; \
25+
echo 'APT::Install-Recommends "false";'; \
26+
echo 'Acquire::Check-Valid-Until "false";'; \
27+
echo 'Acquire::Retries "5";'; \
28+
) > /etc/apt/apt.conf.d/99cpython-portable
29+
30+
RUN apt-get update
31+
32+
# Host building.
33+
RUN apt-get install \
34+
gcc \
35+
libc6-dev \
36+
libffi-dev \
37+
make \
38+
patch \
39+
patchelf \
40+
perl \
41+
pkg-config \
42+
tar \
43+
xz-utils \
44+
unzip \
45+
zlib1g-dev
46+
47+
# Cross-building.
48+
RUN apt-get install \
49+
gcc-arm-linux-gnueabihf \
50+
libc6-dev-armhf-cross

cpython-unix/build.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ def add_target_env(env, build_platform, target_triple, build_env):
9494
env["CC"] = "musl-clang"
9595
else:
9696
env["CC"] = "clang"
97+
elif target_triple == "armv7-unknown-linux-gnueabihf":
98+
env["CC"] = "arm-linux-gnueabihf-gcc"
99+
else:
100+
raise Exception("unhandled target triple: %s" % target_triple)
97101

98102
env["PYBUILD_PLATFORM"] = build_platform
99103
env["TOOLS_PATH"] = build_env.tools_path
@@ -106,7 +110,9 @@ def add_target_env(env, build_platform, target_triple, build_env):
106110
if build_platform == "linux64":
107111
env["BUILD_TRIPLE"] = "x86_64-unknown-linux-gnu"
108112

109-
if target_triple in ("x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"):
113+
if target_triple == "armv7-unknown-linux-gnueabihf":
114+
env["TARGET_TRIPLE"] = "armv7-unknown-linux-gnueabihf"
115+
elif target_triple in ("x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"):
110116
env["TARGET_TRIPLE"] = "x86_64-unknown-linux-gnu"
111117
elif target_triple == "i686-unknown-linux-gnu":
112118
env["TARGET_TRIPLE"] = "i686-unknown-linux-gnu"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Setup.dist doesn't have entries for all modules. This file defines
2+
# what's missing. The content here is reconstructed from logic in
3+
# setup.py and what was observed to execute in a normal build via setup.py.
4+
# We should audit this every time we upgrade CPython.
5+
6+
_bz2 _bz2module.c -lbz2
7+
_crypt _cryptmodule.c -lcrypt
8+
_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c -I/tools/deps/include -L/tools/deps/lib -lffi -ldl
9+
_ctypes_test _ctypes/_ctypes_test.c -lm
10+
_curses _cursesmodule.c -DHAVE_NCURSESW=1 -I/tools/deps/include/ncursesw -L/tools/deps/lib -lncursesw
11+
_curses_panel _curses_panel.c -DHAVE_NCURSESW=1 -I/tools/deps/include/ncursesw -L/tools/deps/lib -lpanelw -lncursesw
12+
_dbm _dbmmodule.c -DHAVE_BERKDB_H -DDB_DBM_HSEARCH -I/tools/deps/include -L/tools/deps/lib -ldb
13+
_decimal _decimal/_decimal.c _decimal/libmpdec/basearith.c _decimal/libmpdec/constants.c _decimal/libmpdec/context.c _decimal/libmpdec/convolute.c _decimal/libmpdec/crt.c _decimal/libmpdec/difradix2.c _decimal/libmpdec/fnt.c _decimal/libmpdec/fourstep.c _decimal/libmpdec/io.c _decimal/libmpdec/mpalloc.c _decimal/libmpdec/mpdecimal.c _decimal/libmpdec/numbertheory.c _decimal/libmpdec/sixstep.c _decimal/libmpdec/transpose.c -DCONFIG_32=1 -DANSI=1 -IModules/_decimal/libmpdec
14+
_elementtree _elementtree.c -DHAVE_EXPAT_CONFIG_H=1 -DXML_POOR_ENTROPY=1 -DUSE_PYEXPAT_CAPI -IModules/expat
15+
_gdbm _gdbmmodule.c -DHAVE_NDBM_H -I/tools/deps/include -L/tools/deps/lib -lgdbm
16+
_hashlib _hashopenssl.c -I/tools/deps/include -L/tools/deps/lib -lssl -lcrypto
17+
_json _json.c
18+
_lsprof _lsprof.c rotatingtree.c
19+
_lzma _lzmamodule.c -I/tools/deps/include -L/tools/deps/lib -llzma
20+
# TODO check setup.py logic for semaphore.c and possibly fix missing
21+
# dependency.
22+
_multiprocessing _multiprocessing/multiprocessing.c _multiprocessing/semaphore.c
23+
_opcode _opcode.c
24+
_posixshmem _multiprocessing/posixshmem.c -IModules/_multiprocessing -lrt
25+
_queue _queuemodule.c
26+
_sqlite3 _sqlite/cache.c _sqlite/connection.c _sqlite/cursor.c _sqlite/microprotocols.c _sqlite/module.c _sqlite/prepare_protocol.c _sqlite/row.c _sqlite/statement.c _sqlite/util.c -I/tools/deps/include -IModules/_sqlite -DMODULE_NAME=\"sqlite3\" -DSQLITE_OMIT_LOAD_EXTENSION=1 -L/tools/deps/lib -lsqlite3
27+
_ssl _ssl.c -I/tools/deps/include -lssl -lcrypto
28+
_testbuffer _testbuffer.c
29+
_testimportmultiple _testimportmultiple.c
30+
_testinternalcapi _testinternalcapi.c -DPy_BUILD_CORE_MODULE
31+
_testmultiphase _testmultiphase.c
32+
_tkinter _tkinter.c tkappinit.c -DWITH_APPINIT -I/tools/deps/include/X11 -L/tools/deps/lib -ltcl8.6 -ltk8.6 -lX11 -lxcb -lXau
33+
_uuid _uuidmodule.c -I/tools/deps/include/uuid -luuid
34+
_xxsubinterpreters _xxsubinterpretersmodule.c
35+
_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
36+
ossaudiodev ossaudiodev.c
37+
pyexpat pyexpat.c expat/xmlparse.c expat/xmlrole.c expat/xmltok.c -DHAVE_EXPAT_CONFIG_H=1 -DXML_POOR_ENTROPY=1 -DUSE_PYEXPAT_CAPI -IModules/expat
38+
# readline variant needs to come first because libreadline is in /tools/deps and is
39+
# picked up by build. We /could/ make libedit first. But since we employ a hack to
40+
# coerce use of libedit on Linux, it seems prudent for the build system to pick
41+
# up readline.
42+
readline VARIANT=readline readline.c -I/tools/deps/include -I/tools/deps/include/ncursesw -L/tools/deps/lib -lreadline -lncursesw
43+
readline VARIANT=libedit readline-libedit.c -DUSE_LIBEDIT=1 -I/tools/deps/libedit/include -I/tools/deps/libedit/include/ncursesw -L/tools/deps/libedit/lib -ledit -lncursesw

cpython-unix/targets.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,32 @@ arm64-apple-tvos:
5757
- zlib
5858
openssl_target: todo
5959

60+
armv7-unknown-linux-gnueabihf:
61+
host_platforms:
62+
- linux
63+
docker_image_suffix: .cross
64+
needs:
65+
- bdb
66+
- binutils
67+
- bzip2
68+
- gdbm
69+
- libedit
70+
- libffi
71+
- libX11
72+
- libXau
73+
- libxcb
74+
- ncurses
75+
- readline
76+
- sqlite
77+
- tcl
78+
- tk
79+
- tix
80+
- uuid
81+
- xorgproto
82+
- xz
83+
- zlib
84+
openssl_target: linux-armv4
85+
6086
i686-unknown-linux-gnu:
6187
host_platforms:
6288
- linux

cpython-unix/xcb.cross.Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% include 'build.cross.Dockerfile' %}
2+
RUN apt-get install \
3+
python

pythonbuild/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,10 @@ def write_triples_makefiles(targets, dest_dir: pathlib.Path):
118118
else:
119119
lines.append("NEED_OPENSSL := 1\n")
120120

121-
lines.append("DOCKER_IMAGE_BUILD := build\n")
122-
lines.append("DOCKER_IMAGE_XCB := xcb\n")
121+
image_suffix = settings.get("docker_image_suffix", "")
122+
123+
lines.append("DOCKER_IMAGE_BUILD := build%s\n" % image_suffix)
124+
lines.append("DOCKER_IMAGE_XCB := xcb%s\n" % image_suffix)
123125

124126
write_if_different(makefile_path, "".join(lines).encode("ascii"))
125127

src/main.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use {
2525
const RECOGNIZED_TRIPLES: &[&str] = &[
2626
"aarch64-apple-darwin",
2727
"aarch64-apple-ios",
28+
"armv7-unknown-linux-gnueabihf",
2829
"arm64-apple-tvos",
2930
"i686-pc-windows-msvc",
3031
"i686-unknown-linux-gnu",
@@ -113,6 +114,13 @@ const PE_ALLOWED_LIBRARIES: &[&str] = &[
113114
lazy_static! {
114115
static ref GLIBC_MAX_VERSION: version_compare::Version<'static> =
115116
version_compare::Version::from("2.19").unwrap();
117+
118+
static ref ELF_ALLOWED_LIBRARIES_BY_TRIPLE: HashMap<&'static str, Vec<&'static str>> = {
119+
[
120+
("armv7-unknown-linux-gnueabihf", vec!["ld-linux-armhf.so.3", "libgcc_s.so.1"]),
121+
].iter().cloned().collect()
122+
};
123+
116124
static ref DARWIN_ALLOWED_DYLIBS: Vec<MachOAllowedDylib> = {
117125
[
118126
MachOAllowedDylib {
@@ -256,6 +264,7 @@ lazy_static! {
256264
[
257265
("aarch64-apple-darwin", "macosx-11.0-arm64"),
258266
("aarch64-apple-ios", "iOS-aarch64"),
267+
("armv7-unknown-linux-gnueabihf", "linux-arm"),
259268
("i686-pc-windows-msvc", "win32"),
260269
("i686-unknown-linux-gnu", "linux-i686"),
261270
("x86_64-apple-darwin", "macosx-10.9-x86_64"),
@@ -277,11 +286,21 @@ fn allowed_dylibs_for_triple(triple: &str) -> Vec<MachOAllowedDylib> {
277286
}
278287
}
279288

280-
fn validate_elf(path: &Path, elf: &goblin::elf::Elf, bytes: &[u8]) -> Result<Vec<String>> {
289+
fn validate_elf(
290+
target_triple: &str,
291+
path: &Path,
292+
elf: &goblin::elf::Elf,
293+
bytes: &[u8],
294+
) -> Result<Vec<String>> {
281295
let mut errors = vec![];
282296

297+
let mut allowed_libraries = ELF_ALLOWED_LIBRARIES.to_vec();
298+
if let Some(extra) = ELF_ALLOWED_LIBRARIES_BY_TRIPLE.get(target_triple) {
299+
allowed_libraries.extend(extra.iter());
300+
}
301+
283302
for lib in &elf.libraries {
284-
if !ELF_ALLOWED_LIBRARIES.contains(lib) {
303+
if !allowed_libraries.contains(lib) {
285304
errors.push(format!("{} loads illegal library {}", path.display(), lib));
286305
}
287306
}
@@ -443,7 +462,7 @@ fn validate_distribution(dist_path: &Path) -> Result<Vec<String>> {
443462
if let Ok(object) = goblin::Object::parse(&data) {
444463
match object {
445464
goblin::Object::Elf(elf) => {
446-
errors.extend(validate_elf(path.as_ref(), &elf, &data)?);
465+
errors.extend(validate_elf(triple, path.as_ref(), &elf, &data)?);
447466
}
448467
goblin::Object::Mach(mach) => match mach {
449468
goblin::mach::Mach::Binary(macho) => {

0 commit comments

Comments
 (0)