#!/bin/sh
# Build quarry on a Linux host, with the zit it is meant to run against.
#
# Why a script and not a cross-compile: the DuckDB bindings ship a static
# C++ library per platform that links against the GNU C++ runtime, and a
# cross-link from macOS with zig's libc++ fails on missing std::string
# symbols (tried 2026-09-04). Building ON Linux is the honest path, and
# the golang image proves it works in a container before a VPS exists:
#
#   docker run --rm -v "$PWD":/src -w /src "golang:$(sed -n 's/^go //p' go.mod)" scripts/build-linux.sh
#
# zit is pinned to a RELEASE, not whatever is on PATH: 0001 §2 makes the
# zit CLI the entire store interface, so quarry's behaviour is zit's,
# and the two must not drift. The release tarballs are qualified per
# target (see zit's release-manifest.json) and checksummed.
#
# Inputs (environment):
#   ZIT_VERSION     zit release to install (default below)
#   GITHUB_TOKEN    read access to the private zit repository, for its release assets
#   ZIT_RELEASE_DIR a directory holding the tarball and SHA256SUMS instead of a token
#   VERSION       stamp for `quarry version` (default: scripts/version-stamp.sh's, from
#                 CHANGELOG.md's newest heading and the checkout: X.Y.Z when it carries
#                 version-X.Y.Z, else the release it is heading for, -dev+<checkout
#                 hash or "local">)
#   PREFIX        where binaries land (default: ./bin)
#   GOARCH        amd64 or arm64: the architecture quarry and its zit are for
#                 (default: this host's). Another than the host's is a cross
#                 build: it needs CC and CXX naming a C toolchain for that
#                 target (aarch64-linux-gnu-gcc and -g++, say) and VERSION set,
#                 since the zit it fetches cannot run here to read the tags
#   NO_RUNNERS    set to skip the runner downloads
set -eu

# 0.3.15 for the file-history index. 0.3.14 introduced it and cut a
# PATH-filtered log on the SQLite mirror from 129s to ~1s, but recorded
# no coverage, so any commit invalidated it and the next query rebuilt
# from the first check-in -- on a repository under development that is
# the common case. 0.3.15 records how much of the log the index covers
# and resumes from there, which is what makes it worth building once.
# 0.3.28 for `config export` (the inverse of import, zit#210) and the
# stack form of `merge --probe` (zit#211), which the strata pre-flight
# asks once per stack.
# 0.3.30 refuses to answer a Basic challenge over plain http to another
# host, and drops the password on a redirect to a different host
# (zit#212), the client half of refusing http on the forge.
# 0.3.31 reads a landing's branch tip once and merges exactly that
# check-in, and adds --expect-tip HASH (exit 6 when the branch moved), so
# an accept merges only the tip quarry checked (zit#214).
# 0.3.32 takes the store's append lock in every writer, purge undo
# included, so no two appenders overwrite each other's frames (zit#216).
# 0.3.34 for `git export --autopush`, which the Git mirror runs (zit#220).
# 0.3.35 finds landing records through an index kept beside the store, so
# accept, comment and reject no longer scan it whole (zit#222), and adds
# `merge-tree`, which builds a landing's merged tree for its CI (zit#223).
# 0.3.36 for `zit uv list|pull|push|rm URL`, the stateless client the
# release workflow publishes with (zit#226, RFC 0020).
# 0.3.37: its sync server stores content only from a request that sent
# push (zit#227), and crosslink names each cherry-pick (zit#228).
ZIT_VERSION="${ZIT_VERSION:-0.3.37}"
PREFIX="${PREFIX:-$PWD/bin}"
mkdir -p "$PREFIX"

goarch_of() {
  case "$1" in
    x86_64|amd64)   echo amd64 ;;
    aarch64|arm64)  echo arm64 ;;
    *) echo "build-linux: unsupported architecture $1" >&2; return 1 ;;
  esac
}
host="$(goarch_of "$(uname -m)")"
GOARCH="$(goarch_of "${GOARCH:-$host}")"
export GOARCH
case "$GOARCH" in amd64) zit_arch=x86_64 ;; arm64) zit_arch=aarch64 ;; esac
cross=
if [ "$GOARCH" != "$host" ]; then
  cross=1
  [ -n "${CC:-}" ] && [ -n "${CXX:-}" ] || { echo "build-linux: building for $GOARCH on $host needs CC and CXX for $GOARCH (the DuckDB bindings link C++)" >&2; exit 1; }
  [ -n "${VERSION:-}" ] || { echo "build-linux: a cross build needs VERSION: its zit cannot run here to read the check-in's tags" >&2; exit 1; }
fi
[ "$(uname -s)" = Linux ] || { echo "build-linux: this builds on Linux (see the header for why)" >&2; exit 1; }

# ---- zit, from its release ---------------------------------------------
# The zit repository is private, so its release assets need a token
# (GITHUB_TOKEN, read access to danmestas/zit) and the API's asset
# route — the browser download URL answers 404 without a session.
# ZIT_RELEASE_DIR names a directory that already holds the tarball and
# SHA256SUMS, for a host with no token: copy them there and set it.
tarball="zit-${ZIT_VERSION}-linux-${zit_arch}.tar.gz"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
if [ -n "${ZIT_RELEASE_DIR:-}" ]; then
  echo "build-linux: using zit ${ZIT_VERSION} from $ZIT_RELEASE_DIR"
  cp "$ZIT_RELEASE_DIR/$tarball" "$ZIT_RELEASE_DIR/SHA256SUMS" "$work/"
else
  [ -n "${GITHUB_TOKEN:-}" ] || { echo "build-linux: set GITHUB_TOKEN (read access to danmestas/zit) or ZIT_RELEASE_DIR" >&2; exit 1; }
  api="https://api.github.com/repos/danmestas/zit/releases/tags/version-${ZIT_VERSION}"
  echo "build-linux: fetching zit ${ZIT_VERSION} for linux-${zit_arch}"
  curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" "$api" > "$work/release.json"
  for name in "$tarball" SHA256SUMS; do
    id="$(grep -o "\"id\": *[0-9]*,\|\"name\": *\"[^\"]*\"" "$work/release.json" | awk -v n="$name" '
      /"id":/ {gsub(/[^0-9]/,"",$0); last=$0}
      /"name":/ {gsub(/"name": *"/,"",$0); gsub(/"/,"",$0); if ($0==n) {print last; exit}}')"
    [ -n "$id" ] || { echo "build-linux: no asset $name on release version-${ZIT_VERSION}" >&2; exit 1; }
    curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" \
      "https://api.github.com/repos/danmestas/zit/releases/assets/$id" -o "$work/$name"
  done
fi
( cd "$work" && grep " $tarball\$" SHA256SUMS | sha256sum -c - >/dev/null ) || {
  echo "build-linux: checksum mismatch for $tarball" >&2; exit 1; }
tar -xzf "$work/$tarball" -C "$work"
install -m 0755 "$work/zit-${ZIT_VERSION}/zit" "$PREFIX/zit"
if [ -n "$cross" ]; then
  echo "build-linux: zit ${ZIT_VERSION} for linux-${zit_arch} (not run: this host is $host)"
else
  echo "build-linux: $("$PREFIX/zit" version)"
fi

# ---- quarry, from this tree -------------------------------------------
if [ -z "${VERSION:-}" ]; then
  tip="$("$PREFIX/zit" status 2>/dev/null | awk '/^checkout:/ {print substr($2,1,12)}')"
  if [ -n "$tip" ]; then
    # A tag lookup that fails stamps the -dev version, never the build.
    tagged=--
    if info="$("$PREFIX/zit" info "$tip" 2>/dev/null)" && raw="$("$PREFIX/zit" tag list --raw "$tip" 2>/dev/null)"; then
      parent="$(printf '%s\n' "$info" | sed -n 's/^parent: *//p')"
      praw=
      if { [ -z "$parent" ] || praw="$("$PREFIX/zit" tag list --raw "$parent" 2>/dev/null)"; } &&
        plain="$("$PREFIX/zit" tag list "$tip" 2>/dev/null)"; then
        tagged="$(printf '%s\n--\n%s\n--\n%s\n' "$raw" "$praw" "$plain")"
        # The repository's releases: whether the newest changelog heading
        # is out yet. Unread, the stamp takes it as released.
        if released="$("$PREFIX/zit" tag list 2>/dev/null)"; then
          tagged="$(printf '%s\n--\n%s\n' "$tagged" "$released")"
        fi
      fi
    fi
    [ "$tagged" != -- ] || echo "build-linux: WARNING: could not read $tip's tags; stamping a -dev build" >&2
    VERSION="$(printf '%s\n' "$tagged" | sh scripts/version-stamp.sh CHANGELOG.md "$tip" -)"
  else
    VERSION="$(sh scripts/version-stamp.sh CHANGELOG.md local)"
  fi
fi
echo "build-linux: building quarry $VERSION with $(go version)"
CGO_ENABLED=1 go build -trimpath -ldflags "-X main.version=$VERSION" -o "$PREFIX/quarry" ./cmd/quarry
echo "build-linux: quarry built: $("$PREFIX/quarry" version 2>/dev/null || echo "$VERSION")"

# ---- quarry-runner, for owners' own machines ---------------------------
#
# The forge serves these at /runner/<file> and the CI setup page links
# to them, so running a repository's CI never means building quarry.
# Pure Go and stdlib only, so every platform cross-compiles from here.
# The list is internal/runnerdist's Platforms; TestBuildScriptBuildsEveryPlatform
# keeps the two in step. One line, by name: scripts/runner-check.sh reads
# it to know what a complete set is before deploy publishes one.
RUNNER_TARGETS="linux/amd64 linux/arm64 darwin/arm64 darwin/amd64 windows/amd64"
# NO_RUNNERS=1 leaves them out: release.sh builds them once, not per forge target.
if [ -z "${NO_RUNNERS:-}" ]; then
  mkdir -p "$PREFIX/runner"
  for target in $RUNNER_TARGETS; do
    os=${target%/*}; arch=${target#*/}; ext=; [ "$os" = windows ] && ext=.exe
    CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -trimpath -ldflags "-s -w -X main.version=$VERSION" \
      -o "$PREFIX/runner/quarry-runner-$os-$arch$ext" ./cmd/quarry-runner
  done
  ( cd "$PREFIX/runner" && sha256sum quarry-runner-* > SHA256SUMS )
  built=0
  for f in "$PREFIX"/runner/quarry-runner-*; do [ -f "$f" ] && built=$((built + 1)); done
  echo "build-linux: quarry-runner built for $built platforms, with SHA256SUMS"
fi

echo "build-linux: binaries in $PREFIX"
