diff --git a/.github/scripts/ci_check_wheel.ps1 b/.github/scripts/ci_check_wheel.ps1 new file mode 100644 index 0000000..e38f85c --- /dev/null +++ b/.github/scripts/ci_check_wheel.ps1 @@ -0,0 +1,34 @@ +# A Powershell port of `ci_check_wheel.sh`. Refer to that script instead. + +# Any change made here should be made in `ci_check_wheel.sh` too. + +param ( + [Parameter(Mandatory = $true)] + [string]$PYTHON_VERSION, + + [Parameter(Mandatory = $true)] + [string]$KEY, + + [string]$EXTRA +) + +# Equivalent to `set -e` +$ErrorActionPreference = "Stop" + +if (Test-Path ".venv") { + Remove-Item -Recurse -Force ".venv" +} + +# When $EXTRA is empty powershell passes the argument as an empty argument to +# uv, so we need to explicitly check the argument and only pass it if it is not +# empty to avoid uv from erroring +if ($EXTRA) { + uv venv .venv -p $PYTHON_VERSION $EXTRA +} else { + uv venv .venv -p $PYTHON_VERSION +} + +.\.venv\Scripts\Activate.ps1 +uv run python --version +uv pip install $(Get-ChildItem -Path .\dist\ -Recurse -Filter "crunch64-*-abi3-*") +uv run python -c "import crunch64; print(crunch64.__version__)" diff --git a/.github/scripts/ci_check_wheel.sh b/.github/scripts/ci_check_wheel.sh new file mode 100755 index 0000000..8df0036 --- /dev/null +++ b/.github/scripts/ci_check_wheel.sh @@ -0,0 +1,33 @@ +# This script checks a given Python wheel inside the `dist` is installable in a +# given Python version. +# +# It recieves the following arguments: +# - The python version to check, it must be compatible with uv. +# - A key value to allow searching for the wheel in the `dist` folder. Only a +# single wheel in the folder must contain this value in its name. +# Recommended values: abi3, cp314t, pypy39 and similar values. +# - (OPTIONAL) A single aditional flag to pass to `uv venv`. +# Usually `--managed-python`. + +# Any change made here should be made in `ci_check_wheel.ps1` too. + +PYTHON_VERSION=$1 +KEY=$2 +EXTRA=$3 + +# Exit with an error value if any command produces an error. +set -e + +# We make a venv with the Python version we were told to. +rm -rf .venv +uv venv -p $PYTHON_VERSION $EXTRA +source .venv/bin/activate +# Allows us to check we are actually using the requested Python version. +uv run python --version + +# We install the wheel by looking it up in the dist folder. +# We need to do a `find` command here because we don't know the exact name of +# the wheel (it can be affected by package version, arch, python version, etc.). +uv pip install $(find ./dist/ -name "crunch64-*-$KEY*") +# Check something basic to make sure it was installed correctly. +uv run python -c "import crunch64; print(crunch64.__version__)" diff --git a/.github/workflows/maturin_upload_pypi.yml b/.github/workflows/maturin_upload_pypi.yml index a9f0587..b0de0ac 100644 --- a/.github/workflows/maturin_upload_pypi.yml +++ b/.github/workflows/maturin_upload_pypi.yml @@ -1,4 +1,4 @@ -# This file is autogenerated by maturin v1.9.6 +# This file is autogenerated by maturin v1.11.4 # To update, run # # maturin generate-ci github @@ -13,42 +13,55 @@ on: permissions: contents: read +# At the time of writing, PyO3 has a hard minimal Python version of 3.7, so +# there's no use trying to go any lower than that. + jobs: linux: runs-on: ${{ matrix.platform.runner }} strategy: + fail-fast: false matrix: platform: - runner: ubuntu-22.04 target: x86_64 + python_version_min: '3.7' + python_version_max: '3.14' - runner: ubuntu-22.04 target: x86 + python_version_min: '3.7' + python_version_max: '3.14' - runner: ubuntu-22.04 target: aarch64 + python_version_min: '3.7' + python_version_max: '3.14' - runner: ubuntu-22.04 target: armv7 + python_version_min: '3.7' + python_version_max: '3.14' - runner: ubuntu-22.04 target: s390x + python_version_min: '3.7' + python_version_max: '3.14' - runner: ubuntu-22.04 target: ppc64le + python_version_min: '3.7' + python_version_max: '3.14' steps: - uses: actions/checkout@main - uses: actions/setup-python@v6 with: python-version: | - 3.8 - 3.14 - 3.x - pypy3.8 + ${{ matrix.platform.python_version_min }} + 3.14t pypy3.9 pypy3.10 pypy3.11 - name: Build wheels uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 with: target: ${{ matrix.platform.target }} - args: --release --out ../dist --find-interpreter + args: --release --out ../dist -i python${{ matrix.platform.python_version_min }} python3.14t pypy3.9 pypy3.10 pypy3.11 sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} manylinux: auto working-directory: lib/ @@ -59,37 +72,72 @@ jobs: path: dist if-no-files-found: error + # Test the built wheels are installable in the min and max Python + # versions we are about. + # We do this by installing the Python version in a venv using uv, + # installing the built wheel into the venv and running some simple + # command like printing the crunch64 version from within Python. + # We can only test the wheels that matches the architecture of the runner + # so we hope the other built wheels will just work, hopefully. + - name: Install uv + uses: astral-sh/setup-uv@v7 + if: ${{ matrix.platform.target == 'x86_64' }} + - name: Test wheels with min version (${{ matrix.platform.python_version_min }}) + if: ${{ matrix.platform.target == 'x86_64' }} + # Check the built wheel is installable on the oldest Python supported + run: | + .github/scripts/ci_check_wheel.sh ${{ matrix.platform.python_version_min }} abi3 + - name: Test wheels with max version (${{ matrix.platform.python_version_max }}) + if: ${{ matrix.platform.target == 'x86_64' }} + # Check the built wheel is installable on the newest Python we know of + run: | + .github/scripts/ci_check_wheel.sh ${{ matrix.platform.python_version_max }} abi3 --managed-python + + - name: Test free threaded wheels + if: ${{ matrix.platform.target == 'x86_64' }} + run: | + .github/scripts/ci_check_wheel.sh 3.14t cp314t --managed-python + + - name: Test pypy wheels + if: ${{ matrix.platform.target == 'x86_64' }} + run: | + .github/scripts/ci_check_wheel.sh pypy3.9 pypy39 --managed-python + .github/scripts/ci_check_wheel.sh pypy3.10 pypy310 --managed-python + .github/scripts/ci_check_wheel.sh pypy3.11 pypy311 --managed-python + musllinux: runs-on: ${{ matrix.platform.runner }} strategy: + fail-fast: false matrix: platform: - runner: ubuntu-22.04 target: x86_64 + python_version_min: '3.7' - runner: ubuntu-22.04 target: x86 + python_version_min: '3.7' - runner: ubuntu-22.04 target: aarch64 + python_version_min: '3.7' - runner: ubuntu-22.04 target: armv7 + python_version_min: '3.7' steps: - uses: actions/checkout@main - uses: actions/setup-python@v6 with: python-version: | - 3.8 - 3.14 - 3.x - pypy3.8 + ${{ matrix.platform.python_version_min }} + 3.14t pypy3.9 pypy3.10 pypy3.11 - name: Build wheels uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 with: target: ${{ matrix.platform.target }} - args: --release --out ../dist --find-interpreter + args: --release --out ../dist -i python${{ matrix.platform.python_version_min }} python3.14t pypy3.9 pypy3.10 pypy3.11 sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} manylinux: musllinux_1_2 working-directory: lib/ @@ -100,33 +148,42 @@ jobs: path: dist if-no-files-found: error + # We can't test musllinux wheels on manylinux runners + windows: runs-on: ${{ matrix.platform.runner }} strategy: + fail-fast: false matrix: platform: - runner: windows-latest target: x64 + python_arch: x64 + python_version_min: '3.7' + python_version_max: '3.14' - runner: windows-latest target: x86 + python_arch: x86 + python_version_min: '3.7' + python_version_max: '3.14' + - runner: windows-11-arm + target: aarch64 + python_arch: arm64 + python_version_min: '3.11' + python_version_max: '3.14' steps: - uses: actions/checkout@main - uses: actions/setup-python@v6 with: - # We can't use pypy3.8 nor pypy3.11 in CI python-version: | - 3.8 - 3.14 - 3.x - pypy3.9 - pypy3.10 - architecture: ${{ matrix.platform.target }} + ${{ matrix.platform.python_version_min }} + 3.14t + architecture: ${{ matrix.platform.python_arch }} - name: Build wheels uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 with: target: ${{ matrix.platform.target }} - args: --release --out ../dist --find-interpreter + args: --release --out ../dist -i python${{ matrix.platform.python_version_min }} python3.14t sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} working-directory: lib/ - name: Upload wheels @@ -136,33 +193,47 @@ jobs: path: dist if-no-files-found: error + - name: Install uvci_check_wheel + uses: astral-sh/setup-uv@v7 + - name: Test wheels with min version (${{ matrix.platform.python_version_min }}) + if: ${{ matrix.platform.target == 'x64' }} + # Check the built wheel is installable on the oldest Python supported + run: | + .github/scripts/ci_check_wheel.ps1 ${{ matrix.platform.python_version_min }} abi3 + - name: Test wheels with max version (${{ matrix.platform.python_version_max }}) + if: ${{ matrix.platform.target == 'x64' }} + run: | + .github/scripts/ci_check_wheel.ps1 ${{ matrix.platform.python_version_max }} abi3 --managed-python + macos: runs-on: ${{ matrix.platform.runner }} strategy: + fail-fast: false matrix: platform: - runner: macos-15-intel target: x86_64 - - runner: macos-14 + python_version_min: '3.7' + python_version_max: '3.14' + - runner: macos-latest target: aarch64 + python_version_min: '3.8' + python_version_max: '3.14' steps: - uses: actions/checkout@main - uses: actions/setup-python@v6 with: - # We can't use pypy3.8 in CI python-version: | - 3.8 - 3.14 - 3.x + ${{ matrix.platform.python_version_min }} + 3.14t pypy3.9 pypy3.10 pypy3.11 - name: Build wheels uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 with: target: ${{ matrix.platform.target }} - args: --release --out ../dist --find-interpreter + args: --release --out ../dist -i python${{ matrix.platform.python_version_min }} python3.14t pypy3.9 pypy3.10 pypy3.11 sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} working-directory: lib/ - name: Upload wheels @@ -172,14 +243,33 @@ jobs: path: dist if-no-files-found: error + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Test wheels with min version (${{ matrix.platform.python_version_min }}) + # Check the built wheel is installable on the oldest Python supported + run: | + .github/scripts/ci_check_wheel.sh ${{ matrix.platform.python_version_min }} abi3 + - name: Test wheels with max version (${{ matrix.platform.python_version_max }}) + # Check the built wheel is installable on the oldest Python supported + run: | + .github/scripts/ci_check_wheel.sh ${{ matrix.platform.python_version_max }} abi3 --managed-python + + - name: Test free threaded wheels + run: | + .github/scripts/ci_check_wheel.sh 3.14t cp314t --managed-python + + - name: Test pypy wheels + run: | + .github/scripts/ci_check_wheel.sh pypy3.9 pypy39 --managed-python + .github/scripts/ci_check_wheel.sh pypy3.10 pypy310 --managed-python + .github/scripts/ci_check_wheel.sh pypy3.11 pypy311 --managed-python + sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@main - name: Build sdist uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 with: command: sdist args: --out ../dist @@ -223,17 +313,18 @@ jobs: # Used to generate artifact attestation attestations: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 - name: Generate artifact attestation - uses: actions/attest-build-provenance@v2 + uses: actions/attest-build-provenance@v3 with: subject-path: 'wheels-*/*' + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Publish to PyPI (dry-run) + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + run: uv publish 'wheels-*/*' --dry-run - name: Publish to PyPI if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: PyO3/maturin-action@v1 + run: uv publish 'wheels-*/*' env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 - MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_PASSWORD }} - with: - command: upload - args: --non-interactive --skip-existing wheels-*/* + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/python_bindings.yml b/.github/workflows/python_bindings.yml index 46473e2..9d8f8c6 100644 --- a/.github/workflows/python_bindings.yml +++ b/.github/workflows/python_bindings.yml @@ -6,11 +6,12 @@ on: [push, pull_request] jobs: tests_cases: name: Test for Python ${{ matrix.py_version }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: py_version: + - '3.7' - '3.8' - '3.9' - '3.10' diff --git a/CHANGELOG.md b/CHANGELOG.md index 262d353..4fa871f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.6.0] - 2026-12-01 +## [0.6.1] - 2026-01-11 + +### Added + +- CI now checks wheels are being installable in the oldest and newest Python versions supported. + - Only the wheels built for the architectures of the CI runner are actually tested, this currently means Windows x64, Linux x86_64, MacOS x86_64 and MacOS aarch64. +- Prebuilt wheels for freethreaded Python 3.14. + +### Fixed + +- Fix prebuilt binaries not being compatible with older Python versions. + - CI now uses the oldest available Python version for each arch/os combination, so users don't have to build the project themselves. + - Wheels should all support the Python `abi3`, meaning they should be future proof and installable by future Python versions. + +## [0.6.0] - 2026-01-01 ### Added @@ -121,7 +135,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Python bindings. - C bindings. -[unreleased]: https://github.com/decompals/crunch64/compare/0.6.0...HEAD +[unreleased]: https://github.com/decompals/crunch64/compare/0.6.1...HEAD +[0.6.1]: https://github.com/decompals/crunch64/compare/0.6.0...0.6.1 [0.6.0]: https://github.com/decompals/crunch64/compare/0.5.4...0.6.0 [0.5.4]: https://github.com/decompals/crunch64/compare/0.5.3...0.5.4 [0.5.3]: https://github.com/decompals/crunch64/compare/0.5.2...0.5.3 diff --git a/Cargo.lock b/Cargo.lock index b8cd98b..87a0700 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,7 +130,7 @@ dependencies = [ [[package]] name = "crunch64" -version = "0.6.0" +version = "0.6.1" dependencies = [ "crc32fast", "pyo3", @@ -140,7 +140,7 @@ dependencies = [ [[package]] name = "crunch64-cli" -version = "0.6.0" +version = "0.6.1" dependencies = [ "clap", "crunch64", diff --git a/Cargo.toml b/Cargo.toml index b95e8ad..527dc33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] # Version should be synced with lib/pyproject.toml and lib/crunch64/__init__.py -version = "0.6.0" +version = "0.6.1" edition = "2021" repository = "https://github.com/decompals/crunch64" license = "MIT" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 46579a6..1adc0b4 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,5 +12,5 @@ name = "crunch64" path = "src/main.rs" [dependencies] -crunch64 = { version = "0.6.0", path = "../lib" } +crunch64 = { version = "0.6.1", path = "../lib" } clap = { version = "4.4.11", features = ["derive"] } diff --git a/lib/crunch64/__init__.py b/lib/crunch64/__init__.py index 3362a43..8330fc7 100644 --- a/lib/crunch64/__init__.py +++ b/lib/crunch64/__init__.py @@ -3,7 +3,7 @@ from __future__ import annotations # Version should be synced with lib/Cargo.toml and lib/pyproject.toml -__version_info__ = (0, 6, 0) +__version_info__ = (0, 6, 1) __version__ = ".".join(map(str, __version_info__)) __author__ = "decompals" diff --git a/lib/pyproject.toml b/lib/pyproject.toml index 4d1be01..82fa5b8 100644 --- a/lib/pyproject.toml +++ b/lib/pyproject.toml @@ -1,9 +1,9 @@ [project] name = "crunch64" # Version should be synced with lib/Cargo.toml and lib/crunch64/__init__.py -version = "0.6.0" +version = "0.6.1" description = "A library for handling common compression formats for N64 games" -requires-python = ">=3.0" +requires-python = ">=3.7" # Required by PyO3 dependencies = [ ] classifiers = [ diff --git a/lib/src/lib.rs b/lib/src/lib.rs index bb38bd1..7b04297 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -51,7 +51,7 @@ impl std::convert::From for PyErr { } #[cfg(feature = "python_bindings")] -#[pymodule] +#[pymodule(gil_used = false)] fn crunch64(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(yay0::python_bindings::decompress_yay0, m)?)?; m.add_function(wrap_pyfunction!(yay0::python_bindings::compress_yay0, m)?)?;