From ec156a0158c85d85445651bc727675c0d65fc54d Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:43:06 +0800 Subject: [PATCH 1/9] ci: build and verify Amulet-LevelDB cp315 compatibility Wheels --- .github/workflows/amulet-py315.yml | 151 +++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 .github/workflows/amulet-py315.yml diff --git a/.github/workflows/amulet-py315.yml b/.github/workflows/amulet-py315.yml new file mode 100644 index 0000000..0a93762 --- /dev/null +++ b/.github/workflows/amulet-py315.yml @@ -0,0 +1,151 @@ +name: Amulet-LevelDB Python 3.15 compatibility Wheels + +on: + push: + branches: [feat/python315-compat] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Amulet cp315 / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + timeout-minutes: 55 + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-24.04, wheel: cp315-manylinux_x86_64 } + - { os: windows-2025, wheel: cp315-win_amd64 } + defaults: + run: + shell: bash + steps: + - name: Checkout EndKeep tests + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Checkout exact upstream Amulet 3.0.7a0 + uses: actions/checkout@v4 + with: + repository: Amulet-Team/Amulet-LevelDB + ref: 736228e375e9f535b6522bd28168fc306a5354b9 + path: amulet-src + fetch-depth: 0 + persist-credentials: false + + - name: Apply Python metadata backport and isolated smoke test + run: | + python - <<'PY' + from pathlib import Path + path = Path("amulet-src/pyproject.toml") + source = path.read_text(encoding="utf-8") + old = 'requires-python = ">=3.14"' + assert source.count(old) == 1 + path.write_text(source.replace(old, 'requires-python = ">=3.12"'), encoding="utf-8") + PY + cp .github/scripts/amulet_leveldb_smoke.py amulet-src/tests/test_backport_smoke.py + git -C amulet-src diff --check + + - name: Set up Python 3.15 + uses: actions/setup-python@v6 + with: + python-version: "3.15" + + - name: Install cibuildwheel + run: python -m pip install 'cibuildwheel~=4.1' + + - name: Build and test cp315 Wheel + env: + CIBW_BEFORE_BUILD: 'python -m pip install "cmake>=4.1,<5"' + CIBW_BEFORE_TEST: 'python -m pip install "cmake>=4.1,<5"' + CIBW_TEST_COMMAND: 'python {project}/tests/test_backport_smoke.py && python {project}/tests/test_db.py' + CIBW_TEST_SOURCES: tests + run: | + mkdir -p amulet-dist + cd amulet-src + python -m cibuildwheel --only '${{ matrix.wheel }}' --output-dir ../amulet-dist . + + - name: Upload cp315 Wheel + uses: actions/upload-artifact@v4 + with: + name: amulet-cp315-${{ matrix.os }} + path: amulet-dist/*.whl + if-no-files-found: error + retention-days: 14 + + verify: + name: Verify cp315 Wheel provenance + needs: build + runs-on: ubuntu-24.04 + steps: + - name: Download cp315 Wheels + uses: actions/download-artifact@v4 + with: + pattern: amulet-cp315-* + path: dist + merge-multiple: true + + - name: Validate Wheel metadata and calculate SHA256 + run: | + python - <<'PY' + from hashlib import sha256 + from pathlib import Path + from zipfile import ZipFile + root = Path("dist") + wheels = sorted(root.glob("*.whl")) + assert len(wheels) == 2, [p.name for p in wheels] + for platform in ("manylinux_", "win_amd64"): + matches = [p for p in wheels if "-cp315-cp315-" in p.name and platform in p.name] + assert len(matches) == 1, (platform, [p.name for p in wheels]) + for wheel in wheels: + with ZipFile(wheel) as archive: + metadata_file = next(f for f in archive.namelist() if f.endswith(".dist-info/METADATA")) + metadata = archive.read(metadata_file).decode("utf-8") + assert "Name: amulet-leveldb" in metadata + assert "Version: 3.0.7a0+0.g736228e.dirty" in metadata + assert "Requires-Python: >=3.12" in metadata + assert any(p.endswith(".dist-info/licenses/LICENSE") for p in archive.namelist()) + (root / "SHA256SUMS").write_text( + "".join(f"{sha256(wheel.read_bytes()).hexdigest()} {wheel.name}\n" for wheel in wheels), + encoding="utf-8", + ) + print((root / "SHA256SUMS").read_text()) + PY + + - name: Upload verified cp315 Wheels + uses: actions/upload-artifact@v4 + with: + name: amulet-cp315-verified + path: | + dist/*.whl + dist/SHA256SUMS + if-no-files-found: error + + publish: + name: Publish pinned cp315 compatibility dependencies + needs: verify + if: github.event_name == 'push' && github.ref == 'refs/heads/feat/python315-compat' + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Download verified assets + uses: actions/download-artifact@v4 + with: + name: amulet-cp315-verified + path: dist + + - name: Publish dedicated prerelease, never overwrite existing assets + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG=amulet-leveldb-3.0.7a0-compat-cp315 + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "::error::Compatibility release $TAG already exists; refusing to overwrite it." + exit 1 + fi + gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "Amulet-LevelDB 3.0.7a0 CPython 3.15 compatibility Wheels" --prerelease --notes "Unofficial CPython 3.15 compatibility builds from Amulet-Team/Amulet-LevelDB commit 736228e375e9f535b6522bd28168fc306a5354b9. The Python metadata floor is relaxed; the native implementation is unchanged. Both Linux and Windows x86-64 Wheels passed upstream DB and EndKeep smoke tests. Upstream Amulet Team License applies." dist/*.whl dist/SHA256SUMS From 3d4c9a282dab517ebc43cb420993f0c6317eb3ec Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:44:08 +0800 Subject: [PATCH 2/9] ci: use cibuildwheel cp315 interpreters instead of setup-python prerelease --- .github/workflows/amulet-py315.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/amulet-py315.yml b/.github/workflows/amulet-py315.yml index 0a93762..19f4c77 100644 --- a/.github/workflows/amulet-py315.yml +++ b/.github/workflows/amulet-py315.yml @@ -50,13 +50,13 @@ jobs: cp .github/scripts/amulet_leveldb_smoke.py amulet-src/tests/test_backport_smoke.py git -C amulet-src diff --check - - name: Set up Python 3.15 + - name: Set up host Python 3.14 for cibuildwheel uses: actions/setup-python@v6 with: - python-version: "3.15" + python-version: "3.14" - name: Install cibuildwheel - run: python -m pip install 'cibuildwheel~=4.1' + run: python -m pip install 'cibuildwheel==4.2.0' - name: Build and test cp315 Wheel env: From 70623b11a783e47a999d28a37aa98b85e287570d Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:50:52 +0800 Subject: [PATCH 3/9] feat: include Python 3.15 in EndKeep compatibility matrix --- pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3999881..5e16e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,12 +7,14 @@ name = "endstone-endkeep" dynamic = ["version"] dependencies = [ "endstone>=0.11,<0.12", - # Official upstream Wheel for Python 3.14; pinned, tested backports for 3.12 and 3.13. + # Official upstream Wheel for Python 3.14; SHA256-pinned compatibility Wheels for 3.12, 3.13 and 3.15. "amulet-leveldb==3.0.7a0; python_version == '3.14'", 'amulet-leveldb @ https://github.com/ReallocAll/endkeep/releases/download/amulet-leveldb-3.0.7a0-compat-cp312-cp313/amulet_leveldb-3.0.7a0%2B0.g736228e.dirty-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=283e7e57e4d15d7ee59457a89db8c43be9a1cd85c7174844f3a1f1bd14d93bbd ; python_version == "3.12" and sys_platform == "linux" and platform_machine == "x86_64"', 'amulet-leveldb @ https://github.com/ReallocAll/endkeep/releases/download/amulet-leveldb-3.0.7a0-compat-cp312-cp313/amulet_leveldb-3.0.7a0%2B0.g736228e.dirty-cp312-cp312-win_amd64.whl#sha256=9033156a570ceef3613486a681b990f442788b636da1018838eb6ffdd3dca73f ; python_version == "3.12" and sys_platform == "win32" and platform_machine == "AMD64"', 'amulet-leveldb @ https://github.com/ReallocAll/endkeep/releases/download/amulet-leveldb-3.0.7a0-compat-cp312-cp313/amulet_leveldb-3.0.7a0%2B0.g736228e.dirty-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=718d5ff3887b4d848f811a49ab6a7fe3e0b1f863821029190714c4ea5362d9fc ; python_version == "3.13" and sys_platform == "linux" and platform_machine == "x86_64"', 'amulet-leveldb @ https://github.com/ReallocAll/endkeep/releases/download/amulet-leveldb-3.0.7a0-compat-cp312-cp313/amulet_leveldb-3.0.7a0%2B0.g736228e.dirty-cp313-cp313-win_amd64.whl#sha256=28dc6037e3d16355a5120baba1ec61065d020f65500ca9de20d97deaaac0ab2f ; python_version == "3.13" and sys_platform == "win32" and platform_machine == "AMD64"', + 'amulet-leveldb @ https://github.com/ReallocAll/endkeep/releases/download/amulet-leveldb-3.0.7a0-compat-cp315/amulet_leveldb-3.0.7a0%2B0.g736228e.dirty-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=7fec1e03a2d72d1c08944726c08909fd811cf7ad905e6f124cea581bcfc554ad ; python_version == "3.15" and sys_platform == "linux" and platform_machine == "x86_64"', + 'amulet-leveldb @ https://github.com/ReallocAll/endkeep/releases/download/amulet-leveldb-3.0.7a0-compat-cp315/amulet_leveldb-3.0.7a0%2B0.g736228e.dirty-cp315-cp315-win_amd64.whl#sha256=a54e2004f50b8fe5ac2a23edb7cd5c5e60fbc9a8d44fadf282e17b0d3416a611 ; python_version == "3.15" and sys_platform == "win32" and platform_machine == "AMD64"', "zstandard>=0.23,<1", "tomlkit>=0.13,<1", ] @@ -23,7 +25,7 @@ description = "Crash-safe logical incremental world backups for Endstone" readme = "README.md" license = { file = "LICENSE" } keywords = ["endstone", "minecraft", "bedrock", "backup", "leveldb"] -requires-python = ">=3.12,<3.15" +requires-python = ">=3.12,<3.16" classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: POSIX :: Linux", @@ -32,6 +34,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", ] [project.optional-dependencies] From 3e739fe0ef96fe1734a9416b0adc1b8bf4e333a8 Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:50:55 +0800 Subject: [PATCH 4/9] feat: include Python 3.15 in EndKeep compatibility matrix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9822c2..d6ed09e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ without storing a complete world copy each time, and lets you restore an earlier ## Installation -Requires **Endstone 0.11** and **Python 3.12–3.14**. +Requires **Endstone 0.11** and **Python 3.12–3.15**. 1. Download the plugin `.whl` from [Releases](https://github.com/ReallocAll/endkeep/releases/latest). 2. Place it in the server's `plugins/` directory. From 5b76483ca01b386e94d77e80376d3718deb666ca Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:50:57 +0800 Subject: [PATCH 5/9] feat: include Python 3.15 in EndKeep compatibility matrix --- AGENTS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3cbd7a4..5ad0ec6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # Working on EndKeep EndKeep is a Python plugin for Endstone that creates incremental backups of -Minecraft Bedrock worlds. EndKeep targets Python 3.12–3.14 +Minecraft Bedrock worlds. EndKeep targets Python 3.12–3.15 on Linux and Windows x86-64 with Endstone 0.11. For Endstone API usage, see [Endstone's documentation](https://endstone.dev/). @@ -36,11 +36,11 @@ uv run --no-sync pytest - Update user documentation for visible behavior changes. Explain what server owners need to do; keep internal implementation notes out of usage guides. - Do not create releases or rewrite version tags unless explicitly requested. -- Python 3.12/3.13 resolves pinned, SHA256-verified Amulet-LevelDB compatibility +- Python 3.12/3.13/3.15 resolves pinned, SHA256-verified Amulet-LevelDB compatibility Wheels from the dedicated GitHub Release. Test a clean EndKeep install without pre-installing the dependency. The upstream Amulet version metadata currently prevents regenerating a universal uv.lock; prefer `uv pip install` instead of `uv sync`. -- The six-combination compatibility workflow is not a substitute for +- The eight-combination compatibility workflow is not a substitute for testing real BDS worlds, especially NTFS crash and directory metadata durability. See [Contributing](CONTRIBUTING.md) and the From f84b99c25853a54990a05a8f8b1c132c028b4a53 Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:50:59 +0800 Subject: [PATCH 6/9] feat: include Python 3.15 in EndKeep compatibility matrix --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b4ff6..ed16cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Support Python 3.15 on Linux and Windows x86-64 using SHA256-pinned Amulet-LevelDB 3.0.7a0 compatibility Wheels. +- Validate Python 3.15 against Endstone CPython 3.15 development Wheels in bds-test-lab. + ## [0.3.0] - 2026-10-10 ### Added From 90e55530ed3d2d37db1a5eafa8230927563dbf3d Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:51:03 +0800 Subject: [PATCH 7/9] feat: include Python 3.15 in EndKeep compatibility matrix --- .github/workflows/compatibility.yml | 42 ++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index 34b96c0..fb1f1cc 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -24,9 +24,11 @@ jobs: - { os: ubuntu-24.04, python: '3.12', wheel: cp312-manylinux_x86_64 } - { os: ubuntu-24.04, python: '3.13', wheel: cp313-manylinux_x86_64 } - { os: ubuntu-24.04, python: '3.14', wheel: '' } + - { os: ubuntu-24.04, python: '3.15', wheel: cp315-manylinux_x86_64 } - { os: windows-2025, python: '3.12', wheel: cp312-win_amd64 } - { os: windows-2025, python: '3.13', wheel: cp313-win_amd64 } - { os: windows-2025, python: '3.14', wheel: '' } + - { os: windows-2025, python: '3.15', wheel: cp315-win_amd64 } defaults: run: shell: bash @@ -59,13 +61,14 @@ jobs: PY cp .github/scripts/amulet_leveldb_smoke.py amulet-src/tests/test_backport_smoke.py git -C amulet-src diff --check - - name: Setup Python + - name: Setup host Python + if: matrix.python != '3.15' uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} - name: Install cibuildwheel if: matrix.python != '3.14' - run: python -m pip install 'cibuildwheel~=4.1' + run: python -m pip install 'cibuildwheel==4.2.0' - name: Build and test Amulet wheel if: matrix.python != '3.14' env: @@ -81,6 +84,18 @@ jobs: uses: astral-sh/setup-uv@v7 with: enable-cache: true + - name: Download exact Endstone CPython 3.15 Wheel + if: matrix.python == '3.15' + uses: actions/download-artifact@v8 + with: + repository: EndstoneMC/endstone + run-id: 38045137884 + github-token: ${{ github.token }} + pattern: endstone-*-cp315-cp315-*.whl + path: endstone-dist + merge-multiple: true + skip-decompress: true + - name: Test installed EndKeep with matched Amulet run: | uv venv .venv --python '${{ matrix.python }}' @@ -89,15 +104,24 @@ jobs: else PYTHON=.venv/bin/python fi + if [[ "${{ matrix.python }}" == "3.15" ]]; then + if [[ "${{ runner.os }}" == "Windows" ]]; then + ENDSTONE_WHEEL="$(find endstone-dist -name '*cp315*win_amd64.whl' -print -quit)" + else + ENDSTONE_WHEEL="$(find endstone-dist -name '*cp315*manylinux*.whl' -print -quit)" + fi + test -n "$ENDSTONE_WHEEL" + uv pip install --python "$PYTHON" "$ENDSTONE_WHEEL" + fi # Fresh dependency resolution: do NOT seed the local Amulet build. - # This must fetch the exact published GitHub Release Wheel on 3.12/3.13. + # This must fetch the exact published GitHub Release Wheel on 3.12/3.13/3.15. uv pip install --python "$PYTHON" -e '.[dev]' "$PYTHON" - <<'PY' from importlib.metadata import distribution from packaging.requirements import Requirement requires = distribution("endstone-endkeep").requires or [] amulet = [Requirement(req) for req in requires if Requirement(req).name == "amulet-leveldb"] - assert len(amulet) == 5, [str(req) for req in amulet] + assert len(amulet) == 7, [str(req) for req in amulet] print("Packaged Amulet selectors:", *amulet, sep="\\n") PY uv pip check --python "$PYTHON" @@ -118,11 +142,11 @@ jobs: retention-days: 7 verified-backports: - name: Collect verified CPython 3.12/3.13 Wheels + name: Collect verified compatibility Wheels needs: full-matrix runs-on: ubuntu-24.04 steps: - - name: Download four tested wheel artifacts + - name: Download tested wheel artifacts uses: actions/download-artifact@v4 with: pattern: amulet-compat-* @@ -138,8 +162,8 @@ jobs: root = Path("dist") wheels = sorted(root.glob("*.whl")) - assert len(wheels) == 4, [p.name for p in wheels] - for abi in ("cp312", "cp313"): + assert len(wheels) == 6, [p.name for p in wheels] + for abi in ("cp312", "cp313", "cp315"): for platform in ("manylinux_", "win_amd64"): matches = [ p for p in wheels @@ -164,7 +188,7 @@ jobs: - name: Upload verified wheel set for review (not a GitHub Release) uses: actions/upload-artifact@v4 with: - name: amulet-backports-verified-four-wheels + name: amulet-backports-verified-six-wheels path: | dist/*.whl dist/SHA256SUMS From 250cf020786312a8cd73854f1e69bbf6c0bc696c Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 19:52:03 +0800 Subject: [PATCH 8/9] ci: avoid repeated Amulet rebuilds for unrelated EndKeep changes --- .github/workflows/amulet-py315.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/amulet-py315.yml b/.github/workflows/amulet-py315.yml index 19f4c77..5836f82 100644 --- a/.github/workflows/amulet-py315.yml +++ b/.github/workflows/amulet-py315.yml @@ -3,6 +3,9 @@ name: Amulet-LevelDB Python 3.15 compatibility Wheels on: push: branches: [feat/python315-compat] + paths: + - '.github/workflows/amulet-py315.yml' + - '.github/scripts/amulet_leveldb_smoke.py' workflow_dispatch: permissions: @@ -145,7 +148,7 @@ jobs: run: | TAG=amulet-leveldb-3.0.7a0-compat-cp315 if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - echo "::error::Compatibility release $TAG already exists; refusing to overwrite it." - exit 1 + echo "Compatibility release $TAG already published; leaving verified assets unchanged." + exit 0 fi gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "Amulet-LevelDB 3.0.7a0 CPython 3.15 compatibility Wheels" --prerelease --notes "Unofficial CPython 3.15 compatibility builds from Amulet-Team/Amulet-LevelDB commit 736228e375e9f535b6522bd28168fc306a5354b9. The Python metadata floor is relaxed; the native implementation is unchanged. Both Linux and Windows x86-64 Wheels passed upstream DB and EndKeep smoke tests. Upstream Amulet Team License applies." dist/*.whl dist/SHA256SUMS From e3079ef7e54da9a5766726e3d8c52d9c37261a4a Mon Sep 17 00:00:00 2001 From: ReallocAll Date: Sat, 10 Oct 2026 20:03:59 +0800 Subject: [PATCH 9/9] ci: keep one-shot Amulet cp315 builder out of permanent workflows --- .github/workflows/amulet-py315.yml | 154 ----------------------------- 1 file changed, 154 deletions(-) delete mode 100644 .github/workflows/amulet-py315.yml diff --git a/.github/workflows/amulet-py315.yml b/.github/workflows/amulet-py315.yml deleted file mode 100644 index 5836f82..0000000 --- a/.github/workflows/amulet-py315.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Amulet-LevelDB Python 3.15 compatibility Wheels - -on: - push: - branches: [feat/python315-compat] - paths: - - '.github/workflows/amulet-py315.yml' - - '.github/scripts/amulet_leveldb_smoke.py' - workflow_dispatch: - -permissions: - contents: read - -jobs: - build: - name: Amulet cp315 / ${{ matrix.os }} - runs-on: ${{ matrix.os }} - timeout-minutes: 55 - strategy: - fail-fast: false - matrix: - include: - - { os: ubuntu-24.04, wheel: cp315-manylinux_x86_64 } - - { os: windows-2025, wheel: cp315-win_amd64 } - defaults: - run: - shell: bash - steps: - - name: Checkout EndKeep tests - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Checkout exact upstream Amulet 3.0.7a0 - uses: actions/checkout@v4 - with: - repository: Amulet-Team/Amulet-LevelDB - ref: 736228e375e9f535b6522bd28168fc306a5354b9 - path: amulet-src - fetch-depth: 0 - persist-credentials: false - - - name: Apply Python metadata backport and isolated smoke test - run: | - python - <<'PY' - from pathlib import Path - path = Path("amulet-src/pyproject.toml") - source = path.read_text(encoding="utf-8") - old = 'requires-python = ">=3.14"' - assert source.count(old) == 1 - path.write_text(source.replace(old, 'requires-python = ">=3.12"'), encoding="utf-8") - PY - cp .github/scripts/amulet_leveldb_smoke.py amulet-src/tests/test_backport_smoke.py - git -C amulet-src diff --check - - - name: Set up host Python 3.14 for cibuildwheel - uses: actions/setup-python@v6 - with: - python-version: "3.14" - - - name: Install cibuildwheel - run: python -m pip install 'cibuildwheel==4.2.0' - - - name: Build and test cp315 Wheel - env: - CIBW_BEFORE_BUILD: 'python -m pip install "cmake>=4.1,<5"' - CIBW_BEFORE_TEST: 'python -m pip install "cmake>=4.1,<5"' - CIBW_TEST_COMMAND: 'python {project}/tests/test_backport_smoke.py && python {project}/tests/test_db.py' - CIBW_TEST_SOURCES: tests - run: | - mkdir -p amulet-dist - cd amulet-src - python -m cibuildwheel --only '${{ matrix.wheel }}' --output-dir ../amulet-dist . - - - name: Upload cp315 Wheel - uses: actions/upload-artifact@v4 - with: - name: amulet-cp315-${{ matrix.os }} - path: amulet-dist/*.whl - if-no-files-found: error - retention-days: 14 - - verify: - name: Verify cp315 Wheel provenance - needs: build - runs-on: ubuntu-24.04 - steps: - - name: Download cp315 Wheels - uses: actions/download-artifact@v4 - with: - pattern: amulet-cp315-* - path: dist - merge-multiple: true - - - name: Validate Wheel metadata and calculate SHA256 - run: | - python - <<'PY' - from hashlib import sha256 - from pathlib import Path - from zipfile import ZipFile - root = Path("dist") - wheels = sorted(root.glob("*.whl")) - assert len(wheels) == 2, [p.name for p in wheels] - for platform in ("manylinux_", "win_amd64"): - matches = [p for p in wheels if "-cp315-cp315-" in p.name and platform in p.name] - assert len(matches) == 1, (platform, [p.name for p in wheels]) - for wheel in wheels: - with ZipFile(wheel) as archive: - metadata_file = next(f for f in archive.namelist() if f.endswith(".dist-info/METADATA")) - metadata = archive.read(metadata_file).decode("utf-8") - assert "Name: amulet-leveldb" in metadata - assert "Version: 3.0.7a0+0.g736228e.dirty" in metadata - assert "Requires-Python: >=3.12" in metadata - assert any(p.endswith(".dist-info/licenses/LICENSE") for p in archive.namelist()) - (root / "SHA256SUMS").write_text( - "".join(f"{sha256(wheel.read_bytes()).hexdigest()} {wheel.name}\n" for wheel in wheels), - encoding="utf-8", - ) - print((root / "SHA256SUMS").read_text()) - PY - - - name: Upload verified cp315 Wheels - uses: actions/upload-artifact@v4 - with: - name: amulet-cp315-verified - path: | - dist/*.whl - dist/SHA256SUMS - if-no-files-found: error - - publish: - name: Publish pinned cp315 compatibility dependencies - needs: verify - if: github.event_name == 'push' && github.ref == 'refs/heads/feat/python315-compat' - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - name: Download verified assets - uses: actions/download-artifact@v4 - with: - name: amulet-cp315-verified - path: dist - - - name: Publish dedicated prerelease, never overwrite existing assets - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG=amulet-leveldb-3.0.7a0-compat-cp315 - if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - echo "Compatibility release $TAG already published; leaving verified assets unchanged." - exit 0 - fi - gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --target "$GITHUB_SHA" --title "Amulet-LevelDB 3.0.7a0 CPython 3.15 compatibility Wheels" --prerelease --notes "Unofficial CPython 3.15 compatibility builds from Amulet-Team/Amulet-LevelDB commit 736228e375e9f535b6522bd28168fc306a5354b9. The Python metadata floor is relaxed; the native implementation is unchanged. Both Linux and Windows x86-64 Wheels passed upstream DB and EndKeep smoke tests. Upstream Amulet Team License applies." dist/*.whl dist/SHA256SUMS