Files
craft-common-actions/.github/workflows/gate.yml
T
dependabot[bot] 9bcbea6fa2 ci(deps): bump taiki-e/install-action (#134)
Bumps the actions group with 1 update in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.88.1 to 2.89.1
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/68a389c788b2cd04016ce8c80ee23b80d2ab3d68...682ce6c3c696a4cd5168a05f98e51aa02b877941)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.89.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-05 09:13:33 -05:00

279 lines
12 KiB
YAML

name: "Gate"
# The quality gate the craft apps run on pull requests and pushes to main: one job per runner in
# `os`, each running `command` (by default `cargo xtask ci`) with the suite's shared setup.
# App-specific checks (fuzzing, corpora, web builds) belong here too, as shared workflows or
# inputs: an app's workflows only call this repository's workflows.
on:
workflow_call:
inputs:
packages:
description: "Ubuntu packages the build needs, separated by spaces or newlines; installed on Linux legs through the cached install-apt-packages action. Empty installs none (Windows-only calls, wasm builds)"
type: string
required: false
default: ""
command:
description: "The gate, run with bash from the repository root"
type: string
required: false
default: "cargo xtask ci"
windows-command:
description: "The gate on Windows legs; empty runs `command`"
type: string
required: false
default: ""
os:
description: 'JSON array of runner labels; one leg per entry, e.g. ["ubuntu-24.04","windows-latest"]'
type: string
required: false
default: '["ubuntu-24.04"]'
craft-fonts-ref:
# The suite's craft-fonts pin, as in release.yml and freebsd.yml.
description: "The commit of storytold/craft-fonts the tests build with. Defaults to the suite's pin; empty builds without craft-fonts"
type: string
required: false
default: "bfcbf714e45a3ea8e98f88ab2b7971372a576414"
require-craft-fonts:
description: "Set CRAFT_FONTS_REQUIRED=1 when craft-fonts is checked out, so a build that silently drops its fonts fails here as it would in a release"
type: boolean
required: false
default: true
toolchain:
description: "rustup toolchain to install when rust-toolchain.toml names no channel"
type: string
required: false
default: "stable"
targets:
description: "Extra rustup targets, separated by spaces or commas"
type: string
required: false
default: "wasm32-unknown-unknown"
tools:
description: "Prebuilt cargo tools from taiki-e/install-action, comma-separated, each with its version (e.g. cargo-nextest@0.9.150,cargo-deny@0.20.2); empty installs none"
type: string
required: false
default: "cargo-nextest@0.9.150"
env:
description: "KEY=VALUE lines added to the environment of every leg before the cache is restored"
type: string
required: false
default: ""
linux-env:
description: "KEY=VALUE lines for Linux legs only"
type: string
required: false
default: ""
windows-env:
description: "KEY=VALUE lines for Windows legs only"
type: string
required: false
default: ""
dev-debug:
description: "CARGO_PROFILE_DEV_DEBUG for the gate (the test profile inherits it); empty leaves the repository's profile alone"
type: string
required: false
default: "line-tables-only"
free-disk-space:
description: "Delete the Linux image's unused toolchains (Android, .NET, GHC, CodeQL) in the background before the build"
type: boolean
required: false
default: true
cache-version:
description: "Bump to start the dependency cache afresh"
type: string
required: false
default: "v1"
cache-save-on-dispatch:
description: "Allow manually dispatched branch runs to save their branch-scoped dependency cache; pull requests never write"
type: boolean
required: false
default: false
timeout-minutes:
description: "Timeout of each leg"
type: number
required: false
default: 60
gate-timeout-minutes:
description: "Stop the gate command after this many minutes, a few before the leg's timeout-minutes, and report it as timed out with the processes still running a minute before; 0 (default) sets no limit of its own"
type: number
required: false
default: 0
permissions:
contents: read
jobs:
gate:
name: gate (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: ${{ inputs.timeout-minutes }}
strategy:
# A failing leg must not cancel the others and hide their failures.
fail-fast: false
matrix:
os: ${{ fromJSON(inputs.os) }}
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUST_BACKTRACE: 1
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check out craft-fonts
if: inputs.craft-fonts-ref != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: storytold/craft-fonts
ref: ${{ inputs.craft-fonts-ref }}
path: craft-fonts
persist-credentials: false
- name: Build with craft-fonts
if: inputs.craft-fonts-ref != ''
shell: bash
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
REQUIRED: ${{ inputs.require-craft-fonts }}
run: |
echo "CRAFT_FONTS_DIR=$CRAFT_FONTS_DIR" >> "$GITHUB_ENV"
if [ "$REQUIRED" = true ]; then echo "CRAFT_FONTS_REQUIRED=1" >> "$GITHUB_ENV"; fi
# Does nothing unless the repository has craft-i18n.rev; then builds require the translations.
- name: Check out craft-i18n
uses: storytold/craft-common-actions/actions/checkout-craft-i18n@main
# Before the cache step: rust-cache keys on CARGO_* and RUST* variables.
- name: Caller environment
if: inputs.env != '' || inputs.linux-env != '' || inputs.windows-env != '' || inputs.dev-debug != ''
shell: bash
env:
ALL: ${{ inputs.env }}
LINUX: ${{ inputs.linux-env }}
WINDOWS: ${{ inputs.windows-env }}
DEV_DEBUG: ${{ inputs.dev-debug }}
run: |
set -euo pipefail
case "$RUNNER_OS" in
Linux) extra=$LINUX ;;
Windows) extra=$WINDOWS ;;
*) extra= ;;
esac
if [ -n "$DEV_DEBUG" ]; then echo "CARGO_PROFILE_DEV_DEBUG=$DEV_DEBUG" >> "$GITHUB_ENV"; fi
printf '%s\n%s\n' "$ALL" "$extra" | while IFS= read -r line; do
[ -n "$line" ] || continue
if ! [[ "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
echo "::error::env: expected KEY=VALUE, got '$line'"
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done
# Nothing waits on it: the space is free before the build needs it.
- name: Free disk space
if: runner.os == 'Linux' && inputs.free-disk-space
run: |
nohup sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL </dev/null >/dev/null 2>&1 &
- name: Install Linux build dependencies
if: runner.os == 'Linux' && inputs.packages != ''
uses: storytold/craft-common-actions/actions/install-apt-packages@main
with:
packages: ${{ inputs.packages }}
# The channel in rust-toolchain.toml wins over `toolchain`, so the gate builds with the
# toolchain contributors use and a new Rust release does not invalidate the cache.
- name: Install Rust
shell: bash
env:
TOOLCHAIN: ${{ inputs.toolchain }}
TARGETS: ${{ inputs.targets }}
run: |
set -euo pipefail
if [ -f rust-toolchain.toml ]; then
channel=$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' rust-toolchain.toml)
TOOLCHAIN=${channel:-$TOOLCHAIN}
fi
args=(--profile minimal --no-self-update --component clippy --component rustfmt)
for target in ${TARGETS//,/ }; do args+=(--target "$target"); done
rustup toolchain install "$TOOLCHAIN" "${args[@]}"
rustup default "$TOOLCHAIN"
rustc --version
- name: Install cargo tools
if: inputs.tools != ''
uses: taiki-e/install-action@682ce6c3c696a4cd5168a05f98e51aa02b877941 # v2.89.1
with:
tool: ${{ inputs.tools }}
# Root workspace profiles are not among rust-cache's member manifests. Hash only
# those profiles here: a dependency/version/comment change must not discard the
# fallback cache for every other dependency. rust-cache still hashes the lockfile,
# toolchain, member manifests and build environment, and Cargo checks fingerprints.
- name: Cache profile key
id: cache-profile
shell: bash
run: |
python - <<'PYTHON'
import hashlib
import json
import os
import tomllib
from pathlib import Path
manifest = tomllib.loads(Path("Cargo.toml").read_text(encoding="utf-8"))
profiles = json.dumps(manifest.get("profile", {}), sort_keys=True, separators=(",", ":"))
key = hashlib.sha256(profiles.encode("utf-8")).hexdigest()
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"key={key}\n")
PYTHON
# Default-branch writes remain the default. Maintainers can explicitly opt manual
# branch runs into branch-scoped writes; pull-request runs never save a cache.
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
with:
prefix-key: ${{ inputs.cache-version }}-rust
key: ${{ steps.cache-profile.outputs.key }}
save-if: ${{ github.event_name != 'pull_request' && (github.ref_name == github.event.repository.default_branch || (inputs.cache-save-on-dispatch && github.event_name == 'workflow_dispatch')) }}
cache-on-failure: true
- name: Gate
shell: bash
env:
GATE: ${{ runner.os == 'Windows' && inputs.windows-command != '' && inputs.windows-command || inputs.command }}
GATE_TIMEOUT: ${{ inputs.gate-timeout-minutes }}
run: |
printf '%s\n' "$GATE" > "$RUNNER_TEMP/gate.sh"
# gate-timeout-minutes: GNU timeout stops a hung command before the job's own timeout
# would, so the log says which processes hung instead of just "cancelled".
if [[ ! "$GATE_TIMEOUT" =~ ^[1-9][0-9]*$ ]] || ! timeout --version >/dev/null 2>&1; then
[[ "$GATE_TIMEOUT" =~ ^0*$ ]] || echo "::warning title=Gate timeout not set::gate-timeout-minutes '$GATE_TIMEOUT' needs a whole number of minutes and GNU timeout"
exec bash -euo pipefail "$RUNNER_TEMP/gate.sh"
fi
snapshot() {
echo "::group::Processes running one minute before the gate timeout"
if [ "$RUNNER_OS" = Windows ]; then ps -W; else ps -eo pid,ppid,etime,args; fi || true
echo "::endgroup::"
}
(
sleep $(( GATE_TIMEOUT * 60 - 60 )) & sleeper=$!
trap 'kill "$sleeper" 2>/dev/null; exit 0' TERM
wait "$sleeper" && snapshot
) &
watchdog=$!
status=0
timeout --kill-after=30s "${GATE_TIMEOUT}m" bash -euo pipefail "$RUNNER_TEMP/gate.sh" || status=$?
kill "$watchdog" 2>/dev/null || true
wait "$watchdog" 2>/dev/null || true
if [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then
echo "::error title=Gate timed out::the gate command ran for over $GATE_TIMEOUT min and was stopped. The 'Processes running one minute before the gate timeout' group shows what was still running; for cargo test, the 'has been running for over 60 seconds' lines name the hung tests."
fi
exit "$status"
# A gate that changed Cargo.lock ran against a lockfile that is not the one in the repository.
- name: Lockfile unchanged
shell: bash
run: git diff --exit-code -- Cargo.lock