From 0d78dda545fad02674197045d7238fa64cda667f Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 15:56:15 -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/workflow_test.go | 70 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d48f964..50cecff 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,12 @@ BIN := build/$(HOST_OS)/$(BINARY)$(EXE) # `make install` copies the host build here (may need sudo). BINDIR ?= /usr/local/bin +# 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 test vet fmt lint cross cross-check dist check-build icons clean help: ## Show this help @@ -43,7 +49,7 @@ build: build-local ## Build $(BIN) for the current OS (the same as build-local) build-local: ## Build $(BIN) with the host's Go toolchain go -C src build -trimpath -ldflags "$(LDFLAGS)" -o ../$(BIN) $(MAIN) - @$(MAKE) --no-print-directory check-build + @$(MAKE) --no-print-directory check-build BUILD_LAYOUT_MIN=2 install: build-local ## Build, then copy $(BIN) to $(BINDIR) (default /usr/local/bin; may need sudo) install -d "$(BINDIR)" @@ -63,7 +69,7 @@ lint: ## Run golangci-lint (config in src/.golangci.yml), if installed cross: ## Cross-compile every target in build/targets.txt into build// 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 )) cross-check: ## Compile every package for the cgo-free targets, writing nothing (the former `make cross`) CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C src build ./... @@ -72,10 +78,10 @@ cross-check: ## Compile every package for the cgo-free targets, writing nothing 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 icons: ## Regenerate brand PNGs + Windows resource (.syso) from assets/vigil.svg cd src/tools/iconz && go run . diff --git a/src/internal/release/workflow_test.go b/src/internal/release/workflow_test.go index b247356..8068bb7 100644 --- a/src/internal/release/workflow_test.go +++ b/src/internal/release/workflow_test.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" "testing" ) @@ -65,6 +66,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 78f357cad62c60572981a2a4d8303ff7863e18eb Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 15:56:16 -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 7d7fedf..86f98bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: contents: write id-token: write attestations: write - uses: simtabi/.github/.github/workflows/go-release.yml@941f7b7ecce5dc8af6daedfc7496dd4367c70877 # simtabi/.github#28 + uses: simtabi/.github/.github/workflows/go-release.yml@d648d8998f95a351001979f9c4527badca588bf1 # simtabi/.github#30 with: tag: ${{ inputs.tag || github.ref_name }} runs-on: macos-latest From 2acb8f36a429c81056d732fa0faf448eb13a264c Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 15:56:16 -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 838e440..bbe6395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). release download fails. ### 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. - **Binaries ship on the GitHub release only, built on a version tag or on request.** A pushed `vX.Y.Z` tag (or `vX.Y.Z-rc.N` pre-release) builds and publishes the release, and