name: Release Binary
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
release_tag:
description: Existing release tag to build and backfill
required: true
type: string
env:
REPO_NAME: ${{ github.event.repository.name }}
RELEASE_TAG: ${{ inputs.release_tag || github.ref_name }}
RELEASE_RUST_VERSION: 1.89.0
CROSS_VERSION: 0.2.5
CROSS_LINUX_X64_SHA256: 642375d1bcf3bd88272c32ba90e999f3d983050adf45e66bd2d3887e8e838bad
permissions:
contents: read
concurrency:
group: release-${{ github.repository }}-${{ inputs.release_tag || github.ref_name }}
cancel-in-progress: false
jobs:
prepare-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ env.RELEASE_TAG }}
- name: Verify release tag matches Cargo.toml
run: |
set -euo pipefail
tag="${RELEASE_TAG#v}"
cargo_version="$(python3 -c \
'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("Cargo.toml").read_text(encoding="utf-8"))["package"]["version"])')"
if [[ "${tag}" != "${cargo_version}" ]]; then
echo "release tag ${tag} does not match Cargo.toml version ${cargo_version}" >&2
exit 1
fi
- name: Create the GitHub release when absent
env:
GH_TOKEN: ${{ github.token }}
run: |
if ! gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
gh release create "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" \
--generate-notes --verify-tag
fi
build:
needs: prepare-release
runs-on: ${{ matrix.runner }}
permissions:
contents: write
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- os: linux
arch: x86_64
target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
builder: cross
- os: linux
arch: aarch64
target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
builder: cross
- os: macos
arch: x86_64
target: x86_64-apple-darwin
runner: macos-15
builder: cargo
- os: macos
arch: aarch64
target: aarch64-apple-darwin
runner: macos-15
builder: cargo
- os: windows
arch: x86_64
target: x86_64-pc-windows-msvc
runner: windows-latest
builder: cargo
- os: freebsd
arch: x86_64
target: x86_64-unknown-freebsd
runner: ubuntu-latest
builder: cross
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ env.RELEASE_TAG }}
- name: Set reproducible release timestamp
run: echo "SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)" >> "${GITHUB_ENV}"
- name: Setup Rust
uses: leynos/shared-actions/.github/actions/setup-rust@6dea5677a84fec60ca51b07202570e3af12ffdb4
with:
toolchain: ${{ env.RELEASE_RUST_VERSION }}
install-binstall: false
use-sccache: false
- name: Cache the official cross binaries
if: matrix.builder == 'cross'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: ~/.local/share/mdtablefix-tools/cross-v${{ env.CROSS_VERSION }}
key: cross-official-v${{ env.CROSS_VERSION }}-${{ runner.os }}-${{ runner.arch }}
- name: Install cross from its official release
if: matrix.builder == 'cross'
run: |
set -euo pipefail
cross_dir="${HOME}/.local/share/mdtablefix-tools/cross-v${CROSS_VERSION}"
cross_binary="${cross_dir}/cross"
expected_version="cross ${CROSS_VERSION}"
probe_cross_version() {
(
cd "${RUNNER_TEMP}"
"${cross_binary}" --version 2>/dev/null
) | sed -n '1p'
}
installed_version=""
if [[ -x "${cross_binary}" ]]; then
if ! installed_version="$(probe_cross_version)"; then
installed_version=""
fi
fi
if [[ "${installed_version}" != "${expected_version}" ]]; then
archive="${RUNNER_TEMP}/cross.tar.gz"
url="https://github.com/cross-rs/cross/releases/download/v${CROSS_VERSION}/cross-x86_64-unknown-linux-gnu.tar.gz"
curl --fail --location --proto '=https' --tlsv1.2 "${url}" -o "${archive}"
echo "${CROSS_LINUX_X64_SHA256} ${archive}" | sha256sum --check --status
mkdir -p "${cross_dir}"
rm -f "${cross_dir}/cross" "${cross_dir}/cross-util"
tar --extract --gzip --file "${archive}" --directory "${cross_dir}" \
cross cross-util
chmod 0755 "${cross_dir}/cross" "${cross_dir}/cross-util"
fi
installed_version="$(probe_cross_version)"
[[ "${installed_version}" == "${expected_version}" ]]
echo "${cross_dir}" >> "${GITHUB_PATH}"
- name: Cache cargo registry
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-${{ matrix.target }}-rust-${{ env.RELEASE_RUST_VERSION }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-rust-${{ env.RELEASE_RUST_VERSION }}-cargo-
- name: Add release target
run: rustup target add --toolchain "${RELEASE_RUST_VERSION}" ${{ matrix.target }}
- name: Build release binary
run: |
set -euo pipefail
if [[ "${{ matrix.builder }}" == "cross" ]]; then
cross +"${RELEASE_RUST_VERSION}" build --release --target ${{ matrix.target }}
else
cargo +"${RELEASE_RUST_VERSION}" build --release --target ${{ matrix.target }}
fi
- name: Prepare artifact
run: |
set -euo pipefail
binary_ext=""
if [[ "${{ matrix.target }}" == *windows* ]]; then
binary_ext=".exe"
fi
# The Windows image exposes the interpreter as `python`; the Linux and
# macOS images expose `python3`.
python_bin=python3
command -v python3 >/dev/null 2>&1 || python_bin=python
"${python_bin}" scripts/package_release_artifacts.py \
--binary "target/${{ matrix.target }}/release/${REPO_NAME}${binary_ext}" \
--artifact-dir "artifacts/${{ matrix.os }}-${{ matrix.arch }}" \
--package-name "${REPO_NAME}" \
--version "${RELEASE_TAG#v}" \
--target "${{ matrix.target }}" \
--os "${{ matrix.os }}" \
--arch "${{ matrix.arch }}"
- name: Upload release artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: ${{ env.REPO_NAME }}-${{ matrix.os }}-${{ matrix.arch }}
path: artifacts/${{ matrix.os }}-${{ matrix.arch }}
overwrite: true
- name: Publish this target's release assets
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
artifact_dir="artifacts/${{ matrix.os }}-${{ matrix.arch }}"
existing_dir="${RUNNER_TEMP}/existing-release-assets/${{ matrix.target }}"
mkdir -p "${existing_dir}"
for file in "${artifact_dir}"/*; do
asset_name="$(basename "${file}")"
asset_id="$(gh api \
"repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" \
--jq ".assets[] | select(.name == \"${asset_name}\") | .id")"
if [[ -n "${asset_id}" ]]; then
existing_file="${existing_dir}/${asset_name}"
gh api --method GET -H "Accept: application/octet-stream" \
"repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" \
> "${existing_file}"
if ! cmp --silent "${file}" "${existing_file}"; then
echo "release asset ${asset_name} exists with different content" >&2
exit 1
fi
echo "release asset ${asset_name} already exists with identical content"
continue
fi
gh release upload "${RELEASE_TAG}" "${file}" \
--repo "${GITHUB_REPOSITORY}"
done