Skip to content

Bindings release workflows - #1809

Draft
spacebear21 wants to merge 12 commits into
payjoin:masterfrom
spacebear21:bindings-release-workflows
Draft

Bindings release workflows#1809
spacebear21 wants to merge 12 commits into
payjoin:masterfrom
spacebear21:bindings-release-workflows

Conversation

@spacebear21

Copy link
Copy Markdown
Collaborator

Unreviewed slop from Fable 5, sketching out comprehensive release & publishing workflows for all downstream language bindings.

Pull Request Checklist

Please confirm the following before requesting review:

Six workflows repeat the same two steps to prepare a job for
nix develop: the Determinate nix installer and the magic nix cache
with its FlakeHub lookup disabled. Upcoming release workflows for
the language bindings would copy the pair four more times.

Move the pair into .github/actions/setup-nix. The Swatinem Rust
build cache stays an explicit separate step in the jobs that
compile, since it is unrelated to nix. No job's steps change beyond
the substitution.
Every registry publish job must refuse to push an artifact whose
version differs from what the pushed tag calls for. The check was
written inline in the NuGet publish job; the upcoming npm, pub.dev,
and PyPI publish jobs would each duplicate it.

Move the prefix-strip-and-compare into a verify-tag-version composite
action taking the tag prefix and the actual packed version. The NuGet
job keeps its locate step (exactly one .nupkg, version parsed from
the filename) and hands the result to the action. Local actions
resolve from the workspace, so the job gains a checkout step it
previously avoided; its contents: read permission already covers it.
Publishing ends the same way for every binding: download the packed
artifact, generate SHA256SUMS, and attach both to the GitHub release
for the pushed tag. The NuGet workflow carried this as an inline job;
the upcoming npm and PyPI publish paths would each copy it.

Move the job to release-assets.yml as a workflow_call and point the
C# github-release job at it. The artifact-pattern input downloads and
merges multiple artifacts for workflows that build one per platform,
and unzip-globs generalizes the C#-specific step that hashes native
libraries nested inside the package. Two behavior notes: the job now
runs on ubuntu-26.04 like the rest of the repo instead of
ubuntu-latest, and pre-release detection derives from a SemVer
hyphen in the tag's version (ignoring + build metadata) instead of
matching only -preview and -rc, which classifies all existing tags
identically.
The two per-target cargo builds run in the background and were
reaped with a bare `wait`, which always returns zero no matter how
the children exited. Under `set -e` a failed build therefore fell
through to lipo, which would reuse a stale library from an earlier
build if one was present. Wait on each build's PID so either
failure stops the script.
The script hardcoded --profile dev and --features _test-utils, each
annotated with a reminder that a real release must not ship the test
utils feature. Adopt the PAYJOIN_FFI_FEATURES / PAYJOIN_FFI_PROFILE
convention the C# generate_bindings.sh already uses: features default
to _test-utils and the profile to dev, so tests behave exactly as
before, while a release build sets PAYJOIN_FFI_FEATURES= and
PAYJOIN_FFI_PROFILE=release to get an optimized library with no
test-only APIs.
contrib/pack.sh builds the package from the maintained recent
lockfile and packs the npm tarball into artifacts/, mirroring how
contrib/test.sh drives the test build. The tarball ships only dist/
(wasm + compiled TypeScript), which is platform-independent, so a
single pack is the entire release build.

The test-utils napi addon is a dev-only helper for the integration
tests, so generate_bindings.sh gains a PAYJOIN_JS_BUILD_TEST_UTILS
toggle (default on, preserving test behavior) that the pack script
switches off.
Add the tag-gated publishing path to the JavaScript workflow,
mirroring the NuGet flow in csharp.yml: every run packs the tarball
and smoke-installs it on Linux and macOS; pushing a
payjoin-javascript-<version> tag additionally reruns the graph at
the tagged commit, then publishes the smoke-tested tarball to
npmjs.com and attaches it to the GitHub release.

The publish job verifies the tag matches the packed version, attests
build provenance, and authenticates with npm trusted publishing
(OIDC), so no long-lived registry token exists anywhere. It runs in
the release environment for a required-reviewer gate and also waits
for the build-and-test jobs at the tagged commit before anything
reaches the registry.

RELEASING.md records the release steps and the one-time npmjs.com
trusted-publisher and environment setup.
The script hardcoded --features dart,_test-utils --profile dev, so
every published release shipped bindings declaring test-only APIs
whose symbols are absent from consumer builds, a limitation
CONTRIBUTING.md called out. Adopt the PAYJOIN_FFI_FEATURES /
PAYJOIN_FFI_PROFILE convention the C# generate_bindings.sh already
uses: defaults are unchanged for tests, and a release build sets
PAYJOIN_FFI_FEATURES= (empty) to emit bindings with no test-only
declarations.
Add the tag-gated publishing path to the Dart workflow, following
the NuGet flow in csharp.yml adapted to a source-only registry:
pub.dev ships Dart source plus the native/ wrapper crate and
consumers compile the Rust through hook/build.dart, so there is no
artifact to pack, smoke test, or attach to a GitHub release.

Every run regenerates the production bindings and validates the
archive with a publish dry run (contrib/prepare-publish.sh). The dry
run tolerates only the known analyzer warning that generated
bindings unavoidably carry; any other validation finding fails the
job, since the real publish runs with --force, which would proceed
over a new warning. Pushing a payjoin-dart-<version> tag reruns
tests and verification at the tagged commit, then publish-pub checks
the tag against pubspec.yaml, regenerates the bindings, and
publishes through pub.dev automated publishing (OIDC), so no
long-lived credential exists anywhere. The job runs in the release
environment for a required-reviewer gate.

CONTRIBUTING.md's releasing section now documents the CI flow, the
one-time pub.dev automated-publishing setup, and the manual
fallback.
contrib/build-wheel.sh builds the production wheel into dist/ from
the maintained recent lockfile, mirroring how contrib/test.sh drives
the test build: release profile, no _test-utils, then uv build.

Two retags follow the build. The bindings load the bundled library
through ctypes, so the wheel is retagged py3-none instead of the
building interpreter's cpXY tag, letting every CPython covered by
requires-python install it. On Linux, auditwheel then verifies the
library's external dependencies and applies the manylinux tag the
binary satisfies, since PyPI rejects raw linux_x86_64 wheels; on
macOS the lipo fat dylib gets the universal2 tag. The wheel CLI and
auditwheel come from the python dev shell (the uv2nix venv omits
build backends).

contrib/test.sh now clears dist/ before building its wheel: the
release wheel carries different tags than the test build, so a
leftover copy would otherwise sit beside the fresh wheel and break
the install step's glob.
Add the tag-gated publishing path to the Python workflow, mirroring
the NuGet flow in csharp.yml: every run builds the release wheels
(manylinux x86_64 and macOS universal2) and smoke-installs them on
Linux, arm64 macOS, and Intel macOS with a CPython other than the
one they were built with, proving the py3-none retag. Pushing a
payjoin-python-<version> tag reruns the graph at the tagged commit,
then publishes the smoke-tested wheels to PyPI and attaches them to
the GitHub release.

The publish job verifies the tag matches the wheel versions (the
version is the payjoin-ffi crate version, read by setup.py), attests
build provenance, and uploads through PyPI trusted publishing (OIDC)
with PEP 740 attestations, so no long-lived registry token exists
anywhere. It runs in the release environment for a required-reviewer
gate and also waits for the build-and-test jobs at the tagged commit
before anything reaches the registry.

RELEASING.md records the release steps and the one-time PyPI
trusted-publisher and environment setup.
The wheel advertised build, setuptools, wheel, semantic-version, and
typing-extensions as exact-pinned runtime requirements, though the
package imports none of them: the generated bindings use only the
standard library and http.py needs httpx. Every consumer pulled in
build tooling, and the stale exact pins (typing-extensions 4.0.1 is
from 2021) conflict with any package wanting a newer release.

Keep httpx as the only runtime dependency, loosened to a compatible
range for the same reason, and leave the build backend's
requirements to build-system.requires, which already lists them.
Regenerate uv.lock accordingly.
@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 31719107638

Coverage remained the same at 86.718%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 16458
Covered Lines: 14272
Line Coverage: 86.72%
Coverage Strength: 343.62 hits per line

💛 - Coveralls

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.

2 participants