Skip to content

Commit 08c1fac

Browse files
authored
Add dynamically linked musl distributions (#541)
As a consequence of #540 I was playing with these concepts and decided to explore it. This includes #546 (could be merged separately or together), which separates "static" builds from the "musl" triple specifically in favor of a dedicated build option. The main implementation downside here is that `$ORIGIN` doesn't work with DT_NEEDED so we need to use RUNPATH instead, which can cause the wrong library to be loaded if LD_LIBRARY_PATH is set. Given the current builds aren't usable at all, I think this is a fine trade-off. We can explore alternatives in the future, like statically linking just libpython. Another caveat here: for consistency with the glibc builds, we're changing the "default" musl build to be dynamically linked. This is a breaking change in the release artifacts. The statically linked musl build will include a `+static` suffix. We could do something for backwards compatibility here, but I _think_ this probably makes sense in the long term. My primary concern is that consumers that combine releases (such as uv) would need to encode this change (e.g., toggle the expectation based on the python-build-standalone version tag). It's challenging to test changes to the release artifact handling. Regardless of approach, this will need a follow-up to adjust that accordingly.
1 parent 432bcbe commit 08c1fac

16 files changed

+309
-80
lines changed

.github/workflows/linux.yml

+14
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,13 @@ jobs:
240240
chmod +x build/pythonbuild
241241
242242
if [ "${{ matrix.run }}" == "true" ]; then
243+
if [ "${{ matrix.libc }}" == "musl" ]; then
244+
sudo apt install musl-dev
245+
246+
# GitHub's setup-python action sets `LD_LIBRARY_PATH` which overrides `RPATH`
247+
# as used in the musl builds.
248+
unset LD_LIBRARY_PATH
249+
fi
243250
EXTRA_ARGS="--run"
244251
fi
245252
@@ -324,6 +331,13 @@ jobs:
324331
chmod +x build/pythonbuild
325332
326333
if [ "${{ matrix.run }}" == "true" ]; then
334+
if [ "${{ matrix.libc }}" == "musl" ]; then
335+
sudo apt install musl-dev
336+
337+
# GitHub's setup-python action sets `LD_LIBRARY_PATH` which overrides `RPATH`
338+
# as used in the musl builds.
339+
unset LD_LIBRARY_PATH
340+
fi
327341
EXTRA_ARGS="--run"
328342
fi
329343

ci-targets.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ linux:
257257
- "3.12"
258258
- "3.13"
259259
build_options:
260+
- debug+static
261+
- noopt+static
262+
- lto+static
260263
- debug
261264
- noopt
262265
- lto
@@ -273,6 +276,9 @@ linux:
273276
- "3.12"
274277
- "3.13"
275278
build_options:
279+
- debug+static
280+
- noopt+static
281+
- lto+static
276282
- debug
277283
- noopt
278284
- lto
@@ -289,6 +295,9 @@ linux:
289295
- "3.12"
290296
- "3.13"
291297
build_options:
298+
- debug+static
299+
- noopt+static
300+
- lto+static
292301
- debug
293302
- noopt
294303
- lto
@@ -305,6 +314,9 @@ linux:
305314
- "3.12"
306315
- "3.13"
307316
build_options:
317+
- debug+static
318+
- noopt+static
319+
- lto+static
308320
- debug
309321
- noopt
310322
- lto

cpython-unix/build-cpython.sh

+42-19
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,8 @@ CONFIGURE_FLAGS="
384384
--without-ensurepip
385385
${EXTRA_CONFIGURE_FLAGS}"
386386

387-
if [ "${CC}" = "musl-clang" ]; then
388-
CFLAGS="${CFLAGS} -static"
389-
CPPFLAGS="${CPPFLAGS} -static"
390-
LDFLAGS="${LDFLAGS} -static"
391-
PYBUILD_SHARED=0
392387

388+
if [ "${CC}" = "musl-clang" ]; then
393389
# In order to build the _blake2 extension module with SSE3+ instructions, we need
394390
# musl-clang to find headers that provide access to the intrinsics, as they are not
395391
# provided by musl. These are part of the include files that are part of clang.
@@ -403,6 +399,13 @@ if [ "${CC}" = "musl-clang" ]; then
403399
fi
404400
cp "$h" /tools/host/include/
405401
done
402+
fi
403+
404+
if [ -n "${CPYTHON_STATIC}" ]; then
405+
CFLAGS="${CFLAGS} -static"
406+
CPPFLAGS="${CPPFLAGS} -static"
407+
LDFLAGS="${LDFLAGS} -static"
408+
PYBUILD_SHARED=0
406409
else
407410
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} --enable-shared"
408411
PYBUILD_SHARED=1
@@ -644,21 +647,41 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then
644647
LIBPYTHON_SHARED_LIBRARY_BASENAME=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.so.1.0
645648
LIBPYTHON_SHARED_LIBRARY=${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}
646649

647-
# If we simply set DT_RUNPATH via --set-rpath, LD_LIBRARY_PATH would be used before
648-
# DT_RUNPATH, which could result in confusion at run-time. But if DT_NEEDED
649-
# contains a slash, the explicit path is used.
650-
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
651-
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
652-
653-
# libpython3.so isn't present in debug builds.
654-
if [ -z "${CPYTHON_DEBUG}" ]; then
655-
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
656-
${ROOT}/out/python/install/lib/libpython3.so
657-
fi
658-
659-
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
650+
if [ "${CC}" == "musl-clang" ]; then
651+
# musl does not support $ORIGIN in DT_NEEDED, so we use RPATH instead. This could be
652+
# problematic, i.e., we could load the shared library from the wrong location if
653+
# `LD_LIBRARY_PATH` is set, but there's not a clear alternative at this time. The
654+
# long term solution is probably to statically link to libpython instead.
655+
patchelf --set-rpath "\$ORIGIN/../lib" \
656+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
657+
658+
# libpython3.so isn't present in debug builds.
659+
if [ -z "${CPYTHON_DEBUG}" ]; then
660+
patchelf --set-rpath "\$ORIGIN/../lib" \
661+
${ROOT}/out/python/install/lib/libpython3.so
662+
fi
663+
664+
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
665+
patchelf --set-rpath "\$ORIGIN/../lib" \
666+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
667+
fi
668+
else
669+
# If we simply set DT_RUNPATH via --set-rpath, LD_LIBRARY_PATH would be used before
670+
# DT_RUNPATH, which could result in confusion at run-time. But if DT_NEEDED contains a
671+
# slash, the explicit path is used.
660672
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
661-
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
673+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
674+
675+
# libpython3.so isn't present in debug builds.
676+
if [ -z "${CPYTHON_DEBUG}" ]; then
677+
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
678+
${ROOT}/out/python/install/lib/libpython3.so
679+
fi
680+
681+
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
682+
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
683+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
684+
fi
662685
fi
663686
fi
664687
fi

cpython-unix/build-libX11.sh

+24
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ if [ -n "${CROSS_COMPILING}" ]; then
7575
s390x-unknown-linux-gnu)
7676
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
7777
;;
78+
x86_64-unknown-linux-musl)
79+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
80+
;;
81+
aarch64-unknown-linux-musl)
82+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
83+
;;
84+
i686-unknown-linux-musl)
85+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
86+
;;
87+
mips-unknown-linux-musl)
88+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
89+
;;
90+
mipsel-unknown-linux-musl)
91+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
92+
;;
93+
ppc64le-unknown-linux-musl)
94+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
95+
;;
96+
riscv64-unknown-linux-musl)
97+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
98+
;;
99+
s390x-unknown-linux-musl)
100+
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
101+
;;
78102
*)
79103
echo "cross-compiling but malloc(0) override not set; failures possible"
80104
;;

cpython-unix/build-main.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def main():
4545
print("Unsupported build platform: %s" % sys.platform)
4646
return 1
4747

48+
# Note these arguments must be synced with `build.py`
4849
parser = argparse.ArgumentParser()
4950

5051
parser.add_argument(
@@ -54,10 +55,14 @@ def main():
5455
help="Target host triple to build for",
5556
)
5657

57-
optimizations = {"debug", "noopt", "pgo", "lto", "pgo+lto"}
58+
# Construct possible options, we use a set here for canonical ordering
59+
options = set()
60+
options.update({"debug", "noopt", "pgo", "lto", "pgo+lto"})
61+
options.update({f"freethreaded+{option}" for option in options})
62+
options.update({f"{option}+static" for option in options})
5863
parser.add_argument(
5964
"--options",
60-
choices=optimizations.union({f"freethreaded+{o}" for o in optimizations}),
65+
choices=options,
6166
default="noopt",
6267
help="Build options to apply when compiling Python",
6368
)

cpython-unix/build-musl.sh

+50-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# License, v. 2.0. If a copy of the MPL was not distributed with this
44
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
55

6-
set -e
6+
set -ex
77

88
cd /build
99

@@ -18,7 +18,43 @@ pushd musl-${MUSL_VERSION}
1818
# added reallocarray(), which gets used by at least OpenSSL.
1919
# Here, we disable this single function so as to not introduce
2020
# symbol dependencies on clients using an older musl version.
21-
patch -p1 <<EOF
21+
if [ "${MUSL_VERSION}" = "1.2.2" ]; then
22+
patch -p1 <<EOF
23+
diff --git a/include/stdlib.h b/include/stdlib.h
24+
index b54a051f..194c2033 100644
25+
--- a/include/stdlib.h
26+
+++ b/include/stdlib.h
27+
@@ -145,7 +145,6 @@ int getloadavg(double *, int);
28+
int clearenv(void);
29+
#define WCOREDUMP(s) ((s) & 0x80)
30+
#define WIFCONTINUED(s) ((s) == 0xffff)
31+
-void *reallocarray (void *, size_t, size_t);
32+
#endif
33+
34+
#ifdef _GNU_SOURCE
35+
diff --git a/src/malloc/reallocarray.c b/src/malloc/reallocarray.c
36+
deleted file mode 100644
37+
index 4a6ebe46..00000000
38+
--- a/src/malloc/reallocarray.c
39+
+++ /dev/null
40+
@@ -1,13 +0,0 @@
41+
-#define _BSD_SOURCE
42+
-#include <errno.h>
43+
-#include <stdlib.h>
44+
-
45+
-void *reallocarray(void *ptr, size_t m, size_t n)
46+
-{
47+
- if (n && m > -1 / n) {
48+
- errno = ENOMEM;
49+
- return 0;
50+
- }
51+
-
52+
- return realloc(ptr, m * n);
53+
-}
54+
EOF
55+
else
56+
# There is a different patch for newer musl versions, used in static distributions
57+
patch -p1 <<EOF
2258
diff --git a/include/stdlib.h b/include/stdlib.h
2359
index b507ca3..8259e27 100644
2460
--- a/include/stdlib.h
@@ -51,10 +87,20 @@ index 4a6ebe4..0000000
5187
- return realloc(ptr, m * n);
5288
-}
5389
EOF
90+
fi
91+
92+
SHARED=
93+
if [ -n "${STATIC}" ]; then
94+
SHARED="--disable-shared"
95+
else
96+
SHARED="--enable-shared"
97+
CFLAGS="${CFLAGS} -fPIC" CPPFLAGS="${CPPFLAGS} -fPIC"
98+
fi
99+
54100

55-
./configure \
101+
CFLAGS="${CFLAGS}" CPPFLAGS="${CPPFLAGS}" ./configure \
56102
--prefix=/tools/host \
57-
--disable-shared
103+
"${SHARED}"
58104

59105
make -j `nproc`
60106
make -j `nproc` install DESTDIR=/build/out

cpython-unix/build-tcl.sh

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ export PKG_CONFIG_PATH=${TOOLS_PATH}/deps/share/pkgconfig:${TOOLS_PATH}/deps/lib
1313
tar -xf tcl${TCL_VERSION}-src.tar.gz
1414
pushd tcl${TCL_VERSION}
1515

16+
17+
if [ -n "${STATIC}" ]; then
18+
if echo "${TARGET_TRIPLE}" | grep -q -- "-unknown-linux-musl"; then
19+
# tcl misbehaves when performing static musl builds
20+
# `checking whether musl-clang accepts -g...` fails with a duplicate definition error
21+
TARGET_TRIPLE="$(echo "${TARGET_TRIPLE}" | sed -e 's/-unknown-linux-musl/-unknown-linux-gnu/g')"
22+
fi
23+
fi
24+
1625
patch -p1 << 'EOF'
1726
diff --git a/unix/Makefile.in b/unix/Makefile.in
1827
--- a/unix/Makefile.in

cpython-unix/build-xextproto.sh

+12-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@ export PKG_CONFIG_PATH=/tools/deps/share/pkgconfig
1515
tar -xf xextproto-${XEXTPROTO_VERSION}.tar.gz
1616
pushd xextproto-${XEXTPROTO_VERSION}
1717

18+
EXTRA_CONFIGURE_FLAGS=
19+
if [ -n "${CROSS_COMPILING}" ]; then
20+
if echo "${TARGET_TRIPLE}" | grep -q -- "-unknown-linux-musl"; then
21+
# xextproto does not support configuration of musl targets so we pretend the target matches the
22+
# build triple and enable cross-compilation manually
23+
TARGET_TRIPLE="$(echo "${TARGET_TRIPLE}" | sed -e 's/-unknown-linux-musl/-unknown-linux-gnu/g')"
24+
EXTRA_CONFIGURE_FLAGS="cross_compiling=yes"
25+
fi
26+
fi
27+
1828
CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure \
1929
--build=${BUILD_TRIPLE} \
2030
--host=${TARGET_TRIPLE} \
21-
--prefix=/tools/deps
31+
--prefix=/tools/deps \
32+
${EXTRA_CONFIGURE_FLAGS}
2233

2334
make -j `nproc`
2435
make -j `nproc` install DESTDIR=${ROOT}/out

cpython-unix/build-xproto.sh

+12-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@ export PKG_CONFIG_PATH=${TOOLS_PATH}/deps/share/pkgconfig
1515
tar -xf xproto-${XPROTO_VERSION}.tar.gz
1616
pushd xproto-${XPROTO_VERSION}
1717

18+
EXTRA_CONFIGURE_FLAGS=
19+
if [ -n "${CROSS_COMPILING}" ]; then
20+
if echo "${TARGET_TRIPLE}" | grep -q -- "-unknown-linux-musl"; then
21+
# xproto does not support configuration of musl targets so we pretend the target matches the
22+
# build triple and enable cross-compilation manually
23+
TARGET_TRIPLE="$(echo "${TARGET_TRIPLE}" | sed -e 's/-unknown-linux-musl/-unknown-linux-gnu/g')"
24+
EXTRA_CONFIGURE_FLAGS="cross_compiling=yes"
25+
fi
26+
fi
27+
1828
CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure \
1929
--build=${BUILD_TRIPLE} \
2030
--host=${TARGET_TRIPLE} \
21-
--prefix=/tools/deps
31+
--prefix=/tools/deps \
32+
${EXTRA_CONFIGURE_FLAGS}
2233

2334
make -j ${NUM_CPUS}
2435
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out

0 commit comments

Comments
 (0)