Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9663760
Dynamically link libc in musl distributions
zanieb Feb 26, 2025
18ff4fc
Fix dynlib loading
zanieb Feb 26, 2025
f156f44
Update musl dist metadata
zanieb Feb 26, 2025
c78bb9b
Update validation to support dynamic musl
zanieb Feb 26, 2025
d590f87
Update comment
zanieb Feb 26, 2025
51a5e29
Fix verify test for dynamic musl
zanieb Feb 26, 2025
7b2b0f2
Add path context to test run failure
zanieb Feb 26, 2025
2cef305
Format
zanieb Feb 26, 2025
a173de8
Install musl-dev for validation
zanieb Feb 26, 2025
94cadca
Fix musl build mode loading
zanieb Feb 26, 2025
7b8ff31
Downgrade to musl 1.2.2
zanieb Feb 26, 2025
280f345
Debug `LD_LIBRARY_PATH`
zanieb Feb 26, 2025
902fcd0
Add comment
zanieb Feb 26, 2025
b60e292
Refactor static / dynamic linking into build options
zanieb Feb 27, 2025
28d5d6e
Merge branch 'zb/ref-static' into zb/musl-dynamic
zanieb Mar 5, 2025
01172ad
Add dynamic CI targets
zanieb Mar 5, 2025
75ff237
Use the musl version from downloads in the metadata
zanieb Mar 5, 2025
d05620a
Remove the `+shared` build option; enable implicitly
zanieb Mar 5, 2025
567768d
Remove commented line
zanieb Mar 5, 2025
fd04e37
Apply `_blake2` musl fixes when doing non-static builds
zanieb Mar 5, 2025
88b335b
Split the musl versions for static and shared buildsl
zanieb Mar 5, 2025
6b6f103
Properly toggle `--enable-shared` when building the musl toolchain
zanieb Mar 6, 2025
6f88ed4
Fix syntax error
zanieb Mar 6, 2025
c0b9ce9
Set `-x` in the musl build
zanieb Mar 6, 2025
44c1959
Only enable `fPIC` during shared musl builds
zanieb Mar 6, 2025
4385eb8
Trim some extraneous whitespace
zanieb Mar 6, 2025
1990014
Change the triple during static musl tcl builds to get past duplicate…
zanieb Mar 6, 2025
7153487
Format
zanieb Mar 6, 2025
4c685db
Fix typo
zanieb Mar 6, 2025
4670133
Fix missing variable
zanieb Mar 6, 2025
c4e081d
Fix configuration of tcl build
zanieb Mar 7, 2025
c410304
Update validation
zanieb Mar 7, 2025
a745841
Remove extra "
zanieb Mar 10, 2025
6a79ece
Clean up ELF validation logic
zanieb Mar 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ jobs:
chmod +x build/pythonbuild

if [ "${{ matrix.run }}" == "true" ]; then
if [ "${{ matrix.libc }}" == "musl" ]; then
sudo apt install musl-dev

# GitHub's setup-python action sets `LD_LIBRARY_PATH` which overrides `RPATH`
# as used in the musl builds.
unset LD_LIBRARY_PATH
fi
EXTRA_ARGS="--run"
fi

Expand Down Expand Up @@ -324,6 +331,13 @@ jobs:
chmod +x build/pythonbuild

if [ "${{ matrix.run }}" == "true" ]; then
if [ "${{ matrix.libc }}" == "musl" ]; then
sudo apt install musl-dev

# GitHub's setup-python action sets `LD_LIBRARY_PATH` which overrides `RPATH`
# as used in the musl builds.
unset LD_LIBRARY_PATH
fi
EXTRA_ARGS="--run"
fi

Expand Down
55 changes: 37 additions & 18 deletions cpython-unix/build-cpython.sh
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,8 @@ CONFIGURE_FLAGS="
${EXTRA_CONFIGURE_FLAGS}"

if [ "${CC}" = "musl-clang" ]; then
CFLAGS="${CFLAGS} -static"
CPPFLAGS="${CPPFLAGS} -static"
LDFLAGS="${LDFLAGS} -static"
PYBUILD_SHARED=0
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} --enable-shared"
PYBUILD_SHARED=1

# In order to build the _blake2 extension module with SSE3+ instructions, we need
# musl-clang to find headers that provide access to the intrinsics, as they are not
Expand Down Expand Up @@ -618,21 +616,41 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then
LIBPYTHON_SHARED_LIBRARY_BASENAME=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.so.1.0
LIBPYTHON_SHARED_LIBRARY=${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}

# If we simply set DT_RUNPATH via --set-rpath, LD_LIBRARY_PATH would be used before
# DT_RUNPATH, which could result in confusion at run-time. But if DT_NEEDED
# contains a slash, the explicit path is used.
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}

# libpython3.so isn't present in debug builds.
if [ -z "${CPYTHON_DEBUG}" ]; then
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
${ROOT}/out/python/install/lib/libpython3.so
fi

if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
if [ "${CC}" == "musl-clang" ]; then
# musl does not support $ORIGIN in DT_NEEDED, so we use RPATH instead. This could be
# problematic, i.e., we could load the shared library from the wrong location if
# `LD_LIBRARY_PATH` is set, but there's not a clear alternative at this time. The
# long term solution is probably to statically link to libpython instead.
patchelf --set-rpath "\$ORIGIN/../lib" \
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}

# libpython3.so isn't present in debug builds.
if [ -z "${CPYTHON_DEBUG}" ]; then
patchelf --set-rpath "\$ORIGIN/../lib" \
${ROOT}/out/python/install/lib/libpython3.so
fi

if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
patchelf --set-rpath "\$ORIGIN/../lib" \
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
fi
else
# If we simply set DT_RUNPATH via --set-rpath, LD_LIBRARY_PATH would be used before
# DT_RUNPATH, which could result in confusion at run-time. But if DT_NEEDED contains a
# slash, the explicit path is used.
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}

# libpython3.so isn't present in debug builds.
if [ -z "${CPYTHON_DEBUG}" ]; then
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
${ROOT}/out/python/install/lib/libpython3.so
fi

if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
patchelf --replace-needed ${LIBPYTHON_SHARED_LIBRARY_BASENAME} "\$ORIGIN/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}" \
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
fi
fi
fi
fi
Expand Down Expand Up @@ -874,6 +892,7 @@ EOF
${BUILD_PYTHON} ${ROOT}/generate_metadata.py ${ROOT}/metadata.json
cat ${ROOT}/metadata.json

# TODO: Output a dynamic library version for musl
if [ "${CC}" != "musl-clang" ]; then
objdump -T ${LIBPYTHON_SHARED_LIBRARY} | grep GLIBC_ | awk '{print $5}' | awk -F_ '{print $2}' | sort -V | tail -n 1 > ${ROOT}/glibc_version.txt
cat ${ROOT}/glibc_version.txt
Expand Down
24 changes: 24 additions & 0 deletions cpython-unix/build-libX11.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,30 @@ if [ -n "${CROSS_COMPILING}" ]; then
s390x-unknown-linux-gnu)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
x86_64-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
aarch64-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
i686-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
mips-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
mipsel-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
Comment on lines +90 to +92
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a little overzealous including all these here, but of no real consequence.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming we were previously implicitly doing this for musl?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We were normalizing the triple to gnu beforehand.

ppc64le-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
riscv64-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
s390x-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
*)
echo "cross-compiling but malloc(0) override not set; failures possible"
;;
Expand Down
13 changes: 7 additions & 6 deletions cpython-unix/build-musl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ pushd musl-${MUSL_VERSION}
# symbol dependencies on clients using an older musl version.
patch -p1 <<EOF
diff --git a/include/stdlib.h b/include/stdlib.h
index b507ca3..8259e27 100644
index b54a051f..194c2033 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -147,7 +147,6 @@ int getloadavg(double *, int);
@@ -145,7 +145,6 @@ int getloadavg(double *, int);
int clearenv(void);
#define WCOREDUMP(s) ((s) & 0x80)
#define WIFCONTINUED(s) ((s) == 0xffff)
-void *reallocarray (void *, size_t, size_t);
void qsort_r (void *, size_t, size_t, int (*)(const void *, const void *, void *), void *);
#endif

#ifdef _GNU_SOURCE
diff --git a/src/malloc/reallocarray.c b/src/malloc/reallocarray.c
deleted file mode 100644
index 4a6ebe4..0000000
index 4a6ebe46..00000000
--- a/src/malloc/reallocarray.c
+++ /dev/null
@@ -1,13 +0,0 @@
Expand All @@ -52,9 +52,10 @@ index 4a6ebe4..0000000
-}
EOF

./configure \

CFLAGS="${CFLAGS} -fPIC" CPPFLAGS="${CPPFLAGS} -fPIC" ./configure \
--prefix=/tools/host \
--disable-shared
--enable-shared

make -j `nproc`
make -j `nproc` install DESTDIR=/build/out
Expand Down
15 changes: 14 additions & 1 deletion cpython-unix/build-xextproto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,23 @@ export PKG_CONFIG_PATH=/tools/deps/share/pkgconfig
tar -xf xextproto-${XEXTPROTO_VERSION}.tar.gz
pushd xextproto-${XEXTPROTO_VERSION}

EXTRA_CONFIGURE_FLAGS=


if [ -n "${CROSS_COMPILING}" ]; then
if echo "${TARGET_TRIPLE}" | grep -q -- "-unknown-linux-musl"; then
# xextproto does not support configuration of musl targets so we pretend the target matches the
# build triple and enable cross-compilation manually
TARGET_TRIPLE="$(echo "${TARGET_TRIPLE}" | sed -e 's/-unknown-linux-musl/-unknown-linux-gnu/g')"
EXTRA_CONFIGURE_FLAGS="cross_compiling=yes"
fi
fi

CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure \
--build=${BUILD_TRIPLE} \
--host=${TARGET_TRIPLE} \
--prefix=/tools/deps
--prefix=/tools/deps \
"${EXTRA_CONFIGURE_FLAGS}"

make -j `nproc`
make -j `nproc` install DESTDIR=${ROOT}/out
15 changes: 14 additions & 1 deletion cpython-unix/build-xproto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,23 @@ export PKG_CONFIG_PATH=${TOOLS_PATH}/deps/share/pkgconfig
tar -xf xproto-${XPROTO_VERSION}.tar.gz
pushd xproto-${XPROTO_VERSION}

EXTRA_CONFIGURE_FLAGS=


if [ -n "${CROSS_COMPILING}" ]; then
if echo "${TARGET_TRIPLE}" | grep -q -- "-unknown-linux-musl"; then
# xproto does not support configuration of musl targets so we pretend the target matches the
# build triple and enable cross-compilation manually
TARGET_TRIPLE="$(echo "${TARGET_TRIPLE}" | sed -e 's/-unknown-linux-musl/-unknown-linux-gnu/g')"
EXTRA_CONFIGURE_FLAGS="cross_compiling=yes"
fi
fi

CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure \
--build=${BUILD_TRIPLE} \
--host=${TARGET_TRIPLE} \
--prefix=/tools/deps
--prefix=/tools/deps \
"${EXTRA_CONFIGURE_FLAGS}"

make -j ${NUM_CPUS}
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out
18 changes: 10 additions & 8 deletions cpython-unix/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def add_target_env(env, build_platform, target_triple, build_env):
.replace("x86_64_v3-", "x86_64-")
.replace("x86_64_v4-", "x86_64-")
# TODO should the musl target be normalized?
.replace("-unknown-linux-musl", "-unknown-linux-gnu")
# .replace("-unknown-linux-musl", "-unknown-linux-gnu")
)

# This will make x86_64_v2, etc count as cross-compiling. This is
Expand Down Expand Up @@ -506,11 +506,10 @@ def python_build_info(
)
)

if not musl:
bi["core"]["shared_lib"] = "install/lib/libpython%s%s.so.1.0" % (
version,
binary_suffix,
)
bi["core"]["shared_lib"] = "install/lib/libpython%s%s.so.1.0" % (
version,
binary_suffix,
)

if lto:
llvm_version = DOWNLOADS[clang_toolchain(platform, target_triple)][
Expand Down Expand Up @@ -835,7 +834,10 @@ def build_cpython(

if host_platform == "linux64":
if "musl" in target_triple:
crt_features.append("static")
extension_module_loading.append("shared-library")
crt_features.append("musl-dynamic")

# TODO: Add musl version
else:
extension_module_loading.append("shared-library")
crt_features.append("glibc-dynamic")
Expand Down Expand Up @@ -874,7 +876,7 @@ def build_cpython(
"python_stdlib_test_packages": sorted(STDLIB_TEST_PACKAGES),
"python_symbol_visibility": python_symbol_visibility,
"python_extension_module_loading": extension_module_loading,
"libpython_link_mode": "static" if "musl" in target_triple else "shared",
"libpython_link_mode": "shared",
"crt_features": crt_features,
"run_tests": "build/run_tests.py",
"build_info": python_build_info(
Expand Down
7 changes: 1 addition & 6 deletions pythonbuild/cpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,12 +466,7 @@ def derive_setup_local(
enabled_extensions[name]["setup_line"] = name.encode("ascii")
continue

# musl is static only. Ignore build-mode override.
if "musl" in target_triple:
section = "static"
else:
section = info.get("build-mode", "static")

section = info.get("build-mode", "static")
enabled_extensions[name]["build-mode"] = section

# Presumably this means the extension comes from the distribution's
Expand Down
7 changes: 4 additions & 3 deletions pythonbuild/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,10 @@ def container_get_archive(container, path):

new_data = io.BytesIO()

with tarfile.open(fileobj=old_data) as itf, tarfile.open(
fileobj=new_data, mode="w"
) as otf:
with (
tarfile.open(fileobj=old_data) as itf,
tarfile.open(fileobj=new_data, mode="w") as otf,
):
for member in sorted(itf.getmembers(), key=operator.attrgetter("name")):
file_data = itf.extractfile(member) if not member.linkname else None
member.mtime = DEFAULT_MTIME
Expand Down
8 changes: 4 additions & 4 deletions pythonbuild/downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@
"license_file": "LICENSE.mpdecimal.txt",
},
"musl": {
"url": "https://musl.libc.org/releases/musl-1.2.5.tar.gz",
"size": 1080786,
"sha256": "a9a118bbe84d8764da0ea0d28b3ab3fae8477fc7e4085d90102b8596fc7c75e4",
"version": "1.2.5",
"url": "https://musl.libc.org/releases/musl-1.2.2.tar.gz",
"size": 1055220,
"sha256": "9b969322012d796dc23dda27a35866034fa67d8fb67e0e2c45c913c3d43219dd",
"version": "1.2.2",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use an older version for dynamic builds, for compatibility with more systems. For example, the GitHub runners we're using did not have 1.2.5.

We retain the latest version for static builds, since compatibility isn't a concern.

},
"ncurses": {
"url": "https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.5.tar.gz",
Expand Down
27 changes: 20 additions & 7 deletions src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const ELF_ALLOWED_LIBRARIES: &[&str] = &[
"libpthread.so.0",
"librt.so.1",
"libutil.so.1",
// musl libc
"libc.so",
Copy link
Member Author

@zanieb zanieb Mar 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should make this conditional on static

];

const PE_ALLOWED_LIBRARIES: &[&str] = &[
Expand Down Expand Up @@ -918,10 +920,7 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
allowed_libraries.extend(extra.iter().map(|x| x.to_string()));
}

allowed_libraries.push(format!(
"$ORIGIN/../lib/libpython{}.so.1.0",
python_major_minor
));
allowed_libraries.push(format!("libpython{}.so.1.0", python_major_minor));
allowed_libraries.push(format!(
"$ORIGIN/../lib/libpython{}d.so.1.0",
python_major_minor
Expand All @@ -935,6 +934,14 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
python_major_minor
));

// On musl, we don't use `$ORIGIN`
if target_triple.contains("-musl") {
allowed_libraries.push(format!("libpython{}.so.1.0", python_major_minor));
allowed_libraries.push(format!("libpython{}d.so.1.0", python_major_minor));
allowed_libraries.push(format!("libpython{}t.so.1.0", python_major_minor));
allowed_libraries.push(format!("libpython{}td.so.1.0", python_major_minor));
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes from line 921 to here seem a little inconsistent, are all the changes here intentional?

Copy link
Member Author

@zanieb zanieb Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh thanks the changes around 924 are confusing? I'll revert that.

I can also probably make this bit conditional on the static build option.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Allow the _crypt extension module - and only it - to link against libcrypt,
// which is no longer universally present in Linux distros.
if let Some(filename) = path.file_name() {
Expand Down Expand Up @@ -1720,7 +1727,8 @@ fn validate_distribution(

let is_debug = dist_filename.contains("-debug-");

let is_static = triple.contains("unknown-linux-musl");
// For now, there are now static builds — this is historic
let is_static = false;

let mut tf = crate::open_distribution_archive(dist_path)?;

Expand Down Expand Up @@ -2074,12 +2082,17 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
std::fs::write(&test_file, PYTHON_VERIFICATIONS.as_bytes())?;

eprintln!(" running interpreter tests (output should follow)");
let output = duct::cmd(python_exe, [test_file.display().to_string()])
let output = duct::cmd(&python_exe, [test_file.display().to_string()])
.stdout_to_stderr()
.unchecked()
.env("TARGET_TRIPLE", &python_json.target_triple)
.env("BUILD_OPTIONS", &python_json.build_options)
.run()?;
.run()
.context(format!(
"Failed to run `{} {}`",
python_exe.display(),
test_file.display()
))?;

if !output.status.success() {
errors.push("errors running interpreter tests".to_string());
Expand Down
3 changes: 2 additions & 1 deletion src/verify_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def test_ctypes(self):
import ctypes

# pythonapi will be None on statically linked binaries.
if os.environ["TARGET_TRIPLE"].endswith("-unknown-linux-musl"):
is_static = False # TODO: Populate if we have statically linked binaries again
if is_static:
self.assertIsNone(ctypes.pythonapi)
else:
self.assertIsNotNone(ctypes.pythonapi)
Expand Down