Notcurses-Native.git | scripts/ci/ | fetch-notcurses-source.sh edit
#!/usr/bin/env bash
# Fetch the notcurses source tree pinned by NOTCURSES_FORK at the
# dist root. Mirrors Build.rakumod's !ensure-notcurses-source logic
# so install-time (Build.rakumod) and release-time (CI) build from
# the exact same SHA — no more "vendor/notcurses and NOTCURSES_FORK
# drift" class of bug.
#
# Behaviour:
# 1. Parse NOTCURSES_FORK for `url=` + `sha=` (40-char lowercase
# hex). Refuses malformed input.
# 2. Cache the checkout under $NOTCURSES_SRC_CACHE/<sha>/. Default
# cache root is workspace-relative ($PWD/_ci-cache/notcurses-source)
# so GHA's actions/cache can persist it across runs without
# needing access to $HOME. Override via $NOTCURSES_SRC_CACHE.
# 3. If the cache dir already has CMakeLists.txt and `git
# rev-parse HEAD` matches the pinned SHA, reuse it. Otherwise
# wipe + `git init` + `fetch --depth 1 origin <sha>` +
# `checkout FETCH_HEAD`.
# 4. Emit the resolved path on stdout (always). When $GITHUB_ENV
# is set (i.e. running inside a GHA step), also append
# `NOTCURSES_SRC_DIR=<path>` so subsequent steps see it.
#
# Escape hatch: $NOTCURSES_NATIVE_VENDOR_DIR=<path> short-circuits
# everything and uses that local checkout verbatim. Matches
# Build.rakumod's behaviour — useful for fork iteration without
# push cycles, and the only way to build in an airgapped env.
set -euo pipefail
# Resolve the repo root. We assume this script lives under
# `<root>/scripts/ci/` and the dist root holds NOTCURSES_FORK.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(cd "$script_dir/../.." && pwd)"
# Local-vendor escape hatch — matches Build.rakumod.
if [[ -n "${NOTCURSES_NATIVE_VENDOR_DIR:-}" ]]; then
if [[ ! -f "$NOTCURSES_NATIVE_VENDOR_DIR/CMakeLists.txt" ]]; then
echo "❌ NOTCURSES_NATIVE_VENDOR_DIR=$NOTCURSES_NATIVE_VENDOR_DIR is not a" >&2
echo " notcurses source tree (no CMakeLists.txt)." >&2
exit 1
fi
echo "Using NOTCURSES_NATIVE_VENDOR_DIR=$NOTCURSES_NATIVE_VENDOR_DIR as source tree." >&2
echo "$NOTCURSES_NATIVE_VENDOR_DIR"
if [[ -n "${GITHUB_ENV:-}" ]]; then
echo "NOTCURSES_SRC_DIR=$NOTCURSES_NATIVE_VENDOR_DIR" >> "$GITHUB_ENV"
fi
exit 0
fi
pin_file="$root/NOTCURSES_FORK"
if [[ ! -f "$pin_file" ]]; then
echo "❌ Missing NOTCURSES_FORK at $pin_file." >&2
exit 1
fi
url=""
sha=""
while IFS= read -r line || [[ -n "$line" ]]; do
# Strip trailing CR — git on Windows may check out NOTCURSES_FORK
# with CRLF line endings, and `read -r` leaves the \r intact, which
# pushes the SHA past 40 chars and trips the hex regex below.
# The `|| [[ -n "$line" ]]` keeps the last line if it's missing a
# trailing newline.
line="${line%$'\r'}"
# Skip comments and blank lines (matches Build.rakumod's parser).
case "$line" in
\#*|'') continue ;;
url=*) url="${line#url=}" ;;
sha=*) sha="${line#sha=}" ;;
*)
echo "❌ Malformed line in NOTCURSES_FORK: '$line'." >&2
echo " Expected '# comment', 'url=…', or 'sha=…'." >&2
exit 1
;;
esac
done < "$pin_file"
[[ -n "$url" ]] || { echo "❌ NOTCURSES_FORK missing required key url=." >&2; exit 1; }
[[ -n "$sha" ]] || { echo "❌ NOTCURSES_FORK missing required key sha=." >&2; exit 1; }
# 40-char lowercase hex — refuse short SHAs or branch names so a
# moving target can never silently change what we build.
if ! [[ "$sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "❌ NOTCURSES_FORK sha=$sha must be a 40-char lowercase hex string." >&2
exit 1
fi
cache_root="${NOTCURSES_SRC_CACHE:-$root/_ci-cache/notcurses-source}"
src_dir="$cache_root/$sha"
# Reuse the cache when the on-disk tree's HEAD matches the pin —
# defends against an aborted earlier fetch that left a partial tree.
if [[ -f "$src_dir/CMakeLists.txt" ]] \
&& head=$(git -C "$src_dir" rev-parse HEAD 2>/dev/null) \
&& [[ "$head" == "$sha" ]]; then
echo "Reusing cached notcurses source at $src_dir." >&2
echo "$src_dir"
if [[ -n "${GITHUB_ENV:-}" ]]; then
echo "NOTCURSES_SRC_DIR=$src_dir" >> "$GITHUB_ENV"
fi
exit 0
fi
# Stale or absent — wipe and re-fetch.
rm -rf "$src_dir"
mkdir -p "$src_dir"
echo "Fetching notcurses source from $url @ $sha..." >&2
git init --quiet "$src_dir"
git -C "$src_dir" remote add origin "$url"
# protocol.version=2 + fetch-by-SHA: GitHub allows fetching arbitrary
# reachable commits via this combination. Without v2 the server can
# refuse non-branch refs.
git -C "$src_dir" -c protocol.version=2 fetch --depth 1 --quiet origin "$sha"
git -C "$src_dir" checkout --quiet FETCH_HEAD
# Belt-and-braces: verify HEAD really is the SHA we asked for.
head=$(git -C "$src_dir" rev-parse HEAD)
if [[ "$head" != "$sha" ]]; then
echo "❌ Fetched HEAD ($head) doesn't match pinned SHA ($sha)." >&2
rm -rf "$src_dir"
exit 1
fi
echo "Fetched notcurses source to $src_dir." >&2
echo "$src_dir"
if [[ -n "${GITHUB_ENV:-}" ]]; then
echo "NOTCURSES_SRC_DIR=$src_dir" >> "$GITHUB_ENV"
fi