Set up CraftBukkit-style patch workflow for xai-org/grok-build
Upstream is a periodic one-way export of xAI's monorepo (linear "Synced from monorepo" commits, SOURCE_REV pinning the internal SHA) and explicitly refuses external contributions. So local changes can never be upstreamed, and upstream re-drops the whole tree on every sync -- structurally the same problem CraftBukkit has with Mojang. Adopt the Spigot/BuildTools model: patches/ is the source of truth, work/ is a disposable build artifact regenerated from upstream.rev plus patches/. scripts/apply-patches.sh ~ applyPatches.sh scripts/rebuild-patches.sh ~ rebuildPatches.sh scripts/update-upstream.sh forward-ports patches onto a new sync scripts/setup.sh toolchain: rust 1.94.0, dotslash/protoc upstream.rev ~ BuildTools versions/*.json pin format-patch uses --zero-commit so rebases do not rewrite the From line of every patch, and apply's git clean preserves work/target so replaying patches does not cost a cold Rust rebuild. Ships two [EXAMPLE PATCH] commits demonstrating a source edit and a new-file addition; both are safe to delete. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared helpers for the patch workflow. Sourced by every script in this dir.
|
||||
#
|
||||
# Layout (see README.md):
|
||||
# upstream/ pristine clone of the vendor repo -- never edited by hand
|
||||
# work/ upstream@REV + patches/ applied -- where you actually edit
|
||||
# patches/ the source of truth -- tracked in git
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||
UPSTREAM_URL="${UPSTREAM_URL:-https://github.com/xai-org/grok-build.git}"
|
||||
UPSTREAM_BRANCH="${UPSTREAM_BRANCH:-main}"
|
||||
UPSTREAM_DIR="$ROOT/upstream"
|
||||
WORK_DIR="$ROOT/work"
|
||||
PATCHES_DIR="$ROOT/patches"
|
||||
REV_FILE="$ROOT/upstream.rev"
|
||||
|
||||
# Branch created inside work/, and the tag marking the pristine upstream commit
|
||||
# that patches are generated against. `base` is the boundary: everything after
|
||||
# it is ours.
|
||||
WORK_BRANCH="fork"
|
||||
BASE_TAG="base"
|
||||
|
||||
# The package that produces the shipping binary.
|
||||
BIN_PKG="xai-grok-pager-bin"
|
||||
BIN_NAME="xai-grok-pager"
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_RED=$'\033[31m'
|
||||
C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_BLUE=$'\033[34m'
|
||||
else
|
||||
C_RESET=""; C_BOLD=""; C_RED=""; C_GREEN=""; C_YELLOW=""; C_BLUE=""
|
||||
fi
|
||||
|
||||
info() { printf '%s==>%s %s\n' "$C_BLUE$C_BOLD" "$C_RESET" "$*"; }
|
||||
step() { printf '%s ->%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
|
||||
warn() { printf '%s==> warning:%s %s\n' "$C_YELLOW$C_BOLD" "$C_RESET" "$*" >&2; }
|
||||
die() { printf '%s==> error:%s %s\n' "$C_RED$C_BOLD" "$C_RESET" "$*" >&2; exit 1; }
|
||||
|
||||
# Rust lives in CARGO_HOME/bin, which is not on PATH by default in
|
||||
# non-interactive shells. Every script that shells out to cargo needs this.
|
||||
setup_path() {
|
||||
export CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}"
|
||||
export RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}"
|
||||
case ":$PATH:" in
|
||||
*":$CARGO_HOME/bin:"*) ;;
|
||||
*) export PATH="$CARGO_HOME/bin:$PATH" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 \
|
||||
|| die "'$1' not found on PATH.${2:+ $2}"
|
||||
}
|
||||
|
||||
# The pinned upstream commit. Comments and blank lines are allowed in the file
|
||||
# so the pin can carry a note about which sync it corresponds to.
|
||||
read_rev() {
|
||||
[[ -f "$REV_FILE" ]] || die "missing $REV_FILE -- run 'make update' to pin an upstream revision"
|
||||
local rev
|
||||
rev="$(grep -vE '^\s*(#|$)' "$REV_FILE" | head -n1 | tr -d '[:space:]')"
|
||||
[[ -n "$rev" ]] || die "$REV_FILE contains no revision"
|
||||
printf '%s' "$rev"
|
||||
}
|
||||
|
||||
write_rev() {
|
||||
local rev="$1"
|
||||
{
|
||||
echo "# Pinned upstream revision of $UPSTREAM_URL"
|
||||
echo "# Bump with: make update (never edit by hand unless you know why)"
|
||||
echo "$rev"
|
||||
} > "$REV_FILE"
|
||||
}
|
||||
|
||||
# Clone on first use, otherwise fetch. Deliberately NOT a shallow clone: forward
|
||||
# -porting patches across upstream syncs needs real history for merge bases.
|
||||
ensure_upstream() {
|
||||
if [[ ! -d "$UPSTREAM_DIR/.git" ]]; then
|
||||
info "Cloning upstream $UPSTREAM_URL -> upstream/"
|
||||
git clone --branch "$UPSTREAM_BRANCH" "$UPSTREAM_URL" "$UPSTREAM_DIR"
|
||||
else
|
||||
step "Fetching upstream"
|
||||
git -C "$UPSTREAM_DIR" fetch --tags --prune origin \
|
||||
"+refs/heads/$UPSTREAM_BRANCH:refs/remotes/origin/$UPSTREAM_BRANCH"
|
||||
fi
|
||||
}
|
||||
|
||||
# Number of .patch files currently in patches/ (0 when the dir is empty).
|
||||
count_patches() {
|
||||
local n=0
|
||||
shopt -s nullglob
|
||||
local f
|
||||
for f in "$PATCHES_DIR"/*.patch; do n=$((n + 1)); done
|
||||
shopt -u nullglob
|
||||
printf '%s' "$n"
|
||||
}
|
||||
|
||||
# Guard against running rebuild/build while a patch application is half-done.
|
||||
assert_no_am_in_progress() {
|
||||
local gitdir
|
||||
gitdir="$(git -C "$WORK_DIR" rev-parse --git-dir)"
|
||||
if [[ -d "$WORK_DIR/$gitdir/rebase-apply" ]]; then
|
||||
die "a 'git am' is still in progress in work/.
|
||||
Resolve it first:
|
||||
cd work && git status
|
||||
# fix conflicts, then: git add -A && git am --continue
|
||||
# or abandon it with: git am --abort"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_work_ready() {
|
||||
[[ -d "$WORK_DIR/.git" ]] || die "work/ does not exist yet -- run 'make apply' first"
|
||||
assert_no_am_in_progress
|
||||
git -C "$WORK_DIR" rev-parse -q --verify "refs/tags/$BASE_TAG" >/dev/null \
|
||||
|| die "work/ has no '$BASE_TAG' tag -- it was not created by apply-patches.sh. Run 'make apply'."
|
||||
}
|
||||
Reference in New Issue
Block a user