yams/scripts/local-ci/push-lane.sh
2026-07-04 09:28:59 -04:00

260 lines
7.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# yams/scripts/local-ci/push-lane.sh
#
# Tiered local GitHub Actions validation for pre-push/release sanity.
# Uses act for Linux workflow parity and repo-native package scripts for package lanes.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
LOG_ROOT_DEFAULT="${REPO_ROOT}/build/local-ci/push-lane"
PROFILE="fast"
DRY_RUN=0
KEEP_GOING=0
LOG_ROOT="${LOG_ROOT_DEFAULT}"
ACT_BIN_LABEL=""
ACT_CMD=()
ARCH_ARG=""
RUN_TAG="$(date -u +%Y%m%dT%H%M%SZ)-$$"
usage() {
cat <<'USAGE'
Usage: push-lane.sh [options]
Profiles:
fast Tool preflight, act sanity, Tests linux-x86_64, Release linux-hosted fast-mode
tests Fast lane plus targeted native Meson tests when a local builddir is available
release Release linux-hosted fast-mode via act
packages Local Docker package build; systemd install validation only on Linux
full tests + release + packages
Options:
--profile NAME fast|tests|release|packages|full (default: fast)
--dry-run Print commands without executing them
--keep-going Continue after failures and report all failed steps
--log-root DIR Log directory (default: build/local-ci/push-lane)
-h, --help Show this help
Examples:
bash scripts/local-ci/push-lane.sh --profile fast
bash scripts/local-ci/push-lane.sh --profile release --dry-run
YAMS_PREPUSH_GH_LANE=1 git push
USAGE
}
log() { printf '\033[1;34m[push-lane]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
fail_msg() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; }
while [ "$#" -gt 0 ]; do
case "$1" in
--profile)
PROFILE="${2:-}"
shift 2
;;
--dry-run)
DRY_RUN=1
shift
;;
--keep-going)
KEEP_GOING=1
shift
;;
--log-root)
LOG_ROOT="${2:-}"
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
fail_msg "unknown argument: $1"
usage
exit 2
;;
esac
done
case "${PROFILE}" in
fast | tests | release | packages | full) ;;
*)
fail_msg "invalid profile: ${PROFILE}"
echo "valid profiles: fast, tests, release, packages, full" >&2
exit 2
;;
esac
mkdir -p "${LOG_ROOT}"
SUMMARY="${LOG_ROOT}/summary-${RUN_TAG}.md"
: >"${SUMMARY}"
FAILURES=()
record_summary() {
printf '%s\n' "$*" >>"${SUMMARY}"
}
find_act() {
if [ -n "${ACT_BIN_LABEL}" ]; then
return 0
fi
if command -v act >/dev/null 2>&1; then
ACT_BIN_LABEL="act"
ACT_CMD=(act)
elif command -v gh >/dev/null 2>&1 && gh act --help >/dev/null 2>&1; then
ACT_BIN_LABEL="gh act"
ACT_CMD=(gh act)
else
fail_msg "act not found. Install with: brew install act"
fail_msg "Docker is also required: https://docs.docker.com/desktop/"
exit 2
fi
}
preflight() {
log "preflight"
command -v docker >/dev/null 2>&1 || {
fail_msg "docker not found on PATH"
exit 2
}
find_act
if [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "arm64" ]; then
ARCH_ARG="--container-architecture linux/amd64"
fi
ok "docker: $(docker --version 2>/dev/null || echo unknown)"
ok "act: $("${ACT_CMD[@]}" --version 2>/dev/null | head -n1 || echo "${ACT_BIN_LABEL}")"
record_summary "# YAMS local push lane (${PROFILE})"
record_summary ""
record_summary "- Started: ${RUN_TAG}"
record_summary "- Host: $(uname -s)/$(uname -m)"
record_summary "- Act: ${ACT_BIN_LABEL} ${ARCH_ARG}"
record_summary ""
}
run_step() {
local name="$1"
shift
local log_file="${LOG_ROOT}/${RUN_TAG}-${name//[^A-Za-z0-9_.-]/_}.log"
log "${name}"
record_summary "## ${name}"
record_summary ""
record_summary '```sh'
printf '%q ' "$@" >>"${SUMMARY}"
record_summary ""
record_summary '```'
if [ "${DRY_RUN}" -eq 1 ]; then
echo "DRY-RUN: $*" | tee "${log_file}"
record_summary "- Result: dry-run"
record_summary "- Log: ${log_file#"${REPO_ROOT}"/}"
record_summary ""
return 0
fi
set +e
"$@" > >(tee "${log_file}") 2> >(tee -a "${log_file}" >&2)
local rc=$?
set -e
if [ "${rc}" -eq 0 ]; then
ok "${name}"
record_summary "- Result: pass"
else
fail_msg "${name} failed (exit ${rc}); log: ${log_file}"
record_summary "- Result: fail (${rc})"
FAILURES+=("${name} (${rc})")
if [ "${KEEP_GOING}" -ne 1 ]; then
record_summary "- Log: ${log_file#"${REPO_ROOT}"/}"
record_summary ""
exit "${rc}"
fi
fi
record_summary "- Log: ${log_file#"${REPO_ROOT}"/}"
record_summary ""
return 0
}
run_act_sanity() {
run_step "act-sanity-workflow-lint" bash -lc "cd '${REPO_ROOT}' && ${ACT_BIN_LABEL} pull_request -W .github/workflows/act-sanity.yml -j workflow-lint ${ARCH_ARG} --artifact-server-path '${LOG_ROOT}/artifacts' --cache-server-path '${LOG_ROOT}/cache'"
run_step "act-sanity-windows-script" bash -lc "cd '${REPO_ROOT}' && ${ACT_BIN_LABEL} pull_request -W .github/workflows/act-sanity.yml -j windows-script-sanity ${ARCH_ARG} --artifact-server-path '${LOG_ROOT}/artifacts' --cache-server-path '${LOG_ROOT}/cache'"
}
run_tests_act() {
run_step "tests-linux-x86_64-act" bash -lc "cd '${REPO_ROOT}' && ${ACT_BIN_LABEL} push -W .github/workflows/tests.yml -j tests --matrix suffix:linux-x86_64 ${ARCH_ARG} --artifact-server-path '${LOG_ROOT}/artifacts' --cache-server-path '${LOG_ROOT}/cache'"
}
run_release_act() {
run_step "release-linux-hosted-fast-act" bash -lc "cd '${REPO_ROOT}' && ${ACT_BIN_LABEL} workflow_dispatch -W .github/workflows/release.yml -j build-release --matrix os:linux-hosted --input channel=nightly --input fast_mode=true ${ARCH_ARG} --artifact-server-path '${LOG_ROOT}/artifacts' --cache-server-path '${LOG_ROOT}/cache'"
}
run_targeted_native_tests() {
local bd="${YAMS_LOCAL_CI_BUILD_DIR:-}"
if [ -z "${bd}" ]; then
for candidate in builddir build/debug build/release; do
if [ -f "${REPO_ROOT}/${candidate}/build.ninja" ]; then
bd="${candidate}"
break
fi
done
fi
if [ -z "${bd}" ]; then
warn "no local Meson builddir found; skipping targeted native tests"
record_summary "## targeted-native-tests"
record_summary ""
record_summary "- Result: skipped (no builddir; set YAMS_LOCAL_CI_BUILD_DIR)"
record_summary ""
return 0
fi
run_step "targeted-native-known-failures" bash -lc "cd '${REPO_ROOT}' && meson test -C '${bd}' metadata_corruption daemon_background_processing integration_smoke --print-errorlogs --timeout-multiplier 2"
}
run_package_lane() {
if [ "$(uname -s)" = "Linux" ]; then
run_step "package-lane-build-and-validate" bash -lc "cd '${REPO_ROOT}' && bash scripts/local-ci/package-lane.sh"
else
warn "non-Linux host: running package build only; systemd install validation requires Linux"
run_step "package-lane-build-only" bash -lc "cd '${REPO_ROOT}' && bash scripts/local-ci/package-lane.sh --build-only"
fi
}
preflight
case "${PROFILE}" in
fast)
run_act_sanity
run_tests_act
run_release_act
;;
tests)
run_act_sanity
run_tests_act
run_targeted_native_tests
;;
release)
run_release_act
;;
packages)
run_package_lane
;;
full)
run_act_sanity
run_tests_act
run_targeted_native_tests
run_release_act
run_package_lane
;;
esac
if [ "${#FAILURES[@]}" -gt 0 ]; then
record_summary "## Failures"
for failure in "${FAILURES[@]}"; do
record_summary "- ${failure}"
done
fail_msg "${#FAILURES[@]} step(s) failed. Summary: ${SUMMARY}"
exit 1
fi
record_summary "## Result"
record_summary "All requested local validation steps passed."
ok "summary: ${SUMMARY#"${REPO_ROOT}"/}"