Skip to content

test(helper): keep the malformed-frame behaviours alive after writing #3

test(helper): keep the malformed-frame behaviours alive after writing

test(helper): keep the malformed-frame behaviours alive after writing #3

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*.*.*'
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
verify-tag:
name: Verify release tag
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Check tag matches the CLI version
id: version
shell: bash
run: |
set -euo pipefail
tag_version="${GITHUB_REF_NAME#v}"
crate_version="$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json, sys; print(next(package["version"] for package in json.load(sys.stdin)["packages"] if package["name"] == "codehelion"))')"
if [ "$tag_version" != "$crate_version" ]; then
echo "tag $GITHUB_REF_NAME does not match codehelion version $crate_version" >&2
exit 1
fi
echo "version=$tag_version" >> "$GITHUB_OUTPUT"
build-helpers:
name: Build helpers (${{ matrix.name }})
needs: verify-tag
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- name: linux-x86_64
runner: ubuntu-latest
extension: ''
archive: tar.gz
# Pinned rather than macos-latest so the helpers and the CLI they are
# loaded by are built against the same macOS version.
- name: macos-aarch64
runner: macos-15
extension: ''
archive: tar.gz
- name: windows-x86_64
runner: windows-latest
extension: .exe
archive: zip
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Build helper executables
run: cargo build --release -p codehelion-backend-rust -p codehelion-backend-clang
- name: Stage Unix helper archive
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
directory="codehelion-helpers-${{ needs.verify-tag.outputs.version }}-${{ matrix.name }}"
mkdir "$directory"
cp "target/release/codehelion-backend-rust${{ matrix.extension }}" "$directory/"
cp "target/release/codehelion-backend-clang${{ matrix.extension }}" "$directory/"
cp LICENSE "$directory/"
cp README.md "$directory/"
tar -czf "$directory.tar.gz" "$directory"
- name: Stage Windows helper archive
if: runner.os == 'Windows'
shell: pwsh
run: |
$directory = "codehelion-helpers-${{ needs.verify-tag.outputs.version }}-${{ matrix.name }}"
New-Item -ItemType Directory -Path $directory | Out-Null
Copy-Item "target/release/codehelion-backend-rust${{ matrix.extension }}" $directory
Copy-Item "target/release/codehelion-backend-clang${{ matrix.extension }}" $directory
Copy-Item LICENSE $directory
Copy-Item README.md $directory
Compress-Archive -Path $directory -DestinationPath "$directory.zip"
- uses: actions/upload-artifact@v4
with:
name: helpers-${{ matrix.name }}
path: codehelion-helpers-${{ needs.verify-tag.outputs.version }}-${{ matrix.name }}.${{ matrix.archive }}
if-no-files-found: error
retention-days: 14
build-cli:
name: Build CLI (${{ matrix.name }})
needs: verify-tag
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- name: linux-x86_64
runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
extension: ''
archive: tar.gz
cross_linker: ''
- name: linux-aarch64
runner: ubuntu-latest
target: aarch64-unknown-linux-gnu
extension: ''
archive: tar.gz
cross_linker: aarch64-linux-gnu-gcc
- name: macos-x86_64
runner: macos-15-intel
target: x86_64-apple-darwin
extension: ''
archive: tar.gz
cross_linker: ''
- name: macos-aarch64
runner: macos-15
target: aarch64-apple-darwin
extension: ''
archive: tar.gz
cross_linker: ''
- name: windows-x86_64
runner: windows-latest
target: x86_64-pc-windows-msvc
extension: .exe
archive: zip
cross_linker: ''
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
# The cross compiler alone is not enough: dependencies that carry C sources
# compile them for the target, and that needs the target's libc headers.
- name: Install Linux cross toolchain
if: matrix.cross_linker != ''
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross
- name: Build CLI
shell: bash
run: cargo build --locked --release -p codehelion --target ${{ matrix.target }}
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.cross_linker }}
- name: Stage Unix CLI archive
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
directory="codehelion-${{ needs.verify-tag.outputs.version }}-${{ matrix.name }}"
mkdir "$directory"
cp "target/${{ matrix.target }}/release/codehelion${{ matrix.extension }}" "$directory/"
cp LICENSE "$directory/"
cp README.md "$directory/"
tar -czf "$directory.tar.gz" "$directory"
- name: Stage Windows CLI archive
if: runner.os == 'Windows'
shell: pwsh
run: |
$directory = "codehelion-${{ needs.verify-tag.outputs.version }}-${{ matrix.name }}"
New-Item -ItemType Directory -Path $directory | Out-Null
Copy-Item "target/${{ matrix.target }}/release/codehelion${{ matrix.extension }}" $directory
Copy-Item LICENSE $directory
Copy-Item README.md $directory
Compress-Archive -Path $directory -DestinationPath "$directory.zip"
- uses: actions/upload-artifact@v4
with:
name: cli-${{ matrix.name }}
path: codehelion-${{ needs.verify-tag.outputs.version }}-${{ matrix.name }}.${{ matrix.archive }}
if-no-files-found: error
retention-days: 14
publish-release:
name: Publish GitHub Release assets
needs: [verify-tag, build-helpers, build-cli]
runs-on: ubuntu-latest
steps:
# Every artifact this run produced, with no pattern to narrow it: the
# release carries both the CLI and the helpers, and a pattern that names
# one of them drops the other from the release without failing.
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Generate archive checksums
shell: bash
run: |
set -euo pipefail
cd dist
sha256sum codehelion-* > SHA256SUMS
- name: Create or update GitHub Release
uses: softprops/action-gh-release@v3
with:
name: Release ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
generate_release_notes: true
files: |
dist/codehelion-*
dist/SHA256SUMS
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Last, because it is the one step nothing undoes: a published version stays
# published, and yanking it does not free the number. Everything that can
# still be corrected — the builds, the archives, the release notes — has
# already succeeded by the time this runs.
publish-crates:
name: Publish to crates.io
needs: [verify-tag, publish-release]
runs-on: ubuntu-latest
permissions:
contents: read
# Trusted publishing: crates.io exchanges this run's identity for a token
# that expires with the job, so no registry credential is stored here.
id-token: write
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# Asked before publishing rather than discovered during it. A run that
# already published cannot publish again, so re-running a tag has to be
# able to end in nothing happening — and a run that got partway through
# has to say so rather than fail halfway a second time.
- name: Ask the registry what is already published
id: registry
shell: bash
run: |
set -euo pipefail
version="${{ needs.verify-tag.outputs.version }}"
crates="$(cargo metadata --no-deps --format-version 1 | python3 -c \
'import json, sys; print("\n".join(package["name"] for package in json.load(sys.stdin)["packages"] if package["publish"] is None))')"
present=()
absent=()
for crate in $crates; do
status="$(curl -sS -o /dev/null -w '%{http_code}' \
-H 'User-Agent: codehelion release workflow (https://github.com/libraz/codehelion)' \
"https://crates.io/api/v1/crates/$crate/$version")"
case "$status" in
200) present+=("$crate") ;;
404) absent+=("$crate") ;;
*) echo "crates.io answered $status for $crate $version" >&2; exit 1 ;;
esac
done
if [ "${#absent[@]}" -eq 0 ]; then
echo "every crate is already at $version; nothing to publish"
echo "publish=false" >> "$GITHUB_OUTPUT"
elif [ "${#present[@]}" -eq 0 ]; then
echo "publish=true" >> "$GITHUB_OUTPUT"
else
echo "version $version is published for ${present[*]} but not for ${absent[*]}." >&2
echo "cargo cannot resume a partial release; finish the remaining crates by hand." >&2
exit 1
fi
# No token is configured for a crate that does not exist yet, so the
# first release of a new name is made from a workstation and this job
# finds nothing left to do. From the second release on it is the whole
# of the publish.
- name: Authenticate with crates.io
if: steps.registry.outputs.publish == 'true'
id: auth
uses: rust-lang/crates-io-auth-action@v1
# Verification builds each crate from its own package before uploading
# it, and cargo waits for each upload to be resolvable before publishing
# what depends on it.
- name: Publish
if: steps.registry.outputs.publish == 'true'
run: cargo publish --workspace --locked
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}