mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 20:07:03 +01:00
The venv rebuild after a requirements.txt change was the largest cold-path cost of `scripts/check_gate_stamp.sh full`. uv is also what CI installs with, so the mirror gets closer to CI, not further. The interpreter is passed as a path: `--python python3` let uv pick an active ~/.venv (3.13) that crdt-sync 0.9.2 (>=3.14) cannot install into. pip stays as fallback. Measured under capped.sh (4G, 20 %): `full` warm 10.9 s; fully cold (no venv, empty PRE_COMMIT_HOME, empty mypy cache) 45.9 s, floor = the pylint hook env install (~24 s, only after .pre-commit-config.yaml changes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xfj1sxKH9VBPqpcAni8Jry
109 lines
4.1 KiB
Bash
Executable File
109 lines
4.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================================
|
|
# ci_mirror.sh — run the exact CI gates locally before a push can leave.
|
|
#
|
|
# Why: the installed pre-commit hook only checks *staged* files, and the local
|
|
# dev environment already has every dependency installed. CI does neither — it
|
|
# runs `pre-commit run --all-files` and installs a fresh environment from
|
|
# requirements.txt. That gap is why green-locally repos went red in CI
|
|
# (missing runtime deps, --all-files lint debt, order-dependent test flakes).
|
|
#
|
|
# This script reproduces both CI workflows on the developer machine:
|
|
# 1. a venv built ONLY from requirements.txt (mirrors the Tests workflow's
|
|
# `pip install -r requirements.txt`), rebuilt only when requirements.txt
|
|
# changes (hash-gated so day-to-day pushes stay fast);
|
|
# 2. `pre-commit run --all-files` (mirrors the pre-commit workflow);
|
|
# 3. `python -m pytest` inside that clean venv (mirrors Tests).
|
|
#
|
|
# Runs on demand (`scripts/check_gate_stamp.sh full`, pre-commit's manual
|
|
# stage) and in CI. It left the pre-push path on 2026-09-20: 10+ minutes
|
|
# per push under the resource cap, for work finish_auto.sh had just done.
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
# Requirements file may be overridden (e.g. testsAndMisc uses meta/…).
|
|
REQUIREMENTS_FILE="${REQUIREMENTS_FILE:-requirements.txt}"
|
|
readonly REQUIREMENTS_FILE
|
|
|
|
ROOT="$(git rev-parse --show-toplevel)"
|
|
readonly ROOT
|
|
cd "$ROOT"
|
|
|
|
readonly VENV_DIR="$ROOT/.ci-mirror-venv"
|
|
readonly HASH_FILE="$VENV_DIR/.requirements.sha256"
|
|
readonly REQ_PATH="$ROOT/$REQUIREMENTS_FILE"
|
|
|
|
log() { printf 'ci-mirror: %s\n' "$1" >&2; }
|
|
|
|
fail() {
|
|
log "FAILED — $1"
|
|
log "CI would be red. Fix the above before pushing."
|
|
exit 1
|
|
}
|
|
|
|
require_file() {
|
|
if [[ ! -f "$REQ_PATH" ]]; then
|
|
fail "requirements file not found: $REQ_PATH"
|
|
fi
|
|
}
|
|
|
|
# Rebuild the venv only when requirements.txt changed since the last build.
|
|
ensure_venv() {
|
|
local current stored
|
|
current="$(sha256sum "$REQ_PATH" | cut -d' ' -f1)"
|
|
stored=""
|
|
if [[ -f "$HASH_FILE" ]]; then
|
|
stored="$(cat "$HASH_FILE")"
|
|
fi
|
|
|
|
if [[ -x "$VENV_DIR/bin/python" && "$current" == "$stored" ]]; then
|
|
log "venv up to date (requirements.txt unchanged)"
|
|
return
|
|
fi
|
|
|
|
log "requirements.txt changed — rebuilding clean venv (mirrors CI install)"
|
|
rm -rf "$VENV_DIR"
|
|
# uv is what CI installs with (`uv pip install -r`), and it rebuilt this
|
|
# venv in 2.1 s where venv + pip took 18.3 s (2026-10-16) -- the largest
|
|
# cold-path cost of `check_gate_stamp.sh full`. pip stays as the fallback.
|
|
if command -v uv >/dev/null 2>&1; then
|
|
# A PATH, not the bare name: `--python python3` let uv prefer an
|
|
# active ~/.venv (3.13) over the python3 the pip path would use.
|
|
uv venv --quiet --python "$(command -v python3)" "$VENV_DIR" \
|
|
|| fail "uv venv for the clean venv"
|
|
uv pip install --quiet --python "$VENV_DIR/bin/python" -r "$REQ_PATH" \
|
|
|| fail "uv pip install -r $REQUIREMENTS_FILE (a dep may be undeclared)"
|
|
printf '%s' "$current" > "$HASH_FILE"
|
|
log "clean venv ready (uv)"
|
|
return
|
|
fi
|
|
python3 -m venv "$VENV_DIR"
|
|
"$VENV_DIR/bin/python" -m pip install --quiet --upgrade pip \
|
|
|| fail "pip self-upgrade in the clean venv"
|
|
"$VENV_DIR/bin/python" -m pip install --quiet -r "$REQ_PATH" \
|
|
|| fail "pip install -r $REQUIREMENTS_FILE (a dep may be undeclared)"
|
|
printf '%s' "$current" > "$HASH_FILE"
|
|
log "clean venv ready"
|
|
}
|
|
|
|
run_precommit_all_files() {
|
|
log "pre-commit run --all-files (mirrors the pre-commit workflow)"
|
|
pre-commit run --all-files || fail "pre-commit --all-files"
|
|
}
|
|
|
|
run_pytest_clean_venv() {
|
|
log "pytest in the clean venv (mirrors the Tests workflow)"
|
|
"$VENV_DIR/bin/python" -m pytest "$@" || fail "pytest (clean requirements.txt venv)"
|
|
}
|
|
|
|
main() {
|
|
require_file
|
|
ensure_venv
|
|
run_precommit_all_files
|
|
run_pytest_clean_venv "$@"
|
|
log "all CI gates passed locally — safe to push"
|
|
}
|
|
|
|
main "$@"
|