#!/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 }