diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..9fd25ff --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,186 @@ +# This file is autogenerated by maturin v1.12.6 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + - runner: ubuntu-22.04 + target: s390x + - runner: ubuntu-22.04 + target: ppc64le + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + python_arch: x64 + - runner: windows-latest + target: x86 + python_arch: x86 + - runner: windows-11-arm + target: aarch64 + python_arch: arm64 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.13 + architecture: ${{ matrix.platform.python_arch }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-15-intel + target: x86_64 + - runner: macos-latest + target: aarch64 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v6 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, windows, macos, sdist] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v7 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v3 + with: + subject-path: 'wheels-*/*' + - name: Install uv + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: astral-sh/setup-uv@v7 + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: uv publish 'wheels-*/*' + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..396aac3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: test + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + pytest: + name: pytest (flake CPython) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + with: + extra-conf: | + experimental-features = nix-command flakes + + - name: Magic Nix cache + uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Build testEnv + run: nix build .#testEnv --print-build-logs + + - name: Run tests + run: ./result/bin/python -m pytest tests/ -v diff --git a/Cargo.toml b/Cargo.toml index 266caa2..76f152a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,5 @@ crate-type = ["cdylib"] [dependencies] -pyo3 = "0.27.0" +pyo3 = { version = "0.27.0", features = ["abi3-py38"] } zlib-rs = "0.6.3" diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ef8308 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# zlib-py + +

+ + Rust for CPython + +

+ +A small pyo3 extension that exposes [`zlib-rs`](https://crates.io/crates/zlib-rs) to Python. +As a small proof of concept for the [Rust for CPython](https://github.com/Rust-for-CPython) effort. + +## Building + +### With Nix + +```sh +nix build # Python env with zlib_py importable, at ./result +nix run .#python -- -c 'import zlib_py; print(zlib_py.compress(...))' +nix develop # cargo + rustc + maturin + the pinned python on PATH +``` + +The flake fetches CPython and pyo3 at the pinned revs (we'll attempt to keep +this close to HEAD in both cases), then builds the extension with +`maturinBuildHook`. The cargo registry is vendored in a fixed-output derivation +so the actual build runs offline. + +### Without Nix + +```sh +uv sync +uv run maturin develop +uv run python -c 'import zlib_py; print(zlib_py.compress(...))' +``` + +This uses whatever CPython + pyo3 your environment resolves (no grantees of working). + + +## Rust for CPython — links + +- [Official GitHub org](https://github.com/Rust-for-CPython) +- [Pre-PEP discussion thread](https://discuss.python.org/t/pre-pep-rust-for-cpython/104906) +- [Latest progress update (2026-04)](https://blog.python.org/2026/04/rust-for-cpython-2026-04/) + +## Acknowledgements + +Prior art and inspiration: [`farhaanaliii/zlib-rs-python`](https://github.com/farhaanaliii/zlib-rs-python) — a separate pyo3 binding to `zlib-rs` with a broader stdlib-`zlib`-compatible surface (`compressobj`, `decompressobj`, checksums). If you want a more complete drop-in replacement today, look there. + +## License + +MIT — see [LICENSE](./LICENSE). diff --git a/flake.nix b/flake.nix index 0899a0c..a47b37a 100644 --- a/flake.nix +++ b/flake.nix @@ -19,7 +19,7 @@ owner = "python"; repo = "cpython"; rev = cpythonRev; - sha256 = "sha256-nbOwTSb3MTICxi0d8nIlMOp2htfAkOLfNnzT2u2TK4k="; + sha256 = "sha256-QtzHQqVUHzTyO9WzlNSz2tZ+Pab82nxPOn6E2RrudSo="; }; # pyo3 source fetched outside the FOD so we can use path patches — @@ -32,22 +32,74 @@ sha256 = "sha256-nbOwTSb3MTICxi0d8nIlMOp2htfAkOLfNnzT2u2TK4k="; }; - # Custom CPython: take a current nixpkgs python derivation as scaffolding - # (we only reuse its configure/build wiring), swap the source for our - # pinned git rev, and add autoreconfHook so the missing ./configure is - # regenerated before nixpkgs' configurePhase runs. + # Custom CPython: take a current nixpkgs python derivation as + # scaffolding (we only reuse its configure/build wiring) and swap the + # source for our pinned git rev. The tarball-shaped source from + # fetchFromGitHub already contains a pregenerated `./configure`, so + # there's no need for autoreconfHook here. + # + # Two-stage override: + # 1. `.override { self = customPython; ... }` re-runs the python + # derivation function with `self` bound to *our* final + # derivation. This is the fix-point that makes passthru attrs + # (`pkgs`, `withPackages`, `pythonForBuild`) reference the + # customized interpreter rather than the unmodified scaffold. + # 2. `.overrideAttrs` then swaps `src` / `version` on top. Passthru + # is already wired to `self`, so withPackages picks up the + # overridden interpreter correctly. + # + # Plain `.overrideAttrs` alone leaves passthru pinned to the original + # python — `customPython.withPackages` silently builds a stock env. # - # The base attribute (`python313`) is just the scaffold — the resulting - # interpreter's actual version comes from the cpython commit. If the - # commit lives on a different branch, swap the scaffold accordingly. + # The base attribute (`python315`) determines the build *scaffolding* + # (configure flags, library deps); the actual interpreter version + # comes from the cpython commit. Pick a scaffold close to the target + # version so configure flags match. cpythonShortRev = builtins.substring 0 7 cpythonRev; - customPython = pkgs.python313.overrideAttrs (old: { + customPython = (pkgs.python315.override (old: { + self = customPython; + # Tell nixpkgs what version we're *actually* building so installed + # paths (lib/python3.16/...) line up with what cpython 3.16-alpha + # writes. Without this nixpkgs computes paths against the scaffold + # version (3.15) and postInstall steps like stripTests fail when + # they can't find lib/python3.15/test/__init__.py. + sourceVersion = { + major = "3"; + minor = "16"; + patch = "0"; + suffix = "a-${cpythonShortRev}"; + }; + # Cross-compile passthru looks up `pkgsBuildTarget.${pythonAttr}`. + # nixpkgs has no `python316` attribute yet, so pin to the closest + # one (`python315`) for that lookup. Build-host side only — the + # actual interpreter is still our overridden 3.16 derivation. + pythonAttr = "python315"; + # Unused (src is overridden below) but the scaffold function + # demands a non-null hash arg. + hash = pkgs.lib.fakeHash; + })).overrideAttrs (old: { pname = "cpython-git"; version = cpythonShortRev; src = cpythonSrc; - nativeBuildInputs = (old.nativeBuildInputs or []) - ++ [ pkgs.autoreconfHook ]; doCheck = false; + + # nixpkgs' preConfigure does a `substituteInPlace configure + # --replace-fail 'libmpdec_machine=universal' …` to defeat Darwin + # universal-build autodetection. CPython 3.16+ rewrote the + # detection and the literal string is gone, so --replace-fail + # aborts the build. Patch our copy to use --replace-quiet, which + # tolerates a missing pattern; `export PYTHON_DECIMAL_WITH_MACHINE` + # earlier in the same script still does the heavy lifting. + preConfigure = builtins.replaceStrings + [ "--replace-fail 'libmpdec_machine=universal'" ] + [ "--replace-quiet 'libmpdec_machine=universal'" ] + (old.preConfigure or ""); + + # Bumping sourceVersion to 3.16 makes nixpkgs look for patches + # under cpython/3.16/, which doesn't exist. The 3.15 patches + # (no-ldconfig, virtualenv-permissions, mimetypes) apply cleanly + # to 3.16-alpha, so re-pin to them. + patches = pkgs.python315.drvAttrs.patches; }); # Two flavours of the same [patch.crates-io] block: