Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
bf8d23b
feat(docker): Add Python 3.14 to manylinux CI images
phacops Feb 20, 2026
99449f4
ref: Revert build.py PYTHONS change
phacops Feb 20, 2026
312b48c
feat: Add Python 3.14 support to build and validation
phacops Feb 19, 2026
cb5f25d
fix: Black formatting and backports-zstd python_versions
phacops Feb 19, 2026
ce43993
fix: Add CMAKE_POLICY_VERSION_MINIMUM for crc32c prebuild
phacops Feb 19, 2026
e617d49
build: only support macos-15 and add cp314
joshuarli Feb 23, 2026
e3c2f2f
use uv pythons and build only docker image tagged with sha and use it…
joshuarli Feb 23, 2026
4fbacc3
install uv externally
joshuarli Feb 23, 2026
4c69d4d
fixed upstream in uv pythons
joshuarli Feb 23, 2026
23f838e
uv install
joshuarli Feb 23, 2026
0b589f1
hm
joshuarli Feb 23, 2026
77bdbd7
uv-installed standalone Python builds bundle their own libcrypt
joshuarli Feb 23, 2026
ec93edd
fix librdkafka build: use system deps instead of building from source
joshuarli Feb 23, 2026
ebcaf4a
build only librdkafka libraries, skip examples
joshuarli Feb 23, 2026
7c21ae6
scope make install to src and src-cpp only
joshuarli Feb 23, 2026
3a051fa
cant build 1.67.0 on <3.14 but i think we dont need it anymore
joshuarli Feb 24, 2026
87cd92f
hold off on building grpcio older versions
joshuarli Feb 24, 2026
66c8203
lief ignore
joshuarli Feb 24, 2026
19a718d
orjson < 3.14
joshuarli Feb 24, 2026
1df4f2c
.
joshuarli Feb 24, 2026
720012e
add python_versions = <3.14 for pydantic-core==2.23.4
joshuarli Feb 24, 2026
04e5b6c
add python_versions = <3.14 for pydantic-core==2.24.2
joshuarli Feb 24, 2026
337573c
add python_versions = <3.14 for pydantic-core==2.33.2
joshuarli Feb 24, 2026
9c6335c
add python_versions = <3.14 for pyuwsgi==2.0.27.post1
joshuarli Feb 24, 2026
8dce9f4
add python_versions = <3.14 for pyuwsgi 2.0.28-2.0.30
joshuarli Feb 24, 2026
cfe7cf5
comment out all packages before pyuwsgi to speed up CI iteration
joshuarli Feb 24, 2026
2c4260e
add python_versions = <3.14 for rpds-py==0.20.0
joshuarli Feb 24, 2026
f92f10b
add python_versions = <3.14 for sentry-forked-jsonnet==0.20.0.post4
joshuarli Feb 24, 2026
0a8b8c4
add python_versions = <3.14 for all sentry-streams and sentry-streams…
joshuarli Feb 24, 2026
d69ac98
[skip ci] initial upgrade-python skill
joshuarli Feb 24, 2026
67167fe
[skip ci] fix skill
joshuarli Feb 24, 2026
13e99d4
build: single-version mode for Python 3.14 upgrade
joshuarli Feb 24, 2026
b5a1cd8
build: fix Dockerfile for single-version Python 3.14 mode
joshuarli Feb 24, 2026
0204263
[skip ci] update skill
joshuarli Feb 24, 2026
6db083c
build: add fail-fast: false for linux matrix jobs
joshuarli Feb 24, 2026
3c6e335
[skip ci] update skill
joshuarli Feb 24, 2026
d96ffbe
mark python 3.14 build failures in packages.ini
joshuarli Feb 24, 2026
cc18f57
[skip ci] update skill
joshuarli Feb 24, 2026
22aabd3
add python_versions = <3.14 for rpds-py, sentry-forked-jsonnet, sentr…
joshuarli Feb 24, 2026
c9f166e
fix pillow, p4python, lxml builds for python 3.14
joshuarli Feb 24, 2026
217a35f
[skip ci] update skill
joshuarli Feb 24, 2026
d829553
fix p4python macos build: set ssl env var via custom_prebuild
joshuarli Feb 24, 2026
226b552
fix p4python macos: use brew_prefix_env instead of custom_prebuild
joshuarli Feb 24, 2026
2a0be2d
p4python: drop brew_requires, keep only brew_prefix_env
joshuarli Feb 24, 2026
c58c4ab
revert p4python and brew_prefix_env changes
joshuarli Feb 24, 2026
d359676
restore all previously-succeeded packages to packages.ini
joshuarli Feb 24, 2026
943f1f7
restore p4python==2025.1.2767466 with python_versions = <3.14
joshuarli Feb 24, 2026
cc27495
stop building grpcio and confluent-kafka on 3.14
joshuarli Feb 24, 2026
7ad2236
add python_versions = <3.14 for uvloop, make validate.py continue on …
joshuarli Feb 24, 2026
631ea7d
document uvloop validation failure and update p4python status
joshuarli Feb 24, 2026
79fa8a4
[skip ci] granian fails validation bc no uvloop
joshuarli Feb 24, 2026
16ae03d
revert single-version mode, build all python versions
joshuarli Feb 24, 2026
6d9a444
build: add apt-get --fix-missing and gate macos on image job
joshuarli Feb 24, 2026
f747324
validate: respect python_versions from packages.ini
joshuarli Feb 24, 2026
68287f0
fix pc and tests
joshuarli Feb 25, 2026
17c9cca
adjustments
joshuarli Feb 25, 2026
71743b6
build: remove temporary image job from CI workflow
joshuarli Feb 25, 2026
d9b5f29
minor edits
joshuarli Feb 25, 2026
7efdb96
fix: always install latest uv
joshuarli Feb 25, 2026
9748073
just switch to clang
joshuarli Feb 25, 2026
8df7dad
fix and temporarily rebuild images
joshuarli Feb 25, 2026
36ee7af
revert testing
joshuarli Feb 25, 2026
3ac5586
temp build pass in order to merge
joshuarli Feb 25, 2026
e0d3c7a
im dumb
joshuarli Feb 25, 2026
d42da34
Merge branch 'main' into feat/python314-support
joshuarli Feb 25, 2026
4d0ee35
Merge branch 'main' into feat/python314-support
joshuarli Feb 26, 2026
14e6a22
im dumb x2
joshuarli Feb 26, 2026
6eb8463
wrong sha lol
joshuarli Feb 26, 2026
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
34 changes: 27 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,65 @@
on:
pull_request:
push:
branches: [main, test-me-*]
branches: [main]

concurrency:
# serialize runs on the default branch
group: ${{ github.event_name == 'push' && github.workflow || github.sha }}${{ github.workflow }}

jobs:
image:
strategy:
matrix:
include:
- {arch: amd64, os: ubuntu-latest}
- {arch: arm64, os: ubuntu-24.04-arm}
runs-on: ${{ matrix.os }}
permissions:
packages: write
steps:
- uses: actions/checkout@v3
- run: docker login --username '${{ github.actor }}' --password-stdin ghcr.io <<< '${{ secrets.GITHUB_TOKEN }}'
- run: |
docker buildx build \
--cache-from ghcr.io/getsentry/pypi-manylinux-${{ matrix.arch }}-ci:latest \
--cache-to type=inline \
--platform linux/${{ matrix.arch }} \
--tag ghcr.io/getsentry/pypi-manylinux-${{ matrix.arch }}-ci:${{ github.sha }} \
${{ github.ref == 'refs/heads/main' && format('--tag ghcr.io/getsentry/pypi-manylinux-{0}-ci:latest', matrix.arch) || '' }} \
--push \
docker
linux:
needs: [image]
strategy:
matrix:
include:
- {arch: amd64, os: ubuntu-latest}
- {arch: arm64, os: ubuntu-24.04-arm}
runs-on: ${{ matrix.os }}
container: ghcr.io/getsentry/pypi-manylinux-${{ matrix.arch }}-ci
container: ghcr.io/getsentry/pypi-manylinux-${{ matrix.arch }}-ci:${{ github.sha }}
steps:
- uses: actions/checkout@v3
- run: python3 -um build --pypi-url https://pypi.devinfra.sentry.io
- run: python3 -um validate --index-url https://pypi.devinfra.sentry.io/simple
- uses: actions/upload-artifact@v4
with:
name: dist-linux-${{ matrix.arch }}
path: dist/*
macos:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
strategy:
matrix:
runs-on: [macos-14]
runs-on: [macos-15]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v3
- run: |
# work around https://github.com/indygreg/python-build-standalone/issues/208
HOMEBREW_NO_AUTO_UPDATE=1 brew install gnu-tar
echo "$(brew --prefix gnu-tar)/libexec/gnubin" >> "$GITHUB_PATH"
- run: pip install --break-system-packages uv
- run: python3 -u docker/install-pythons --dest pythons
- run: |
echo "$PWD/pythons/cp311-cp311/bin" >> "$GITHUB_PATH"
echo "$PWD/pythons/cp312-cp312/bin" >> "$GITHUB_PATH"
echo "$PWD/pythons/cp313-cp313/bin" >> "$GITHUB_PATH"
echo "$PWD/pythons/cp314-cp314/bin" >> "$GITHUB_PATH"
echo "$PWD/venv/bin" >> "$GITHUB_PATH"
- run: python3 -um venv venv && pip install -r docker/requirements.txt
- run: python3 -um build --pypi-url https://pypi.devinfra.sentry.io
Expand Down
2 changes: 1 addition & 1 deletion build.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from packaging.utils import parse_wheel_filename
from packaging.version import Version

PYTHONS = ((3, 11), (3, 12), (3, 13))
PYTHONS = ((3, 11), (3, 12), (3, 13), (3, 14))

BINARY_EXTS = frozenset(
(".c", ".cc", ".cpp", ".cxx", ".pxd", ".pxi", ".pyx", ".go", ".rs")
Expand Down
2 changes: 2 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ ENV \
PIP_NO_CACHE_DIR=1 \
PIP_NO_WARN_ABOUT_ROOT_USER=0

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

COPY install-pythons /tmp/install-pythons
RUN /tmp/install-pythons

Expand Down
135 changes: 34 additions & 101 deletions docker/install-pythons
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,11 @@
from __future__ import annotations

import argparse
import hashlib
import os.path
import platform
import re
import secrets
import glob
import os
import subprocess
import sys
import tempfile

RELEASE = (
"https://github.com/indygreg/python-build-standalone/releases/download/20260211/"
)
# curl --silent --location https://github.com/indygreg/python-build-standalone/releases/download/20260211/SHA256SUMS | grep -E '(aarch64-apple-darwin-pgo\+lto-full|x86_64-apple-darwin-pgo\+lto-full|aarch64-unknown-linux-gnu-pgo\+lto-full|x86_64-unknown-linux-gnu-pgo\+lto-full)' | grep -Ev '(cpython-3\.(8|9|10|15)|freethreaded)'
CHECKSUMS = """\
ceda72c76ecfd4294ae3fdf275202a3cfe912cf1dc7076c9526171aaaedbd3e3 cpython-3.11.14+20260211-aarch64-apple-darwin-pgo+lto-full.tar.zst
355c4a10233a2e3ac1e511e7cf052116404e197bc70fcc22b67aba635e209808 cpython-3.11.14+20260211-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst
898995bc172df26f5e0ce9bac316254756094466b9d472234e62e4045e1ddbf6 cpython-3.11.14+20260211-x86_64-apple-darwin-pgo+lto-full.tar.zst
9dd3129d78fc42a63690b09f9f48b27d35b0fbe7b580fd1cd85bb554c82671b6 cpython-3.11.14+20260211-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst
bf70a8ba4d44eb243af9dc3485656e0ce3757588eefe27e1801b36ff9773805a cpython-3.12.12+20260211-aarch64-apple-darwin-pgo+lto-full.tar.zst
48cccc8970f32586b60125199c955da870c5b9c52c05afb2bce28714eeb17cc6 cpython-3.12.12+20260211-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst
14fe4f2213f9b89d5649b2c50636be20393ec0092960d1acd11f7c84a4e1b2e9 cpython-3.12.12+20260211-x86_64-apple-darwin-pgo+lto-full.tar.zst
75428635145d4eb8de86cff5d00a823009a21fe8c173c7899959d0f41f73ad4a cpython-3.12.12+20260211-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst
3baef69715ffc554a7f173e9419cfb75ddf25b7cae91ab141032843d53fa34c4 cpython-3.13.12+20260211-aarch64-apple-darwin-pgo+lto-full.tar.zst
0ad848cab9031fc80c64442698f6eff112d81d45eaf53f49ece6ecbfc97f6ea6 cpython-3.13.12+20260211-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst
8ad36a0b44b03f2c236d05135600d626ae73245eae0361a17ddabb9e7163e50b cpython-3.13.12+20260211-x86_64-apple-darwin-pgo+lto-full.tar.zst
2483028342db1e31a8a4004a859a856fade2563bae97f18812a2d27a123773e6 cpython-3.13.12+20260211-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst
d016c5a16c6a246f56cf2fae2a4150a339311a30787347ff1c1e063295c82401 cpython-3.14.3+20260211-aarch64-apple-darwin-pgo+lto-full.tar.zst
13a08dca6f29df3701f1846184db78499d23014f6d5a70fa6c2c1f29baee350a cpython-3.14.3+20260211-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst
3e55c3d0914e7e4f2e7a135c80077a0ac635de9dcfa0c08f2544fc2165e264a4 cpython-3.14.3+20260211-x86_64-apple-darwin-pgo+lto-full.tar.zst
96c6684fffd6da9d219400b2e3c020d9bc2c838cbb4ac202e2dd652dda3d1914 cpython-3.14.3+20260211-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst
"""
VERSIONS = ("3.11.14", "3.12.12", "3.13.12", "3.14.3")
ARCH_MAP = {"arm64": "aarch64"}
ARCH = ARCH_MAP.get(platform.machine(), platform.machine())

CLANG_PP = re.compile(r"\bclang\+\+")
CLANG = re.compile(r"\bclang\b")


def _must_sub(reg: re.Pattern[str], new: str, s: str) -> str:
after = reg.sub(new, s)
if after == s:
raise AssertionError(f"expected replacement by {reg} => {new}!")
return after


def _checksum_url(version: str) -> tuple[str, str]:
for line in CHECKSUMS.splitlines():
sha256, filename = line.split()
_, f_version_release, arch, _, plat, *_ = filename.split("-")
f_version, _ = f_version_release.split("+")
if version == f_version and sys.platform == plat and ARCH == arch:
return (sha256, f"{RELEASE}/{filename}")
else:
raise NotImplementedError(version, sys.platform, platform.machine())


def main() -> int:
Expand All @@ -66,57 +16,40 @@ def main() -> int:

os.makedirs(args.dest, exist_ok=True)

for version in VERSIONS:
with tempfile.TemporaryDirectory() as tmpdir:
expected, url = _checksum_url(version)

major, minor, *_ = version.split(".")
dest = os.path.join(args.dest, f"cp{major}{minor}-cp{major}{minor}")
tgz_dest = os.path.join(tmpdir, "python.tgz")
subprocess.check_call(
("uv", "python", "install", *VERSIONS, "--install-dir", args.dest),
)

curl_cmd = ("curl", "--silent", "--location", "--output", tgz_dest, url)
subprocess.check_call(curl_cmd)

with open(tgz_dest, "rb") as f:
sha256 = hashlib.sha256(f.read()).hexdigest()
if not secrets.compare_digest(sha256, expected):
raise AssertionError(f"checksum mismatch {sha256=} {expected=}")

os.makedirs(dest, exist_ok=True)
tar_cmd = (
"tar",
"-C",
dest,
"--strip-components=2",
"-xf",
tgz_dest,
"python/install",
)
subprocess.check_call(tar_cmd)

# https://github.com/indygreg/python-build-standalone/issues/209
if sys.platform == "linux" and ARCH == "x86_64":
for fname in (
f"{dest}/lib/python{major}.{minor}/config-{major}.{minor}-x86_64-linux-gnu/Makefile",
f"{dest}/lib/python{major}.{minor}/_sysconfigdata__linux_x86_64-linux-gnu.py",
):
print(f"XXX: fixing up build metadata in {fname}")
with open(fname) as f:
contents = f.read()
contents = _must_sub(CLANG_PP, "c++", contents)
contents = _must_sub(CLANG, "cc", contents)
with open(fname, "w") as f:
f.write(contents)

py = os.path.join(dest, "bin", "python3")
subprocess.check_call((py, "-mensurepip"))
subprocess.check_call(
(
*(py, "-mpip", "install"),
*("pip==25.0.1", "setuptools==75.8.0", "wheel==0.45.1"),
)
for version in VERSIONS:
major, minor, *_ = version.split(".")

# uv installs to e.g. cpython-3.14.3-linux-aarch64-gnu/
matches = glob.glob(os.path.join(args.dest, f"cpython-{major}.{minor}.*"))
if len(matches) != 1:
raise AssertionError(
f"expected exactly one match for cpython-{major}.{minor}.*, "
f"got {matches}"
)
subprocess.check_call((py, "--version", "--version"))
installed_dir = matches[0]

# symlink cpXY-cpXY -> the uv-installed directory for PATH compat
link = os.path.join(args.dest, f"cp{major}{minor}-cp{major}{minor}")
os.symlink(os.path.basename(installed_dir), link)

py = os.path.join(link, "bin", "python3")
subprocess.check_call(
(
"uv",
"pip",
"install",
"--python",
py,
"pip==25.0.1",
"setuptools==75.8.0",
"wheel==0.45.1",
),
)
subprocess.check_call((py, "--version", "--version"))

return 0

Expand Down
1 change: 1 addition & 0 deletions packages.ini
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ python_versions = <3.13
[babel==2.17.0]

[backports-zstd==1.3.0]
python_versions = <3.14

[backrefs==5.9]

Expand Down
1 change: 1 addition & 0 deletions prebuild/crc32c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def main() -> int:
subprocess.check_call(
(
"cmake",
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
f"-DCMAKE_INSTALL_PREFIX={args.prefix}",
"-DCRC32C_BUILD_TESTS=no",
"-DCRC32C_BUILD_BENCHMARKS=no",
Expand Down
10 changes: 5 additions & 5 deletions tests/validate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,24 @@ def test_pythons_to_check_no_pythons_raises_error():

def test_pythons_to_check_py2_ignored():
ret = validate._pythons_to_check(parse_tag("py2.py3-none-any"))
assert ret == ("python3.11", "python3.12", "python3.13")
assert ret == ("python3.11", "python3.12", "python3.13", "python3.14")


def test_pythons_to_check_py3_gives_all():
ret = validate._pythons_to_check(parse_tag("py3-none-any"))
assert ret == ("python3.11", "python3.12", "python3.13")
assert ret == ("python3.11", "python3.12", "python3.13", "python3.14")


def test_pythons_to_check_abi3():
tag = "cp37-abi3-manylinux1_x86_64"
ret = validate._pythons_to_check(parse_tag(tag))
assert ret == ("python3.11", "python3.12", "python3.13")
assert ret == ("python3.11", "python3.12", "python3.13", "python3.14")


def test_pythons_to_check_minimum_abi3():
tag = "cp312-abi3-manylinux1_x86_64"
ret = validate._pythons_to_check(parse_tag(tag))
assert ret == ("python3.12", "python3.13")
assert ret == ("python3.12", "python3.13", "python3.14")


def test_pythons_to_check_specific_cpython_tag():
Expand All @@ -76,7 +76,7 @@ def test_pythons_to_check_multi_platform_with_musllinux():
tags = parse_tag("py3-none-any") | parse_tag("py3-none-musllinux_1_2_x86_64")
ret = validate._pythons_to_check(tags)
# Should succeed because at least one tag (py3-none-any) is compatible
assert ret == ("python3.11", "python3.12", "python3.13")
assert ret == ("python3.11", "python3.12", "python3.13", "python3.14")


def test_top_imports_record(tmp_path):
Expand Down
2 changes: 1 addition & 1 deletion validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from packaging.utils import parse_wheel_filename
from packaging.version import Version

PYTHONS = ((3, 11), (3, 12), (3, 13))
PYTHONS = ((3, 11), (3, 12), (3, 13), (3, 14))
DIST_INFO_RE = re.compile(r"^[^/]+.dist-info/[^/]+$")


Expand Down
Loading