diff --git a/cpython-unix/Makefile b/cpython-unix/Makefile index 69dfe5534..25a6b2aa2 100644 --- a/cpython-unix/Makefile +++ b/cpython-unix/Makefile @@ -220,6 +220,9 @@ $(OUTDIR)/xcb-proto-$(XCB_PROTO_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEP $(OUTDIR)/xorgproto-$(XORGPROTO_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/build-xorgproto.sh $(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) xorgproto +$(OUTDIR)/linux-uapi-$(LINUX_UAPI_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/build-linux-uapi.sh + $(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) linux-uapi + $(OUTDIR)/xtrans-$(XTRANS_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/build-xtrans.sh $(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) xtrans @@ -255,6 +258,7 @@ PYTHON_DEPENDS_$(1) := \ $$(if $$(NEED_LIBEDIT),$$(OUTDIR)/libedit-$$(LIBEDIT_VERSION)-$$(PACKAGE_SUFFIX).tar) \ $$(if $$(NEED_LIBFFI_3_3),$$(OUTDIR)/libffi-3.3-$$(LIBFFI_3.3_VERSION)-$$(PACKAGE_SUFFIX).tar) \ $$(if $$(NEED_LIBFFI),$$(OUTDIR)/libffi-$$(LIBFFI_VERSION)-$$(PACKAGE_SUFFIX).tar) \ + $$(if $$(NEED_LINUX_UAPI),$$(OUTDIR)/linux-uapi-$$(LINUX_UAPI_VERSION)-$$(PACKAGE_SUFFIX).tar) \ $$(if $$(NEED_m4),$$(OUTDIR)/m4-$$(M4_VERSION)-$$(PACKAGE_SUFFIX).tar) \ $$(if $$(NEED_MPDECIMAL),$$(OUTDIR)/mpdecimal-$$(MPDECIMAL_VERSION)-$$(PACKAGE_SUFFIX).tar) \ $$(if $$(NEED_NCURSES),$$(OUTDIR)/ncurses-$$(NCURSES_VERSION)-$$(PACKAGE_SUFFIX).tar) \ diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 8224aca26..2dbf09081 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -338,11 +338,24 @@ if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_15}" ]; then patch -p1 -i "${ROOT}/patch-testinternalcapi-interpreter-extern.patch" fi +# CPython uses HAVE_GETRANDOM_SYSCALL to decide whether to include +# , but other APIs independently depend on __NR_* definitions +# from that header. Include it whenever configure finds the header. +patch -p1 -i "${ROOT}/patch-posixmodule-sys-syscall-header.patch" + # Most bits look at CFLAGS. But setup.py only looks at CPPFLAGS. # So we need to set both. CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I${TOOLS_PATH}/deps/include -I${TOOLS_PATH}/deps/include/ncursesw" LDFLAGS="${EXTRA_TARGET_LDFLAGS} -L${TOOLS_PATH}/deps/lib" +if [[ -n "${LINUX_UAPI_INCLUDE_ARCH:-}" ]]; then + if [[ ! -d "${TOOLS_PATH}/deps/linux-uapi/usr/include/${LINUX_UAPI_INCLUDE_ARCH}" ]]; then + echo "missing linux-uapi package for ${LINUX_UAPI_INCLUDE_ARCH}" + exit 1 + fi + CFLAGS="-isystem ${TOOLS_PATH}/deps/linux-uapi/usr/include/${LINUX_UAPI_INCLUDE_ARCH} -isystem ${TOOLS_PATH}/deps/linux-uapi/usr/include ${CFLAGS}" +fi + # Some target configurations use `-fvisibility=hidden`. Python's configure handles # symbol visibility properly itself. So let it do its thing. CFLAGS=${CFLAGS//-fvisibility=hidden/} @@ -1062,6 +1075,14 @@ replace_in_all("-I%s/deps/include/ncursesw" % tools_path, "") replace_in_all("-I%s/deps/include/uuid" % tools_path, "") replace_in_all("-I%s/deps/include" % tools_path, "") replace_in_all("-L%s/deps/lib" % tools_path, "") +linux_uapi_include_arch = os.environ.get("LINUX_UAPI_INCLUDE_ARCH") +if linux_uapi_include_arch: + replace_in_all( + "-isystem %s/deps/linux-uapi/usr/include/%s" + % (tools_path, linux_uapi_include_arch), + "", + ) +replace_in_all("-isystem %s/deps/linux-uapi/usr/include" % tools_path, "") # See https://github.com/python/cpython/issues/145810#issuecomment-4068139183 replace_in_all("-LModules/_hacl", "") diff --git a/cpython-unix/build-linux-uapi.sh b/cpython-unix/build-linux-uapi.sh new file mode 100755 index 000000000..1d04f0544 --- /dev/null +++ b/cpython-unix/build-linux-uapi.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +set -ex + +ROOT=$(pwd) + +export PATH=${TOOLS_PATH}/${TOOLCHAIN}/bin:${TOOLS_PATH}/host/bin:$PATH + +archives=("${ROOT}"/linux-libc-dev_*.deb) +if [[ ${#archives[@]} -ne 1 || ! -f "${archives[0]}" ]]; then + echo "expected exactly one linux-libc-dev package" + exit 1 +fi + +mkdir -p "${ROOT}/linux-uapi-package" "${ROOT}/out/tools/deps/linux-uapi" +pushd "${ROOT}/linux-uapi-package" +ar x "${archives[0]}" data.tar.xz +tar -xf data.tar.xz -C "${ROOT}/out/tools/deps/linux-uapi" +popd diff --git a/cpython-unix/build.py b/cpython-unix/build.py index eb0e6ae86..872566368 100755 --- a/cpython-unix/build.py +++ b/cpython-unix/build.py @@ -812,6 +812,10 @@ def build_cpython( "TOOLCHAIN": "clang-%s" % host_platform, } + # Use modern kernel UAPI headers without changing the target's libc. + if linux_uapi_include_arch := settings.get("linux_uapi_include_arch"): + env["LINUX_UAPI_INCLUDE_ARCH"] = linux_uapi_include_arch + # Set environment variables allowing convenient testing for Python # version ranges. for v in ("3.10", "3.11", "3.12", "3.13", "3.14", "3.15"): @@ -1139,6 +1143,7 @@ def main(): "libffi-3.3", "libffi", "libpthread-stubs", + "linux-uapi", "m4", "mpdecimal", "ncurses", diff --git a/cpython-unix/patch-posixmodule-sys-syscall-header.patch b/cpython-unix/patch-posixmodule-sys-syscall-header.patch new file mode 100644 index 000000000..76527060f --- /dev/null +++ b/cpython-unix/patch-posixmodule-sys-syscall-header.patch @@ -0,0 +1,6 @@ +diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c +--- a/Modules/posixmodule.c ++++ b/Modules/posixmodule.c +@@ -296 +296 @@ +-#ifdef HAVE_GETRANDOM_SYSCALL ++#ifdef HAVE_SYS_SYSCALL_H diff --git a/cpython-unix/targets.yml b/cpython-unix/targets.yml index d0db3e39e..b913168be 100644 --- a/cpython-unix/targets.yml +++ b/cpython-unix/targets.yml @@ -14,6 +14,11 @@ # needs # Packages required to build Python. # +# linux_uapi_include_arch +# Architecture-specific include directory in the linux-uapi package. When +# defined, the package's modern Linux UAPI headers are used to build Python. +# The linux-uapi package must also be listed in the target's needs. +# # host_cc # Path to C compiler to use when producing binaries targeting the # current build environment. @@ -112,6 +117,7 @@ aarch64-apple-darwin: openssl_target: darwin64-arm64-cc aarch64-unknown-linux-gnu: + linux_uapi_include_arch: aarch64-linux-gnu host_platforms: - linux_x86_64 - linux_aarch64 @@ -150,6 +156,7 @@ aarch64-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -171,6 +178,7 @@ aarch64-unknown-linux-gnu: # bolt_capable: true armv7-unknown-linux-gnueabi: + linux_uapi_include_arch: arm-linux-gnueabi host_platforms: - linux_x86_64 pythons_supported: @@ -199,6 +207,7 @@ armv7-unknown-linux-gnueabi: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -215,6 +224,7 @@ armv7-unknown-linux-gnueabi: openssl_target: linux-armv4 armv7-unknown-linux-gnueabihf: + linux_uapi_include_arch: arm-linux-gnueabihf host_platforms: - linux_x86_64 pythons_supported: @@ -243,6 +253,7 @@ armv7-unknown-linux-gnueabihf: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -259,6 +270,7 @@ armv7-unknown-linux-gnueabihf: openssl_target: linux-armv4 loongarch64-unknown-linux-gnu: + linux_uapi_include_arch: loongarch64-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -288,6 +300,7 @@ loongarch64-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -304,6 +317,7 @@ loongarch64-unknown-linux-gnu: openssl_target: linux64-loongarch64 mips-unknown-linux-gnu: + linux_uapi_include_arch: mips-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -332,6 +346,7 @@ mips-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -348,6 +363,7 @@ mips-unknown-linux-gnu: openssl_target: linux-mips32 mipsel-unknown-linux-gnu: + linux_uapi_include_arch: mipsel-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -376,6 +392,7 @@ mipsel-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -392,6 +409,7 @@ mipsel-unknown-linux-gnu: openssl_target: linux-mips32 ppc64le-unknown-linux-gnu: + linux_uapi_include_arch: powerpc64le-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -420,6 +438,7 @@ ppc64le-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -436,6 +455,7 @@ ppc64le-unknown-linux-gnu: openssl_target: linux-ppc64le riscv64-unknown-linux-gnu: + linux_uapi_include_arch: riscv64-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -464,6 +484,7 @@ riscv64-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -480,6 +501,7 @@ riscv64-unknown-linux-gnu: openssl_target: linux64-riscv64 s390x-unknown-linux-gnu: + linux_uapi_include_arch: s390x-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -511,6 +533,7 @@ s390x-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -583,6 +606,7 @@ x86_64-apple-darwin: openssl_target: darwin64-x86_64-cc x86_64-unknown-linux-gnu: + linux_uapi_include_arch: x86_64-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -618,6 +642,7 @@ x86_64-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -635,6 +660,7 @@ x86_64-unknown-linux-gnu: bolt_capable: true x86_64_v2-unknown-linux-gnu: + linux_uapi_include_arch: x86_64-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -671,6 +697,7 @@ x86_64_v2-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -688,6 +715,7 @@ x86_64_v2-unknown-linux-gnu: bolt_capable: true x86_64_v3-unknown-linux-gnu: + linux_uapi_include_arch: x86_64-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -724,6 +752,7 @@ x86_64_v3-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses @@ -741,6 +770,7 @@ x86_64_v3-unknown-linux-gnu: bolt_capable: true x86_64_v4-unknown-linux-gnu: + linux_uapi_include_arch: x86_64-linux-gnu host_platforms: - linux_x86_64 pythons_supported: @@ -777,6 +807,7 @@ x86_64_v4-unknown-linux-gnu: - libX11 - libXau - libxcb + - linux-uapi - m4 - mpdecimal - ncurses diff --git a/pythonbuild/disttests/__init__.py b/pythonbuild/disttests/__init__.py index 7762fda27..14f670629 100644 --- a/pythonbuild/disttests/__init__.py +++ b/pythonbuild/disttests/__init__.py @@ -352,6 +352,21 @@ def test_socket_af_vsock(self): self.assertTrue(hasattr(socket, "AF_VSOCK")) self.assertEqual(socket.AF_VSOCK, 40) + @unittest.skipUnless( + "-linux-gnu" in os.environ["TARGET_TRIPLE"], + "modern kernel UAPI headers are currently enabled for Linux GNU targets", + ) + def test_os_pidfd_open(self): + self.assertTrue(hasattr(os, "pidfd_open")) + + def test_linux_uapi_not_in_sysconfig(self): + import sysconfig + + for key, value in sysconfig.get_config_vars().items(): + if isinstance(value, str): + with self.subTest(key=key): + self.assertNotIn("linux-uapi", value) + @unittest.skipUnless(sys.platform == "linux", "Linux-specific prctl") @unittest.skipIf( "static" in os.environ["BUILD_OPTIONS"], diff --git a/pythonbuild/downloads.py b/pythonbuild/downloads.py index d592f7aa0..21d922ac5 100644 --- a/pythonbuild/downloads.py +++ b/pythonbuild/downloads.py @@ -176,6 +176,14 @@ "licenses": ["MIT"], "license_file": "LICENSE.libxcb.txt", }, + # Pre-sanitized Linux UAPI headers from Debian 13 backports. This does not + # change the glibc headers or libraries used by selected targets. + "linux-uapi": { + "url": "https://snapshot.debian.org/archive/debian/20260624T024300Z/pool/main/l/linux/linux-libc-dev_7.0.12-2~bpo13+1_all.deb", + "size": 2637888, + "sha256": "162ba55a6e6eca68b32a80e6774f8dda4e145bd68049fb2e85760ec1546bee3c", + "version": "7.0.12-2~bpo13+1", + }, # Remember to update LLVM_URL in src/release.rs whenever upgrading. "llvm-aarch64-linux": { "url": "https://github.com/indygreg/toolchain-tools/releases/download/toolchain-bootstrap%2F20260410/llvm-22.1.3+20260410-gnu_only-aarch64-unknown-linux-gnu.tar.zst",