Skip to content

Commit 20179d0

Browse files
committed
feat: add support for Python 3.13t, 3.14t, and 3.15t in CI and Docker configurations
- Updated GitLab CI and GitHub Actions workflows to include new Python versions 3.13t, 3.14t, and 3.15t in the build matrix. - Modified Dockerfiles for x86 and x86_64 architectures to support the new free-threaded Python versions, ensuring compatibility with musl. - Enhanced the psutil compatibility smoke test to skip execution for the new free-threaded builds. - Updated build scripts to handle the new Python versions and their specific configurations.
1 parent 1f2a138 commit 20179d0

15 files changed

Lines changed: 1178 additions & 12 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ jobs: {
1313
"strategy": {
1414
"matrix": {
1515
"architecture": ["x86_64", "x86"],
16-
"version": ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "2.7"],
16+
"version": ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "2.7",
17+
"3.15t", "3.14t", "3.13t"],
1718
},
1819
},
1920
"steps": [
@@ -49,8 +50,8 @@ jobs: {
4950
/opt/python/bin/python /tmp/smoke_stdlib.py",
5051
},
5152
{
52-
"name": "psutil compatibility smoke test (Py3 only)",
53-
"if": "matrix.version != '2.7'",
53+
"name": "psutil compatibility smoke test (GIL Py3 only)",
54+
"if": "matrix.version != '2.7' && !endsWith(matrix.version, 't')",
5455
"run": "docker run --rm \
5556
-v \"${{ github.workspace }}/ci/smoke_psutil.py:/tmp/smoke_psutil.py:ro\" \
5657
release-${{ github.ref_name }}:${{ matrix.version }}-${{ matrix.architecture }} \

.gitlab-ci.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,27 @@ build:
3333
# PyPI (exercises the musllinux tag hook) and run a broad surface-area
3434
# smoke test against the shipped libc / /proc / subprocess plumbing.
3535
# smoke_psutil.py uses Py3-only syntax (f-strings), so skip it on 2.7.
36+
# Skip on 2.7 (no f-strings in the script) and on free-threaded builds
37+
# (PyPI doesn't ship `cp3Nt` psutil wheels yet → sdist fallback → source
38+
# build in the debian final image would hit the glibc-headers mismatch).
3639
- |
37-
if [ "$version" != "2.7" ]; then
38-
docker run --rm \
39-
-v "${CI_PROJECT_DIR}/ci/smoke_psutil.py:/tmp/smoke_psutil.py:ro" \
40-
release-${PACKAGE_NAME}:${version}-${architecture} \
41-
sh -c '/opt/python/bin/pip install --quiet --no-cache-dir psutil && /opt/python/bin/python /tmp/smoke_psutil.py'
42-
fi
40+
case "$version" in
41+
2.7|*t) ;;
42+
*)
43+
docker run --rm \
44+
-v "${CI_PROJECT_DIR}/ci/smoke_psutil.py:/tmp/smoke_psutil.py:ro" \
45+
release-${PACKAGE_NAME}:${version}-${architecture} \
46+
sh -c '/opt/python/bin/pip install --quiet --no-cache-dir psutil && /opt/python/bin/python /tmp/smoke_psutil.py'
47+
;;
48+
esac
4349
- docker save release-${PACKAGE_NAME}:${version}-${architecture} > release-${version}-${architecture}.tar
4450
- bash ci/packing_release_tar.sh release-${version}-${architecture}.tar
4551
- gzip -9 build/release-${version}-${architecture}.tar
4652
artifacts:
4753
paths: ["build/release-${version}-${architecture}.tar.gz", "${version}-${architecture}.log"]
4854
parallel:
4955
matrix:
50-
- version: ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "2.7"]
56+
- version: ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "2.7", "3.15t", "3.14t", "3.13t"]
5157
architecture: ["x86_64", "x86"]
5258
tags: [shell]
5359
rules:
@@ -80,7 +86,7 @@ upload:
8086
"${PACKAGE_REGISTRY_URL}${PACKAGE_NAME}/${PACKAGE_VERSION}/release-${version}-${architecture}.tar.gz?${GET_FILE_ATTR_MAGIC}" >> ${version}-${architecture}-result.txt
8187
parallel:
8288
matrix:
83-
- version: ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "2.7"]
89+
- version: ["3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "2.7", "3.15t", "3.14t", "3.13t"]
8490
architecture: ["x86_64", "x86"]
8591
artifacts:
8692
paths: ["*-result.txt"]

3.13t/x86/Dockerfile

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
FROM --platform=linux/386 alpine:3.22 AS base_builder
2+
3+
ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
4+
LANG=C.UTF-8 \
5+
IS_32BIT=1 \
6+
ZLIB_VERSION=1.3.2 \
7+
FFI_VERSION=3.5.2 \
8+
EXPAT_VERSION=2.7.5 \
9+
GDBM_VERSION=1.26 \
10+
LZMA_VERSION=4.32.7 \
11+
GETTEXT_VERSION=1.0 \
12+
NCURSES_VERSION=6.6 \
13+
OPENSSL_VERSION=3.5.6 \
14+
READLINE_VERSION=8.3 \
15+
TCL_VERSION=8.6.17 \
16+
XZ_VERSION=5.8.3 \
17+
BZIP2_VERSION=1.0.8 \
18+
UUID_VERSION=2.40.4 \
19+
SQLITE_VERSION=3530000 \
20+
SQLITE_VERSION_LITERIAL=3.53.0 \
21+
SQLITE_YEAR=2026
22+
WORKDIR /src
23+
RUN set -eux && apk add --no-cache ca-certificates tzdata && \
24+
apk add --no-cache --virtual .build-deps tar xz bluez-dev dpkg-dev dpkg findutils gcc file g++ \
25+
libc-dev libnsl-dev libtirpc-dev linux-headers make pax-utils tk tk-dev util-linux-dev wget \
26+
bison flex gawk patch
27+
28+
# # =========== musl ===========
29+
FROM --platform=linux/386 debian:bookworm AS musl_builder
30+
ARG DEBIAN_FRONTEND=noninteractive
31+
ENV TZ=Asia/Taipei \
32+
IS_32BIT=1
33+
34+
RUN apt-get update -y && apt-get upgrade -y && \
35+
apt-get install -y build-essential wget git file tar xz-utils dpkg-dev libnsl-dev libtirpc-dev uuid-dev patch
36+
37+
WORKDIR /src
38+
COPY ["common/build/deplib/build_musl.sh", "3.13t/x86/deplib/config.mak", "."]
39+
RUN set -eux && ./build_musl.sh
40+
41+
# =========== libz ===========
42+
FROM base_builder AS libz_builder
43+
COPY ["common/build/deplib/build_zlib.sh", "."]
44+
RUN set -eux && ./build_zlib.sh
45+
46+
# =========== ffi ===========
47+
FROM libz_builder AS ffi_builder
48+
COPY ["common/build/deplib/build_ffi.sh", "."]
49+
RUN set -eux && ./build_ffi.sh
50+
51+
# =========== expat ===========
52+
FROM ffi_builder AS expat_builder
53+
COPY ["common/build/deplib/build_expat.sh", "."]
54+
RUN set -eux && ./build_expat.sh
55+
56+
# =========== gdbm ===========
57+
FROM expat_builder AS gdbm_builder
58+
COPY ["common/build/deplib/build_gdbm.sh", "."]
59+
RUN set -eux && ./build_gdbm.sh
60+
61+
# =========== lzma ===========
62+
FROM gdbm_builder AS lzma_builder
63+
COPY ["common/build/deplib/build_lzma.sh", "."]
64+
RUN set -eux && ./build_lzma.sh
65+
66+
# =========== gettext ===========
67+
FROM lzma_builder AS gettext_builder
68+
COPY ["common/build/deplib/build_gettext.sh", "."]
69+
RUN set -eux && ./build_gettext.sh
70+
71+
# =========== ncurses ===========
72+
FROM gettext_builder AS ncurses_builder
73+
COPY ["common/build/deplib/build_ncurses.sh", "."]
74+
RUN set -eux && ./build_ncurses.sh
75+
76+
# =========== openssl ===========
77+
FROM ncurses_builder AS openssl_builder
78+
COPY ["common/build/deplib/build_openssl.sh", "."]
79+
RUN set -eux && ./build_openssl.sh
80+
81+
# =========== readline ===========
82+
FROM openssl_builder AS readline_builder
83+
COPY ["common/build/deplib/build_readline.sh", "common/patches/common/ncurses/fix-ncurses-underlinking.patch", "."]
84+
RUN set -eux && ./build_readline.sh
85+
86+
# =========== tcl ===========
87+
FROM readline_builder AS tcl_builder
88+
COPY ["common/build/deplib/build_tcl.sh", "."]
89+
RUN set -eux && ./build_tcl.sh
90+
91+
# =========== xz ===========
92+
FROM tcl_builder AS xz_builder
93+
COPY ["common/build/deplib/build_xz.sh", "."]
94+
RUN set -eux && ./build_xz.sh
95+
96+
# =========== bzip2 ===========
97+
FROM xz_builder AS bzip2_builder
98+
COPY ["common/build/deplib/build_bzip2.sh", "."]
99+
RUN set -eux && ./build_bzip2.sh
100+
101+
# =========== uuid ===========
102+
FROM bzip2_builder AS uuid_builder
103+
COPY ["common/build/deplib/build_uuid.sh", "."]
104+
RUN set -eux && ./build_uuid.sh
105+
106+
# =========== sqlite3 ===========
107+
FROM uuid_builder AS sqlite3_builder
108+
COPY ["common/build/deplib/build_sqlite3.sh", "."]
109+
RUN set -eux && ./build_sqlite3.sh
110+
111+
# =========== Python ===========
112+
FROM sqlite3_builder AS python_builder
113+
ENV PYTHON_VERSION=3.13.13 \
114+
DISABLE_GIL=1 \
115+
PYTHON_PIP_VERSION=26.0.1 \
116+
PYTHON_SETUPTOOLS_VERSION=82.0.1
117+
118+
COPY ["common/build/deplib/build_python.sh", "common/build/deplib/install_pip.sh", "."]
119+
RUN set -eux && ./build_python.sh && ./install_pip.sh
120+
121+
# =========== launcher (static C binary, replaces shell wrappers) ===========
122+
FROM musl_builder AS launcher_builder
123+
ENV MUSL_ARCH=i386
124+
WORKDIR /src
125+
COPY ["common/build/wrappers/launcher.c", "./launcher.c"]
126+
# config.mak carries `MUSL_VER = X.Y.Z`; drop the patch component and bake
127+
# `X.Y` into the launcher as MUSL_VERSION so the site hook can hand pip the
128+
# matching `musllinux_X_Y_*` wheel tag without subprocess'ing ld.so every
129+
# Python startup.
130+
RUN set -eux && \
131+
MUSL_VER=$(awk -F'=' '/^MUSL_VER[[:space:]]*=/ { gsub(/[[:space:]]/, "", $2); print $2 }' /src/config.mak) && \
132+
MUSL_TAG=$(echo "$MUSL_VER" | cut -d. -f1,2) && \
133+
test -n "$MUSL_TAG" && \
134+
/opt/musl/bin/${MUSL_ARCH}-linux-musl-gcc -static -Os -s \
135+
-DMUSL_ARCH="\"${MUSL_ARCH}\"" \
136+
-DMUSL_VERSION="\"${MUSL_TAG}\"" \
137+
launcher.c -o launcher && \
138+
file launcher
139+
140+
# =========== patch_stage ===========
141+
FROM --platform=linux/386 debian:latest AS patch_stage
142+
ARG DEBIAN_FRONTEND=noninteractive
143+
ENV TZ=Asia/Taipei \
144+
IS_32BIT=1
145+
146+
RUN apt-get update -y && apt-get install -y patchelf file
147+
148+
COPY --from=python_builder /opt/shared_libraries /opt/shared_libraries
149+
COPY --from=python_builder /opt/python /opt/python
150+
151+
COPY ["common/build/wrappers/packing-initializer", "."]
152+
RUN ./packing-initializer && rm -rf packing-initializer
153+
154+
COPY --from=launcher_builder /src/launcher /tmp/launcher
155+
RUN install -m 0755 /tmp/launcher /opt/python/bin/python3 && \
156+
install -m 0755 /tmp/launcher /opt/python/bin/pip3 && \
157+
rm /tmp/launcher && \
158+
cd /opt/python/bin && \
159+
ln -sf python3 python && \
160+
ln -sf pip3 pip
161+
162+
# Copy shipped musl runtime and rewrite RPATH on Python's bundled ELFs so
163+
# $ORIGIN-relative lookups resolve inside the installed tree. No .interp
164+
# patching is needed — the static launcher invokes ld-musl.so directly.
165+
COPY --from=musl_builder /opt/musl /opt/musl
166+
COPY ["common/build/wrappers/rpath-patcher.sh", "."]
167+
RUN set -eux && ./rpath-patcher.sh
168+
169+
# Site-level hook that tells pip/packaging the shipped libc is musl so wheel
170+
# resolution picks `musllinux_*` wheels instead of glibc-built manylinux
171+
# wheels. Runs on every Python startup via the .pth but is a no-op unless
172+
# the launcher has set _STANDALONE_PYTHON_MUSL_VERSION in the env.
173+
COPY ["common/build/wrappers/standalone_python_musl.py", \
174+
"common/build/wrappers/standalone_python_musl.pth", \
175+
"/opt/python/lib/python3.13t/site-packages/"]
176+
177+
# =========== final ===========
178+
FROM --platform=linux/386 debian:latest AS final
179+
ARG DEBIAN_FRONTEND=noninteractive
180+
ENV TZ=Asia/Taipei
181+
182+
COPY --from=patch_stage /opt/python /opt/python

3.13t/x86/deplib/config.mak

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
TARGET = i386-linux-musl
2+
GCC_VER = 15.1.0
3+
MUSL_VER = 1.2.5
4+
COMMON_CONFIG += CFLAGS="-g0 -O3" CXXFLAGS="-g0 -O3" LDFLAGS="-s"
5+
GCC_CONFIG += --enable-default-pie --enable-static-pie

0 commit comments

Comments
 (0)