From 3f727da93e31a74d364e370a4143b3eed30b075c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 25 Jul 2025 03:41:26 +0200 Subject: [PATCH 1/7] Add Python 3.14 + drop Python 3.11 --- .github/workflows/builder.yml | 4 +- README.md | 13 +- python/3.14/Dockerfile | 133 ++++++++++++++++++ python/3.14/build.yaml | 18 +++ ...ze-state-transfer-between-concurrent.patch | 89 ++++++++++++ python/3.14/musl-find_library.patch | 45 ++++++ 6 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 python/3.14/Dockerfile create mode 100644 python/3.14/build.yaml create mode 100644 python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch create mode 100644 python/3.14/musl-find_library.patch diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index d7a9f4be..52addd75 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -12,7 +12,7 @@ env: DEBIAN_LATEST: "trixie" UBUNTU_LATEST: "24.04" RASPBIAN_LATEST: "trixie" - PYTHON_LATEST: "3.13" + PYTHON_LATEST: "3.14" jobs: init: @@ -251,7 +251,7 @@ jobs: matrix: arch: ${{ fromJson(needs.init.outputs.architectures_alpine) }} version: ["3.20", "3.21", "3.22"] - python: ["3.11", "3.12", "3.13"] + python: ["3.12", "3.13", "3.14"] steps: - name: Checkout the repository uses: actions/checkout@v5.0.0 diff --git a/README.md b/README.md index 0b35894d..dcb82756 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,16 @@ We support on our platforms jemalloc. On the application which you want to enabl We support the latest 3 release with the latest 3 Alpine version. +> [!NOTE] +> Python 3.14 is build from the release candidate. It's only recommended for wheel builds at the moment. + | Image | OS | Python versions | Tags | latest | |-------|----|-----------------|------|--------| -| armhf-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| armv7-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| aarch64-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| amd64-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| i386-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | +| armhf-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | +| armv7-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | +| aarch64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | +| amd64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | +| i386-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | ## Others diff --git a/python/3.14/Dockerfile b/python/3.14/Dockerfile new file mode 100644 index 00000000..596c0da1 --- /dev/null +++ b/python/3.14/Dockerfile @@ -0,0 +1,133 @@ +ARG BUILD_FROM +FROM $BUILD_FROM + +ARG \ + PYTHON_VERSION \ + PIP_VERSION \ + CERT_IDENTITY \ + CERT_OIDC_ISSUER \ + QEMU_CPU + +# ensure local python is preferred over distribution python +ENV PATH=/usr/local/bin:$PATH + +# Set shell +SHELL ["/bin/ash", "-o", "pipefail", "-c"] + +COPY *.patch /usr/src/ +RUN set -ex \ + && export PYTHON_VERSION=${PYTHON_VERSION} \ + && apk add --no-cache --virtual .fetch-deps \ + openssl \ + tar \ + xz \ + && apk add --no-cache --virtual .cosign cosign \ + --repository="https://dl-cdn.alpinelinux.org/alpine/v3.21/community" \ + \ + && curl -L -o python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \ + && curl -L -o python.tar.xz.sigstore "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.sigstore" \ + && cosign verify-blob \ + --new-bundle-format \ + --certificate-identity "${CERT_IDENTITY}" \ + --certificate-oidc-issuer "${CERT_OIDC_ISSUER}" \ + --bundle python.tar.xz.sigstore \ + python.tar.xz \ + && mkdir -p /usr/src/python \ + && tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \ + && rm python.tar.xz \ + \ + && apk add --no-cache --virtual .build-deps \ + patch \ + bzip2-dev \ + coreutils \ + dpkg-dev dpkg \ + expat-dev \ + findutils \ + build-base \ + gdbm-dev \ + libc-dev \ + libffi-dev \ + libnsl-dev \ + openssl \ + openssl-dev \ + libtirpc-dev \ + linux-headers \ + make \ + mpdecimal-dev \ + ncurses-dev \ + pax-utils \ + readline-dev \ + sqlite-dev \ + tcl-dev \ + tk \ + tk-dev \ + xz-dev \ + zlib-dev \ + bluez-dev \ + # add build deps before removing fetch deps in case there's overlap + && apk del .fetch-deps .cosign \ + \ + && for i in /usr/src/*.patch; do \ + patch -d /usr/src/python -p 1 < "${i}"; done \ + && cd /usr/src/python \ + && gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \ + && ./configure \ + --build="$gnuArch" \ + --enable-loadable-sqlite-extensions \ + --enable-optimizations \ + --enable-option-checking=fatal \ + --enable-shared \ + --with-lto \ + --with-system-libmpdec \ + --with-system-expat \ + --without-ensurepip \ + --without-static-libpython \ + && make -j "$(nproc)" \ + LDFLAGS="-Wl,--strip-all" \ + CFLAGS="-fno-semantic-interposition -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free" \ +# set thread stack size to 1MB so we don't segfault before we hit sys.getrecursionlimit() +# https://github.com/alpinelinux/aports/commit/2026e1259422d4e0cf92391ca2d3844356c649d0 + EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" \ + && make install \ + \ + && find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + | xargs -rt apk add --no-cache --virtual .python-rundeps \ + && apk del .build-deps \ + \ + && find /usr/local -depth \ + \( \ + -type d -a \( -name test -o -name tests \) \ + \) -exec rm -rf '{}' + \ + && rm -rf /usr/src/python \ + && rm -f /usr/src/*.patch + +# make some useful symlinks that are expected to exist +RUN cd /usr/local/bin \ + && ln -s idle3 idle \ + && ln -s pydoc3 pydoc \ + && ln -s python3 python \ + && ln -s python3-config python-config + +RUN set -ex; \ + \ + apk add --no-cache --virtual .fetch-deps openssl; \ + \ + curl -L -o get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ + \ + apk del .fetch-deps; \ + \ + python get-pip.py \ + --disable-pip-version-check \ + --no-cache-dir \ + pip==${PIP_VERSION} \ + ; \ + pip --version; \ + \ + find /usr/local -depth \ + \( \ + -type d -a \( -name test -o -name tests \) \ + \) -exec rm -rf '{}' +; \ + rm -f get-pip.py diff --git a/python/3.14/build.yaml b/python/3.14/build.yaml new file mode 100644 index 00000000..733c843d --- /dev/null +++ b/python/3.14/build.yaml @@ -0,0 +1,18 @@ +image: ghcr.io/home-assistant/{arch}-base-python +build_from: + aarch64: "ghcr.io/home-assistant/aarch64-base:" + armv7: "ghcr.io/home-assistant/armv7-base:" + armhf: "ghcr.io/home-assistant/armhf-base:" + amd64: "ghcr.io/home-assistant/amd64-base:" + i386: "ghcr.io/home-assistant/i386-base:" +cosign: + base_identity: https://github.com/home-assistant/docker-base/.* + identity: https://github.com/home-assistant/docker-base/.* +args: + PYTHON_VERSION: "3.14.0rc3" + PIP_VERSION: "25.2" + CERT_IDENTITY: hugo@python.org + CERT_OIDC_ISSUER: https://github.com/login/oauth +labels: + io.hass.base.name: python + org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch b/python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch new file mode 100644 index 00000000..9e4a6c6b --- /dev/null +++ b/python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch @@ -0,0 +1,89 @@ +From 53da1e8c8ccbe3161ebc42e8b8b7ebd1ab70e05b Mon Sep 17 00:00:00 2001 +From: "J. Nick Koston" +Date: Sun, 18 May 2025 11:56:20 -0400 +Subject: [PATCH] gh-134173: optimize state transfer between + `concurrent.futures.Future` and `asyncio.Future` (#134174) + +Co-authored-by: Kumar Aditya +--- + Lib/asyncio/futures.py | 17 +++--- + Lib/concurrent/futures/_base.py | 27 +++++++++ + Lib/test/test_asyncio/test_futures.py | 58 +++++++++++++++++-- + .../test_concurrent_futures/test_future.py | 57 ++++++++++++++++++ + ...-05-18-07-25-15.gh-issue-134173.53oOoF.rst | 3 + + 5 files changed, 148 insertions(+), 14 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst + +diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py +index d1df6707302..6bd00a64478 100644 +--- a/Lib/asyncio/futures.py ++++ b/Lib/asyncio/futures.py +@@ -351,22 +351,19 @@ def _set_concurrent_future_state(concurrent, source): + def _copy_future_state(source, dest): + """Internal helper to copy state from another Future. + +- The other Future may be a concurrent.futures.Future. ++ The other Future must be a concurrent.futures.Future. + """ +- assert source.done() + if dest.cancelled(): + return + assert not dest.done() +- if source.cancelled(): ++ done, cancelled, result, exception = source._get_snapshot() ++ assert done ++ if cancelled: + dest.cancel() ++ elif exception is not None: ++ dest.set_exception(_convert_future_exc(exception)) + else: +- exception = source.exception() +- if exception is not None: +- dest.set_exception(_convert_future_exc(exception)) +- else: +- result = source.result() +- dest.set_result(result) +- ++ dest.set_result(result) + + def _chain_future(source, destination): + """Chain two futures so that when one completes, so does the other. +diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py +index d98b1ebdd58..f506ce68aea 100644 +--- a/Lib/concurrent/futures/_base.py ++++ b/Lib/concurrent/futures/_base.py +@@ -558,6 +558,33 @@ def set_exception(self, exception): + self._condition.notify_all() + self._invoke_callbacks() + ++ def _get_snapshot(self): ++ """Get a snapshot of the future's current state. ++ ++ This method atomically retrieves the state in one lock acquisition, ++ which is significantly faster than multiple method calls. ++ ++ Returns: ++ Tuple of (done, cancelled, result, exception) ++ - done: True if the future is done (cancelled or finished) ++ - cancelled: True if the future was cancelled ++ - result: The result if available and not cancelled ++ - exception: The exception if available and not cancelled ++ """ ++ # Fast path: check if already finished without lock ++ if self._state == FINISHED: ++ return True, False, self._result, self._exception ++ ++ # Need lock for other states since they can change ++ with self._condition: ++ # We have to check the state again after acquiring the lock ++ # because it may have changed in the meantime. ++ if self._state == FINISHED: ++ return True, False, self._result, self._exception ++ if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED}: ++ return True, True, None, None ++ return False, False, None, None ++ + __class_getitem__ = classmethod(types.GenericAlias) + + class Executor(object): + diff --git a/python/3.14/musl-find_library.patch b/python/3.14/musl-find_library.patch new file mode 100644 index 00000000..7899abb7 --- /dev/null +++ b/python/3.14/musl-find_library.patch @@ -0,0 +1,45 @@ +diff -ru Python-2.7.12.orig/Lib/ctypes/util.py Python-2.7.12/Lib/ctypes/util.py +--- Python-2.7.12.orig/Lib/ctypes/util.py 2016-06-26 00:49:30.000000000 +0300 ++++ Python-2.7.12/Lib/ctypes/util.py 2016-11-03 16:05:46.954665040 +0200 +@@ -204,6 +204,41 @@ + def find_library(name, is64 = False): + return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) + ++ elif True: ++ ++ # Patched for Alpine Linux / musl - search manually system paths ++ def _is_elf(filepath): ++ try: ++ with open(filepath, 'rb') as fh: ++ return fh.read(4) == b'\x7fELF' ++ except: ++ return False ++ ++ def find_library(name): ++ from glob import glob ++ # absolute name? ++ if os.path.isabs(name): ++ return name ++ # special case for libm, libcrypt and libpthread and musl ++ if name in ['m', 'crypt', 'pthread']: ++ name = 'c' ++ elif name in ['libm.so', 'libcrypt.so', 'libpthread.so']: ++ name = 'libc.so' ++ # search in standard locations (musl order) ++ paths = ['/lib', '/usr/local/lib', '/usr/lib'] ++ if 'LD_LIBRARY_PATH' in os.environ: ++ paths = os.environ['LD_LIBRARY_PATH'].split(':') + paths ++ for d in paths: ++ f = os.path.join(d, name) ++ if _is_elf(f): ++ return os.path.basename(f) ++ ++ prefix = os.path.join(d, 'lib'+name) ++ for suffix in ['.so', '.so.*']: ++ for f in glob('{0}{1}'.format(prefix, suffix)): ++ if _is_elf(f): ++ return os.path.basename(f) ++ + else: + + def _findSoname_ldconfig(name): From 1c878c3601c0aa23b365aaca27a34951202ed3f7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:46:39 +0200 Subject: [PATCH 2/7] Remove Python 3.11 files --- python/3.11/Dockerfile | 134 ------------------ python/3.11/arm-alignment.patch | 17 --- python/3.11/asynctio_unix_events.patch | 16 --- python/3.11/build.yaml | 18 --- ...o-optimize-to-add-remove-readers-and.patch | 113 --------------- ...e-_BaseSelectorImpl._key_from_fd-wit.patch | 73 ---------- ...ors-add-get-method-to-_SelectorMappi.patch | 43 ------ ...ors-optimize-EpollSelector.select-10.patch | 57 -------- ...ze-_run_once-for-many-iterations-of-.patch | 31 ---- ...o-Reduce-overhead-to-connect-sockets.patch | 39 ----- python/3.11/musl-find_library.patch | 45 ------ 11 files changed, 586 deletions(-) delete mode 100644 python/3.11/Dockerfile delete mode 100644 python/3.11/arm-alignment.patch delete mode 100644 python/3.11/asynctio_unix_events.patch delete mode 100644 python/3.11/build.yaml delete mode 100644 python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch delete mode 100644 python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch delete mode 100644 python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch delete mode 100644 python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch delete mode 100644 python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch delete mode 100644 python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch delete mode 100644 python/3.11/musl-find_library.patch diff --git a/python/3.11/Dockerfile b/python/3.11/Dockerfile deleted file mode 100644 index e7be73ab..00000000 --- a/python/3.11/Dockerfile +++ /dev/null @@ -1,134 +0,0 @@ -ARG BUILD_FROM -FROM $BUILD_FROM - -ARG \ - PYTHON_VERSION \ - PIP_VERSION \ - CERT_IDENTITY \ - CERT_OIDC_ISSUER \ - QEMU_CPU - -# ensure local python is preferred over distribution python -ENV PATH=/usr/local/bin:$PATH - -# Set shell -SHELL ["/bin/ash", "-o", "pipefail", "-c"] - -COPY *.patch /usr/src/ -RUN set -ex \ - && export PYTHON_VERSION=${PYTHON_VERSION} \ - && apk add --no-cache --virtual .fetch-deps \ - openssl \ - tar \ - xz \ - && apk add --no-cache --virtual .cosign cosign \ - --repository="https://dl-cdn.alpinelinux.org/alpine/v3.21/community" \ - \ - && curl -L -o python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \ - && curl -L -o python.tar.xz.sigstore "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.sigstore" \ - && cosign verify-blob \ - --new-bundle-format \ - --certificate-identity "${CERT_IDENTITY}" \ - --certificate-oidc-issuer "${CERT_OIDC_ISSUER}" \ - --bundle python.tar.xz.sigstore \ - python.tar.xz \ - && mkdir -p /usr/src/python \ - && tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \ - && rm python.tar.xz \ - \ - && apk add --no-cache --virtual .build-deps \ - patch \ - bzip2-dev \ - coreutils \ - dpkg-dev dpkg \ - expat-dev \ - findutils \ - build-base \ - gdbm-dev \ - libc-dev \ - libffi-dev \ - libnsl-dev \ - openssl \ - openssl-dev \ - libtirpc-dev \ - linux-headers \ - make \ - mpdecimal-dev \ - ncurses-dev \ - pax-utils \ - readline-dev \ - sqlite-dev \ - tcl-dev \ - tk \ - tk-dev \ - xz-dev \ - zlib-dev \ - bluez-dev \ - # add build deps before removing fetch deps in case there's overlap - && apk del .fetch-deps .cosign \ - \ - && for i in /usr/src/*.patch; do \ - patch -d /usr/src/python -p 1 < "${i}"; done \ - && cd /usr/src/python \ - && gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \ - && ./configure \ - --build="$gnuArch" \ - --enable-loadable-sqlite-extensions \ - --enable-optimizations \ - --enable-option-checking=fatal \ - --enable-shared \ - --with-lto \ - --with-system-libmpdec \ - --with-system-expat \ - --with-system-ffi \ - --without-ensurepip \ - --without-static-libpython \ - && make -j "$(nproc)" \ - LDFLAGS="-Wl,--strip-all" \ - CFLAGS="-fno-semantic-interposition -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free" \ -# set thread stack size to 1MB so we don't segfault before we hit sys.getrecursionlimit() -# https://github.com/alpinelinux/aports/commit/2026e1259422d4e0cf92391ca2d3844356c649d0 - EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" \ - && make install \ - \ - && find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ - | tr ',' '\n' \ - | sort -u \ - | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ - | xargs -rt apk add --no-cache --virtual .python-rundeps \ - && apk del .build-deps \ - \ - && find /usr/local -depth \ - \( \ - -type d -a \( -name test -o -name tests \) \ - \) -exec rm -rf '{}' + \ - && rm -rf /usr/src/python \ - && rm -f /usr/src/*.patch - -# make some useful symlinks that are expected to exist -RUN cd /usr/local/bin \ - && ln -s idle3 idle \ - && ln -s pydoc3 pydoc \ - && ln -s python3 python \ - && ln -s python3-config python-config - -RUN set -ex; \ - \ - apk add --no-cache --virtual .fetch-deps openssl; \ - \ - curl -L -o get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ - \ - apk del .fetch-deps; \ - \ - python get-pip.py \ - --disable-pip-version-check \ - --no-cache-dir \ - pip==${PIP_VERSION} \ - ; \ - pip --version; \ - \ - find /usr/local -depth \ - \( \ - -type d -a \( -name test -o -name tests \) \ - \) -exec rm -rf '{}' +; \ - rm -f get-pip.py diff --git a/python/3.11/arm-alignment.patch b/python/3.11/arm-alignment.patch deleted file mode 100644 index a7a4b394..00000000 --- a/python/3.11/arm-alignment.patch +++ /dev/null @@ -1,17 +0,0 @@ -Author: Dave Jones -Description: Use aligned access for _sha3 module on ARM. ---- a/Modules/_sha3/sha3module.c -+++ b/Modules/_sha3/sha3module.c -@@ -64,6 +64,12 @@ - #define PLATFORM_BYTE_ORDER IS_BIG_ENDIAN - #endif - -+/* Bus error on 32-bit ARM due to un-aligned memory accesses; 64-bit ARM -+ * doesn't complain but un-aligned memory accesses are sub-optimal */ -+#if defined(__arm__) || defined(__aarch64__) -+#define NO_MISALIGNED_ACCESSES -+#endif -+ - /* mangle names */ - #define KeccakF1600_FastLoop_Absorb _PySHA3_KeccakF1600_FastLoop_Absorb - #define Keccak_HashFinal _PySHA3_Keccak_HashFinal diff --git a/python/3.11/asynctio_unix_events.patch b/python/3.11/asynctio_unix_events.patch deleted file mode 100644 index 626198dd..00000000 --- a/python/3.11/asynctio_unix_events.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py -index f34a5b4b44..b1d0f1e61e 100644 ---- a/Lib/asyncio/unix_events.py -+++ b/Lib/asyncio/unix_events.py -@@ -369,6 +369,11 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): - fut.set_result(total_sent) - return - -+ # On 32-bit architectures truncate to 1GiB to avoid OverflowError, -+ # see bpo-38319. -+ if sys.maxsize < 2 ** 32: -+ blocksize = min(blocksize, 2 ** 30) -+ - try: - sent = os.sendfile(fd, fileno, offset, blocksize) - except (BlockingIOError, InterruptedError): \ No newline at end of file diff --git a/python/3.11/build.yaml b/python/3.11/build.yaml deleted file mode 100644 index 8cdea411..00000000 --- a/python/3.11/build.yaml +++ /dev/null @@ -1,18 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base-python -build_from: - aarch64: "ghcr.io/home-assistant/aarch64-base:" - armv7: "ghcr.io/home-assistant/armv7-base:" - armhf: "ghcr.io/home-assistant/armhf-base:" - amd64: "ghcr.io/home-assistant/amd64-base:" - i386: "ghcr.io/home-assistant/i386-base:" -cosign: - base_identity: https://github.com/home-assistant/docker-base/.* - identity: https://github.com/home-assistant/docker-base/.* -args: - PYTHON_VERSION: "3.11.13" - PIP_VERSION: "25.2" - CERT_IDENTITY: pablogsal@python.org - CERT_OIDC_ISSUER: https://accounts.google.com -labels: - io.hass.base.name: python - org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch b/python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch deleted file mode 100644 index 29f94f64..00000000 --- a/python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch +++ /dev/null @@ -1,113 +0,0 @@ -From b7dc795dfd175c0d25a479cfaf94a13c368a5a7b Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Sat, 22 Jul 2023 16:07:40 -0500 -Subject: [PATCH] gh-106527: asyncio: optimize to add/remove readers and - writers (#106528) - ---- - Lib/asyncio/selector_events.py | 64 +++++++++---------- - Lib/test/test_asyncio/test_selector_events.py | 36 +++++------ - 2 files changed, 47 insertions(+), 53 deletions(-) - -diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py -index f895750e3c..d521b4e2e2 100644 ---- a/Lib/asyncio/selector_events.py -+++ b/Lib/asyncio/selector_events.py -@@ -274,9 +274,8 @@ def _ensure_fd_no_transport(self, fd): - def _add_reader(self, fd, callback, *args): - self._check_closed() - handle = events.Handle(callback, args, self, None) -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - self._selector.register(fd, selectors.EVENT_READ, - (handle, None)) - else: -@@ -290,30 +289,27 @@ def _add_reader(self, fd, callback, *args): - def _remove_reader(self, fd): - if self.is_closed(): - return False -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - return False -+ mask, (reader, writer) = key.events, key.data -+ mask &= ~selectors.EVENT_READ -+ if not mask: -+ self._selector.unregister(fd) - else: -- mask, (reader, writer) = key.events, key.data -- mask &= ~selectors.EVENT_READ -- if not mask: -- self._selector.unregister(fd) -- else: -- self._selector.modify(fd, mask, (None, writer)) -+ self._selector.modify(fd, mask, (None, writer)) - -- if reader is not None: -- reader.cancel() -- return True -- else: -- return False -+ if reader is not None: -+ reader.cancel() -+ return True -+ else: -+ return False - - def _add_writer(self, fd, callback, *args): - self._check_closed() - handle = events.Handle(callback, args, self, None) -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - self._selector.register(fd, selectors.EVENT_WRITE, - (None, handle)) - else: -@@ -328,24 +324,22 @@ def _remove_writer(self, fd): - """Remove a writer callback.""" - if self.is_closed(): - return False -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - return False -+ mask, (reader, writer) = key.events, key.data -+ # Remove both writer and connector. -+ mask &= ~selectors.EVENT_WRITE -+ if not mask: -+ self._selector.unregister(fd) - else: -- mask, (reader, writer) = key.events, key.data -- # Remove both writer and connector. -- mask &= ~selectors.EVENT_WRITE -- if not mask: -- self._selector.unregister(fd) -- else: -- self._selector.modify(fd, mask, (reader, None)) -+ self._selector.modify(fd, mask, (reader, None)) - -- if writer is not None: -- writer.cancel() -- return True -- else: -- return False -+ if writer is not None: -+ writer.cancel() -+ return True -+ else: -+ return False - - def add_reader(self, fd, callback, *args): - """Add a reader callback.""" --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch b/python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch deleted file mode 100644 index e742d485..00000000 --- a/python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch +++ /dev/null @@ -1,73 +0,0 @@ -From aeef8591e41b68341af308e56a744396c66879cc Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Fri, 14 Jul 2023 08:46:30 -1000 -Subject: [PATCH] gh-106554: replace `_BaseSelectorImpl._key_from_fd` with - `dict.get` (#106555) - ---- - Lib/selectors.py | 21 ++++----------------- - 1 file changed, 4 insertions(+), 17 deletions(-) - -diff --git a/Lib/selectors.py b/Lib/selectors.py -index dfcc125dcd..6d82935445 100644 ---- a/Lib/selectors.py -+++ b/Lib/selectors.py -@@ -276,19 +276,6 @@ def close(self): - def get_map(self): - return self._map - -- def _key_from_fd(self, fd): -- """Return the key associated to a given file descriptor. -- -- Parameters: -- fd -- file descriptor -- -- Returns: -- corresponding key, or None if not found -- """ -- try: -- return self._fd_to_key[fd] -- except KeyError: -- return None - - - class SelectSelector(_BaseSelectorImpl): -@@ -336,7 +323,7 @@ def select(self, timeout=None): - if fd in w: - events |= EVENT_WRITE - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready -@@ -426,7 +413,7 @@ def select(self, timeout=None): - if event & ~self._EVENT_WRITE: - events |= EVENT_READ - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready -@@ -479,7 +466,7 @@ def select(self, timeout=None): - if event & ~select.EPOLLOUT: - events |= EVENT_READ - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready -@@ -574,7 +561,7 @@ def select(self, timeout=None): - if flag == select.KQ_FILTER_WRITE: - events |= EVENT_WRITE - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch b/python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch deleted file mode 100644 index b1ec461e..00000000 --- a/python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 8d2f3c36caf9ecdee1176314b18388aef6e7f2c2 Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Thu, 13 Jul 2023 09:18:53 -1000 -Subject: [PATCH] gh-106664: selectors: add get() method to _SelectorMapping - (#106665) - -It can be used to avoid raising and catching KeyError twice via __getitem__. - -Co-authored-by: Inada Naoki ---- - Lib/selectors.py | 14 +++++++++----- - Lib/test/test_selectors.py | 6 ++++++ - 2 files changed, 15 insertions(+), 5 deletions(-) - -diff --git a/Lib/selectors.py b/Lib/selectors.py -index af6a4f94b5..dfcc125dcd 100644 ---- a/Lib/selectors.py -+++ b/Lib/selectors.py -@@ -66,12 +66,16 @@ def __init__(self, selector): - def __len__(self): - return len(self._selector._fd_to_key) - -+ def get(self, fileobj, default=None): -+ fd = self._selector._fileobj_lookup(fileobj) -+ return self._selector._fd_to_key.get(fd, default) -+ - def __getitem__(self, fileobj): -- try: -- fd = self._selector._fileobj_lookup(fileobj) -- return self._selector._fd_to_key[fd] -- except KeyError: -- raise KeyError("{!r} is not registered".format(fileobj)) from None -+ fd = self._selector._fileobj_lookup(fileobj) -+ key = self._selector._fd_to_key.get(fd) -+ if key is None: -+ raise KeyError("{!r} is not registered".format(fileobj)) -+ return key - - def __iter__(self): - return iter(self._selector._fd_to_key) --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch b/python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch deleted file mode 100644 index 79cfcc6e..00000000 --- a/python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch +++ /dev/null @@ -1,57 +0,0 @@ -From aecf6aca515a203a823a87c711f15cbb82097c8b Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Tue, 18 Jul 2023 00:16:32 -1000 -Subject: [PATCH] gh-106751: selectors: optimize EpollSelector.select() - (#106754) - -Co-authored-by: Pieter Eendebak ---- - Lib/selectors.py | 17 +++++++++-------- - 1 file changed, 9 insertions(+), 8 deletions(-) - -diff --git a/Lib/selectors.py b/Lib/selectors.py -index 6d82935445..a42d156340 100644 ---- a/Lib/selectors.py -+++ b/Lib/selectors.py -@@ -430,6 +430,9 @@ class PollSelector(_PollLikeSelector): - - if hasattr(select, 'epoll'): - -+ _NOT_EPOLLIN = ~select.EPOLLIN -+ _NOT_EPOLLOUT = ~select.EPOLLOUT -+ - class EpollSelector(_PollLikeSelector): - """Epoll-based selector.""" - _selector_cls = select.epoll -@@ -452,22 +455,20 @@ def select(self, timeout=None): - # epoll_wait() expects `maxevents` to be greater than zero; - # we want to make sure that `select()` can be called when no - # FD is registered. -- max_ev = max(len(self._fd_to_key), 1) -+ max_ev = len(self._fd_to_key) or 1 - - ready = [] - try: - fd_event_list = self._selector.poll(timeout, max_ev) - except InterruptedError: - return ready -- for fd, event in fd_event_list: -- events = 0 -- if event & ~select.EPOLLIN: -- events |= EVENT_WRITE -- if event & ~select.EPOLLOUT: -- events |= EVENT_READ - -- key = self._fd_to_key.get(fd) -+ fd_to_key = self._fd_to_key -+ for fd, event in fd_event_list: -+ key = fd_to_key.get(fd) - if key: -+ events = ((event & _NOT_EPOLLIN and EVENT_WRITE) -+ | (event & _NOT_EPOLLOUT and EVENT_READ)) - ready.append((key, events & key.events)) - return ready - --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch b/python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch deleted file mode 100644 index a139ab22..00000000 --- a/python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 86a112f61954dede0a777544e59068c0b01c8439 Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Wed, 11 Oct 2023 08:34:01 -1000 -Subject: [PATCH] gh-110733: Optimize _run_once for many iterations of the - event loop - ---- - Lib/asyncio/base_events.py | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py -index b092c93436..956864e424 100644 ---- a/Lib/asyncio/base_events.py -+++ b/Lib/asyncio/base_events.py -@@ -1907,8 +1907,11 @@ def _run_once(self): - timeout = 0 - elif self._scheduled: - # Compute the desired timeout. -- when = self._scheduled[0]._when -- timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT) -+ timeout = self._scheduled[0]._when - self.time() -+ if timeout > MAXIMUM_SELECT_TIMEOUT: -+ timeout = MAXIMUM_SELECT_TIMEOUT -+ elif timeout < 0: -+ timeout = 0 - - event_list = self._selector.select(timeout) - self._process_events(event_list) --- -2.39.3 (Apple Git-145) - diff --git a/python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch b/python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch deleted file mode 100644 index aa1c4d32..00000000 --- a/python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 7e2d93f30b157e414924c32232bb748c8f66c828 Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Tue, 12 Dec 2023 14:29:21 -1000 -Subject: [PATCH] gh-112989: asyncio: Reduce overhead to connect sockets with - SelectorEventLoop (#112991) - -_ensure_fd_no_transport had a KeyError in the success path ---- - Lib/asyncio/selector_events.py | 14 +++++--------- - .../2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst | 1 + - 2 files changed, 6 insertions(+), 9 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst - -diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py -index d521b4e2e2..dcd5e0aa34 100644 ---- a/Lib/asyncio/selector_events.py -+++ b/Lib/asyncio/selector_events.py -@@ -261,15 +261,11 @@ def _ensure_fd_no_transport(self, fd): - except (AttributeError, TypeError, ValueError): - # This code matches selectors._fileobj_to_fd function. - raise ValueError(f"Invalid file object: {fd!r}") from None -- try: -- transport = self._transports[fileno] -- except KeyError: -- pass -- else: -- if not transport.is_closing(): -- raise RuntimeError( -- f'File descriptor {fd!r} is used by transport ' -- f'{transport!r}') -+ transport = self._transports.get(fileno) -+ if transport and not transport.is_closing(): -+ raise RuntimeError( -+ f'File descriptor {fd!r} is used by transport ' -+ f'{transport!r}') - - def _add_reader(self, fd, callback, *args): - self._check_closed() - diff --git a/python/3.11/musl-find_library.patch b/python/3.11/musl-find_library.patch deleted file mode 100644 index 7899abb7..00000000 --- a/python/3.11/musl-find_library.patch +++ /dev/null @@ -1,45 +0,0 @@ -diff -ru Python-2.7.12.orig/Lib/ctypes/util.py Python-2.7.12/Lib/ctypes/util.py ---- Python-2.7.12.orig/Lib/ctypes/util.py 2016-06-26 00:49:30.000000000 +0300 -+++ Python-2.7.12/Lib/ctypes/util.py 2016-11-03 16:05:46.954665040 +0200 -@@ -204,6 +204,41 @@ - def find_library(name, is64 = False): - return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) - -+ elif True: -+ -+ # Patched for Alpine Linux / musl - search manually system paths -+ def _is_elf(filepath): -+ try: -+ with open(filepath, 'rb') as fh: -+ return fh.read(4) == b'\x7fELF' -+ except: -+ return False -+ -+ def find_library(name): -+ from glob import glob -+ # absolute name? -+ if os.path.isabs(name): -+ return name -+ # special case for libm, libcrypt and libpthread and musl -+ if name in ['m', 'crypt', 'pthread']: -+ name = 'c' -+ elif name in ['libm.so', 'libcrypt.so', 'libpthread.so']: -+ name = 'libc.so' -+ # search in standard locations (musl order) -+ paths = ['/lib', '/usr/local/lib', '/usr/lib'] -+ if 'LD_LIBRARY_PATH' in os.environ: -+ paths = os.environ['LD_LIBRARY_PATH'].split(':') + paths -+ for d in paths: -+ f = os.path.join(d, name) -+ if _is_elf(f): -+ return os.path.basename(f) -+ -+ prefix = os.path.join(d, 'lib'+name) -+ for suffix in ['.so', '.so.*']: -+ for f in glob('{0}{1}'.format(prefix, suffix)): -+ if _is_elf(f): -+ return os.path.basename(f) -+ - else: - - def _findSoname_ldconfig(name): From dd481818fc50f5ba5ecf0c9fb3d87474d44c1e8c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 24 Jul 2025 20:13:07 +0200 Subject: [PATCH 3/7] Update thread stack size to 2MB --- python/3.14/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/3.14/Dockerfile b/python/3.14/Dockerfile index 596c0da1..61da9c63 100644 --- a/python/3.14/Dockerfile +++ b/python/3.14/Dockerfile @@ -85,9 +85,10 @@ RUN set -ex \ && make -j "$(nproc)" \ LDFLAGS="-Wl,--strip-all" \ CFLAGS="-fno-semantic-interposition -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free" \ -# set thread stack size to 1MB so we don't segfault before we hit sys.getrecursionlimit() +# set thread stack size to 2MB so we don't segfault before we hit sys.getrecursionlimit() # https://github.com/alpinelinux/aports/commit/2026e1259422d4e0cf92391ca2d3844356c649d0 - EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" \ +# https://github.com/alpinelinux/aports/commit/675f1babb7ac89068d32b8f4b4e8bbfbb04c96ac + EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x200000" \ && make install \ \ && find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ From 392dc027365c869e7f8c9cfa8b62e34aa1cffd13 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:05:49 +0200 Subject: [PATCH 4/7] Update readme + revert PYTHON_LATEST change --- .github/workflows/builder.yml | 2 +- README.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 52addd75..a2503b13 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -12,7 +12,7 @@ env: DEBIAN_LATEST: "trixie" UBUNTU_LATEST: "24.04" RASPBIAN_LATEST: "trixie" - PYTHON_LATEST: "3.14" + PYTHON_LATEST: "3.13" jobs: init: diff --git a/README.md b/README.md index dcb82756..4c7351bb 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ We support the latest 3 release with the latest 3 Alpine version. > Python 3.14 is build from the release candidate. It's only recommended for wheel builds at the moment. | Image | OS | Python versions | Tags | latest | -|-------|----|-----------------|------|--------| -| armhf-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | -| armv7-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | -| aarch64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | -| amd64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | -| i386-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.14-alpine3.22 | +|-------|----|----------------|------|--------| +| armhf-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| armv7-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| aarch64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| amd64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| i386-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | ## Others From ba22f81c3cb6021f139ead08caf4cb97ac000b17 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:53:06 +0200 Subject: [PATCH 5/7] Skip 3.14 builds on deprecated architectures --- .github/workflows/builder.yml | 7 +++++++ README.md | 10 ++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index a2503b13..446024c7 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -252,6 +252,13 @@ jobs: arch: ${{ fromJson(needs.init.outputs.architectures_alpine) }} version: ["3.20", "3.21", "3.22"] python: ["3.12", "3.13", "3.14"] + exclude: + - python: "3.14" + arch: "armv7" + - python: "3.14" + arch: "armhf" + - python: "3.14" + arch: "i386" steps: - name: Checkout the repository uses: actions/checkout@v5.0.0 diff --git a/README.md b/README.md index 4c7351bb..ea245a1f 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,16 @@ We support the latest 3 release with the latest 3 Alpine version. > [!NOTE] > Python 3.14 is build from the release candidate. It's only recommended for wheel builds at the moment. +> Furthermore, 3.14 will only be available on `aarch64` and `amd64` following the deprecation of `armhf`, `armv7` and `i386`. +> See https://www.home-assistant.io/blog/2025/05/22/deprecating-core-and-supervised-installation-methods-and-32-bit-systems/ | Image | OS | Python versions | Tags | latest | -|-------|----|----------------|------|--------| -| armhf-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | -| armv7-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +|-------|----|-----------------|------|--------| +| armhf-base-python | Alpine | 3.12, 3.13 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | +| armv7-base-python | Alpine | 3.12, 3.13 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | | aarch64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | | amd64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | -| i386-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| i386-base-python | Alpine | 3.12, 3.13 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | ## Others From b7c608a112ee034d31dfd27b3a02d18260137b37 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:48:34 +0200 Subject: [PATCH 6/7] Build Python 3.14.0 --- README.md | 3 +-- python/3.14/build.yaml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea245a1f..1724530a 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,7 @@ We support on our platforms jemalloc. On the application which you want to enabl We support the latest 3 release with the latest 3 Alpine version. > [!NOTE] -> Python 3.14 is build from the release candidate. It's only recommended for wheel builds at the moment. -> Furthermore, 3.14 will only be available on `aarch64` and `amd64` following the deprecation of `armhf`, `armv7` and `i386`. +> Python 3.14 will only be available on `aarch64` and `amd64` following the deprecation of `armhf`, `armv7` and `i386`. > See https://www.home-assistant.io/blog/2025/05/22/deprecating-core-and-supervised-installation-methods-and-32-bit-systems/ | Image | OS | Python versions | Tags | latest | diff --git a/python/3.14/build.yaml b/python/3.14/build.yaml index 733c843d..cfd611ac 100644 --- a/python/3.14/build.yaml +++ b/python/3.14/build.yaml @@ -9,7 +9,7 @@ cosign: base_identity: https://github.com/home-assistant/docker-base/.* identity: https://github.com/home-assistant/docker-base/.* args: - PYTHON_VERSION: "3.14.0rc3" + PYTHON_VERSION: "3.14.0" PIP_VERSION: "25.2" CERT_IDENTITY: hugo@python.org CERT_OIDC_ISSUER: https://github.com/login/oauth From 512afcc9273c50b9691e71c39d6ac2e7648385d4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:49:32 +0200 Subject: [PATCH 7/7] Update Python to 3.13.8 --- python/3.13/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/3.13/build.yaml b/python/3.13/build.yaml index fcb181d2..88260515 100644 --- a/python/3.13/build.yaml +++ b/python/3.13/build.yaml @@ -9,7 +9,7 @@ cosign: base_identity: https://github.com/home-assistant/docker-base/.* identity: https://github.com/home-assistant/docker-base/.* args: - PYTHON_VERSION: "3.13.7" + PYTHON_VERSION: "3.13.8" PIP_VERSION: "25.2" CERT_IDENTITY: thomas@python.org CERT_OIDC_ISSUER: https://accounts.google.com