name: Jao release CI
on:
workflow_dispatch:
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
prepare-release:
name: prepare release
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.release-metadata.outputs.release_tag }}
release_version: ${{ steps.release-metadata.outputs.release_version }}
steps:
- name: Checkout source commit
uses: actions/checkout@v5
with:
fetch-depth: 1
sparse-checkout: |
Cargo.toml
sparse-checkout-cone-mode: false
- name: Derive release metadata
id: release-metadata
shell: bash
run: |
release_version="$(grep '^version = "' Cargo.toml | head -n1 | cut -d'"' -f2)"
test -n "${release_version}"
release_tag="v${release_version}"
echo "release_tag=${release_tag}" >> "${GITHUB_OUTPUT}"
echo "release_version=${release_version}" >> "${GITHUB_OUTPUT}"
verify:
name: verify (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: prepare-release
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Report toolchain
run: |
rustc --version
cargo --version
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Build all feature combinations
run: cargo hack build --locked --each-feature
- name: Run tests
run: cargo test --locked
build-binaries:
name: build binary (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: prepare-release
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Report toolchain
run: |
rustc --version
cargo --version
- name: Build release binary
run: cargo build --release --locked
- name: Detect native target triple
shell: bash
run: |
host_target="$(rustc -vV | awk '/^host:/ { print $2 }')"
test -n "${host_target}"
echo "HOST_TARGET=${host_target}" >> "${GITHUB_ENV}"
- name: Prepare Unix release archive
if: runner.os != 'Windows'
shell: bash
run: |
mkdir -p dist package
cp target/release/jao package/jao
cp README.md LICENSE NOTICE package/
tar -czf "dist/jao-${HOST_TARGET}.tar.gz" -C package jao README.md LICENSE NOTICE
- name: Prepare Windows release archive
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist | Out-Null
New-Item -ItemType Directory -Force -Path package | Out-Null
Copy-Item "target/release/jao.exe" "package/jao.exe"
Copy-Item README.md, LICENSE, NOTICE package/
Compress-Archive -Path "package/jao.exe", "package/README.md", "package/LICENSE", "package/NOTICE" -DestinationPath "dist/jao-$env:HOST_TARGET.zip" -Force
- name: Upload release artifact bundle
uses: actions/upload-artifact@v4
with:
name: release-assets-${{ matrix.os }}
path: dist/*
publish-release:
name: publish release
runs-on: ubuntu-latest
environment: release
needs:
- prepare-release
- verify
- build-binaries
env:
IMAGE_REPOSITORY: ${{ vars.DOCKERHUB_REPOSITORY }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
RELEASE_ASSET_DIR: /tmp/jao-release-assets
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Download archived binaries
uses: actions/download-artifact@v4
with:
path: ${{ env.RELEASE_ASSET_DIR }}
pattern: release-assets-*
merge-multiple: true
- name: Generate SHA256SUMS
shell: bash
run: |
cd "${RELEASE_ASSET_DIR}"
sha256sum * > SHA256SUMS
- name: Validate release configuration
run: |
test -n "${IMAGE_REPOSITORY}"
test -n "${{ vars.DOCKERHUB_USERNAME }}"
test -n "${{ secrets.DOCKERHUB_TOKEN }}"
test -n "${CARGO_REGISTRY_TOKEN}"
- name: Validate release tag availability
shell: bash
env:
RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
SOURCE_SHA: ${{ github.sha }}
run: |
git fetch origin main release --tags
if git ls-remote --exit-code --tags --refs origin "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1; then
remote_tag_sha="$(git ls-remote --tags --refs origin "refs/tags/${RELEASE_TAG}" | awk '{print $1}')"
if [[ "${remote_tag_sha}" != "${SOURCE_SHA}" ]]; then
echo "Existing tag '${RELEASE_TAG}' points to ${remote_tag_sha}, expected ${SOURCE_SHA}." >&2
exit 1
fi
else
git tag "${RELEASE_TAG}" "${SOURCE_SHA}"
git push origin "refs/tags/${RELEASE_TAG}"
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Package crate
run: cargo package --locked
- name: Publish crate
run: cargo publish --locked
- name: Build and push latest image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ env.IMAGE_REPOSITORY }}:latest
${{ env.IMAGE_REPOSITORY }}:${{ needs.prepare-release.outputs.release_version }}
- name: Build and push latest-no-manifest image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
build-args: |
CARGO_BUILD_ARGS=--no-default-features
push: true
tags: |
${{ env.IMAGE_REPOSITORY }}:latest-no-manifest
${{ env.IMAGE_REPOSITORY }}:${{ needs.prepare-release.outputs.release_version }}-no-manifest
- name: Update release branch
shell: bash
env:
SOURCE_SHA: ${{ github.sha }}
run: git push origin "${SOURCE_SHA}:refs/heads/release"
- name: Create or update GitHub release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
SOURCE_SHA: ${{ github.sha }}
run: |
if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
gh release upload "${RELEASE_TAG}" "${RELEASE_ASSET_DIR}"/* --clobber
else
gh release create "${RELEASE_TAG}" "${RELEASE_ASSET_DIR}"/* \
--target "${SOURCE_SHA}" \
--title "${RELEASE_TAG}" \
--generate-notes
fi