chore: version 1.10.0 #14
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: read | |
| defaults: | |
| run: | |
| shell: bash | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| build-and-package: | |
| runs-on: ubuntu-24.04 | |
| container: | |
| image: ubuntu:24.04@sha256:023f8a753c22258c9fe2d0005a7d28258038da7d620e9f93e9ad78aa266f9f11 # ubuntu:24.04 linux/amd64 | |
| env: | |
| NFPM_VERSION: v2.46.3 | |
| GO_VERSION: 1.25.0 # nFPM v2.46.3 go directive | |
| GOTOOLCHAIN: local | |
| RELEASE_GLIBC_VERSION: 2.39 | |
| MIN_PIPEWIRE_VERSION: 0.3.65 | |
| outputs: | |
| version: ${{ steps.release.outputs.version }} | |
| package_release: ${{ steps.release.outputs.package_release }} | |
| display_version: ${{ steps.release.outputs.display_version }} | |
| tag: ${{ steps.release.outputs.tag }} | |
| commit: ${{ steps.release.outputs.commit }} | |
| steps: | |
| - name: Install workflow prerequisites | |
| run: | | |
| set -euo pipefail | |
| apt-get update | |
| apt-get install -y --no-install-recommends \ | |
| build-essential \ | |
| ca-certificates \ | |
| curl \ | |
| git \ | |
| libclang-dev \ | |
| libpipewire-0.3-dev \ | |
| libwayland-dev \ | |
| libx11-dev \ | |
| libx11-xcb-dev \ | |
| libxext-dev \ | |
| libxfixes-dev \ | |
| libxrandr-dev \ | |
| libxcursor-dev \ | |
| libxi-dev \ | |
| libxinerama-dev \ | |
| libxkbcommon-dev \ | |
| libfontconfig1-dev \ | |
| libfreetype6-dev \ | |
| libvulkan-dev \ | |
| pkg-config \ | |
| xz-utils \ | |
| zstd | |
| rm -rf /var/lib/apt/lists/* | |
| - name: Validate native dependency versions | |
| run: | | |
| set -euo pipefail | |
| glibc_version="$(ldd --version | awk 'NR==1 {print $NF}')" | |
| pipewire_version="$(pkg-config --modversion libpipewire-0.3)" | |
| echo "glibc version: ${glibc_version}" | |
| echo "PipeWire/SPA development headers: ${pipewire_version}" | |
| if [ "${glibc_version}" != "${RELEASE_GLIBC_VERSION}" ]; then | |
| echo "Release pipeline expected glibc ${RELEASE_GLIBC_VERSION}; got ${glibc_version}" >&2 | |
| exit 1 | |
| fi | |
| if ! dpkg --compare-versions "${pipewire_version}" ge "${MIN_PIPEWIRE_VERSION}"; then | |
| echo "Release pipeline requires PipeWire/SPA development headers >= ${MIN_PIPEWIRE_VERSION}" >&2 | |
| exit 1 | |
| fi | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Mark workspace as safe for Git | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| - name: Validate release tag and prepare notes | |
| id: release | |
| env: | |
| TAG_NAME: ${{ github.ref_name }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| run: | | |
| set -euo pipefail | |
| cargo_value() { | |
| local key="$1" | |
| awk -F'"' -v key="$key" ' | |
| /^[[:space:]]*\[package\][[:space:]]*$/ { in_pkg = 1; next } | |
| /^[[:space:]]*\[/ { in_pkg = 0 } | |
| in_pkg && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { print $2; exit } | |
| ' | |
| } | |
| version="$(cargo_value version < Cargo.toml)" | |
| if [ -z "$version" ]; then | |
| echo "Could not parse package.version from Cargo.toml" >&2 | |
| exit 1 | |
| fi | |
| tag="${TAG_NAME}" | |
| tag_body="${tag#v}" | |
| package_release="1" | |
| display_version="${version}" | |
| if [ "$tag_body" = "$version" ]; then | |
| : | |
| elif [[ "$tag_body" == "${version}-"* ]]; then | |
| package_release="${tag_body#"${version}-"}" | |
| if [[ ! "$package_release" =~ ^[1-9][0-9]*$ ]]; then | |
| echo "Release tag ${tag} has invalid package release suffix; expected v${version}-<positive integer>." >&2 | |
| exit 1 | |
| fi | |
| display_version="${version}-${package_release}" | |
| else | |
| echo "Release tag ${tag} does not match Cargo.toml package.version (${version}); expected v${version} or v${version}-<package release>." >&2 | |
| exit 1 | |
| fi | |
| head_commit="$(git rev-parse HEAD)" | |
| tag_commit="$(git rev-parse "${tag}^{commit}")" | |
| if [ "$tag_commit" != "$head_commit" ]; then | |
| echo "Release tag ${tag} points at ${tag_commit}, but checkout is ${head_commit}." >&2 | |
| exit 1 | |
| fi | |
| default_branch="${DEFAULT_BRANCH:-main}" | |
| default_branch_ref="origin/${default_branch}" | |
| if ! git rev-parse --verify "${default_branch_ref}^{commit}" >/dev/null; then | |
| echo "Default branch ref ${default_branch_ref} is unavailable from checkout." >&2 | |
| exit 1 | |
| fi | |
| if ! git merge-base --is-ancestor "$head_commit" "$default_branch_ref"; then | |
| echo "Release commit ${head_commit} is not contained in ${default_branch_ref}." >&2 | |
| exit 1 | |
| fi | |
| rust_toolchain="$(cargo_value rust-version < Cargo.toml)" | |
| rust_toolchain="${rust_toolchain:-stable}" | |
| previous_tag="$(git tag --merged "$head_commit" --sort=-v:refname --list 'v[0-9]*.[0-9]*.[0-9]*' \ | |
| | grep -Fvx "$tag" \ | |
| | head -n 1 || true)" | |
| changelog_range="${head_commit}" | |
| if [ -n "$previous_tag" ] && git rev-parse "${previous_tag}^{commit}" >/dev/null 2>&1; then | |
| changelog_range="${previous_tag}..${head_commit}" | |
| fi | |
| changelog="$(git log --no-merges --pretty=format:'%h %s' "$changelog_range" 2>/dev/null || true)" | |
| if [ -z "$changelog" ]; then | |
| changelog="(no changelog entries)" | |
| fi | |
| notes_file="release-notes.md" | |
| { | |
| echo "## Changelog" | |
| echo | |
| echo '```' | |
| printf '%s\n' "$changelog" | |
| echo '```' | |
| } > "$notes_file" | |
| { | |
| echo "version=${version}" | |
| echo "package_release=${package_release}" | |
| echo "display_version=${display_version}" | |
| echo "rust_toolchain=${rust_toolchain}" | |
| echo "tag=${tag}" | |
| echo "commit=${head_commit}" | |
| echo "notes_file=${notes_file}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Set up Rust toolchain | |
| uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 | |
| with: | |
| toolchain: ${{ steps.release.outputs.rust_toolchain }} | |
| components: rustfmt, clippy | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Cache Cargo builds | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| - name: Lint | |
| run: cargo clippy --workspace --locked --all-targets -- -D warnings | |
| - name: Test | |
| run: cargo test --workspace --locked --all-targets | |
| - name: Set up Go | |
| uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| cache: false | |
| - name: Build and package release assets | |
| run: | | |
| set -euo pipefail | |
| go install "github.com/goreleaser/nfpm/v2/cmd/nfpm@${NFPM_VERSION}" | |
| export PATH="${HOME}/go/bin:${PATH}" | |
| cargo build --locked --release | |
| make -C packaging all \ | |
| RELEASE="${{ steps.release.outputs.package_release }}" \ | |
| MAX_GLIBC_VERSION="${RELEASE_GLIBC_VERSION}" | |
| version="${{ steps.release.outputs.version }}" | |
| package_release="${{ steps.release.outputs.package_release }}" | |
| dist_dir="${PWD}/dist" | |
| shopt -s nullglob | |
| assets=( | |
| "${dist_dir}"/openmeters-"${version}"-*.tar.xz | |
| "${dist_dir}"/openmeters_"${version}"-"${package_release}"_*.deb | |
| "${dist_dir}"/openmeters-"${version}"-"${package_release}".*.rpm | |
| "${dist_dir}"/SHA256SUMS | |
| ) | |
| shopt -u nullglob | |
| if [ "${#assets[@]}" -ne 4 ]; then | |
| echo "Expected 4 release artifacts in ${dist_dir}, found ${#assets[@]}." >&2 | |
| for asset in "${assets[@]}"; do | |
| echo " ${asset}" >&2 | |
| done | |
| exit 1 | |
| fi | |
| - name: Upload release assets | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: release-assets | |
| path: | | |
| dist/openmeters-${{ steps.release.outputs.version }}-*.tar.xz | |
| dist/openmeters_${{ steps.release.outputs.version }}-${{ steps.release.outputs.package_release }}_*.deb | |
| dist/openmeters-${{ steps.release.outputs.version }}-${{ steps.release.outputs.package_release }}.*.rpm | |
| dist/SHA256SUMS | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload release notes | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: release-notes | |
| path: ${{ steps.release.outputs.notes_file }} | |
| if-no-files-found: error | |
| retention-days: 7 | |
| publish: | |
| needs: build-and-package | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: write | |
| env: | |
| VERSION: ${{ needs.build-and-package.outputs.version }} | |
| PACKAGE_RELEASE: ${{ needs.build-and-package.outputs.package_release }} | |
| DISPLAY_VERSION: ${{ needs.build-and-package.outputs.display_version }} | |
| TAG_NAME: ${{ needs.build-and-package.outputs.tag }} | |
| COMMIT: ${{ needs.build-and-package.outputs.commit }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| GH_REPO: ${{ github.repository }} | |
| steps: | |
| - name: Validate remote release target | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| tag_ref="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" --jq '.object.type + " " + .object.sha')" | |
| read -r object_type object_sha <<< "$tag_ref" | |
| case "$object_type" in | |
| commit) | |
| tag_commit="$object_sha" | |
| ;; | |
| tag) | |
| tag_target="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${object_sha}" --jq '.object.type + " " + .object.sha')" | |
| read -r target_type tag_commit <<< "$tag_target" | |
| if [ "$target_type" != "commit" ]; then | |
| echo "Release tag ${TAG_NAME} ultimately points at a ${target_type}, not a commit." >&2 | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| echo "Release tag ${TAG_NAME} points at unsupported git object type ${object_type}." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| if [ "$tag_commit" != "$COMMIT" ]; then | |
| echo "Remote release tag ${TAG_NAME} points at ${tag_commit}, but validated build commit is ${COMMIT}." >&2 | |
| exit 1 | |
| fi | |
| default_branch="${DEFAULT_BRANCH:-main}" | |
| compare_status="$(gh api "repos/${GITHUB_REPOSITORY}/compare/${COMMIT}...${default_branch}" --jq '.status')" | |
| case "$compare_status" in | |
| identical|ahead) | |
| ;; | |
| *) | |
| echo "Default branch ${default_branch} does not contain release commit ${COMMIT}; compare status: ${compare_status}." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| - name: Download release assets | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: release-assets | |
| path: dist | |
| - name: Download release notes | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: release-notes | |
| path: release-notes | |
| - name: Publish GitHub release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| notes_file="${PWD}/release-notes/release-notes.md" | |
| if [ ! -s "$notes_file" ]; then | |
| echo "Release notes file ${notes_file} is missing or empty." >&2 | |
| exit 1 | |
| fi | |
| dist_dir="${PWD}/dist" | |
| (cd "$dist_dir" && sha256sum --check SHA256SUMS) | |
| shopt -s nullglob | |
| assets=( | |
| "${dist_dir}"/openmeters-"${VERSION}"-*.tar.xz | |
| "${dist_dir}"/openmeters_"${VERSION}"-"${PACKAGE_RELEASE}"_*.deb | |
| "${dist_dir}"/openmeters-"${VERSION}"-"${PACKAGE_RELEASE}".*.rpm | |
| "${dist_dir}"/SHA256SUMS | |
| ) | |
| shopt -u nullglob | |
| if [ "${#assets[@]}" -ne 4 ]; then | |
| echo "Expected 4 downloaded release artifacts in ${dist_dir}, found ${#assets[@]}." >&2 | |
| for asset in "${assets[@]}"; do | |
| echo " ${asset}" >&2 | |
| done | |
| exit 1 | |
| fi | |
| if is_draft="$(gh release view "$TAG_NAME" --json isDraft --jq .isDraft 2>/dev/null)"; then | |
| if [ "$is_draft" = "true" ]; then | |
| echo "Deleting existing draft release ${TAG_NAME} before recreating it." | |
| gh release delete "$TAG_NAME" --yes | |
| else | |
| echo "Release ${TAG_NAME} already exists and is not a draft; refusing to overwrite it." >&2 | |
| exit 1 | |
| fi | |
| fi | |
| gh release create "$TAG_NAME" \ | |
| --draft \ | |
| --target "$COMMIT" \ | |
| --title "openmeters ${DISPLAY_VERSION}" \ | |
| --notes-file "$notes_file" \ | |
| --verify-tag | |
| gh release upload "$TAG_NAME" "${assets[@]}" | |
| gh release edit "$TAG_NAME" --draft=false |