Skip to content

Deployment

Deployment #32

Workflow file for this run

name: Deployment
permissions:
contents: write
packages: write
actions: write
on:
workflow_dispatch:
inputs:
publish:
description: "Run side-effecting release steps (tags, uploads,
registry/package-manager publication)"
type: boolean
required: true
default: true
concurrency:
group: deployment-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
build-test:
name: Build and verify
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
fetch-tags: true
- name: Install dependencies
run: |
sudo apt-get update || true
sudo apt-get install -y --no-install-recommends \
cmake build-essential pkg-config libfontconfig1-dev libfreetype6-dev libexpat1-dev
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Verify crate
run: |
cargo package --allow-dirty -p h5v-macros
cargo check --workspace --locked
generate-changelog:
name: Generate changelog and tag
needs: build-test
runs-on: ubuntu-latest
outputs:
release_body: ${{ steps.git-cliff.outputs.content }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
fetch-tags: true
- name: Generate a changelog
uses: orhun/git-cliff-action@07dda386992489f0a6151dee53f3a137713644de
id: git-cliff
with:
# Only generate a changelog for the latest release, and bump the version in Cargo.toml
args: -vv --bump --unreleased
- name: Extract version
id: version
shell: bash
run: |
version=$(echo "${{ steps.git-cliff.outputs.content }}" | grep -m1 -oP '(?<=## \[)[^]]+')
if [ -z "$version" ]; then
echo "Failed to extract version from changelog"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Create and push tag
if: ${{ inputs.publish }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
tag="v${{ steps.version.outputs.version }}"
git tag "$tag"
git push origin "$tag"
publish-binaries:
name: Publish binaries
needs: generate-changelog
runs-on: ${{ matrix.build.OS }}
strategy:
fail-fast: false
matrix:
build:
- NAME: linux-x64-glibc
OS: ubuntu-latest
TARGET: x86_64-unknown-linux-gnu
BIN: h5v
ARCHIVE_FORMAT: tar.gz
- NAME: windows-x64-msvc
OS: windows-latest
TARGET: x86_64-pc-windows-msvc
BIN: h5v.exe
ARCHIVE_FORMAT: zip
- NAME: macos-arm64
OS: macos-14
TARGET: aarch64-apple-darwin
BIN: h5v
ARCHIVE_FORMAT: tar.gz
env:
RELEASE_VERSION: ${{ needs.generate-changelog.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
fetch-tags: true
- name: Install dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake build-essential pkg-config libfontconfig1-dev libfreetype6-dev libexpat1-dev
- name: Install macOS dependencies
if: runner.os == 'macOS'
run: brew install cmake pkg-config
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.build.TARGET }}
- uses: Swatinem/rust-cache@v2
- name: Install cargo-wix
if: runner.os == 'Windows'
run: cargo install --locked cargo-wix
- name: Build
run: cargo build --release --target ${{ matrix.build.TARGET }}
- name: Build Windows installer
if: runner.os == 'Windows'
shell: bash
env:
TARGET: ${{ matrix.build.TARGET }}
run: |
cargo wix \
--package h5v \
--no-build \
--target "$TARGET" \
--target-bin-dir "target/$TARGET/release" \
--install-version "${RELEASE_VERSION}" \
--output "h5v-${TARGET}-v${RELEASE_VERSION}.msi"
python <<'PY'
import hashlib
import os
import pathlib
release_version = os.environ["RELEASE_VERSION"]
target = os.environ["TARGET"]
msi_path = pathlib.Path(f"h5v-{target}-v{release_version}.msi")
if not msi_path.is_file():
raise FileNotFoundError(
f"Expected MSI at {msi_path}, but cargo wix did not create it"
)
digest = hashlib.sha256(msi_path.read_bytes()).hexdigest()
pathlib.Path(f"{msi_path.name}.sha256").write_text(
f"{digest} {msi_path.name}\n",
encoding="utf-8",
)
PY
- name: Prepare release assets
shell: bash
env:
TARGET: ${{ matrix.build.TARGET }}
BIN_NAME: ${{ matrix.build.BIN }}
ARCHIVE_FORMAT: ${{ matrix.build.ARCHIVE_FORMAT }}
run: |
python <<'PY'
import hashlib
import os
import pathlib
import shutil
import tarfile
import zipfile
release_version = os.environ["RELEASE_VERSION"]
target = os.environ["TARGET"]
bin_name = os.environ["BIN_NAME"]
archive_format = os.environ["ARCHIVE_FORMAT"]
archive_stem = f"h5v-{target}-v{release_version}"
root_dir = pathlib.Path(archive_stem)
root_dir.mkdir(exist_ok=True)
binary_path = pathlib.Path("target") / target / "release" / bin_name
shutil.copy2(binary_path, root_dir / bin_name)
if archive_format == "tar.gz":
archive_path = pathlib.Path(f"{archive_stem}.tar.gz")
with tarfile.open(archive_path, "w:gz") as tar:
tar.add(root_dir, arcname=root_dir.name)
elif archive_format == "zip":
archive_path = pathlib.Path(f"{archive_stem}.zip")
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for path in root_dir.rglob("*"):
archive.write(path, arcname=path.relative_to(root_dir.parent))
else:
raise RuntimeError(f"Unsupported archive format: {archive_format}")
digest = hashlib.sha256(archive_path.read_bytes()).hexdigest()
pathlib.Path(f"{archive_path.name}.sha256").write_text(
f"{digest} {archive_path.name}\n",
encoding="utf-8",
)
shutil.rmtree(root_dir)
PY
- name: Upload packaged artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.build.TARGET }}
path: h5v-${{ matrix.build.TARGET }}-v${{ env.RELEASE_VERSION }}*
if-no-files-found: error
publish-release-assets:
name: Publish release assets
needs:
- generate-changelog
- publish-binaries
runs-on: ubuntu-latest
env:
RELEASE_VERSION: ${{ needs.generate-changelog.outputs.version }}
steps:
- name: Download packaged artifacts
uses: actions/download-artifact@v4
with:
pattern: release-*
merge-multiple: true
path: release-assets
- name: Publish release assets to GitHub
uses: svenstaro/upload-release-action@v2
if: ${{ inputs.publish }}
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: release-assets/**
file_glob: true
overwrite: false
tag: v${{ env.RELEASE_VERSION }}
release_name: Release v${{ env.RELEASE_VERSION }}
body: ${{ needs.generate-changelog.outputs.release_body }}
package-manager-assets:
name: Generate package manager assets
needs:
- generate-changelog
- publish-binaries
- publish-release-assets
runs-on: ubuntu-latest
env:
RELEASE_VERSION: ${{ needs.generate-changelog.outputs.version }}
REPOSITORY: ${{ github.repository }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download packaged artifacts
uses: actions/download-artifact@v4
with:
pattern: release-*
merge-multiple: true
path: release-assets
- name: Generate Homebrew and WinGet assets
run: |
python scripts/generate_package_manager_assets.py \
--version "${RELEASE_VERSION}" \
--repo "${REPOSITORY}" \
--asset-dir release-assets \
--output-dir package-manager-assets
- name: Upload package manager artifacts
uses: actions/upload-artifact@v4
with:
name: package-manager-assets
path: package-manager-assets/**
if-no-files-found: error
- name: Publish package manager assets to GitHub
uses: svenstaro/upload-release-action@v2
if: ${{ inputs.publish }}
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: package-manager-assets/**
file_glob: true
overwrite: true
tag: v${{ env.RELEASE_VERSION }}
release_name: Release v${{ env.RELEASE_VERSION }}
publish-package-manager-manifests:
name: Publish package manager manifests
needs:
- generate-changelog
- package-manager-assets
runs-on: ubuntu-latest
env:
RELEASE_VERSION: ${{ needs.generate-changelog.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
fetch-tags: true
- name: Download package manager artifacts
uses: actions/download-artifact@v4
with:
name: package-manager-assets
path: package-manager-assets
- name: Update Homebrew formula and Scoop manifest
run: |
mkdir -p Formula
cp package-manager-assets/homebrew/h5v.rb Formula/h5v.rb
mkdir -p bucket
cp package-manager-assets/scoop/h5v.json bucket/h5v.json
- name: Commit package manager manifest updates
if: ${{ inputs.publish }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
branch="${{ github.ref_name }}"
git fetch origin "$branch"
git add Formula/h5v.rb
git add bucket/h5v.json
if git diff --cached --quiet; then
echo "Package manager manifests unchanged"
exit 0
fi
git commit -m "chore: update package manager manifests for v${RELEASE_VERSION}"
git pull --rebase origin "$branch"
git push origin HEAD:"$branch"
deploy-site:
name: Build and deploy site
needs:
- generate-changelog
- publish-binaries
- package-manager-assets
- publish-package-manager-manifests
if: ${{ inputs.publish }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
fetch-tags: true
- name: Generate latest changelog for site
uses: orhun/git-cliff-action@07dda386992489f0a6151dee53f3a137713644de
env:
OUTPUT: CHANGELOG.md
GITHUB_REPO: ${{ github.repository }}
- name: Install and run oranda
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/latest/download/oranda-installer.sh | sh
oranda build
touch public/.nojekyll
- name: Prepare HTML for link checking
run: |
mkdir -p /tmp/public/
cp -R public /tmp/public/h5v
- name: Check HTML for broken internal links
uses: untitaker/hyperlink@0.1.29
with:
args: /tmp/public/
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
folder: public
token: ${{ secrets.GITHUB_TOKEN }}
single-commit: true
publish-aur:
name: Publish AUR package
needs:
- package-manager-assets
if: ${{ inputs.publish }}
runs-on: ubuntu-latest
steps:
- name: Download package manager artifacts
uses: actions/download-artifact@v4
with:
name: package-manager-assets
path: package-manager-assets
- name: Generate .SRCINFO
run: |
docker run --rm \
-v "${PWD}/package-manager-assets/aur:/work" \
-w /work \
archlinux:base-devel \
bash -lc 'useradd -m builder && chown -R builder:builder /work && su builder -c "bash -lc '\''cd /work && makepkg --printsrcinfo > .SRCINFO'\''"'
- name: Configure AUR SSH
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
run: |
install -d -m 700 ~/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
cat >> ~/.ssh/config <<'EOF'
Host aur.archlinux.org
IdentityFile ~/.ssh/aur
IdentitiesOnly yes
StrictHostKeyChecking accept-new
EOF
- name: Push PKGBUILD to AUR
run: |
git clone ssh://aur@aur.archlinux.org/h5v-bin.git aur-repo
cp package-manager-assets/aur/PKGBUILD aur-repo/PKGBUILD
cp package-manager-assets/aur/.SRCINFO aur-repo/.SRCINFO
cd aur-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add PKGBUILD .SRCINFO
if git diff --cached --quiet; then
echo "AUR package unchanged"
exit 0
fi
git commit -m "Update h5v-bin package"
git push origin HEAD
publish-crates-io:
name: Publish crates.io
needs:
- generate-changelog
- publish-release-assets
- publish-package-manager-manifests
- deploy-site
- publish-aur
if: ${{ inputs.publish }}
runs-on: ubuntu-latest
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Publish to crates.io
run: |
sed -i -E "s|^version = \".*\"$|version = \"${{ needs.generate-changelog.outputs.version }}\"|" Cargo.toml
sed -i -E "s|^macros = \\{ package = \"h5v-macros\", version = \".*\", path = \"\\./macros\" \\}$|macros = { package = \"h5v-macros\", version = \"${{ needs.generate-changelog.outputs.version }}\", path = \"./macros\" }|" Cargo.toml
sed -i -E "s|^version = \".*\"$|version = \"${{ needs.generate-changelog.outputs.version }}\"|" macros/Cargo.toml
for i in {1..5}; do
cargo publish -p h5v-macros --allow-dirty && break
echo "Publishing h5v-macros failed, retrying in 10s..."
sleep 10
done
for i in {1..10}; do
cargo publish -p h5v --allow-dirty && break
echo "Publishing h5v failed, retrying in 15s..."
sleep 15
done