mirror of
https://github.com/storytold/craft-common-actions.git
synced 2026-11-07 03:01:35 +00:00
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>
279 lines
12 KiB
YAML
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
|