Skip to content

Commit 67f6b8c

Browse files
author
Bryan C. Mills
committed
cmd/go: avoid stamping VCS metadata in test binaries
Invoking a VCS tool requires that the VCS tool be installed, and also adds latency to build commands. Unfortunately, we had been mistakenly loading VCS metadata for tests of "main" packages. Users almost never care about versioning for test binaries, because 'go test' runs the test in the source tree and test binaries are only rarely used outside of 'go test'. So the user already knows exactly which version the test is built against, because the source code is right there — it's not worth the overhead to stamp. Fixes #51723. Change-Id: I96f191c5a765f5183e5e10b6dfb75a0381c99814 Reviewed-on: https://go-review.googlesource.com/c/go/+/393894 Trust: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Trust: Daniel Martí <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 9465878 commit 67f6b8c

File tree

5 files changed

+123
-11
lines changed

5 files changed

+123
-11
lines changed

src/cmd/go/internal/list/list.go

+1
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
567567
pkgOpts := load.PackageOpts{
568568
IgnoreImports: *listFind,
569569
ModResolveTests: *listTest,
570+
LoadVCS: cfg.BuildBuildvcs,
570571
}
571572
pkgs := load.PackagesAndErrors(ctx, pkgOpts, args)
572573
if !*listE {

src/cmd/go/internal/load/pkg.go

+25-6
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ func (p *Package) Desc() string {
193193
return p.ImportPath
194194
}
195195

196+
// IsTestOnly reports whether p is a test-only package.
197+
//
198+
// A “test-only” package is one that:
199+
// - is a test-only variant of an ordinary package, or
200+
// - is a synthesized "main" package for a test binary, or
201+
// - contains only _test.go files.
202+
func (p *Package) IsTestOnly() bool {
203+
return p.ForTest != "" ||
204+
p.Internal.TestmainGo != nil ||
205+
len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 && len(p.GoFiles)+len(p.CgoFiles) == 0
206+
}
207+
196208
type PackageInternal struct {
197209
// Unexported fields are not part of the public API.
198210
Build *build.Package
@@ -1926,8 +1938,12 @@ func (p *Package) load(ctx context.Context, opts PackageOpts, path string, stk *
19261938
}
19271939
p.Internal.Imports = imports
19281940
p.collectDeps()
1929-
if p.Error == nil && p.Name == "main" && len(p.DepsErrors) == 0 {
1930-
p.setBuildInfo()
1941+
if p.Error == nil && p.Name == "main" && !p.Internal.ForceLibrary && len(p.DepsErrors) == 0 {
1942+
// TODO(bcmills): loading VCS metadata can be fairly slow.
1943+
// Consider starting this as a background goroutine and retrieving the result
1944+
// asynchronously when we're actually ready to build the package, or when we
1945+
// actually need to evaluate whether the package's metadata is stale.
1946+
p.setBuildInfo(opts.LoadVCS)
19311947
}
19321948

19331949
// unsafe is a fake package.
@@ -2216,7 +2232,7 @@ var vcsStatusCache par.Cache
22162232
//
22172233
// Note that the GoVersion field is not set here to avoid encoding it twice.
22182234
// It is stored separately in the binary, mostly for historical reasons.
2219-
func (p *Package) setBuildInfo() {
2235+
func (p *Package) setBuildInfo(includeVCS bool) {
22202236
// TODO: build and vcs information is not embedded for executables in GOROOT.
22212237
// cmd/dist uses -gcflags=all= -ldflags=all= by default, which means these
22222238
// executables always appear stale unless the user sets the same flags.
@@ -2346,8 +2362,8 @@ func (p *Package) setBuildInfo() {
23462362
// Add VCS status if all conditions are true:
23472363
//
23482364
// - -buildvcs is enabled.
2349-
// - p is contained within a main module (there may be multiple main modules
2350-
// in a workspace, but local replacements don't count).
2365+
// - p is a non-test contained within a main module (there may be multiple
2366+
// main modules in a workspace, but local replacements don't count).
23512367
// - Both the current directory and p's module's root directory are contained
23522368
// in the same local repository.
23532369
// - We know the VCS commands needed to get the status.
@@ -2359,7 +2375,7 @@ func (p *Package) setBuildInfo() {
23592375
var vcsCmd *vcs.Cmd
23602376
var err error
23612377
const allowNesting = true
2362-
if cfg.BuildBuildvcs && p.Module != nil && p.Module.Version == "" && !p.Standard {
2378+
if includeVCS && p.Module != nil && p.Module.Version == "" && !p.Standard && !p.IsTestOnly() {
23632379
repoDir, vcsCmd, err = vcs.FromDir(base.Cwd(), "", allowNesting)
23642380
if err != nil && !errors.Is(err, os.ErrNotExist) {
23652381
setVCSError(err)
@@ -2648,6 +2664,9 @@ type PackageOpts struct {
26482664
// are not be matched, and their dependencies may not be loaded. A warning
26492665
// may be printed for non-literal arguments that match no main packages.
26502666
MainOnly bool
2667+
2668+
// LoadVCS controls whether we also load version-control metadata for main packages.
2669+
LoadVCS bool
26512670
}
26522671

26532672
// PackagesAndErrors returns the packages named by the command line arguments

src/cmd/go/internal/load/test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,9 @@ func TestPackagesAndErrors(ctx context.Context, opts PackageOpts, p *Package, co
368368
if err != nil && pmain.Error == nil {
369369
pmain.Error = &PackageError{Err: err}
370370
}
371-
if data != nil {
372-
pmain.Internal.TestmainGo = &data
373-
}
371+
// Set TestmainGo even if it is empty: the presence of a TestmainGo
372+
// indicates that this package is, in fact, a test main.
373+
pmain.Internal.TestmainGo = &data
374374

375375
return pmain, ptest, pxtest
376376
}

src/cmd/go/internal/work/build.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ func runBuild(ctx context.Context, cmd *base.Command, args []string) {
379379
var b Builder
380380
b.Init()
381381

382-
pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args)
382+
pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{LoadVCS: cfg.BuildBuildvcs}, args)
383383
load.CheckPackageErrors(pkgs)
384384

385385
explicitO := len(cfg.BuildO) > 0
@@ -603,7 +603,7 @@ func runInstall(ctx context.Context, cmd *base.Command, args []string) {
603603

604604
modload.InitWorkfile()
605605
BuildInit()
606-
pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args)
606+
pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{LoadVCS: cfg.BuildBuildvcs}, args)
607607
if cfg.ModulesEnabled && !modload.HasModRoot() {
608608
haveErrors := false
609609
allMissingErrors := true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# https://go.dev/issue/51723: 'go test' should not stamp VCS metadata
2+
# in the build settings. (It isn't worth the latency hit, given that
3+
# test binaries are almost never distributed to users.)
4+
5+
[short] skip
6+
[!exec:git] skip
7+
8+
exec git init
9+
10+
# The test binaries should not have VCS settings stamped.
11+
# (The test itself verifies that.)
12+
go test . ./testonly
13+
14+
15+
# Remove 'git' from $PATH. The test should still build.
16+
# This ensures that we aren't loading VCS metadata that
17+
# we subsequently throw away.
18+
env PATH=''
19+
env path=''
20+
21+
# Compiling the test should not require the VCS tool.
22+
go test -c -o $devnull .
23+
24+
25+
# When listing a main package, in general we need its VCS metadata to determine
26+
# the .Stale and .StaleReason fields.
27+
! go list .
28+
stderr '^go: missing Git command\. See https://golang\.org/s/gogetcmd\nerror obtaining VCS status: .*\n\tUse -buildvcs=false to disable VCS stamping.'
29+
30+
# Adding the -test flag should be strictly additive — it should not suppress the error.
31+
! go list -test .
32+
stderr '^go: missing Git command\. See https://golang\.org/s/gogetcmd\nerror obtaining VCS status: .*\n\tUse -buildvcs=false to disable VCS stamping.'
33+
34+
# Adding the suggested flag should suppress the error.
35+
go list -test -buildvcs=false .
36+
! stderr .
37+
38+
39+
# Since the ./testonly package can't produce an actual binary, we shouldn't
40+
# invoke a VCS tool to compute a build stamp when listing it.
41+
go list ./testonly
42+
! stderr .
43+
go list -test ./testonly
44+
! stderr .
45+
46+
47+
-- go.mod --
48+
module example
49+
50+
go 1.18
51+
-- example.go --
52+
package main
53+
-- example_test.go --
54+
package main
55+
56+
import (
57+
"runtime/debug"
58+
"strings"
59+
"testing"
60+
)
61+
62+
func TestDetail(t *testing.T) {
63+
bi, ok := debug.ReadBuildInfo()
64+
if !ok {
65+
t.Fatal("BuildInfo not present")
66+
}
67+
for _, s := range bi.Settings {
68+
if strings.HasPrefix(s.Key, "vcs.") {
69+
t.Fatalf("unexpected VCS setting: %s=%s", s.Key, s.Value)
70+
}
71+
}
72+
}
73+
-- testonly/main_test.go --
74+
package main
75+
76+
import (
77+
"runtime/debug"
78+
"strings"
79+
"testing"
80+
)
81+
82+
func TestDetail(t *testing.T) {
83+
bi, ok := debug.ReadBuildInfo()
84+
if !ok {
85+
t.Fatal("BuildInfo not present")
86+
}
87+
for _, s := range bi.Settings {
88+
if strings.HasPrefix(s.Key, "vcs.") {
89+
t.Fatalf("unexpected VCS setting: %s=%s", s.Key, s.Value)
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)