xt 0.21.0

Translate between serialized data formats
Documentation
---
name: Release Process

on:
  push:
    branches:
    - start-release
    - check-release

# In many automated release workflows, a human author's push of a new Git tag
# triggers a CI system to build and ship release artifacts from the tagged
# commit. But if this tagged code happens to contain an unrecognized defect that
# blocks a complete release, it's too late to revert or change the tag without
# potentially breaking downstream Git clones.
#
# This release workflow is a spin on Graydon Hoare's "not rocket science" rule
# for integrating code into a repository's mainline [1]. A release begins with a
# human author pushing a _provisional_ release commit to a well-known branch.
# The CI system runs every conceivable test and prepares every expected artifact
# for this provisional commit, _then_ pushes a Git tag, then starts the process
# of publishing the built artifacts.
#
# The successful push of a Git tag marks the atomic and irreversible transition
# from a provisional release to a valid release. Changes to this workflow MUST
# continue to satisfy the property that artifact publishing failures are
# recoverable without changes to the tagged code or built artifacts.
# For example, the GitHub release can be cut by hand using archives uploaded to
# the workflow run, and the crate can be published from a local checkout of the
# tag.
#
# [1]: https://graydon2.dreamwidth.org/1597.html

env:
  CARGO_TERM_COLOR: always

jobs:
  check-release-branch:
    runs-on: ubuntu-24.04
    steps:
    - name: Checkout
      uses: actions/checkout@v6
      with:
        filter: blob:none
        fetch-depth: 0
    - name: Check Release Branch
      id: check-release
      run: |
        set -x

        release_version="$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].version')"
        release_branch="${GITHUB_REF#refs/heads/}"
        release_tag="v${release_version}"
        commit_title="$(git show --pretty=format:%s --no-patch)"

        if [[ ! $release_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
          echo "$release_version is not a release version"
          exit 1
        elif [[ $commit_title != "Release $release_tag" ]]; then
          echo "'$commit_title' is not a release commit title"
          exit 1
        fi

        git checkout main
        git merge --no-commit --ff-only "$release_branch"

        echo "release_version=$release_version" >> "$GITHUB_OUTPUT"
        echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT"
        echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT"
    outputs:
      release_version: ${{ steps.check-release.outputs.release_version }}
      release_branch: ${{ steps.check-release.outputs.release_branch }}
      release_tag: ${{ steps.check-release.outputs.release_tag }}

  run-deep-tests:
    needs: check-release-branch
    uses: ./.github/workflows/deep-tests.yml

  build-archives:
    needs: check-release-branch
    uses: ./.github/workflows/build-release-archives.yml
    permissions:
      contents: write
      id-token: write
      attestations: write

  merge-release:
    needs:
    - check-release-branch
    - run-deep-tests
    - build-archives
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    env:
      RELEASE_BRANCH: ${{ needs.check-release-branch.outputs.release_branch }}
      RELEASE_TAG: ${{ needs.check-release-branch.outputs.release_tag }}
      GPG_KEY_ID: ${{ vars.GPG_KEY_ID }}
    steps:
    - name: Checkout
      uses: actions/checkout@v6
      with:
        filter: blob:none
        fetch-depth: 0
    - name: Import Signing Key
      run: gpg --batch --import <<< "$GPG_PRIVATE_KEY"
      env:
        GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
    - name: Merge Release
      run: |
        set -x

        git config --global user.name "github-actions[bot]"
        git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

        git tag -asu "$GPG_KEY_ID" -m "$RELEASE_TAG" "$RELEASE_TAG"

        git checkout main
        git merge --ff-only "$RELEASE_BRANCH"

    # THIS IS THE ATOMIC AND IRREVERSIBLE POINT OF NO RETURN.
    # See the explanation of this workflow's design at the top of the file.
    - name: Push Release
      if: ${{ github.ref == 'refs/heads/start-release' }}
      run: git push --atomic origin main "$RELEASE_TAG" :"$RELEASE_BRANCH"

  create-github-release:
    if: ${{ github.ref == 'refs/heads/start-release' }}
    needs:
    - check-release-branch
    - build-archives
    - merge-release
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    steps:
    - name: Download Artifacts
      uses: actions/download-artifact@v8
      with:
        name: release
    - name: List Artifacts
      run: ls -lR
    - name: Create Release
      uses: softprops/action-gh-release@v3
      with:
        tag_name: ${{ needs.check-release-branch.outputs.release_tag }}
        files: |
          xt-*.tar.gz
          SHA256SUMS
        body: >-
          **[See the xt CHANGELOG][changelog] for release information.**


          Binary releases of xt are available for Linux and macOS as
          attachments to this GitHub Release. They are statically linked (on
          Linux), or link only to the platform's standard libraries (on macOS).
          Before using them, review the [Installation][install] section of the
          xt README. Your platform may support a more robust installation
          mechanism.


          [changelog]: https://github.com/featherbread/xt/blob/main/CHANGELOG.md

          [install]: https://github.com/featherbread/xt?tab=readme-ov-file#installation

  publish-crate:
    if: ${{ github.ref == 'refs/heads/start-release' }}
    needs: merge-release
    runs-on: ubuntu-24.04
    permissions:
      id-token: write
    steps:
    - name: Checkout
      uses: actions/checkout@v6
    - name: Download Toolchain
      run: |
        rustup set profile minimal
        rustup toolchain install stable
        rustup default stable
        rustc --version
    - name: Authenticate to Crates.io
      id: crates-io-auth
      uses: rust-lang/crates-io-auth-action@v1
    - name: Publish
      run: cargo publish
      env:
        CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}