Skip to content

Prepare JSON pipeline crate for publication #278

Prepare JSON pipeline crate for publication

Prepare JSON pipeline crate for publication #278

Workflow file for this run

name: CI and release
on:
push:
branches:
- main
tags:
- "v*"
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CRATE_NAME: hubuum-cli
GITHUB_TOKEN: ${{ github.token }}
RUST_BACKTRACE: 1
permissions:
contents: read
jobs:
lint:
name: Lint
if: "!startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Install clippy
run: rustup component add clippy
- name: Run clippy
run: cargo clippy --locked --all-targets -- -D warnings
- name: Run Host extension wrapper tests
run: examples/hubuum-wrappers/tests/test_wrappers.sh
- name: Validate extension JSON Schema
run: npx --yes ajv-cli@5.0.0 compile --spec=draft2020 -s schemas/hubuum-extension.schema.json
- name: Markdown lint
uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24
with:
config: ".markdownlint.json"
globs: "**/*.md"
test:
name: ${{ matrix.platform.os_name }} with rust ${{ matrix.toolchain }}
if: "!startsWith(github.ref, 'refs/tags/')"
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
platform:
- os_name: Linux-x86_64
os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
skip_tests: false
- os_name: macOS-aarch64
os: macos-latest
target: aarch64-apple-darwin
skip_tests: true
- os_name: Windows-x86_64
os: windows-latest
target: x86_64-pc-windows-msvc
skip_tests: true
toolchain:
- stable
- beta
- nightly
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Cache cargo and target directories
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@21b0f18dc621b25bfae556ff2791fca4173121e8 # v1
with:
command: build
target: ${{ matrix.platform.target }}
toolchain: ${{ matrix.toolchain }}
args: "--locked --release"
- name: Run tests
if: ${{ !matrix.platform.skip_tests }}
uses: houseabsolute/actions-rust-cross@21b0f18dc621b25bfae556ff2791fca4173121e8 # v1
with:
command: test
target: ${{ matrix.platform.target }}
toolchain: ${{ matrix.toolchain }}
args: "--locked --release"
verify-tag-main-ci-success:
name: Verify tagged commit already passed CI on main
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
steps:
- name: Check for successful main CI run on this commit
env:
GH_TOKEN: ${{ github.token }}
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
SHA: ${{ github.sha }}
shell: bash
run: |
set -euo pipefail
api="https://api.github.com/repos/${OWNER}/${REPO}/actions/workflows/ci.yml/runs?event=push&branch=main&head_sha=${SHA}&status=completed&per_page=100"
response="$(
curl --silent --show-error --fail \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header "Accept: application/vnd.github+json" \
"${api}"
)"
successful_run_count="$(
jq '[.workflow_runs[] | select(.head_branch == "main" and .conclusion == "success")] | length' <<<"${response}"
)"
if [[ "${successful_run_count}" -eq 0 ]]; then
echo "No successful CI run on main was found for commit ${SHA}." >&2
echo "Version tags must point at a green main commit." >&2
exit 1
fi
echo "Found a successful main CI run for ${SHA}."
validate-release:
name: Validate tagged release metadata
if: startsWith(github.ref, 'refs/tags/v')
needs: verify-tag-main-ci-success
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Validate version and changelog
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
manifest_version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n 1)"
if [[ -z "${manifest_version}" ]]; then
echo "Unable to determine package version from Cargo.toml" >&2
exit 1
fi
if [[ "${manifest_version}" != "${version}" ]]; then
echo "Tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${manifest_version}" >&2
exit 1
fi
if [[ ! -f CHANGELOG.md ]]; then
echo "CHANGELOG.md is required for versioned releases" >&2
exit 1
fi
if ! grep -q "^## \[${version}\]" CHANGELOG.md; then
echo "CHANGELOG.md is missing an entry for ${version}" >&2
exit 1
fi
- name: Validate lockfile
run: cargo check --locked
build-release-artifacts:
name: Build release artifact (${{ matrix.platform.name }})
if: >-
always() &&
(
(
github.ref == 'refs/heads/main' &&
needs.lint.result == 'success' &&
needs.test.result == 'success'
) ||
(
startsWith(github.ref, 'refs/tags/v') &&
needs.validate-release.result == 'success'
)
)
needs:
- lint
- test
- validate-release
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- name: Linux-x86_64-musl
kind: linux
arch: amd64
os_name: linux-x86_64-musl
runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
rustflags: ""
- name: Linux-aarch64-musl
kind: linux
arch: arm64
os_name: linux-aarch64-musl
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
rustflags: ""
- name: macOS-ARM64
kind: native
arch: arm64
os_name: macos-aarch64
runner: macos-latest
target: aarch64-apple-darwin
rustflags: "-C link-arg=-Wl,-dead_strip"
- name: Windows-x86_64
kind: native
arch: amd64
os_name: windows-x86_64
runner: windows-latest
target: x86_64-pc-windows-msvc
rustflags: "-C target-feature=+crt-static"
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Set up Buildx
if: matrix.platform.kind == 'linux'
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- name: Export stripped static Linux binary
if: matrix.platform.kind == 'linux'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: .
file: Dockerfile
target: release-artifacts
platforms: linux/${{ matrix.platform.arch }}
outputs: type=local,dest=dist/bin
build-args: |
CARGO_BUILD_FLAGS=--locked --release
HUBUUM_CLI_BUILD_CHANNEL=${{ github.ref == 'refs/heads/main' && 'main' || 'release' }}
HUBUUM_CLI_BUILD_GIT_SHA=${{ github.sha }}
cache-from: type=gha,scope=static-${{ matrix.platform.os_name }}
cache-to: type=gha,mode=max,scope=static-${{ matrix.platform.os_name }}
- name: Install stable Rust toolchain
if: matrix.platform.kind == 'native'
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
targets: ${{ matrix.platform.target }}
- name: Cache cargo and target directories
if: matrix.platform.kind == 'native'
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: ${{ matrix.platform.target }}-stable-release
- name: Build self-contained native binary
if: matrix.platform.kind == 'native'
env:
HUBUUM_CLI_BUILD_CHANNEL: ${{ github.ref == 'refs/heads/main' && 'main' || 'release' }}
HUBUUM_CLI_BUILD_GIT_SHA: ${{ github.sha }}
RUSTFLAGS: ${{ matrix.platform.rustflags }}
run: cargo build --locked --release --target ${{ matrix.platform.target }}
- name: Verify static linkage and package Linux archive
if: matrix.platform.kind == 'linux'
shell: bash
run: |
set -euo pipefail
binary_path="dist/bin/hubuum-cli"
if readelf --dynamic "${binary_path}" | grep --quiet '(NEEDED)'; then
echo "${binary_path} unexpectedly has dynamic runtime dependencies" >&2
readelf --dynamic "${binary_path}" >&2
exit 1
fi
"${binary_path}" --version
suffix="main"
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
suffix="${GITHUB_REF_NAME}"
fi
archive_name="hubuum-cli-${{ matrix.platform.os_name }}-${suffix}.tar.gz"
tar czf "dist/${archive_name}" -C dist/bin hubuum-cli
(
cd dist
sha256sum "${archive_name}" > "${archive_name}.sha256"
)
- name: Verify macOS runtime dependencies
if: matrix.platform.os_name == 'macos-aarch64'
shell: bash
run: |
set -euo pipefail
binary_path="target/${{ matrix.platform.target }}/release/hubuum-cli"
"${binary_path}" --version
if xcrun otool -L "${binary_path}" | grep --extended-regexp --quiet '(/opt/homebrew|/usr/local|libssl|libcrypto)'; then
echo "${binary_path} unexpectedly depends on a package-manager library" >&2
xcrun otool -L "${binary_path}" >&2
exit 1
fi
- name: Package macOS archive
if: matrix.platform.os_name == 'macos-aarch64'
shell: bash
run: |
set -euo pipefail
suffix="main"
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
suffix="${GITHUB_REF_NAME}"
fi
archive_name="hubuum-cli-${{ matrix.platform.os_name }}-${suffix}.tar.gz"
mkdir -p dist
tar czf "dist/${archive_name}" \
-C "target/${{ matrix.platform.target }}/release" \
hubuum-cli
(
cd dist
shasum -a 256 "${archive_name}" > "${archive_name}.sha256"
)
- name: Verify Windows binary starts
if: matrix.platform.os_name == 'windows-x86_64'
shell: pwsh
run: |
& "target/${{ matrix.platform.target }}/release/hubuum-cli.exe" --version
- name: Package Windows archive
if: matrix.platform.os_name == 'windows-x86_64'
shell: pwsh
run: |
$suffix = "main"
if ($env:GITHUB_REF -like "refs/tags/v*") {
$suffix = $env:GITHUB_REF_NAME
}
$archiveName = "hubuum-cli-${{ matrix.platform.os_name }}-$suffix.zip"
New-Item -ItemType Directory -Path dist -Force | Out-Null
Compress-Archive `
-Path "target/${{ matrix.platform.target }}/release/hubuum-cli.exe" `
-DestinationPath "dist/$archiveName" `
-Force
$hash = (Get-FileHash "dist/$archiveName" -Algorithm SHA256).Hash.ToLower()
[System.IO.File]::WriteAllText(
"dist/$archiveName.sha256",
"$hash $archiveName`n",
[System.Text.UTF8Encoding]::new($false)
)
- name: Upload packaged artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: binaries-${{ matrix.platform.os_name }}
path: |
dist/*.tar.gz
dist/*.zip
dist/*.sha256
if-no-files-found: error
publish-release:
name: Publish release
if: >-
always() &&
needs.build-release-artifacts.result == 'success' &&
(
github.ref == 'refs/heads/main' ||
startsWith(github.ref, 'refs/tags/v')
)
needs: build-release-artifacts
runs-on: ubuntu-24.04
permissions:
contents: write
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Download packaged artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: binaries-*
path: dist
merge-multiple: true
- name: Verify artifact checksums
shell: bash
run: |
set -euo pipefail
(
cd dist
sha256sum --check --strict ./*.sha256
)
- name: Move main release tag
if: github.ref == 'refs/heads/main'
shell: bash
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f main-latest "${GITHUB_SHA}"
git push --force origin refs/tags/main-latest
- name: Write main release notes
if: github.ref == 'refs/heads/main'
shell: bash
run: |
cat <<EOF > RELEASE_NOTES.md
Rolling binaries from the latest successful push to \`main\`.
Commit: ${GITHUB_SHA}
EOF
- name: Extract tagged release notes from changelog
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
awk -v version="${version}" '
$0 ~ "^## \\[" version "\\]" { capture = 1 }
capture && /^## \[/ && $0 !~ "^## \\[" version "\\]" { exit }
capture { print }
' CHANGELOG.md > RELEASE_NOTES.md
if [[ ! -s RELEASE_NOTES.md ]]; then
echo "Failed to extract release notes for ${version}" >&2
exit 1
fi
- name: Publish GitHub release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
with:
tag_name: ${{ github.ref == 'refs/heads/main' && 'main-latest' || github.ref_name }}
name: ${{ github.ref == 'refs/heads/main' && 'main' || github.ref_name }}
body_path: RELEASE_NOTES.md
draft: false
prerelease: false
make_latest: ${{ github.ref == 'refs/heads/main' && 'false' || 'true' }}
overwrite_files: true
fail_on_unmatched_files: true
files: dist/*