diff --git a/README.rst b/README.rst index 388a88d7..a288ad9a 100644 --- a/README.rst +++ b/README.rst @@ -97,6 +97,14 @@ for repeatable builds. manylinux_2_34 (AlmaLinux 9 based) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Caveat: +On x86_64, RHEL 9+ derivatives are using x86-64-v2 target architecture. +While manylinux worked around that when building from sources by intercepting compiler calls to target +x86_64 instead, every library installed with dnf will most likely target the more recent x86-64-v2 which, if +grafted into a wheel, will fail to run on older hardware. There's no PEP to handle micro-architecture variants +yet when it comes to packaging or installing wheels. Auditwheel doesn't detect this either. +See https://github.com/pypa/manylinux/issues/1725 + Toolchain: GCC 14 - x86_64 image: ``quay.io/pypa/manylinux_2_34_x86_64`` diff --git a/build.sh b/build.sh index 25a720fe..2b85d6c7 100755 --- a/build.sh +++ b/build.sh @@ -47,7 +47,7 @@ elif [ "${POLICY}" == "manylinux_2_28" ]; then elif [ "${POLICY}" == "manylinux_2_34" ]; then BASEIMAGE="almalinux:9" DEVTOOLSET_ROOTPATH="/opt/rh/gcc-toolset-14/root" - PREPEND_PATH="${DEVTOOLSET_ROOTPATH}/usr/bin:" + PREPEND_PATH="/usr/local/bin:${DEVTOOLSET_ROOTPATH}/usr/bin:" LD_LIBRARY_PATH_ARG="${DEVTOOLSET_ROOTPATH}/usr/lib64:${DEVTOOLSET_ROOTPATH}/usr/lib:${DEVTOOLSET_ROOTPATH}/usr/lib64/dyninst:${DEVTOOLSET_ROOTPATH}/usr/lib/dyninst" elif [ "${POLICY}" == "musllinux_1_2" ]; then BASEIMAGE="alpine:3.20" diff --git a/docker/Dockerfile b/docker/Dockerfile index 823fc034..331af2db 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ ARG POLICY=manylinux_2_34 ARG PLATFORM=x86_64 ARG DEVTOOLSET_ROOTPATH=/opt/rh/gcc-toolset-14/root ARG LD_LIBRARY_PATH_ARG=${DEVTOOLSET_ROOTPATH}/usr/lib64:${DEVTOOLSET_ROOTPATH}/usr/lib:${DEVTOOLSET_ROOTPATH}/usr/lib64/dyninst:${DEVTOOLSET_ROOTPATH}/usr/lib/dyninst -ARG PREPEND_PATH=${DEVTOOLSET_ROOTPATH}/usr/bin: +ARG PREPEND_PATH=/usr/local/bin:${DEVTOOLSET_ROOTPATH}/usr/bin: FROM $BASEIMAGE AS runtime_base ARG POLICY diff --git a/docker/build_scripts/build-cpython.sh b/docker/build_scripts/build-cpython.sh index a913760f..d36892ba 100755 --- a/docker/build_scripts/build-cpython.sh +++ b/docker/build_scripts/build-cpython.sh @@ -73,6 +73,10 @@ fi unset _PYTHON_HOST_PLATFORM +if [ "${AUDITWHEEL_ARCH}" == "x86_64" ] && echo | gcc -S -x c -v - 2>&1 | grep 'march=x86-64-v' > /dev/null; then + export EXTRA_CFLAGS="-mtune=generic -march=x86-64" +fi + # configure with hardening options only for the interpreter & stdlib C extensions # do not change the default for user built extension (yet?) ./configure \ diff --git a/docker/build_scripts/finalize.sh b/docker/build_scripts/finalize.sh index 915f3a5a..c9f5f006 100755 --- a/docker/build_scripts/finalize.sh +++ b/docker/build_scripts/finalize.sh @@ -98,3 +98,6 @@ hardlink -c /opt/_internal # update system packages LC_ALL=C ${MY_DIR}/update-system-packages.sh + +# wrap compilers (see https://github.com/pypa/manylinux/issues/1725) +${MY_DIR}/install-gcc-wrapper.sh diff --git a/docker/build_scripts/install-gcc-wrapper.sh b/docker/build_scripts/install-gcc-wrapper.sh new file mode 100755 index 00000000..bb8a938d --- /dev/null +++ b/docker/build_scripts/install-gcc-wrapper.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Stop at any error, show all commands +set -exuo pipefail + +if [ "${AUDITWHEEL_ARCH}" != "x86_64" ]; then + exit 0 +fi + +if ! echo | gcc -S -x c -v - 2>&1 | grep 'march=x86-64-v' > /dev/null; then + exit 0 +fi + +# Get script directory +MY_DIR=$(dirname "${BASH_SOURCE[0]}") + +# Get build utilities +source "${MY_DIR}/build_utils.sh" + +# create wrapper to override default -march=x86-64-v? and replace it with -march=x86-64 +cat < /tmp/manylinux-gcc-wrapper.c +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + int has_march = 0; + int has_mtune = 0; + for (int i = 1; i < argc; ++i) { + if (!has_march && (strncmp(argv[i], "-march=", 7) == 0)) { + has_march = 1; + if (has_mtune) break; + } + else if (!has_mtune && (strncmp(argv[i], "-mtune=", 7) == 0)) { + has_mtune = 1; + if (has_march) break; + } + } + int insert = 0; + if (!has_march) { + insert += 1; + if (!has_mtune) insert += 1; + } + if (argc > (INT_MAX - insert - 1)) { + fputs("too many arguments\n", stderr); + return EXIT_FAILURE; + } + size_t argc_ = argc + insert + 1; + if (argc_ > SIZE_MAX / sizeof(char*)) { + fputs("too many arguments2\n", stderr); + return EXIT_FAILURE; + } + char** argv_ = malloc(argc_ * sizeof(char*)); + if (argv_ == NULL) { + fputs("can't allocate memory for arguments\n", stderr); + return EXIT_FAILURE; + } + char* progname = basename(argv[0]); + char argv0[128]; + int len = snprintf(argv0, sizeof(argv0), "${DEVTOOLSET_ROOTPATH}/usr/bin/%s", progname); + if ((len <= 0) || (len >= sizeof(argv0))) { + fputs("can't compute argv0\n", stderr); + return EXIT_FAILURE; + } + argv_[0] = argv0; + if (insert > 0) { + if (insert == 2) argv_[1] = "-mtune=generic"; + argv_[insert] = "-march=x86-64"; + } + for (int i = 1; i < argc; ++i) { + argv_[i + insert] = argv[i]; + } + argv_[argc_ - 1] = NULL; + if (execv(argv0, argv_) == -1) { + fprintf(stderr, "failed to start '%s'\n", argv0); + return EXIT_FAILURE; + } + return 0; +} +EOF + +gcc ${MANYLINUX_CFLAGS} -std=c11 -Os -s -Werror -o /usr/local/bin/manylinux-gcc-wrapper /tmp/manylinux-gcc-wrapper.c + +for EXE in "${DEVTOOLSET_ROOTPATH}"/usr/bin/*; do + if diff -q "${EXE}" "${DEVTOOLSET_ROOTPATH}/usr/bin/gcc"; then + LINK_NAME=/usr/local/bin/$(basename "${EXE}") + ln -s manylinux-gcc-wrapper "${LINK_NAME}" + if echo | "${LINK_NAME}" -S -x c -v - 2>&1 | grep 'march=x86-64-v' > /dev/null; then + exit 1 + fi + elif diff -q "${EXE}" "${DEVTOOLSET_ROOTPATH}/usr/bin/g++"; then + LINK_NAME=/usr/local/bin/$(basename "${EXE}") + ln -s manylinux-gcc-wrapper "${LINK_NAME}" + if echo | "${LINK_NAME}" -S -x c++ -v - 2>&1 | grep 'march=x86-64-v' > /dev/null; then + exit 1 + fi + elif diff -q "${EXE}" "${DEVTOOLSET_ROOTPATH}/usr/bin/gfortran"; then + LINK_NAME=/usr/local/bin/$(basename "${EXE}") + ln -s manylinux-gcc-wrapper "${LINK_NAME}" + if echo | "${LINK_NAME}" -S -x f77 -v - 2>&1 | grep 'march=x86-64-v' > /dev/null; then + exit 1 + fi + fi +done diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 7e1b1af8..d93ff172 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -161,5 +161,16 @@ fi # check the default shell is /bin/bash test "$SHELL" = "/bin/bash" +# https://github.com/pypa/manylinux/issues/1725 +# check the compiler does not default to x86-64-v? +if [ "${AUDITWHEEL_ARCH}" == "x86_64" ]; then + which gcc + gcc --version + if echo | gcc -S -x c -v - 2>&1 | grep 'march=x86-64-v'; then + echo "wrong target architecture" + exit 1 + fi +fi + # final report echo "run_tests successful!"