|
|
|
@@ -44,10 +44,28 @@ cd "$REPO_ROOT" || exit 1
|
|
|
|
|
|
|
|
|
|
MODE="$(basename "$0")"
|
|
|
|
|
ZERO="0000000000000000000000000000000000000000"
|
|
|
|
|
CACHE_DIR=".git/lmc-verify"
|
|
|
|
|
# `git rev-parse --git-common-dir`, not a literal ".git" (#258). In a linked worktree `.git` is a
|
|
|
|
|
# FILE containing `gitdir: ...`, so `mkdir -p .git/lmc-verify` fails with "Not a directory" -- and
|
|
|
|
|
# because the write is the last thing this script does, it failed while the gate still printed
|
|
|
|
|
# green and exited 0. Every push from a worktree then re-swept 33-36 for nothing, silently, which
|
|
|
|
|
# is the worst shape a cache can fail in: invisible and expensive.
|
|
|
|
|
#
|
|
|
|
|
# --git-common-dir rather than --git-dir so the cache is SHARED across worktrees. The key is the
|
|
|
|
|
# app/src tree hash, and identical content is identical content whichever worktree produced it.
|
|
|
|
|
CACHE_DIR="$(git rev-parse --git-common-dir)/lmc-verify"
|
|
|
|
|
GRADLE_GATE=(:app:assembleDebug :app:testDebugUnitTest :app:compileDebugAndroidTestKotlin
|
|
|
|
|
:app:ktlintCheck :app:detekt :app:lintDebug)
|
|
|
|
|
|
|
|
|
|
# Says so when it cannot record, rather than leaving a cache that silently never fills (#258).
|
|
|
|
|
record_sweep() {
|
|
|
|
|
[ -n "$tree" ] || return 0
|
|
|
|
|
if mkdir -p "$CACHE_DIR" 2>/dev/null && : > "$CACHE_DIR/$tree" 2>/dev/null; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
printf '\n\033[1m[local-gate]\033[0m could not record the sweep under %s -- it will re-run next
|
|
|
|
|
time. Not fatal, but it means every commit and push pays for it again.\n' "$CACHE_DIR"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
say() { printf '\n\033[1m[local-gate]\033[0m %s\n' "$*"; }
|
|
|
|
|
die() {
|
|
|
|
|
printf '\n\033[1;31m[local-gate] BLOCKED\033[0m %s\n' "$*"
|
|
|
|
@@ -105,6 +123,72 @@ done <<< "$changed_files"
|
|
|
|
|
|
|
|
|
|
# --- the cheap gate always runs -----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
# --- shellcheck, at CI's exact pin ---------------------------------------------------------
|
|
|
|
|
# WHY THIS IS HERE. The gate ran ktlint, detekt and Android lint but not shellcheck, so a new or
|
|
|
|
|
# edited `.sh` file was precisely the case where this hook passed and CI's Static analysis leg
|
|
|
|
|
# still went red. That is not hypothetical: this script is itself a new `.sh` file, and the first
|
|
|
|
|
# thing it could not check was itself. It was caught by hand twice before it was caught here.
|
|
|
|
|
#
|
|
|
|
|
# THE DIGEST IS READ OUT OF status_check.yml, NOT COPIED INTO THIS FILE. shellcheck 0.9.0 and
|
|
|
|
|
# 0.11.0 disagree about how to report a trap handler -- SC2317 on seven body lines versus SC2329
|
|
|
|
|
# once on the declaration, same script, same directive, one red and one green. That disagreement
|
|
|
|
|
# is why CI pins by digest, and a second copy of the digest here would drift from it silently.
|
|
|
|
|
# When it drifts, the symptom is this gate passing and CI failing: the exact thing this section
|
|
|
|
|
# exists to prevent. So there is one digest in the repo and this reads it.
|
|
|
|
|
#
|
|
|
|
|
# ALL TRACKED FILES, not just changed ones, because that is what CI does -- `git ls-files '*.sh'`.
|
|
|
|
|
# The point is to predict that leg, not to audit the diff.
|
|
|
|
|
shellcheck_pin="$(grep -oE 'koalaman/shellcheck@sha256:[0-9a-f]{64}' \
|
|
|
|
|
.github/workflows/status_check.yml | head -1)"
|
|
|
|
|
# :z is podman's SELinux relabel and is what this host needs; docker on CI does without it.
|
|
|
|
|
runtime=""
|
|
|
|
|
mount=":z"
|
|
|
|
|
for candidate in podman docker; do
|
|
|
|
|
if command -v "$candidate" >/dev/null 2>&1; then
|
|
|
|
|
runtime="$candidate"
|
|
|
|
|
[ "$candidate" = "docker" ] && mount=""
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ -z "$shellcheck_pin" ]; then
|
|
|
|
|
say "NOT COVERED: shellcheck. Could not read the pinned digest out of
|
|
|
|
|
.github/workflows/status_check.yml -- if that pin moved or was reformatted, fix this grep
|
|
|
|
|
rather than leaving the check silently absent."
|
|
|
|
|
elif [ -z "$runtime" ]; then
|
|
|
|
|
say "NOT COVERED: shellcheck. Neither podman nor docker is on PATH, and there is no shellcheck
|
|
|
|
|
system package on this host. CI's Static analysis leg is what answers for .sh files then."
|
|
|
|
|
else
|
|
|
|
|
say "shellcheck ($runtime, $shellcheck_pin)"
|
|
|
|
|
if ! git ls-files -z '*.sh' |
|
|
|
|
|
xargs -0 -r "$runtime" run --rm -v "$PWD:/mnt$mount" "docker.io/$shellcheck_pin"; then
|
|
|
|
|
die "shellcheck failed. CI runs the same digest over the same files, so this is a red
|
|
|
|
|
Static analysis leg waiting to happen."
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# --- actionlint, the half shellcheck cannot see ---------------------------------------------
|
|
|
|
|
# A good deal of this repo's bash lives in workflow `run:` blocks, which `git ls-files '*.sh'`
|
|
|
|
|
# does not match at all -- so without this a workflow edit is the same hole the section above
|
|
|
|
|
# just closed: green here, red on Static analysis. Pinned by digest for the reason in that
|
|
|
|
|
# section, and for actionlint's own: its documented install is `curl | bash` off a moving branch,
|
|
|
|
|
# which does not belong in a repo that pins every action by SHA.
|
|
|
|
|
actionlint_pin="$(grep -oE 'rhysd/actionlint@sha256:[0-9a-f]{64}' \
|
|
|
|
|
.github/workflows/status_check.yml | head -1)"
|
|
|
|
|
|
|
|
|
|
if [ -z "$actionlint_pin" ]; then
|
|
|
|
|
say "NOT COVERED: actionlint. Could not read the pinned digest out of
|
|
|
|
|
.github/workflows/status_check.yml -- fix this grep rather than leaving the check absent."
|
|
|
|
|
elif [ -z "$runtime" ]; then
|
|
|
|
|
say "NOT COVERED: actionlint. Neither podman nor docker is on PATH; CI's Static analysis leg
|
|
|
|
|
is what answers for the workflows then."
|
|
|
|
|
else
|
|
|
|
|
say "actionlint ($runtime, $actionlint_pin)"
|
|
|
|
|
if ! "$runtime" run --rm -v "$PWD:/repo$mount" -w /repo "docker.io/$actionlint_pin" -color; then
|
|
|
|
|
die "actionlint failed. CI runs the same digest over the same workflows."
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
say "$MODE: running the JVM gate"
|
|
|
|
|
if ! ./gradlew "${GRADLE_GATE[@]}" --continue; then
|
|
|
|
|
die "the JVM gate failed (assemble, unit tests, androidTest compile, ktlint, detekt, lint)."
|
|
|
|
@@ -114,7 +198,7 @@ fi
|
|
|
|
|
|
|
|
|
|
if [ "$touches_source" -eq 0 ] && [ "$touches_tests" -eq 0 ]; then
|
|
|
|
|
say "no app/src changes; the instrumented sweep is not required for this one"
|
|
|
|
|
mkdir -p "$CACHE_DIR" && [ -n "$tree" ] && : > "$CACHE_DIR/$tree"
|
|
|
|
|
record_sweep
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
@@ -157,7 +241,7 @@ else
|
|
|
|
|
attach the Pixel 10 Pro XL to have this hook cover it too."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
mkdir -p "$CACHE_DIR" && [ -n "$tree" ] && : > "$CACHE_DIR/$tree"
|
|
|
|
|
record_sweep
|
|
|
|
|
# Name the levels rather than claiming "every supported level". The first cut said the latter on
|
|
|
|
|
# both paths, including the one that had just printed NOT COVERED two lines above -- a false claim
|
|
|
|
|
# printed by the tool whose whole job is to stop false claims reaching CI.
|
|
|
|
|