From 1beaf3329fe195ae780ad7e4d3989e4d23f4fe59 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 15:56:13 -0500 Subject: [PATCH 1/3] Make the build-layout check assert what it inspected TestBuildLayout now counts the files it inspected in build/ (those at the root and in each OS folder) and fails below BUILD_LAYOUT_MIN. make build passes 2, and make cross and make dist pass one per target plus targets.txt and checksums.txt, so a check that reads an empty or wrong directory fails instead of passing. A new test pins the count. --- Makefile | 14 ++++-- src/internal/release/layout_test.go | 70 +++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index b741157..01d6b18 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,12 @@ HOST_OS := $(if $(filter darwin,$(HOST_GOOS)),macos,$(HOST_GOOS)) EXE := $(if $(filter windows,$(HOST_GOOS)),.exe,) BIN := build/$(HOST_OS)/$(BINARY)$(EXE) +# The fewest entries `make check-build` must inspect in build/: the files at its +# root and in each OS folder. Each build passes what it just wrote (the host +# build: targets.txt and the binary; cross: targets.txt, checksums.txt and one +# binary per target), so a check reading an empty or wrong directory fails. +BUILD_LAYOUT_MIN ?= 1 + .PHONY: help build build-local install run test cover vet fmt lint tidy cross dist check-build \ sync-docs release-check bundle snapshot clean @@ -38,7 +44,7 @@ build: build-local ## Build $(BIN) (the same as build-local) build-local: ## Build $(BIN) with the host's Go toolchain CGO_ENABLED=0 go -C src build -trimpath -ldflags "$(LDFLAGS)" -o ../$(BIN) ./cmd/$(BINARY) - @$(MAKE) --no-print-directory check-build + @$(MAKE) --no-print-directory check-build BUILD_LAYOUT_MIN=2 install: ## Install probaci into GOBIN from this checkout (go -C src install) go -C src install -ldflags "$(LDFLAGS)" ./cmd/$(BINARY) @@ -67,14 +73,14 @@ tidy: ## go mod tidy cross: ## Cross-compile every target in build/targets.txt into build// ARCHIVES=0 VERSION=$(VERSION) COMMIT=$(COMMIT) DATE=$(DATE) ./src/scripts/build-all.sh - @$(MAKE) --no-print-directory check-build + @$(MAKE) --no-print-directory check-build BUILD_LAYOUT_MIN=$$(( $$(grep -cvE '^[[:space:]]*(#|$$)' build/targets.txt) + 2 )) dist: ## cross, plus .tar.gz/.zip archives in build/archives/ (publishes nothing) ARCHIVES=1 VERSION=$(VERSION) COMMIT=$(COMMIT) DATE=$(DATE) ./src/scripts/build-all.sh - @$(MAKE) --no-print-directory check-build + @$(MAKE) --no-print-directory check-build BUILD_LAYOUT_MIN=$$(( $$(grep -cvE '^[[:space:]]*(#|$$)' build/targets.txt) + 2 )) check-build: ## Fail when build/ holds anything but the OS folders, inputs, checksums.txt and archives/ - go -C src test ./internal/release -run 'TestBuildLayout$$' -count=1 + BUILD_LAYOUT_MIN=$(BUILD_LAYOUT_MIN) go -C src test ./internal/release -run 'TestBuildLayout$$' -count=1 # The module in src/ cannot embed files above it, so `probaci docs` reads a copy # of README.md and docs/ in src/embedded/. TestEmbeddedDocsMatchTheRepository diff --git a/src/internal/release/layout_test.go b/src/internal/release/layout_test.go index 2174180..6b185a2 100644 --- a/src/internal/release/layout_test.go +++ b/src/internal/release/layout_test.go @@ -6,6 +6,7 @@ package release_test import ( "os" "path/filepath" + "strconv" "strings" "testing" ) @@ -60,6 +61,75 @@ func TestBuildLayout(t *testing.T) { if problems := buildLayoutProblems(t, dir); len(problems) > 0 { t.Errorf("build/ breaks the layout (one folder per OS, see the Makefile):\n %s", strings.Join(problems, "\n ")) } + n, floor := buildEntries(t, dir), buildFloor(t) + if n < floor { + t.Errorf("inspected %d entries in build/, expected at least %d (BUILD_LAYOUT_MIN): the check read less than the build wrote", n, floor) + } + t.Logf("inspected %d entries in build/ (floor %d)", n, floor) +} + +// buildFloor is the fewest entries build/ must hold: BUILD_LAYOUT_MIN, which the +// Makefile sets to what the build that just ran wrote, else 1 (targets.txt). +func buildFloor(t *testing.T) int { + t.Helper() + raw := os.Getenv("BUILD_LAYOUT_MIN") + if raw == "" { + return 1 + } + n, err := strconv.Atoi(raw) + if err != nil || n < 1 { + t.Fatalf("BUILD_LAYOUT_MIN=%q is not a positive number", raw) + } + return n +} + +// buildEntries counts what the layout check inspected: the files at the root of +// build/ and the files in each OS folder. Folders themselves do not count. +func buildEntries(t *testing.T, dir string) int { + t.Helper() + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("reading %s: %v", dir, err) + } + n := 0 + for _, e := range entries { + switch { + case e.Name() == ".DS_Store": + case !e.IsDir(): + n++ + case osFolders[e.Name()]: + inner, err := os.ReadDir(filepath.Join(dir, e.Name())) + if err != nil { + t.Fatal(err) + } + for _, f := range inner { + if !f.IsDir() && f.Name() != ".DS_Store" { + n++ + } + } + } + } + return n +} + +// The count is what the floor stands on, so it has to count what is there. +func TestBuildEntriesCountsWhatItInspected(t *testing.T) { + dir := t.TempDir() + for _, f := range []string{ + "targets.txt", "checksums.txt", ".DS_Store", + "macos/app_macos_arm64", "linux/app_linux_amd64", "archives/app_linux_amd64.tar.gz", + } { + p := filepath.Join(dir, filepath.FromSlash(f)) + if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(p, nil, 0o644); err != nil { + t.Fatal(err) + } + } + if got := buildEntries(t, dir); got != 5 { + t.Errorf("buildEntries counted %d, want 5", got) + } } // The layout check has to be able to fail: each of these trees breaks it. From ec351944c9241c4f12cf7337f59f10e9b97da1e5 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 15:56:14 -0500 Subject: [PATCH 2/3] Pin the shared Go release workflow to d648d89 release.yml calls simtabi/.github's go-release.yml at its latest commit, d648d89 (simtabi/.github#30). The commits since the old pin add optional inputs only; every input this caller passes is unchanged. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1085cc..0503b18 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: contents: write id-token: write attestations: write - uses: simtabi/.github/.github/workflows/go-release.yml@745880bdb9b35a21eb16824cfc995f16e1e82fd5 # simtabi/.github#27 + uses: simtabi/.github/.github/workflows/go-release.yml@d648d8998f95a351001979f9c4527badca588bf1 # simtabi/.github#30 with: tag: ${{ inputs.tag || github.ref_name }} go-version-file: .go-version From b305c5ecd26f46ec2a4ce0423f0a0ad78f99aeea Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 15:56:15 -0500 Subject: [PATCH 3/3] Note the build-layout floor and release pin in the changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b5d5d..ed32aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed +- `release.yml` calls simtabi/.github's shared `go-release.yml` at `d648d89` (simtabi/.github#30), its latest commit, which adds an optional `node-version` input and changes nothing a caller passes. +- `make check-build` asserts how many files it inspected in `build/`, not only that nothing is out of place: `make build` passes a floor of 2 (`targets.txt` and the binary) and `make cross` and `make dist` one per target plus `targets.txt` and `checksums.txt`, through `BUILD_LAYOUT_MIN`, so a check that reads an empty or wrong directory fails instead of passing. - Building probaci needs Go 1.26, and CI runs on it. Go 1.25 has ten standard-library vulnerabilities fixed only in 1.26, which the web UI's `net/http` server reaches; `golang.org/x/net` moves to v0.60.0 for the same reason, and gosec to v2.30.0.