tailscale/pull-toolchain.sh
Brad Fitzpatrick 88cb6f58f8 tool/updateflakes, cmd/nardump: replace update-flake.sh with Go tool
Consolidate go.mod.sri and go.toolchain.rev.sri into a single
flakehashes.json file at the repo root, owned by a new Go program at
tool/updateflakes. The JSON is consumed by flake.nix via
builtins.fromJSON and by any future Go code via the FlakeHashes
struct that defines its schema.

Each block records its input fingerprint alongside the SRI it
produced: the goModSum (a sha256 over go.mod and go.sum) for the
vendor block, and the literal rev string from go.toolchain.rev for
the toolchain block. updateflakes regenerates a block only when its
recorded fingerprint disagrees with the current input.

Doing the gating by content rather than file mtimes avoids the usual
mtime hazards across git checkouts, clones, and merges. It also
means re-runs with no input changes are essentially free, and a
re-run that touches only one input pays only for that one block.

The two blocks have no shared state -- vendor invokes go mod vendor
into one tempdir, toolchain fetches and extracts a tarball into
another -- so they run concurrently via errgroup. Cold time is
bounded by the slower of the two rather than their sum.

Also takes the opportunity to fold the toolchain fetch into a single
curl|tar pipeline (no intermediate .tar.gz on disk).

Split cmd/nardump into a thin package main and a new package nardump
library at cmd/nardump/nardump that holds the NAR encoder and SRI
helper. tool/updateflakes imports the library directly rather than
building and exec'ing the nardump binary at runtime. The library
uses fs.ReadLink (Go 1.25+) instead of os.Readlink, so it no longer
requires the caller to chdir into the FS root for symlink targets to
resolve. WriteNAR now wraps its writer in a bufio.Writer internally
(unless the caller already passed one) and flushes on return, so
callers don't pay for tiny writes against slow underlying writers.

The cache-busting line in flake.nix and shell.nix is known to live
at end of file, so updateCacheBust walks the lines in reverse.

make tidy timings on this machine, before: ~14s every run.
After:

  warm (no input changes):       0.05s
  vendor block stale only:       1.4s
  toolchain block stale only:    5.0s
  cold (no flakehashes.json):    5.0s

Updates #6845

Change-Id: I0340608798f1614abf147a491bf7c68a198a0db4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-28 10:18:32 -07:00

61 lines
2.7 KiB
Bash
Executable File

#!/bin/sh
# Retrieve the latest Go toolchain.
# Set TS_GO_NEXT=1 to update go.toolchain.next.rev instead.
#
set -eu
cd "$(dirname "$0")"
if [ "${TS_GO_NEXT:-}" = "1" ]; then
go_toolchain_branch_file="go.toolchain.next.branch"
go_toolchain_rev_file="go.toolchain.next.rev"
else
go_toolchain_branch_file="go.toolchain.branch"
go_toolchain_rev_file="go.toolchain.rev"
fi
read -r go_branch <"$go_toolchain_branch_file"
upstream=$(git ls-remote https://github.com/tailscale/go "$go_branch" | awk '{print $1}')
current=$(cat "$go_toolchain_rev_file")
if [ "$upstream" != "$current" ]; then
echo "$upstream" >"$go_toolchain_rev_file"
fi
# When updating the regular (non-next) toolchain, also bump go.toolchain.next.rev
# if it has fallen behind on the same branch. This happens when "next" was tracking
# a release candidate (e.g. Go 1.26.0rc2) and the regular toolchain later gets
# bumped to a newer release (e.g. Go 1.26.2) on the same branch. At that point
# the "next" rev shouldn't still point at the older RC.
if [ "${TS_GO_NEXT:-}" != "1" ]; then
read -r next_branch <go.toolchain.next.branch
if [ "$go_branch" = "$next_branch" ]; then
next_rev=$(cat go.toolchain.next.rev)
new_rev=$(cat go.toolchain.rev)
if [ "$next_rev" != "$new_rev" ]; then
# Fetch only commit objects (no trees/blobs) with limited depth
# to keep this fast — we just need the commit graph for ancestry check.
tmpdir="/tmp/tailscale-pull-toolchain-$$"
if git clone --bare --filter=tree:0 --depth=20000 --single-branch --branch "$go_branch" \
https://github.com/tailscale/go "$tmpdir" 2>/dev/null; then
if git -C "$tmpdir" merge-base --is-ancestor "$next_rev" "$new_rev" 2>/dev/null; then
echo "$new_rev" >go.toolchain.next.rev
echo "pull-toolchain.sh: also bumped go.toolchain.next.rev to match (was behind on same branch)" >&2
fi
fi
rm -rf "$tmpdir"
fi
fi
fi
# Only update go.toolchain.version and flakehashes.json for the main toolchain,
# skipping it if TS_GO_NEXT=1. Those two files are only used by Nix, and as of 2026-01-26
# don't yet support TS_GO_NEXT=1 with flake.nix or in our corp CI.
if [ "${TS_GO_NEXT:-}" != "1" ]; then
./tool/go version 2>/dev/null | awk '{print $3}' | sed 's/^go//' > go.toolchain.version
./tool/go mod edit -go "$(cat go.toolchain.version)"
./tool/go run ./tool/updateflakes
fi
if [ -n "$(git diff-index --name-only HEAD -- "$go_toolchain_rev_file" go.toolchain.next.rev flakehashes.json go.toolchain.version)" ]; then
echo "pull-toolchain.sh: changes imported. Use git commit to make them permanent." >&2
fi