Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 44 additions & 22 deletions .github/workflows/bindings-release.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
name: Release language bindings

# Pushing a signed `payjoin-ffi-<version>` tag verifies the tag and the
# shared binding version, runs every language's build, package and smoke
# checks at that commit, then publishes Python, JavaScript, C#, and Dart
# through the `release` environment (required reviewer) with keyless OIDC
# and cuts one GitHub release holding every package.
# shared binding version, builds the native libraries once, runs every
# language's build, package and smoke checks at that commit, then
# publishes Python, JavaScript, C#, and Dart through the `release`
# environment (required reviewer) with keyless OIDC and cuts one GitHub
# release holding every package and native library.
#
# Each registry has its own publish job. All four wait on every language
# build, so they reach the environment gate together and one approval
Expand Down Expand Up @@ -63,31 +64,34 @@ jobs:
set -euo pipefail
source contrib/release/crates.sh
core="$(nix --quiet develop .#release -c bash -c 'source contrib/release/crates.sh && manifest_version payjoin')"
# Dart consumers build the bindings from source against it.
# Dart's source build mode builds the bindings against it.
crate_published payjoin "$core" || {
echo "::error::payjoin $core is not on crates.io; publish it before the bindings"
exit 1
}

python:
needs: verify-tag
needs: [verify-tag, native]
uses: $/.github/workflows/python.yml
javascript:
needs: verify-tag
uses: $/.github/workflows/javascript.yml
csharp:
needs: verify-tag
needs: [verify-tag, native]
uses: $/.github/workflows/csharp.yml
dart:
needs: verify-tag
needs: [verify-tag, native]
uses: $/.github/workflows/dart.yml
kotlin:
needs: verify-tag
uses: $/.github/workflows/kotlin.yml
native:
needs: verify-tag
uses: $/.github/workflows/native.yml

publish-python:
name: Publish to PyPI
needs: [python, javascript, csharp, dart, kotlin]
needs: [python, javascript, csharp, dart, kotlin, native]
runs-on: ubuntu-26.04
environment: release
permissions:
Expand Down Expand Up @@ -121,7 +125,7 @@ jobs:

publish-javascript:
name: Publish to npmjs.com
needs: [verify-tag, python, javascript, csharp, dart, kotlin]
needs: [verify-tag, python, javascript, csharp, dart, kotlin, native]
runs-on: ubuntu-26.04
environment: release
permissions:
Expand Down Expand Up @@ -165,7 +169,7 @@ jobs:

publish-csharp:
name: Publish to nuget.org
needs: [python, javascript, csharp, dart, kotlin]
needs: [python, javascript, csharp, dart, kotlin, native]
runs-on: ubuntu-26.04
environment: release
permissions:
Expand Down Expand Up @@ -216,11 +220,12 @@ jobs:

publish-dart:
name: Publish to pub.dev
needs: [python, javascript, csharp, dart, kotlin]
needs: [python, javascript, csharp, dart, kotlin, native]
runs-on: ubuntu-26.04
environment: release
permissions:
id-token: write # OIDC: pub.dev automated publishing
id-token: write # OIDC: pub.dev automated publishing and actions/attest
attestations: write # actions/attest writes the attestation
contents: read
steps:
- name: Checkout repo
Expand All @@ -234,6 +239,11 @@ jobs:
with:
name: payjoin-dart-pub-package
path: dist
- name: Attest build provenance
# A consumer runs: gh attestation verify payjoin-dart.tar.gz -R payjoin/rust-payjoin
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: dist/payjoin-dart.tar.gz
- name: Extract the staged package
run: |
mkdir -p "$RUNNER_TEMP/payjoin-pub"
Expand All @@ -254,6 +264,8 @@ jobs:
runs-on: ubuntu-26.04
permissions:
contents: write # create the Release for this tag and upload assets
id-token: write # OIDC: used by actions/attest
attestations: write # actions/attest writes the attestation
env:
VERSION: ${{ needs.verify-tag.outputs.version }}
steps:
Expand All @@ -267,26 +279,36 @@ jobs:
pattern: payjoin-*
merge-multiple: true
path: dist
- name: Name the native libraries for the release
# payjoin-native lands as one directory per target.
run: |
set -euo pipefail
for lib in dist/*/*; do
target=$(basename "$(dirname "$lib")")
mv "$lib" "dist/payjoin_ffi-$VERSION-$target.${lib##*.}"
rmdir "$(dirname "$lib")"
done
- name: Check the asset set is complete
run: |
set -euo pipefail
shopt -s nullglob
wheels=(dist/*.whl)
npm=(dist/*.tgz)
nuget=(dist/*.nupkg)
if [[ ${#wheels[@]} != 2 || ${#npm[@]} != 1 || ${#nuget[@]} != 1 || ! -f dist/payjoin-dart.tar.gz ]]; then
echo "::error::expected two wheels, one npm tarball, one NuGet package, and the Dart archive in dist/"
native=(dist/payjoin_ffi-*)
if [[ ${#wheels[@]} != 2 || ${#npm[@]} != 1 || ${#nuget[@]} != 1 || ! -f dist/payjoin-dart.tar.gz || ${#native[@]} != 12 ]]; then
echo "::error::expected two wheels, one npm tarball, one NuGet package, the Dart archive and 12 native libraries in dist/"
ls dist
exit 1
fi
- name: Attest build provenance of the native libraries
# A consumer runs: gh attestation verify <file> -R payjoin/rust-payjoin
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: dist/payjoin_ffi-*
- name: Generate SHA256SUMS
# The NuGet package's native libraries are listed individually so
# each can be verified on its own. A maintainer's GPG signature over
# SHA256SUMS is made locally and uploaded as SHA256SUMS.asc; no
# signing key reaches a runner.
env:
ARCHIVE_GLOBS: "*.nupkg"
MEMBER_GLOBS: runtimes/*/native/*
# A maintainer's GPG signature over SHA256SUMS is made locally and
# uploaded as SHA256SUMS.asc; no signing key reaches a runner.
run: |
./contrib/release/generate-sha256sums.sh dist
cat dist/SHA256SUMS
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/cron-daily-bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ permissions:

jobs:
python:
needs: native
uses: $/.github/workflows/python.yml
javascript:
uses: $/.github/workflows/javascript.yml
csharp:
needs: native
uses: $/.github/workflows/csharp.yml
dart:
needs: native
uses: $/.github/workflows/dart.yml
kotlin:
uses: $/.github/workflows/kotlin.yml
native:
uses: $/.github/workflows/native.yml
134 changes: 33 additions & 101 deletions .github/workflows/csharp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
if: github.event_name != 'pull_request'
runs-on: windows-latest
env:
RUSTUP_TOOLCHAIN: 1.85
RUSTUP_TOOLCHAIN: 1.85.0
defaults:
run:
working-directory: payjoin-ffi/csharp
Expand Down Expand Up @@ -106,106 +106,24 @@ jobs:
- name: Run tests
run: dotnet test Payjoin.Tests.csproj --logger "console;verbosity=minimal"

build-nuget-native:
# Every RID cross-compiles from Linux: cargo-zigbuild links the Linux targets
# against a pinned glibc floor and the macOS targets against zig's bundled
# libSystem stubs; cargo-xwin links MSVC-ABI Windows targets against the
# Microsoft CRT/SDK. The per-RID smoke jobs verify each produced asset on
# real hardware.
name: "Build NuGet native asset"
if: github.event_name != 'pull_request'
runs-on: ubuntu-26.04
env:
# Must match the dtolnay/rust-toolchain version exactly: the cross-target
# std components are installed for that toolchain, and a partial version
# here selects a different rustup toolchain entry without them (E0463).
RUSTUP_TOOLCHAIN: 1.85.0
defaults:
run:
working-directory: payjoin-ffi/csharp
strategy:
# Let every RID report its own result; a failure in one target should not
# cancel the signal from the others.
fail-fast: false
matrix:
include:
- rid: linux-arm64
target: aarch64-unknown-linux-gnu
- rid: linux-x64
target: x86_64-unknown-linux-gnu
- rid: osx-arm64
target: aarch64-apple-darwin
- rid: osx-x64
target: x86_64-apple-darwin
- rid: win-arm64
target: aarch64-pc-windows-msvc
- rid: win-x64
target: x86_64-pc-windows-msvc
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false # unneeded: no git auth beyond checkout

- name: Install Rust 1.85.0
uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
with:
toolchain: 1.85.0
targets: ${{ matrix.target }}

- name: Use cache
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
with:
lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }}

- name: Install cross toolchain
# Version- and hash-pinned: these tools produce the shipped binaries.
run: pip install --require-hashes -r scripts/cross-requirements.txt

- name: Install MSVC toolchain frontends (clang-cl, llvm-lib)
if: startsWith(matrix.rid, 'win-')
# cargo-xwin compiles C dependencies with clang-cl and archives them with
# llvm-lib. The runner image ships LLVM with versioned binary names only,
# so expose the unversioned names cc-rs looks for.
run: |
for tool in clang-cl llvm-lib; do
if command -v "$tool" >/dev/null; then
continue
fi
latest=$(ls -1 /usr/bin/"$tool"-* 2>/dev/null | sort -V | tail -n 1)
if [ -z "$latest" ]; then
sudo apt-get update -q
sudo apt-get install -y -q llvm clang
latest=$(ls -1 /usr/bin/"$tool"-* 2>/dev/null | sort -V | tail -n 1)
fi
if [ -z "$latest" ]; then
echo "$tool not found on the runner" >&2
exit 1
fi
sudo ln -s "$latest" /usr/local/bin/"$tool"
done
command -v clang-cl llvm-lib

- name: Build release native asset
run: |
PAYJOIN_FFI_CROSS=1 PAYJOIN_FFI_RID=${{ matrix.rid }} bash ./scripts/build_nuget_native.sh

- name: Upload NuGet native asset
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ matrix.rid }}
path: payjoin-ffi/csharp/artifacts/runtimes/${{ matrix.rid }}/native/*
if-no-files-found: error

pack-nuget:
# Every RID's native asset comes from the native workflow's checked
# double build in the same run. The per-RID smoke jobs verify each one
# on real hardware.
name: "Pack C# NuGet package"
if: github.event_name != 'pull_request'
runs-on: ubuntu-26.04
needs: build-nuget-native
env:
# Exact version so the packaged bindings build with the toolchain the
# action installs, not an implicitly-installed latest 1.85.x.
RUSTUP_TOOLCHAIN: 1.85.0
# action installs, the release toolchain of the native build contract.
RUSTUP_TOOLCHAIN: 1.85.1
RIDS: >-
linux-arm64=aarch64-unknown-linux-gnu
linux-x64=x86_64-unknown-linux-gnu
osx-arm64=aarch64-apple-darwin
osx-x64=x86_64-apple-darwin
win-arm64=aarch64-pc-windows-msvc
win-x64=x86_64-pc-windows-msvc
defaults:
run:
working-directory: payjoin-ffi/csharp
Expand All @@ -215,10 +133,10 @@ jobs:
with:
persist-credentials: false # unneeded: no git auth beyond checkout

- name: Install Rust 1.85.0
- name: Install Rust 1.85.1
uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
with:
toolchain: 1.85.0
toolchain: 1.85.1

- name: Use cache
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
Expand All @@ -230,16 +148,19 @@ jobs:
with:
dotnet-version: "10.0.x"

- name: Download native assets
- name: Download the native libraries
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
path: payjoin-ffi/csharp/artifacts/downloaded
name: payjoin-native
path: ${{ runner.temp }}/native

- name: Assemble RID assets
run: |
for rid in linux-arm64 linux-x64 osx-arm64 osx-x64 win-arm64 win-x64; do
set -euo pipefail
for pair in $RIDS; do
rid=${pair%%=*}
mkdir -p "artifacts/runtimes/$rid/native"
cp "artifacts/downloaded/$rid"/* "artifacts/runtimes/$rid/native/"
cp "$RUNNER_TEMP/native/${pair#*=}"/* "artifacts/runtimes/$rid/native/"
done

- name: Generate production bindings
Expand All @@ -249,6 +170,17 @@ jobs:
- name: Pack NuGet package
run: dotnet pack Payjoin.csproj --configuration Release --output artifacts/packages

# The release lists each library on its own, so the package must carry
# them unchanged.
- name: Check the packed libraries
run: |
set -euo pipefail
for pair in $RIDS; do
for lib in "$RUNNER_TEMP/native/${pair#*=}"/*; do
unzip -p artifacts/packages/*.nupkg "runtimes/${pair%%=*}/native/$(basename "$lib")" | cmp - "$lib"
done
done

- name: Upload NuGet package
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
Expand Down
Loading
Loading