Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
inputs: [
"{mix,.formatter,.credo}.exs",
"{config,lib,test}/**/*.{ex,exs}"
"{lib,test}/**/*.{ex,exs}"
],
line_length: 120
]
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ concurrency:

env:
MIX_ENV: test
# Build the NIF from source: no release has the precompiled NIF of the code under test.
WHISPER_CPP_BUILD: "1"
ELIXIR_VERSION: "1.20.5"
OTP_VERSION: "29.1.2"
RUST_TOOLCHAIN: "1.99.1"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ concurrency:

env:
MIX_ENV: test
# Build the NIF from source: no release has the precompiled NIF of the code under test.
WHISPER_CPP_BUILD: "1"
ELIXIR_VERSION: "1.20.5"
OTP_VERSION: "29.1.2"
RUST_TOOLCHAIN: "1.99.1"
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
cancel-in-progress: false
outputs:
should_release: ${{ steps.resolve.outputs.should_release }}
should_publish: ${{ steps.resolve.outputs.should_publish }}
tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
sha: ${{ steps.resolve.outputs.sha }}
Expand All @@ -48,13 +49,15 @@ jobs:
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ inputs.tag }}
BEFORE: ${{ github.event.before }}
run: |
VERSION="$(sed -n 's/^ @version "\([^"]*\)"$/\1/p' mix.exs | head -n1)"
if [ -z "$VERSION" ]; then
echo "Failed to read current mix.exs version" >&2
exit 1
fi
TAG="v$VERSION"
SHOULD_PUBLISH=false

if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
if [ "$TAG" != "$INPUT_TAG" ]; then
Expand All @@ -64,6 +67,12 @@ jobs:
SHOULD_RELEASE=true
elif git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
SHOULD_RELEASE=false

# The merge of the checksum PR publishes: only then does main hold the checksums of the
# release assets, and the Hex package must carry them.
if git diff --name-only "$BEFORE" "$GITHUB_SHA" | grep -qxF checksum-Elixir.WhisperCpp.Native.exs; then
SHOULD_PUBLISH=true
fi
else
SHOULD_RELEASE=true
fi
Expand All @@ -76,6 +85,7 @@ jobs:
fi

echo "should_release=$SHOULD_RELEASE" >> "$GITHUB_OUTPUT"
echo "should_publish=$SHOULD_PUBLISH" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -376,3 +386,50 @@ jobs:
artifacts/*.tar.gz
artifacts/SHA256SUMS
generate_release_notes: true

publish:
name: Publish to Hex
needs: detect
if: needs.detect.outputs.should_publish == 'true'
runs-on: ubuntu-24.04
# The Hex workload identity of the package names this environment and this workflow file.
environment: hex
permissions:
contents: read # check out the repository
id-token: write # mix hex.publish exchanges the job's OIDC token for a Hex token
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-publish
cancel-in-progress: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.detect.outputs.sha }}
persist-credentials: false

# Hex versions are immutable, so a package with checksums of other archives stays broken.
- name: Compare the checksum file with the release
env:
TAG: ${{ needs.detect.outputs.tag }}
run: |
diff \
<(curl -fsSL "https://github.com/$GITHUB_REPOSITORY/releases/download/$TAG/SHA256SUMS" | awk '{print $2 " sha256:" $1}' | sort) \
<(sed -n 's/^ "\(.*\)" => "\(sha256:.*\)",$/\1 \2/p' checksum-Elixir.WhisperCpp.Native.exs | sort)

- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
env:
INSTALL_DIR_FOR_OTP: ${{ runner.tool_cache }}/setup-beam/otp-29.1.2
INSTALL_DIR_FOR_ELIXIR: ${{ runner.tool_cache }}/setup-beam/elixir-1.20.5
with:
elixir-version: "1.20.5"
otp-version: "29.1.2"

- name: Install dependencies
run: |
mix local.hex --force
mix local.rebar --force
mix deps.get

# With no Hex credential in the job, Hex publishes through the workload identity. The docs
# compile downloads the precompiled NIF of this runner and checks it against the checksum file.
- name: Publish the package and its docs
run: mix hex.publish --yes
16 changes: 13 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ users never build Rust.
## Checks

- `ci.yml` runs `task check` on pushes to `main` and on pull requests. A push to a branch without a pull request runs
nothing.
nothing. `ci.yml` and `integration.yml` set `WHISPER_CPP_BUILD=1`, so they test the NIF of the code under test.
- `task test:integration` runs real inference. `integration.yml` runs it weekly and on manual dispatch, never on a pull
request. Run it locally when you change the NIF boundary.
- `release.yml` builds six NIFs (target and variant). One backend takes minutes to build from source, so leave the
Expand Down Expand Up @@ -54,11 +54,21 @@ users never build Rust.
3. After the tag exists, a manual dispatch builds the tag's commit. It fails when the tag is missing or its `mix.exs`
version differs, and it uploads only the assets the release does not have yet. It never replaces a published
tarball, because a new tarball breaks the checksum file in the Hex package.
4. Regenerate `checksum-Elixir.WhisperCpp.Native.exs` from the published assets, then commit and push it. This keeps
4. Regenerate `checksum-Elixir.WhisperCpp.Native.exs` from the published assets, and open a PR with it. This keeps
the checksum of each tag reproducible from the repo:

```bash
mix rustler_precompiled.download WhisperCpp.Native --all --no-config --ignore-unavailable --print
```

5. Run `mix hex.publish` from a clean tree. `mix.exs` puts `checksum-*.exs` in the Hex tarball.
5. Merge the checksum PR. A push to `main` that changes the checksum file, for a version that has its tag, starts the
`Publish to Hex` job of `release.yml`. It compares the file with the release's `SHA256SUMS`, stops when they differ,
and runs `mix hex.publish --yes` in the GitHub environment `hex`, which only `main` can use. The job keeps no Hex
key: the package has a workload identity on hex.pm for `rubas/whisper_cpp`, workflow `release.yml`, environment
`hex`, and Hex trades the job's OIDC token for a short token that can only publish `whisper_cpp`.
6. The package has staged publishing on. The job ends with `Package staged for approval at <url>`, and an owner
approves the release on hex.pm with their 2FA within 7 days. A failed docs upload prints `Publishing failed` but
leaves the job green, so read the end of its log. When the job fails, fix the cause and re-run it.

Merge nothing else to `main` between the version bump and the checksum PR, or the package holds sources that the NIFs
were not built from. Hex versions are immutable, so change the checksum file only in the checksum PR of a release.
8 changes: 0 additions & 8 deletions config/config.exs

This file was deleted.

Loading