diff --git a/.github/actions/build-wheel/action.yml b/.github/actions/build-wheel/action.yml index 25f75d1f8..3995caaaf 100644 --- a/.github/actions/build-wheel/action.yml +++ b/.github/actions/build-wheel/action.yml @@ -28,7 +28,7 @@ inputs: required: false default: "" python-tag: - description: "abi3 (covers 3.10..3.14 GIL builds) or a free-threaded interpreter such as 3.13t / 3.14t" + description: "abi3 (covers 3.10..3.14 GIL builds), abi3t (covers 3.15+, GIL and free-threaded; built with 3.15t), or a version-specific free-threaded interpreter such as 3.14t" required: true build-mode: description: "release or debug" @@ -67,6 +67,14 @@ runs: BUILD_ARGS="--features ${FEATURES}" else # Free-threaded build: disable abi3, force mimalloc back in, pin interpreter. + # `abi3t` builds a PEP 803 wheel (cp315-abi3t), importable on 3.15+ + # GIL and free-threaded builds, using the 3.15 free-threaded + # interpreter. Older free-threaded tags get version-specific wheels. + ABI_FEATURE="" + if [ "$TAG" = "abi3t" ]; then + ABI_FEATURE="abi3t," + TAG="3.15t" + fi if [ "${RUNNER_OS:-}" = "Windows" ]; then # Windows free-threaded builds ship as `python.exe` (no `tN` # suffix). Resolve sys.executable so the path is independent of @@ -83,7 +91,7 @@ runs: else INTERP="python${TAG}" fi - BUILD_ARGS="--no-default-features --features mimalloc,${FEATURES} --interpreter ${INTERP}" + BUILD_ARGS="--no-default-features --features mimalloc,${ABI_FEATURE}${FEATURES} --interpreter ${INTERP}" fi if [ "${{ inputs.build-mode }}" = "release" ]; then BUILD_ARGS="--release --strip ${BUILD_ARGS}" @@ -98,7 +106,7 @@ runs: with: target: ${{ inputs.target }} manylinux: ${{ inputs.manylinux }} - maturin-version: "1.13.3" + maturin-version: "1.15.0" args: ${{ steps.args.outputs.args }} rustup-components: rust-std @@ -107,4 +115,4 @@ runs: shell: bash # Use `uvx` so maturin is available even when `uv sync` was skipped # (free-threaded matrix entries don't pre-populate the project venv). - run: uvx maturin@1.13.3 build ${{ steps.args.outputs.args }} + run: uvx maturin@1.15.0 build ${{ steps.args.outputs.args }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 23570ff70..f6c4ff766 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -144,7 +144,7 @@ jobs: strategy: fail-fast: false matrix: - python-tag: ["abi3", "3.14t"] + python-tag: ["abi3", "3.14t", "abi3t"] steps: - uses: actions/checkout@v7 @@ -235,7 +235,7 @@ jobs: strategy: fail-fast: false matrix: - python-tag: ["abi3", "3.14t"] + python-tag: ["abi3", "3.14t", "abi3t"] steps: - uses: actions/checkout@v7 @@ -298,7 +298,7 @@ jobs: fail-fast: false matrix: os: [macos-latest, windows-latest] - python-tag: ["abi3", "3.14t"] + python-tag: ["abi3", "3.14t", "abi3t"] steps: - uses: actions/checkout@v7 @@ -321,7 +321,8 @@ jobs: if: matrix.python-tag != 'abi3' uses: actions/setup-python@v7 with: - python-version: ${{ matrix.python-tag }} + # abi3t wheels are built with the 3.15 free-threaded interpreter. + python-version: ${{ matrix.python-tag == 'abi3t' && '3.15' || matrix.python-tag }} freethreaded: true - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 @@ -381,7 +382,7 @@ jobs: strategy: fail-fast: false matrix: - python-tag: ["abi3", "3.14t"] + python-tag: ["abi3", "3.14t", "abi3t"] steps: - uses: actions/checkout@v7 @@ -404,7 +405,8 @@ jobs: if: matrix.python-tag != 'abi3' uses: actions/setup-python@v7 with: - python-version: ${{ matrix.python-tag }} + # abi3t wheels are built with the 3.15 free-threaded interpreter. + python-version: ${{ matrix.python-tag == 'abi3t' && '3.15' || matrix.python-tag }} freethreaded: true - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1670e2bd0..dbb639303 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,8 +16,9 @@ # under the License. # Reusable workflow for running tests. -# Single matrix covers both GIL (abi3 wheel) and free-threaded -# (per-interpreter wheels) builds. +# Single matrix covers GIL (abi3 wheel), free-threaded (per-interpreter +# wheels) and 3.15+ (abi3t wheel, loadable on both GIL and free-threaded +# builds) interpreters. name: Test @@ -43,8 +44,12 @@ jobs: - { python-version: "3.12", wheel-tag: "abi3", freethreaded: false } - { python-version: "3.13", wheel-tag: "abi3", freethreaded: false } - { python-version: "3.14", wheel-tag: "abi3", freethreaded: false } - # Free-threaded builds — one wheel per interpreter. + - { python-version: "3.15", wheel-tag: "abi3", freethreaded: false } + # The abi3t wheel (PEP 803) loads on both GIL and free-threaded 3.15+. + - { python-version: "3.15", wheel-tag: "abi3t", freethreaded: false } + # Free-threaded builds — one wheel per interpreter (abi3t covers 3.15+). - { python-version: "3.14t", wheel-tag: "3.14t", freethreaded: true } + - { python-version: "3.15t", wheel-tag: "abi3t", freethreaded: true } steps: - uses: actions/checkout@v7 diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index c5f1e0167..bf1d8fe19 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -81,6 +81,10 @@ default = ["mimalloc", "abi3"] # Mutually exclusive with free-threaded interpreters (cp313t / cp314t); the # free-threaded wheel build must pass --no-default-features. abi3 = ["pyo3/abi3", "pyo3/abi3-py310"] +# PEP 803 stable ABI — single wheel covers Python 3.15+ on both GIL and +# free-threaded builds. Requires a 3.15+ host interpreter and maturin >= 1.14. +# Enable via `--no-default-features` so it is not combined with `abi3`. +abi3t = ["pyo3/abi3t", "pyo3/abi3t-py315"] protoc = ["datafusion-substrait/protoc"] substrait = ["dep:datafusion-substrait"] diff --git a/pyproject.toml b/pyproject.toml index b115b1198..0200a6c58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ # under the License. [build-system] -requires = ["maturin>=1.8.1"] +requires = ["maturin>=1.14.0"] build-backend = "maturin" [project] @@ -217,7 +217,7 @@ constraint-dependencies = ["typing-extensions>=4.15.0"] dev = [ "arro3-core==0.6.5", "codespell==2.4.1", - "maturin>=1.8.1", + "maturin>=1.14.0", "nanoarrow==0.8.0", "numpy>1.25.0;python_version<'3.14'", "numpy>=2.3.2;python_version>='3.14'", diff --git a/uv.lock b/uv.lock index 6ab83ac06..13d5ec9f7 100644 --- a/uv.lock +++ b/uv.lock @@ -538,7 +538,7 @@ requires-dist = [ dev = [ { name = "arro3-core", specifier = "==0.6.5" }, { name = "codespell", specifier = "==2.4.1" }, - { name = "maturin", specifier = ">=1.8.1" }, + { name = "maturin", specifier = ">=1.14.0" }, { name = "nanoarrow", specifier = "==0.8.0" }, { name = "numpy", marker = "python_full_version < '3.14'", specifier = ">1.25.0" }, { name = "numpy", marker = "python_full_version >= '3.14'", specifier = ">=2.3.2" },