5c50742a61
`scripts/ci.sh` is the source of truth for what must pass before a PR is mergeable. CLAUDE.md section 10.4 requires the Gitea workflow to run the same checks; the workflow calls this script rather than reimplementing the steps, so the two cannot drift apart. Runs: cargo fmt --check, clippy -D warnings, cargo test, the section 5 size gate, Android lint/assembleRelease, and a gitleaks scan. Verified behaviour, rather than assumed: - **Failures really fail.** Injected a syntax error into `core/src/lib.rs` and confirmed `ci.sh --keep-going` exits 1 and names all four dependent checks; then restored the file and confirmed a clean exit 0. `--keep-going` captures failures per step in a subshell instead of letting `set -e` abort the run, so the summary records what already passed. - **Skips are never passes.** Android and gitleaks both skip on this machine (no SDK, no gitleaks), and each SKIP names what was skipped and why, with the summary listing them separately from the passes. A skip that reads as a green tick is how CI quietly stops testing anything. - **Runs from any CWD.** `cargo` resolves a workspace from the CWD, not from the script's location, so `ci.sh` cds to the repo root. Verified by running it from `/tmp`. - **`cargo` is on PATH.** rustup adds `~/.cargo/bin` via `~/.bashrc`, which a non-interactive shell does not read — so a script invoked from CI or by absolute path failed with "command not found" until `lib.sh` prepends it. - **Size gate resolves absolute paths.** `CARGO_TARGET_DIR` is absolute by default, so joining it onto the repo root produced a doubled path; the default is now only joined when relative. Build artifacts go to `$HOME/.blocker-target`, outside the repo, so `target/` never appears in `git status` and can be deleted in one command. Co-Authored-By: Claude Code <noreply@anthropic.com>
108 lines
3.6 KiB
Bash
Executable File
108 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Shared helpers for the Blocker CI scripts.
|
|
#
|
|
# Sourced, never executed.
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Colours ---------------------------------------------------------------
|
|
# Only when stdout is a terminal, so CI logs stay clean and diffable.
|
|
if [[ -t 1 ]]; then
|
|
C_RESET=$'\033[0m'
|
|
C_RED=$'\033[31m'
|
|
C_GREEN=$'\033[32m'
|
|
C_YELLOW=$'\033[33m'
|
|
C_BLUE=$'\033[34m'
|
|
C_DIM=$'\033[2m'
|
|
else
|
|
C_RESET='' C_RED='' C_GREEN='' C_YELLOW='' C_BLUE='' C_DIM=''
|
|
fi
|
|
|
|
# --- Paths -----------------------------------------------------------------
|
|
# Resolve the repo root from this file's location so every script works from
|
|
# any working directory. CI invokes these from the workspace root; humans
|
|
# invoke them from wherever they happen to be.
|
|
LIB_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd -- "${LIB_DIR}/.." && pwd)"
|
|
|
|
# --- Build output ----------------------------------------------------------
|
|
# Keep build artifacts OUT of the repo (section 10.1: build outputs are not in
|
|
# git) and in one deletable place. Overridable, but the default matters: a
|
|
# target/ inside the repo shows up in `git status` and tempts a `git add -A`.
|
|
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${HOME}/.blocker-target}"
|
|
|
|
# --- Toolchain on PATH -----------------------------------------------------
|
|
# rustup installs shims into ~/.cargo/bin and adds a line to ~/.bashrc and
|
|
# ~/.profile. A non-interactive shell does not read ~/.bashrc, so a script run
|
|
# from CI, a cron job, or a different CWD can fail with "cargo: command not
|
|
# found" even on a correctly installed machine. Adding the directory here is
|
|
# idempotent and does not modify the user's shell config.
|
|
if [[ -d "${HOME}/.cargo/bin" ]]; then
|
|
case ":${PATH}:" in
|
|
*":${HOME}/.cargo/bin:"*) ;;
|
|
*) PATH="${HOME}/.cargo/bin:${PATH}" ;;
|
|
esac
|
|
export PATH
|
|
fi
|
|
|
|
# --- Section 5 budgets -----------------------------------------------------
|
|
MAX_DAEMON_BYTES=$((5 * 1024 * 1024)) # 5 MB stripped daemon
|
|
|
|
# --- Reporting -------------------------------------------------------------
|
|
_step=0
|
|
|
|
step() {
|
|
_step=$((_step + 1))
|
|
printf '\n%s[%d/%d]%s %s\n' "${C_BLUE}" "${_step}" "${TOTAL_STEPS:-?}" "${C_RESET}" "$1"
|
|
}
|
|
|
|
pass() {
|
|
printf ' %sPASS%s %s\n' "${C_GREEN}" "${C_RESET}" "$1"
|
|
}
|
|
|
|
fail() {
|
|
printf ' %sFAIL%s %s\n' "${C_RED}" "${C_RESET}" "$1"
|
|
}
|
|
|
|
# A skip that reads as a pass is the classic CI lie (CLAUDE.md section 10.4).
|
|
# Skips must always name what was skipped and why.
|
|
skip() {
|
|
printf ' %sSKIP%s %s\n' "${C_YELLOW}" "${C_RESET}" "$1"
|
|
printf ' %sreason:%s %s\n' "${C_DIM}" "${C_RESET}" "$2"
|
|
}
|
|
|
|
info() {
|
|
printf ' %s%s%s\n' "${C_DIM}" "$1" "${C_RESET}"
|
|
}
|
|
|
|
# Announce a non-fatal environment note once, before the checks run, so a
|
|
# reader knows what to expect rather than wondering why a stage vanished.
|
|
announce_skip() {
|
|
printf '%s%s%s\n' "${C_YELLOW}" "SKIP: $1 — $2" "${C_RESET}"
|
|
}
|
|
|
|
die() {
|
|
printf '\n%sfatal:%s %s\n' "${C_RED}" "${C_RESET}" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# --- Android SDK detection -------------------------------------------------
|
|
# CLAUDE.md section 10.4 asks whether the runner has the SDK. Rather than assume
|
|
# either way, detect it and report honestly.
|
|
android_sdk_root() {
|
|
if [[ -n "${ANDROID_HOME:-}" && -d "${ANDROID_HOME}" ]]; then
|
|
printf '%s' "${ANDROID_HOME}"
|
|
return 0
|
|
fi
|
|
if [[ -n "${ANDROID_SDK_ROOT:-}" && -d "${ANDROID_SDK_ROOT}" ]]; then
|
|
printf '%s' "${ANDROID_SDK_ROOT}"
|
|
return 0
|
|
fi
|
|
# Common default install location.
|
|
if [[ -d "${HOME}/Android/Sdk" ]]; then
|
|
printf '%s' "${HOME}/Android/Sdk"
|
|
return 0
|
|
fi
|
|
return 1
|
|
} |