#!/bin/sh set -eu usage() { cat <<'EOF' Usage: update-tribes-pin [options] [rev] Pin guix-tribes to a Tribes commit and refresh all fixed-output hashes. By default, REV is "master" resolved from the local Tribes checkout. Hashing and Guix builds run with local `guix` unless --build-host is provided. Options: --tribes-repo PATH Local Tribes git checkout --guix-repo PATH Local guix-tribes checkout --build-host HOST SSH host used for Guix builds and hashing -h, --help Show this help EOF } script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) default_guix_repo=$(CDPATH= cd -- "$script_dir/.." && pwd) default_tribes_repo=$(CDPATH= cd -- "$default_guix_repo/../tribes" && pwd) tribes_repo=$default_tribes_repo guix_repo=$default_guix_repo build_host= rev=master while [ "$#" -gt 0 ]; do case "$1" in --tribes-repo) tribes_repo=$2 shift 2 ;; --guix-repo) guix_repo=$2 shift 2 ;; --build-host) build_host=$2 shift 2 ;; -h|--help) usage exit 0 ;; --) shift break ;; -*) printf 'Unknown option: %s\n' "$1" >&2 usage >&2 exit 1 ;; *) rev=$1 shift ;; esac done if [ "$#" -gt 0 ]; then printf 'Unexpected arguments: %s\n' "$*" >&2 usage >&2 exit 1 fi source_file=$guix_repo/tribes/packages/source.scm if [ ! -d "$tribes_repo/.git" ]; then printf 'Tribes repo not found: %s\n' "$tribes_repo" >&2 exit 1 fi if [ ! -f "$source_file" ]; then printf 'guix-tribes source file not found: %s\n' "$source_file" >&2 exit 1 fi require_tool() { if ! command -v "$1" >/dev/null 2>&1; then printf 'Missing required tool: %s\n' "$1" >&2 exit 1 fi } require_tool env require_tool git require_tool tar require_tool perl require_tool mktemp if [ -n "$build_host" ]; then use_remote=true else use_remote=false require_tool guix fi commit=$(git -C "$tribes_repo" rev-parse "$rev^{commit}") version=$(env LC_ALL=C perl -0ne 'print $1 if /\(git-version "([^"]+)"/' "$source_file") if [ -z "$version" ]; then printf 'Could not read Tribes version from %s\n' "$source_file" >&2 exit 1 fi local_tmp=$(mktemp -d "${TMPDIR:-/tmp}/tribes-pin.XXXXXX") remote_tmp= cleanup() { if [ -n "${remote_tmp:-}" ]; then ssh "$build_host" "rm -rf '$remote_tmp'" >/dev/null 2>&1 || true fi rm -rf "$local_tmp" } trap cleanup EXIT INT TERM mkdir -p "$local_tmp/tribes-source" git -C "$tribes_repo" archive "$commit" | tar -x -C "$local_tmp/tribes-source" setup_remote() { require_tool rsync require_tool ssh if [ -z "${remote_tmp:-}" ]; then printf 'Using build host %s.\n' "$build_host" >&2 remote_tmp=$(ssh "$build_host" 'mktemp -d /tmp/tribes-pin.XXXXXX') rsync -az --delete --exclude .git "$guix_repo/" "$build_host:$remote_tmp/guix-tribes/" rsync -az --delete "$local_tmp/tribes-source/" "$build_host:$remote_tmp/tribes-source/" fi source_root=$remote_tmp/tribes-source guix_load_path=$remote_tmp/guix-tribes source_hash=$(ssh "$build_host" "guix hash -rx '$source_root'" | tr -d '\r') } if [ "$use_remote" = true ]; then setup_remote else source_root=$local_tmp/tribes-source guix_load_path=$guix_repo source_hash=$(guix hash -rx "$source_root" | tr -d '\r') fi run_scheme() { name=$1 body=$2 if [ "$use_remote" = true ]; then ssh "$build_host" "cat > '$remote_tmp/$name.scm' <<'EOF' $body EOF" ssh "$build_host" "guix build -L '$guix_load_path' -f '$remote_tmp/$name.scm' --no-grafts 2>&1" || true else scheme_file=$local_tmp/$name.scm cat > "$scheme_file" <&1 || true fi } extract_hash() { env LC_ALL=C perl -ne ' if (s/.*\bgot:\s*([0-9a-z]{52}).*/$1/) { chomp; $hash = $_; } elsif (s/.*\bactual hash:\s*([0-9a-z]{52}).*/$1/) { chomp; $hash = $_; } END { if (defined $hash) { print $hash; } else { exit 1; } } ' } dummy_hash=0000000000000000000000000000000000000000000000000000 raw_mix_deps_output() { run_scheme raw-mix-deps "(use-modules (guix gexp) (tribes packages source)) (fetch-mix-deps (local-file \"$source_root\" #:recursive? #t) #:name \"tribes-mix-deps-raw\" #:version \"$version\" #:sha256 \"$dummy_hash\")" } mix_deps_output() { run_scheme mix-deps "(use-modules (guix gexp) (tribes packages source)) (tribes-mix-deps (local-file \"$source_root\" #:recursive? #t) #:name \"tribes-mix-deps\" #:version \"$version\" #:raw-sha256 \"$raw_mix_hash\" #:sha256 \"$dummy_hash\")" } npm_deps_output() { run_scheme npm-deps "(use-modules (guix gexp) (tribes packages source)) (fetch-npm-deps (local-file \"$source_root\" #:recursive? #t) #:mix-fod-deps (tribes-mix-deps (local-file \"$source_root\" #:recursive? #t) #:name \"tribes-mix-deps\" #:version \"$version\" #:raw-sha256 \"$raw_mix_hash\" #:sha256 \"$mix_hash\") #:name \"tribes-npm-deps\" #:version \"$version\" #:sha256 \"$dummy_hash\")" } hash_failure_hint() { label=$1 output=$2 printf 'Failed to extract %s hash.\n' "$label" >&2 if [ "$use_remote" = false ]; then printf 'Local Guix did not complete the hash refresh. To run this step on a build host, rerun with --build-host HOST.\n' >&2 else printf 'Build host Guix did not complete the hash refresh.\n' >&2 fi printf '%s\n' "$output" >&2 exit 1 } raw_output=$(raw_mix_deps_output) raw_mix_hash=$(printf '%s\n' "$raw_output" | extract_hash) || hash_failure_hint 'raw mix deps' "$raw_output" mix_output=$(mix_deps_output) mix_hash=$(printf '%s\n' "$mix_output" | extract_hash) || hash_failure_hint 'prepared mix deps' "$mix_output" npm_output=$(npm_deps_output) npm_hash=$(printf '%s\n' "$npm_output" | extract_hash) || hash_failure_hint 'npm deps' "$npm_output" COMMIT=$commit \ SOURCE_HASH=$source_hash \ RAW_MIX_HASH=$raw_mix_hash \ MIX_HASH=$mix_hash \ NPM_HASH=$npm_hash \ env LC_ALL=C perl -0pi -e ' s/\(define %tribes-raw-mix-deps-sha256\n "[^"]+"\)/(define %tribes-raw-mix-deps-sha256\n "$ENV{RAW_MIX_HASH}")/; s/\(define %tribes-mix-deps-sha256\n "[^"]+"\)/(define %tribes-mix-deps-sha256\n "$ENV{MIX_HASH}")/; s/\(define %tribes-npm-deps-sha256\n "[^"]+"\)/(define %tribes-npm-deps-sha256\n "$ENV{NPM_HASH}")/; s/\(define %tribes-commit\n "[^"]+"\)/(define %tribes-commit\n "$ENV{COMMIT}")/; s/\(define %tribes-source-sha256\n "[^"]+"\)/(define %tribes-source-sha256\n "$ENV{SOURCE_HASH}")/; ' "$source_file" printf 'Updated %s\n' "$source_file" printf 'commit: %s\n' "$commit" printf 'source sha256: %s\n' "$source_hash" printf 'raw mix deps sha256: %s\n' "$raw_mix_hash" printf 'mix deps sha256: %s\n' "$mix_hash" printf 'npm deps sha256: %s\n' "$npm_hash"