Stamp no commit into a Go bundle, so one build source is one digest (hq issue 363, ADR 0267 rule 6)
mesh/merge-gate out of date: judged against trunk e0d69ab7a05b, the trunk is now 1d244d5aa24e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e0d69ab7a05b, the trunk is now 1d244d5aa24e; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk e0d69ab7a05b, the trunk is now 1d244d5aa24e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e0d69ab7a05b, the trunk is now 1d244d5aa24e; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
A module built from its repository's root is compiled with the clone's .git in view, and Go stamped the commit into the binary: a merge that changed only a README made a new digest of an unchanged program.
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// **A Go bundle does not vary with the commit it was built at** (novox/hq ADR 0267 rule 6, issue 363). A
|
||||||
|
// module built from its repository's root — the controller — is compiled with the clone's `.git` in what
|
||||||
|
// the toolchain sees, and Go stamps the commit, its time and whether the tree was modified into every
|
||||||
|
// binary built there unless told not to. Two builds of one program at commits that differ only in a
|
||||||
|
// README then made two binaries, so an unchanged build source was a new digest.
|
||||||
|
//
|
||||||
|
// The toolchain's own compile line is run here, as the build seat runs it inside the toolchain image: in
|
||||||
|
// two clones at two paths, of two commits that differ outside the program. One digest is wanted.
|
||||||
|
func TestAGoBundleBuiltTwiceFromOneBuildSourceIsOneDigest(t *testing.T) {
|
||||||
|
for _, tool := range []string{"go", "git"} {
|
||||||
|
if _, err := exec.LookPath(tool); err != nil {
|
||||||
|
t.Skipf("%s is not on this machine: %v", tool, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain, err := ToolchainFor("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
root := t.TempDir()
|
||||||
|
repo := filepath.Join(root, "origin")
|
||||||
|
files := map[string]string{
|
||||||
|
"go.mod": "module example.com/ctl\n\ngo 1.22\n",
|
||||||
|
"cmd/ctl/main.go": "package main\n\nfunc main() { println(\"ctl\") }\n",
|
||||||
|
"README.md": "one\n",
|
||||||
|
}
|
||||||
|
for name, body := range files {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(filepath.Join(repo, name)), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(repo, name), []byte(body), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
git := func(dir string, args ...string) string {
|
||||||
|
t.Helper()
|
||||||
|
cmd := exec.Command("git", append([]string{"-c", "user.name=t", "-c", "user.email=t@example.invalid",
|
||||||
|
"-c", "commit.gpgsign=false", "-c", "init.defaultBranch=main"}, args...)...)
|
||||||
|
cmd.Dir = dir
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out))
|
||||||
|
}
|
||||||
|
git(repo, "init", "-q")
|
||||||
|
git(repo, "add", ".")
|
||||||
|
git(repo, "commit", "-q", "-m", "one")
|
||||||
|
first := git(repo, "rev-parse", "HEAD")
|
||||||
|
if err := os.WriteFile(filepath.Join(repo, "README.md"), []byte("two\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
git(repo, "commit", "-q", "-am", "only the README")
|
||||||
|
second := git(repo, "rev-parse", "HEAD")
|
||||||
|
|
||||||
|
build := func(commit, at string) string {
|
||||||
|
t.Helper()
|
||||||
|
clone := filepath.Join(root, at)
|
||||||
|
git(root, "clone", "-q", repo, clone)
|
||||||
|
git(clone, "checkout", "-q", commit)
|
||||||
|
out := filepath.Join(clone, Out("server"), "ctl")
|
||||||
|
line := append([]string(nil), chain.Compile...)
|
||||||
|
line = append(line, "-ldflags", strings.Join(chain.LinkerFlags, " "), chain.OutputFlag, out, "./cmd/ctl")
|
||||||
|
cmd := exec.Command(line[0], line[1:]...)
|
||||||
|
cmd.Dir = clone
|
||||||
|
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local", "GOPROXY=off", "GOWORK=off")
|
||||||
|
if b, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("%s: %v\n%s", strings.Join(line, " "), err, b)
|
||||||
|
}
|
||||||
|
body, err := os.ReadFile(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if bytes.Contains(body, []byte(commit)) {
|
||||||
|
t.Errorf("the binary built at %s carries the commit it was built at", commit[:12])
|
||||||
|
}
|
||||||
|
sum := sha256.Sum256(body)
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
a, b := build(first, "a"), build(second, "elsewhere/b")
|
||||||
|
if a != b {
|
||||||
|
t.Fatalf("one build source, two digests: %s at %s, %s at %s (compiled with %q)",
|
||||||
|
a[:12], first[:12], b[:12], second[:12], strings.Join(chain.Compile, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The toolchain says so itself, so a reader of the compile line sees it without running a build.
|
||||||
|
func TestTheGoToolchainStampsNoVersionControl(t *testing.T) {
|
||||||
|
chain, err := ToolchainFor("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if joined := strings.Join(chain.Compile, " "); !strings.Contains(joined, "-buildvcs=false") {
|
||||||
|
t.Fatalf("the go toolchain stamps the commit into what it compiles: %q", joined)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -167,8 +167,13 @@ var toolchains = []Toolchain{
|
|||||||
// binary that needs a libc it did not bring is a delivery that works until a machine
|
// binary that needs a libc it did not bring is a delivery that works until a machine
|
||||||
// differs. Trimmed of its own paths for the same reason a version comes from where it sits
|
// differs. Trimmed of its own paths for the same reason a version comes from where it sits
|
||||||
// rather than from the linker: two builds of one commit produce the same bytes.
|
// rather than from the linker: two builds of one commit produce the same bytes.
|
||||||
|
//
|
||||||
|
// **And of one build source at two commits** (novox/hq ADR 0267 rule 6, issue 363): a module built
|
||||||
|
// from its repository's root is compiled with the clone's `.git` in view, and Go stamps the commit,
|
||||||
|
// its time and whether the tree was modified into the binary unless told not to — so a merge that
|
||||||
|
// changed only a README made a new digest of an unchanged program.
|
||||||
Compile: []string{
|
Compile: []string{
|
||||||
"env", "CGO_ENABLED=0", "GOFLAGS=-trimpath",
|
"env", "CGO_ENABLED=0", "GOFLAGS=-trimpath -buildvcs=false",
|
||||||
"go", "build",
|
"go", "build",
|
||||||
},
|
},
|
||||||
// Stripped of symbols and debug info: what a machine holds is a file it runs, not one it
|
// Stripped of symbols and debug info: what a machine holds is a file it runs, not one it
|
||||||
|
|||||||
Reference in New Issue
Block a user