Skip to content

Commit a1a0b60

Browse files
authored
Fix go_tool_binary non-hermeticity and Go 1.19 incompatibility (bazel-contrib#4167)
**What type of PR is this?** Bug fix **What does this PR do? Why is it needed?** `go_tool_binary` used to non-hermetically pick up rules_go's `go.mod` file when run unsandboxed, which resulted in it downloading a different toolchain and messing up version checks. Also make `nogo_main.go` compatible with Go 1.19 and test with nogo (which requires fixing a bunch of violations). **Which issues(s) does this PR fix?** Fixes # **Other notes for review**
1 parent a54de7c commit a1a0b60

File tree

17 files changed

+138
-35
lines changed

17 files changed

+138
-35
lines changed

WORKSPACE

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
workspace(name = "io_bazel_rules_go")
22

33
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
4-
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
4+
load("@io_bazel_rules_go//go:deps.bzl", "go_download_sdk", "go_register_nogo", "go_register_toolchains", "go_rules_dependencies")
55

66
# Required by toolchains_protoc.
77
http_archive(
@@ -29,6 +29,15 @@ go_rules_dependencies()
2929

3030
go_register_toolchains(version = "1.23.1")
3131

32+
go_download_sdk(
33+
name = "rules_go_internal_compatibility_sdk",
34+
version = "1.19.13",
35+
)
36+
37+
go_register_nogo(
38+
nogo = "@//internal:nogo",
39+
)
40+
3241
http_archive(
3342
name = "rules_proto",
3443
sha256 = "303e86e722a520f6f326a50b41cfc16b98fe6d1955ce46642a5b7a67c11c0f5d",
@@ -137,7 +146,7 @@ grpc_dependencies()
137146

138147
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
139148

140-
gazelle_dependencies()
149+
gazelle_dependencies(go_sdk = "go_sdk")
141150

142151
go_repository(
143152
name = "com_github_google_go_github_v36",

go/private/nogo.bzl

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
DEFAULT_NOGO = "@io_bazel_rules_go//:default_nogo"
16-
NOGO_DEFAULT_INCLUDES = ["@@//:__subpackages__"]
16+
NOGO_DEFAULT_INCLUDES = [str(Label("//:__subpackages__"))]
1717
NOGO_DEFAULT_EXCLUDES = []
1818

1919
# repr(Label(...)) does not emit a canonical label literal.

go/private/rules/binary.bzl

+24-2
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,17 @@ go_non_executable_binary = rule(executable = False, **_go_binary_kwargs(
464464
))
465465

466466
def _go_tool_binary_impl(ctx):
467+
# Keep in mind that the actions registered by this rule may not be
468+
# sandboxed, so care must be taken to make them hermetic, for example by
469+
# preventing `go build` from searching for go.mod or downloading a
470+
# different toolchain version.
471+
#
472+
# A side effect of the usage of GO111MODULE below is that the absolute
473+
# path to the sources is included in the buildid, which would make the
474+
# resulting binary non-reproducible. We thus need to blank it out.
475+
# https://github.com/golang/go/blob/583d750fa119d504686c737be6a898994b674b69/src/cmd/go/internal/load/pkg.go#L1764-L1766
476+
# https://github.com/golang/go/blob/583d750fa119d504686c737be6a898994b674b69/src/cmd/go/internal/work/exec.go#L284
477+
467478
sdk = ctx.attr.sdk[GoSDK]
468479
name = ctx.label.name
469480
if sdk.goos == "windows":
@@ -478,7 +489,9 @@ def _go_tool_binary_impl(ctx):
478489
set GOMAXPROCS=1
479490
set GOCACHE=%cd%\\{gotmp}\\gocache
480491
set GOPATH=%cd%"\\{gotmp}\\gopath
481-
{go} build -o {out} -trimpath -ldflags \"{ldflags}\" {srcs}
492+
set GOTOOLCHAIN=local
493+
set GO111MODULE=off
494+
{go} build -o {out} -trimpath -ldflags \"-buildid='' {ldflags}\" {srcs}
482495
set GO_EXIT_CODE=%ERRORLEVEL%
483496
RMDIR /S /Q "{gotmp}"
484497
MKDIR "{gotmp}"
@@ -506,7 +519,16 @@ exit /b %GO_EXIT_CODE%
506519
)
507520
else:
508521
# Note: GOPATH is needed for Go 1.16.
509-
cmd = """GOTMP=$(mktemp -d);trap "rm -rf \"$GOTMP\"" EXIT;GOMAXPROCS=1 GOCACHE="$GOTMP"/gocache GOPATH="$GOTMP"/gopath {go} build -o {out} -trimpath -ldflags '{ldflags}' {srcs}""".format(
522+
cmd = """
523+
GOTMP=$(mktemp -d)
524+
trap "rm -rf \"$GOTMP\"" EXIT
525+
GOMAXPROCS=1 \
526+
GOCACHE="$GOTMP"/gocache \
527+
GOPATH="$GOTMP"/gopath \
528+
GOTOOLCHAIN=local \
529+
GO111MODULE=off \
530+
{go} build -o {out} -trimpath -ldflags '-buildid="" {ldflags}' {srcs}
531+
""".format(
510532
go = sdk.go.path,
511533
out = out.path,
512534
srcs = " ".join([f.path for f in ctx.files.srcs]),

go/runfiles/manifest.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ func (m *manifest) open(name string) (fs.File, error) {
138138
// name refers to an actual file or dir listed in the manifest. The
139139
// basename of name may not match the basename of the underlying
140140
// file (e.g. in the case of a root symlink), so patch it.
141-
f, err := os.Open(r)
141+
var f *os.File
142+
f, err = os.Open(r)
142143
if err != nil {
143144
return nil, err
144145
}

go/tools/builders/nogo_main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func checkPackage(analyzers []*analysis.Analyzer, packagePath string, packageFil
223223
if _, ok := ignoreFilesSet[pkg.fset.Position(f.Pos()).Filename]; ok {
224224
for _, act := range actions {
225225
act.nolint = append(act.nolint, &Range{
226-
from: pkg.fset.Position(f.FileStart),
226+
from: pkg.fset.Position(f.Pos()),
227227
to: pkg.fset.Position(f.End()).Line,
228228
})
229229
}

go/tools/releaser/prepare.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func runPrepare(ctx context.Context, stderr io.Writer, args []string) error {
125125
branchName := "release-" + majorMinor[len("v"):]
126126
if !gitBranchExists(ctx, rootDir, branchName) {
127127
if !isMinorRelease {
128-
return fmt.Errorf("release branch %q does not exist locally. Fetch it, set RULES_GO_VERSION, add commits, and run this command again.")
128+
return fmt.Errorf("release branch %q does not exist locally. Fetch it, set RULES_GO_VERSION, add commits, and run this command again.", branchName)
129129
}
130130
if err := checkRulesGoVersion(ctx, rootDir, "HEAD", version); err != nil {
131131
return err
@@ -247,7 +247,7 @@ func checkRulesGoVersion(ctx context.Context, dir, refName, version string) erro
247247
}
248248
rulesGoVersionStr := []byte(fmt.Sprintf(`RULES_GO_VERSION = "%s"`, version[len("v"):]))
249249
if !bytes.Contains(data, rulesGoVersionStr) {
250-
return fmt.Errorf("RULES_GO_VERSION was not set to %q in go/def.bzl. Set it, add commits, and run this command again.")
250+
return fmt.Errorf("RULES_GO_VERSION was not set to %q in go/def.bzl. Set it, add commits, and run this command again.", version)
251251
}
252252
return nil
253253
}

go/tools/releaser/upgradedep.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,8 @@ func upgradeDepDecl(ctx context.Context, gh *githubClient, workDir, name string,
461461
}
462462
patchName := patchLabelValue[len("//third_party:"):]
463463
patchPath := filepath.Join(rootDir, "third_party", patchName)
464-
prevDir := filepath.Join(workDir, name, string('a'+patchIndex))
465-
patchDir := filepath.Join(workDir, name, string('a'+patchIndex+1))
464+
prevDir := filepath.Join(workDir, name, fmt.Sprintf("a%d", patchIndex))
465+
patchDir := filepath.Join(workDir, name, fmt.Sprintf("a%d", patchIndex+1))
466466
var patchCmd []string
467467
for _, c := range comments.Before {
468468
words := strings.Fields(strings.TrimPrefix(c.Token, "#"))
@@ -484,7 +484,7 @@ func upgradeDepDecl(ctx context.Context, gh *githubClient, workDir, name string,
484484
return err
485485
}
486486
}
487-
patch, _ := runForOutput(ctx, filepath.Join(workDir, name), "diff", "-urN", string('a'+patchIndex), string('a'+patchIndex+1))
487+
patch, _ := runForOutput(ctx, filepath.Join(workDir, name), "diff", "-urN", fmt.Sprintf("a%d", patchIndex), fmt.Sprintf("a%d", patchIndex+1))
488488
patch = sanitizePatch(patch)
489489
if err := os.WriteFile(patchPath, patch, 0666); err != nil {
490490
return err

internal/BUILD.bazel

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load("//go:def.bzl", "TOOLS_NOGO", "nogo")
2+
3+
nogo(
4+
name = "nogo",
5+
visibility = ["//visibility:public"],
6+
deps = [
7+
a
8+
for a in TOOLS_NOGO
9+
# Filter out shadow as it triggers on err and is very noisy.
10+
if a != "@org_golang_x_tools//go/analysis/passes/shadow:go_default_library"
11+
],
12+
)

tests/core/compatibility/BUILD.bazel

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load("//go:def.bzl", "go_binary", "go_cross_binary")
2+
3+
go_binary(
4+
name = "hello",
5+
srcs = ["hello.go"],
6+
)
7+
8+
go_cross_binary(
9+
name = "hello_old",
10+
sdk_version = "1.19",
11+
target = ":hello",
12+
)

tests/core/compatibility/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Compatibility
2+
3+
## compatibility_test
4+
5+
Verifies that a simple Go project builds with an older version of Go.

tests/core/compatibility/hello.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
fmt.Println("hello")
7+
}

tests/core/go_proto_library_importpath/importpath_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func Test(t *testing.T) {
1616
var expected int64 = 5
1717
if bar.Value.Value != expected {
1818
t.Errorf(fmt.Sprintf("Not equal: \n"+
19-
"expected: %s\n"+
20-
"actual : %s", expected, bar.Value.Value))
19+
"expected: %d\n"+
20+
"actual : %d", expected, bar.Value.Value))
2121
}
2222
}

tests/core/go_test/example_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ import "fmt"
44

55
var Expected = "Expected"
66

7+
// Please the "tests" nogo check.
8+
var (
9+
HelloWorld string
10+
DontTestMe string
11+
TestEmptyOutput string
12+
TestQuoting string
13+
)
14+
715
func ExampleHelloWorld() {
816
fmt.Println("Hello Example!")
917
fmt.Println("expected: " + Expected)

tests/core/go_test/filter_test_cases_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func Test(t *testing.T) {
8181
}
8282
p, err := bazel_testing.BazelOutput("info", "bazel-testlogs")
8383
if err != nil {
84-
t.Fatal("could not find testlog root: %s", err)
84+
t.Fatalf("could not find testlog root: %s", err)
8585
}
8686
path := filepath.Join(strings.TrimSpace(string(p)), "filter_test/test.xml")
8787
b, err := ioutil.ReadFile(path)

tests/core/go_test/timeout_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestTimeout(t *testing.T) {
4545
if err := bazel_testing.RunBazel("test", "//:timeout_test", "--test_timeout=3", "--test_arg=-test.v"); err == nil {
4646
t.Fatal("expected bazel test to fail")
4747
} else if exitErr, ok := err.(*bazel_testing.StderrExitError); !ok || exitErr.Err.ExitCode() != 3 {
48-
t.Fatalf("expected bazel test to fail with exit code 3", err)
48+
t.Fatalf("expected bazel test to fail with exit code 3, got %v", err)
4949
} else {
5050
stderr = string(exitErr.Err.Stderr)
5151
}

tests/core/go_test/xmlreport_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func Test(t *testing.T) {
142142

143143
p, err := bazel_testing.BazelOutput("info", "bazel-testlogs")
144144
if err != nil {
145-
t.Fatal("could not find testlog root: %s", err)
145+
t.Fatalf("could not find testlog root: %s", err)
146146
}
147147
path := filepath.Join(strings.TrimSpace(string(p)), "xml_test/test.xml")
148148
b, err := ioutil.ReadFile(path)

tests/integration/reproducibility/reproducibility_test.go

+45-18
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ func Test(t *testing.T) {
184184
// Build the targets in each directory.
185185
var wg sync.WaitGroup
186186
wg.Add(len(dirs))
187+
var errs []error
188+
var mu sync.Mutex
187189
for _, dir := range dirs {
188190
go func(dir string) {
189191
defer wg.Done()
@@ -196,11 +198,19 @@ func Test(t *testing.T) {
196198
)
197199
cmd.Dir = dir
198200
if err := cmd.Run(); err != nil {
199-
t.Fatalf("in %s, error running %s: %v", dir, strings.Join(cmd.Args, " "), err)
201+
mu.Lock()
202+
errs = append(errs, fmt.Errorf("in %s, error running %s: %v", dir, strings.Join(cmd.Args, " "), err))
203+
mu.Unlock()
200204
}
201205
}(dir)
202206
}
203207
wg.Wait()
208+
if len(errs) > 0 {
209+
for _, err := range errs {
210+
t.Error(err)
211+
}
212+
t.Fatal("errors building")
213+
}
204214

205215
t.Run("Check Hashes", func(t *testing.T) {
206216
// Hash files in each bazel-bin directory.
@@ -232,7 +242,7 @@ func Test(t *testing.T) {
232242

233243
// Compare dir0 and dir2. They should be different.
234244
if err := compareHashes(dirHashes[0], dirHashes[2]); err == nil {
235-
t.Fatalf("dir0 and dir2 are the same)", len(dirHashes[0]))
245+
t.Fatal("dir0 and dir2 are the same")
236246
}
237247
})
238248

@@ -309,24 +319,29 @@ func copyTree(dstRoot, srcRoot string) error {
309319

310320
if info.IsDir() {
311321
return os.Mkdir(dstPath, 0777)
322+
} else {
323+
return copyFile(dstPath, srcPath)
312324
}
313-
r, err := os.Open(srcPath)
314-
if err != nil {
315-
return nil
316-
}
317-
defer r.Close()
318-
w, err := os.Create(dstPath)
319-
if err != nil {
320-
return err
321-
}
322-
defer w.Close()
323-
if _, err := io.Copy(w, r); err != nil {
324-
return err
325-
}
326-
return w.Close()
327325
})
328326
}
329327

328+
func copyFile(dstPath, srcPath string) error {
329+
r, err := os.Open(srcPath)
330+
if err != nil {
331+
return nil
332+
}
333+
defer r.Close()
334+
w, err := os.Create(dstPath)
335+
if err != nil {
336+
return err
337+
}
338+
defer w.Close()
339+
if _, err := io.Copy(w, r); err != nil {
340+
return err
341+
}
342+
return w.Close()
343+
}
344+
330345
func compareHashes(lhs, rhs []fileHash) error {
331346
buf := &bytes.Buffer{}
332347
for li, ri := 0, 0; li < len(lhs) || ri < len(rhs); {
@@ -342,6 +357,12 @@ func compareHashes(lhs, rhs []fileHash) error {
342357
}
343358
if lhs[li].hash != rhs[ri].hash {
344359
fmt.Fprintf(buf, "%s is different: %s %s\n", lhs[li].rel, lhs[li].hash, rhs[ri].hash)
360+
if err := copyToTestOutputs(lhs[li]); err != nil {
361+
fmt.Fprintf(buf, "failed to copy file: %v\n", err)
362+
}
363+
if err := copyToTestOutputs(rhs[li]); err != nil {
364+
fmt.Fprintf(buf, "failed to copy file: %v\n", err)
365+
}
345366
}
346367
li++
347368
ri++
@@ -353,7 +374,7 @@ func compareHashes(lhs, rhs []fileHash) error {
353374
}
354375

355376
type fileHash struct {
356-
rel, hash string
377+
abs, rel, hash string
357378
}
358379

359380
func hashFiles(dir string, exclude func(root, path string) bool) ([]fileHash, error) {
@@ -426,7 +447,7 @@ func hashFiles(dir string, exclude func(root, path string) bool) ([]fileHash, er
426447
if _, err := io.Copy(h, r); err != nil {
427448
return err
428449
}
429-
hashes = append(hashes, fileHash{rel: rel, hash: hex.EncodeToString(h.Sum(sum[:0]))})
450+
hashes = append(hashes, fileHash{abs: path, rel: rel, hash: hex.EncodeToString(h.Sum(sum[:0]))})
430451

431452
return nil
432453
})
@@ -435,3 +456,9 @@ func hashFiles(dir string, exclude func(root, path string) bool) ([]fileHash, er
435456
}
436457
return hashes, nil
437458
}
459+
460+
// copyToTestOutputs copies a hashed file to the test outputs directory so that
461+
// it can be inspected manually under bazel-testlogs, e.g. using diffoscope.
462+
func copyToTestOutputs(hash fileHash) error {
463+
return copyFile(filepath.Join(os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"), hash.hash), hash.abs)
464+
}

0 commit comments

Comments
 (0)