Skip to content

ci: release workflow fixes (node + python)#2043

Merged
ArthurZucker merged 8 commits intomainfrom
release-workflow-fixes
Apr 27, 2026
Merged

ci: release workflow fixes (node + python)#2043
ArthurZucker merged 8 commits intomainfrom
release-workflow-fixes

Conversation

@ArthurZucker
Copy link
Copy Markdown
Collaborator

Picks up the release-pipeline fixes that surfaced while tagging `v0.23.1rc0`. The version-bump commit on `v0.23-release` (PR #2032) is intentionally not included here — that one stays release-specific.

What's in here

  • node-release.yml

    • `--zig` → `--cross-compile` (napi-rs/cli v3 renamed the flag).
    • Combine the universal macOS binary with `lipo` directly instead of `napi universal --dir …` (v3 dropped that syntax — `napi universalize` expects `napi artifacts` layout, `lipo` is the same thing the wrapper was running internally).
    • Add `napi create-npm-dirs` before `yarn artifacts` so target triples without a checked-in `npm//` template (e.g. `darwin-universal`) don't `ENOENT` at publish time.
    • Skip the `publish:` job on `rc*` tags (build + universal-macOS still run end-to-end), and bump its permission to `contents: write` so `napi prepublish` can create the GitHub Release on real releases.
  • python-release.yml

    • Drop `3.14t` from the `windows-11-arm` matrix entry — `actions/python-versions` ships a broken `python-3.14.4-win32-arm64-freethreaded` package (0-byte `python.exe`).
    • Build proper non-abi3 wheels for 3.14t on the platforms that have a working free-threaded interpreter, via a `flavor: ft` discriminator. Each 3.14t row is a single YAML flow entry; the `--no-default-features --features pyo3/extension-module` and `--interpreter 3.14t` are derived from `flavor` in the maturin invocation.
  • bindings/python/pyproject.toml

    • `requires-python` raised to `>=3.10` (matches the `abi3-py310` baseline; pip won't offer the wheel to 3.9 users).
    • Drop `Python :: 3.9` trove classifier, add `Python :: 3.14`.

Not in this PR

  • The `update version and add locks` commits from `v0.23-release`. Those stay on the release branch.

Two release-blocker fixes that surfaced when tagging v0.23.1rc0:

1. node-release.yml: napi-rs/cli v3 (which we adopted in #2034) renamed
   the cross-compile flag from `--zig` to `--cross-compile` (or `-x`).
   The build steps still passed `--zig`, so every linux-musl /
   linux-aarch64 / armv7 entry failed with
       Unknown Syntax Error: Unsupported option name ("--zig").
   Replace `--zig` with `--cross-compile` everywhere (build commands +
   the `contains(...)` gate on the zig setup step).

2. python-release.yml: drop 3.14t from the windows-11-arm matrix entry.
   actions/python-versions currently ships a broken
   `python-3.14.4-win32-arm64-freethreaded` package (0-byte python.exe,
   pip install fails with `ModuleNotFoundError: encodings`). Keep the
   regular 3.14 arm64 build; 3.14t arm64 windows can return when the
   upstream package is fixed.
The pre-existing matrix had `interpreter: "3.14 3.14t"` on most
entries, which made maturin build a single wheel per cell intended to
serve both interpreters. With the project's `abi3` cargo feature
enabled by default that wheel was abi3-compiled — fine for 3.14, but
3.14t can't load abi3 extensions (no limited API on free-threaded
Python), so the published cp314t wheels would fail to import.

Split into two flavors via a discriminator:

- All existing entries set `interpreter: "3.14"` only (so the abi3
  wheel they produce serves 3.10–3.14 cleanly).

- New `flavor: ft` include: entries for the platforms we want 3.14t
  wheels on (linux x86_64/aarch64 manylinux+musllinux, macos x86_64/
  aarch64, windows x86_64). Each passes
  `extra-build-args: --no-default-features --features pyo3/extension-module`
  which drops the abi3 cargo feature so maturin produces a proper
  cp314t-tagged, non-abi3 wheel that actually loads on free-threaded
  Python.

- Base matrix gets `flavor: [abi3]` so the `flavor: ft` includes
  create new matrix cells instead of merging with the abi3 combos.

- Artifact name suffixed with `${{ matrix.flavor || 'abi3' }}` so the
  abi3 and ft wheels for the same os/target/manylinux don't collide
  on upload-artifact.

- Skipped 3.14t for windows-11-arm because the upstream 3.14t arm64
  package is currently broken (0-byte python.exe). Skipped for the
  niche linux targets (i686, armv7, ppc64le, s390x) too — easy to add
  later.

Job name now includes the interpreter for log readability.
The 3.14t matrix cells only differ from their abi3 siblings on three
fields: `flavor`, `interpreter`, and `extra-build-args`. The latter
two are fully determined by the first, so derive them in the maturin
invocation:

  args: >-
    --release --out dist
    --interpreter ${{ matrix.flavor == 'ft' && '3.14t' || matrix.interpreter || '3.14' }}
    ${{ matrix.flavor == 'ft' && '--no-default-features --features pyo3/extension-module' || '' }}

Each 3.14t cell is now a single-line YAML flow entry — the only thing
the matrix says about it is `flavor: ft`. Drops ~30 lines of
repetition without changing behaviour.
napi-rs/cli v3 renamed `napi universal` -> `napi universalize` and
dropped the `--dir` flag — it now expects per-target .node files in
the standard `npm/<triple>/` layout that `napi artifacts` produces,
not in a flat directory.

Rather than insert a `napi artifacts` step + reorganize, just call
`lipo` directly. The two darwin .node files we already download into
./artifacts have known names; combining them into the universal
binary is one line. That's exactly what `napi universal` was wrapping
internally.
The crate is built with `abi3-py310`, so 3.10 is the actual minimum
the wheels can load on. Update the PyPI metadata accordingly:

- requires-python: ">=3.9" -> ">=3.10"
- drop the "Python :: 3.9" trove classifier
- add the "Python :: 3.14" trove classifier

This stops pip from offering the wheel to 3.9 users (where it would
fail to import) and makes the PyPI page accurate.
`yarn artifacts` (napi-cli v3 `napi artifacts`) writes each per-target
.node file into `npm/<triple>/`. The repo has hand-written templates
for most triples but darwin-universal was added to `napi.targets`
without one, so the publish step failed with:

  ENOENT: no such file or directory, open
  '.../bindings/node/npm/darwin-universal/tokenizers.darwin-universal.node'

Add `napi create-npm-dirs` before `yarn artifacts` — it scans
`napi.targets` and creates any missing dirs (with a generated
package.json) from scratch. Idempotent for triples that already have
hand-written templates.
Two issues stacked when v0.23.1rc0 hit the publish job:

1. `napi prepublish` POSTs to /repos/.../releases to create a
   GitHub Release and got 403 — the workflow's default GITHUB_TOKEN
   has only `contents: read`, but creating a release needs
   `contents: write`. Bump the publish-job permission.

2. `napi prepublish` then runs `npm publish` for each per-platform
   sub-package; modern npm refuses to auto-route a `0.23.1-rc0`
   version to `latest` and demands an explicit `--tag`. Rather than
   wire up `--tag rc` in every sub-publish (and risk an rc landing
   on `latest` by accident), gate the entire publish job on
   `!contains(github.ref, 'rc')`. Matches the existing pattern in
   rust-release.yml.

rc tags still run the build + universal-macOS matrix end-to-end —
they just skip the registry push, so they're useful for verifying
the cross-compile / lipo / artifact-collection flow before a real
release.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Node and Python release GitHub Actions workflows to match upstream CLI/tooling changes and adjust release behavior, plus aligns Python packaging metadata with the project’s abi3 baseline.

Changes:

  • Node release workflow: update napi-rs CLI flags, switch universal macOS build to direct lipo, add create-npm-dirs, and skip publishing on rc tags while allowing GitHub Release creation on stable tags.
  • Python release workflow: restructure the build matrix to add free-threaded (3.14t) wheel variants via a flavor discriminator and drop the broken Windows ARM 3.14t entry.
  • Python packaging metadata: raise requires-python to >=3.10 and update trove classifiers (drop 3.9, add 3.14).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
bindings/python/pyproject.toml Raises minimum supported Python version and updates trove classifiers.
.github/workflows/python-release.yml Adds flavor-based matrix entries for 3.14t wheels and adjusts build args / artifact naming.
.github/workflows/node-release.yml Updates cross-compilation flag usage, changes universal macOS combination strategy, and adjusts publish gating/permissions and packaging steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -188,6 +203,15 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: ./bindings/node/artifacts
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actions/download-artifact without name will extract each artifact into its own subdirectory under ./bindings/node/artifacts. yarn artifacts / napi artifacts typically expects the .node files to be directly in the artifacts directory, so this layout can cause it to miss the binaries and fail at publish time. Consider setting merge-multiple: true (or downloading with an explicit pattern and merging) so the .node files land in a flat ./bindings/node/artifacts/ directory.

Suggested change
path: ./bindings/node/artifacts
path: ./bindings/node/artifacts
merge-multiple: true

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/python-release.yml Outdated
Comment on lines +152 to +155
args: >-
--release --out dist
--interpreter ${{ matrix.flavor == 'ft' && '3.14t' || matrix.interpreter || '3.14' }}
${{ matrix.flavor == 'ft' && '--no-default-features --features pyo3/extension-module' || '' }}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For flavor: ft builds you set --interpreter to 3.14t, but actions/setup-python still installs the default 3.14 on macOS/Linux unless matrix.python-install is overridden. This will likely make maturin fail to find a 3.14t interpreter on at least the macOS flavor: ft matrix cells. Add python-install: "3.14t" (and any required python-architecture) to the flavor: ft include entries that run on host Python (macOS, and any non-container Linux builds).

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/python-release.yml Outdated
args: >-
--release --out dist
--interpreter ${{ matrix.flavor == 'ft' && '3.14t' || matrix.interpreter || '3.14' }}
${{ matrix.flavor == 'ft' && '--no-default-features --features pyo3/extension-module' || '' }}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The free-threaded path disables default features but then enables --features pyo3/extension-module directly. Since this repo already defines a crate feature ext-module = ["pyo3/extension-module"] in bindings/python/Cargo.toml, using --features ext-module would be less coupled to the dependency name and easier to maintain if the feature wiring changes.

Suggested change
${{ matrix.flavor == 'ft' && '--no-default-features --features pyo3/extension-module' || '' }}
${{ matrix.flavor == 'ft' && '--no-default-features --features ext-module' || '' }}

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/python-release.yml Outdated

build:
name: build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})
name: build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }} - ${{ matrix.interpreter || '3.14' }})
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job name reports matrix.interpreter || '3.14', but for the new flavor: ft matrix cells matrix.interpreter is unset, so these jobs will still display 3.14 even though the build is targeting 3.14t via the args expression. Consider deriving the displayed interpreter from matrix.flavor (or setting interpreter: "3.14t" on the flavor: ft include entries) to avoid confusion when debugging CI.

Suggested change
name: build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }} - ${{ matrix.interpreter || '3.14' }})
name: build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }} - ${{ matrix.flavor == 'ft' && '3.14t' || matrix.interpreter || '3.14' }})

Copilot uses AI. Check for mistakes.
…dule alias, name from flavor

Three review fixes folded into one commit:

1. macOS `flavor: ft` cells were missing `python-install: "3.14t"`;
   setup-python defaulted to 3.14, so maturin's `--interpreter 3.14t`
   would have failed to find an interpreter. Linux `manylinux`/
   `musllinux` ft cells run inside the maturin docker image (which
   ships 3.14t), so they don't need it. Windows ft already had it.

2. Switch the dropped-feature flag from `pyo3/extension-module` to the
   project-level `ext-module` feature alias defined in
   `bindings/python/Cargo.toml`. Less coupled to the dependency name —
   if the feature wiring changes (e.g. pyo3 renames the feature, or
   we add wrapper logic to ext-module), the workflow doesn't have to
   move with it.

3. Job-name `interpreter` segment now derives from `flavor`, so 3.14t
   cells display as `3.14t` instead of the misleading `3.14` they got
   from the unset `matrix.interpreter`.
@HuggingFaceDocBuilderDev
Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@ArthurZucker ArthurZucker merged commit bbe43ad into main Apr 27, 2026
30 checks passed
@ArthurZucker ArthurZucker deleted the release-workflow-fixes branch April 27, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants