Skip to content

Commit 2ce6da0

Browse files
committed
cmd/go: fix -gcflags, -ldflags not applying to current directory
A flag setting like -gcflags=-e applies only to the packages named on the command line, not to their dependencies. The way we used to implement this was to remember the command line arguments, reinterpret them as pattern matches instead of package argument generators (globs), and apply them during package load. The reason for this complexity was to address a command-line like: go build -gcflags=-e fmt runtime The load of fmt will load dependencies, including runtime, and the load of runtime will reuse the result of the earlier load. Because we were computing the effective -gcflags for each package during the load, we had to have a way to tell, when encountering runtime during the load of fmt, that runtime had been named on the command line, even though we hadn't gotten that far. That would be easy if the only possible arguments were import paths, but we also need to handle go build -gcflags=-e fmt runt... go build -gcflags=-e fmt $GOROOT/src/runtime go build -gcflags=-e fmt $GOROOT/src/runt... and so on. The match predicates usually did their job well, but not always. In particular, thanks to symlinks and case-insensitive file systems and unusual ways to spell file paths, it's always been possible in various corner cases to give an argument that evalutes to the runtime package during loading but failed to match it when reused to determine "was this package named on the command line?" CL 109235 fixed one instance of this problem by making a directory pattern match case-insensitive on Windows, but that is incorrect in some other cases and doesn't address the root problem, namely that there will probably always be odd corner cases where pattern matching and pattern globbing are not exactly aligned. This CL eliminates the assumption that pattern matching and pattern globbing are always completely in agreement, by simply marking the packages named on the command line after the package load returns them. This means delaying the computation of tool flags until after the load too, for a few different ways packages are loaded. The different load entry points add some complexity, which is why the original approach seemed more attractive, but the original approach had complexity that we simply didn't recognize at the time. This CL then rolls back the CL 109235 pattern-matching change, but it keeps the test introduced in that CL. That test still passes. In addition to fixing ambiguity due to case-sensitive file systems, this new approach also very likely fixes various ambiguities that might arise from abuse of symbolic links. Fixes #24232. Fixes #24456. Fixes #24750. Fixes #25046. Fixes #25878. Change-Id: I0b09825785dfb5112fb11494cff8527ebf57966f Reviewed-on: https://go-review.googlesource.com/129059 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent d46587c commit 2ce6da0

File tree

6 files changed

+128
-117
lines changed

6 files changed

+128
-117
lines changed

src/cmd/go/go_test.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5648,37 +5648,6 @@ func TestRelativePkgdir(t *testing.T) {
56485648
tg.run("build", "-i", "-pkgdir=.", "runtime")
56495649
}
56505650

5651-
func TestGcflagsPatterns(t *testing.T) {
5652-
skipIfGccgo(t, "gccgo has no standard packages")
5653-
tg := testgo(t)
5654-
defer tg.cleanup()
5655-
tg.setenv("GOPATH", "")
5656-
tg.setenv("GOCACHE", "off")
5657-
5658-
tg.run("build", "-n", "-v", "-gcflags= \t\r\n -e", "fmt")
5659-
tg.grepStderr("^# fmt", "did not rebuild fmt")
5660-
tg.grepStderrNot("^# reflect", "incorrectly rebuilt reflect")
5661-
5662-
tg.run("build", "-n", "-v", "-gcflags=-e", "fmt", "reflect")
5663-
tg.grepStderr("^# fmt", "did not rebuild fmt")
5664-
tg.grepStderr("^# reflect", "did not rebuild reflect")
5665-
tg.grepStderrNot("^# runtime", "incorrectly rebuilt runtime")
5666-
5667-
tg.run("build", "-n", "-x", "-v", "-gcflags= \t\r\n reflect \t\r\n = \t\r\n -N", "fmt")
5668-
tg.grepStderr("^# fmt", "did not rebuild fmt")
5669-
tg.grepStderr("^# reflect", "did not rebuild reflect")
5670-
tg.grepStderr("compile.* -N .*-p reflect", "did not build reflect with -N flag")
5671-
tg.grepStderrNot("compile.* -N .*-p fmt", "incorrectly built fmt with -N flag")
5672-
5673-
tg.run("test", "-c", "-n", "-gcflags=-N", "-ldflags=-X=x.y=z", "strings")
5674-
tg.grepStderr("compile.* -N .*compare_test.go", "did not compile strings_test package with -N flag")
5675-
tg.grepStderr("link.* -X=x.y=z", "did not link strings.test binary with -X flag")
5676-
5677-
tg.run("test", "-c", "-n", "-gcflags=strings=-N", "-ldflags=strings=-X=x.y=z", "strings")
5678-
tg.grepStderr("compile.* -N .*compare_test.go", "did not compile strings_test package with -N flag")
5679-
tg.grepStderr("link.* -X=x.y=z", "did not link strings.test binary with -X flag")
5680-
}
5681-
56825651
func TestGoTestMinusN(t *testing.T) {
56835652
// Intent here is to verify that 'go test -n' works without crashing.
56845653
// This reuses flag_test.go, but really any test would do.

src/cmd/go/internal/get/get.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func download(arg string, parent *load.Package, stk *load.ImportStack, mode int)
240240
}
241241
load1 := func(path string, mode int) *load.Package {
242242
if parent == nil {
243-
return load.LoadPackage(path, stk)
243+
return load.LoadPackageNoFlags(path, stk)
244244
}
245245
return load.LoadImport(path, parent.Dir, parent, stk, nil, mode|load.ResolveModule)
246246
}
@@ -329,7 +329,7 @@ func download(arg string, parent *load.Package, stk *load.ImportStack, mode int)
329329
base.Run(cfg.BuildToolexec, str.StringList(base.Tool("fix"), files))
330330

331331
// The imports might have changed, so reload again.
332-
p = load.ReloadPackage(arg, stk)
332+
p = load.ReloadPackageNoFlags(arg, stk)
333333
if p.Error != nil {
334334
base.Errorf("%s", p.Error)
335335
return

src/cmd/go/internal/load/flag.go

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package load
66

77
import (
88
"cmd/go/internal/base"
9-
"cmd/go/internal/search"
109
"cmd/go/internal/str"
1110
"fmt"
1211
"strings"
@@ -92,52 +91,3 @@ func (f *PerPackageFlag) For(p *Package) []string {
9291
}
9392
return flags
9493
}
95-
96-
var (
97-
cmdlineMatchers []func(*Package) bool
98-
cmdlineMatcherLiterals []func(*Package) bool
99-
)
100-
101-
// SetCmdlinePatterns records the set of patterns given on the command line,
102-
// for use by the PerPackageFlags.
103-
func SetCmdlinePatterns(args []string) {
104-
setCmdlinePatterns(args, base.Cwd)
105-
}
106-
107-
func setCmdlinePatterns(args []string, cwd string) {
108-
if len(args) == 0 {
109-
args = []string{"."}
110-
}
111-
cmdlineMatchers = nil // allow reset for testing
112-
cmdlineMatcherLiterals = nil
113-
for _, arg := range args {
114-
cmdlineMatchers = append(cmdlineMatchers, MatchPackage(arg, cwd))
115-
}
116-
for _, arg := range args {
117-
if !strings.Contains(arg, "...") && !search.IsMetaPackage(arg) {
118-
cmdlineMatcherLiterals = append(cmdlineMatcherLiterals, MatchPackage(arg, cwd))
119-
}
120-
}
121-
}
122-
123-
// isCmdlinePkg reports whether p is a package listed on the command line.
124-
func isCmdlinePkg(p *Package) bool {
125-
for _, m := range cmdlineMatchers {
126-
if m(p) {
127-
return true
128-
}
129-
}
130-
return false
131-
}
132-
133-
// isCmdlinePkgLiteral reports whether p is a package listed as
134-
// a literal package argument on the command line
135-
// (as opposed to being the result of expanding a wildcard).
136-
func isCmdlinePkgLiteral(p *Package) bool {
137-
for _, m := range cmdlineMatcherLiterals {
138-
if m(p) {
139-
return true
140-
}
141-
}
142-
return false
143-
}

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

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,17 @@ func ClearPackageCachePartial(args []string) {
374374
}
375375
}
376376

377-
// reloadPackage is like loadPackage but makes sure
377+
// ReloadPackageNoFlags is like LoadPackageNoFlags but makes sure
378378
// not to use the package cache.
379-
func ReloadPackage(arg string, stk *ImportStack) *Package {
379+
// It is only for use by GOPATH-based "go get".
380+
// TODO(rsc): When GOPATH-based "go get" is removed, delete this function.
381+
func ReloadPackageNoFlags(arg string, stk *ImportStack) *Package {
380382
p := packageCache[arg]
381383
if p != nil {
382384
delete(packageCache, p.Dir)
383385
delete(packageCache, p.ImportPath)
384386
}
385-
return LoadPackage(arg, stk)
387+
return LoadPackageNoFlags(arg, stk)
386388
}
387389

388390
// dirToImportPath returns the pseudo-import path we use for a package
@@ -431,6 +433,9 @@ const (
431433
// but possibly a local import path (an absolute file system path or one beginning
432434
// with ./ or ../). A local relative path is interpreted relative to srcDir.
433435
// It returns a *Package describing the package found in that directory.
436+
// LoadImport does not set tool flags and should only be used by
437+
// this package, as part of a bigger load operation, and by GOPATH-based "go get".
438+
// TODO(rsc): When GOPATH-based "go get" is removed, unexport this function.
434439
func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPos []token.Position, mode int) *Package {
435440
stk.Push(path)
436441
defer stk.Pop()
@@ -1185,27 +1190,6 @@ var foldPath = make(map[string]string)
11851190
func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
11861191
p.copyBuild(bp)
11871192

1188-
// Decide whether p was listed on the command line.
1189-
// Given that load is called while processing the command line,
1190-
// you might think we could simply pass a flag down into load
1191-
// saying whether we are loading something named on the command
1192-
// line or something to satisfy an import. But the first load of a
1193-
// package named on the command line may be as a dependency
1194-
// of an earlier package named on the command line, not when we
1195-
// get to that package during command line processing.
1196-
// For example "go test fmt reflect" will load reflect as a dependency
1197-
// of fmt before it attempts to load as a command-line argument.
1198-
// Because loads are cached, the later load will be a no-op,
1199-
// so it is important that the first load can fill in CmdlinePkg correctly.
1200-
// Hence the call to a separate matching check here.
1201-
p.Internal.CmdlinePkg = isCmdlinePkg(p)
1202-
p.Internal.CmdlinePkgLiteral = isCmdlinePkgLiteral(p)
1203-
1204-
p.Internal.Asmflags = BuildAsmflags.For(p)
1205-
p.Internal.Gcflags = BuildGcflags.For(p)
1206-
p.Internal.Ldflags = BuildLdflags.For(p)
1207-
p.Internal.Gccgoflags = BuildGccgoflags.For(p)
1208-
12091193
// The localPrefix is the path we interpret ./ imports relative to.
12101194
// Synthesized main packages sometimes override this.
12111195
if p.Internal.Local {
@@ -1740,11 +1724,31 @@ func ClearCmdCache() {
17401724
}
17411725
}
17421726

1727+
// LoadPackage loads the package named by arg.
1728+
func LoadPackage(arg string, stk *ImportStack) *Package {
1729+
p := loadPackage(arg, stk)
1730+
setToolFlags(p)
1731+
return p
1732+
}
1733+
1734+
// LoadPackageNoFlags is like LoadPackage
1735+
// but does not guarantee that the build tool flags are set in the result.
1736+
// It is only for use by GOPATH-based "go get"
1737+
// and is only appropriate for preliminary loading of packages.
1738+
// A real load using LoadPackage or (more likely)
1739+
// Packages, PackageAndErrors, or PackagesForBuild
1740+
// must be done before passing the package to any build
1741+
// steps, so that the tool flags can be set properly.
1742+
// TODO(rsc): When GOPATH-based "go get" is removed, delete this function.
1743+
func LoadPackageNoFlags(arg string, stk *ImportStack) *Package {
1744+
return loadPackage(arg, stk)
1745+
}
1746+
17431747
// loadPackage is like loadImport but is used for command-line arguments,
17441748
// not for paths found in import statements. In addition to ordinary import paths,
17451749
// loadPackage accepts pseudo-paths beginning with cmd/ to denote commands
17461750
// in the Go command directory, as well as paths to those directories.
1747-
func LoadPackage(arg string, stk *ImportStack) *Package {
1751+
func loadPackage(arg string, stk *ImportStack) *Package {
17481752
if build.IsLocalImport(arg) {
17491753
dir := arg
17501754
if !filepath.IsAbs(dir) {
@@ -1843,7 +1847,14 @@ func PackagesAndErrors(patterns []string) []*Package {
18431847

18441848
for _, m := range matches {
18451849
for _, pkg := range m.Pkgs {
1846-
p := LoadPackage(pkg, &stk)
1850+
p := loadPackage(pkg, &stk)
1851+
p.Internal.CmdlinePkg = true
1852+
if m.Literal {
1853+
// Note: do not set = m.Literal unconditionally
1854+
// because maybe we'll see p matching both
1855+
// a literal and also a non-literal pattern.
1856+
p.Internal.CmdlinePkgLiteral = true
1857+
}
18471858
if seenPkg[p] {
18481859
continue
18491860
}
@@ -1852,9 +1863,24 @@ func PackagesAndErrors(patterns []string) []*Package {
18521863
}
18531864
}
18541865

1866+
// Now that CmdlinePkg is set correctly,
1867+
// compute the effective flags for all loaded packages
1868+
// (not just the ones matching the patterns but also
1869+
// their dependencies).
1870+
setToolFlags(pkgs...)
1871+
18551872
return pkgs
18561873
}
18571874

1875+
func setToolFlags(pkgs ...*Package) {
1876+
for _, p := range PackageList(pkgs) {
1877+
p.Internal.Asmflags = BuildAsmflags.For(p)
1878+
p.Internal.Gcflags = BuildGcflags.For(p)
1879+
p.Internal.Ldflags = BuildLdflags.For(p)
1880+
p.Internal.Gccgoflags = BuildGccgoflags.For(p)
1881+
}
1882+
}
1883+
18581884
func ImportPaths(args []string) []*search.Match {
18591885
if ModInit(); cfg.ModulesEnabled {
18601886
return ModImportPaths(args)
@@ -1986,5 +2012,7 @@ func GoFilesPackage(gofiles []string) *Package {
19862012
}
19872013
}
19882014

2015+
setToolFlags(pkg)
2016+
19892017
return pkg
19902018
}

src/cmd/go/internal/load/search.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package load
66

77
import (
88
"path/filepath"
9-
"runtime"
109
"strings"
1110

1211
"cmd/go/internal/search"
@@ -28,13 +27,7 @@ func MatchPackage(pattern, cwd string) func(*Package) bool {
2827
}
2928
dir = filepath.Join(cwd, dir)
3029
if pattern == "" {
31-
return func(p *Package) bool {
32-
// TODO(rsc): This is wrong. See golang.org/issue/25878.
33-
if runtime.GOOS != "windows" {
34-
return p.Dir == dir
35-
}
36-
return strings.EqualFold(p.Dir, dir)
37-
}
30+
return func(p *Package) bool { return p.Dir == dir }
3831
}
3932
matchPath := search.MatchPattern(pattern)
4033
return func(p *Package) bool {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
[!gc] skip 'using -gcflags and -ldflags'
2+
3+
# -gcflags=-e applies to named packages, not dependencies
4+
go build -n -v -gcflags=-e z1 z2
5+
stderr 'compile.* -e .*-p z1'
6+
stderr 'compile.* -e .*-p z2'
7+
stderr 'compile.* -p y'
8+
! stderr 'compile.* -e .*-p [^z]'
9+
10+
# -gcflags can specify package=flags, and can be repeated; last match wins
11+
go build -n -v -gcflags=-e -gcflags=z1=-N z1 z2
12+
stderr 'compile.* -N .*-p z1'
13+
! stderr 'compile.* -e .*-p z1'
14+
! stderr 'compile.* -N .*-p z2'
15+
stderr 'compile.* -e .*-p z2'
16+
stderr 'compile.* -p y'
17+
! stderr 'compile.* -e .*-p [^z]'
18+
! stderr 'compile.* -N .*-p [^z]'
19+
20+
# -gcflags can have arbitrary spaces around the flags
21+
go build -n -v -gcflags=' z1 = -e ' z1
22+
stderr 'compile.* -e .*-p z1'
23+
24+
# -ldflags for implicit test package applies to test binary
25+
go test -c -n -gcflags=-N -ldflags=-X=x.y=z z1
26+
stderr 'compile.* -N .*z_test.go'
27+
stderr 'link.* -X=x.y=z'
28+
29+
# -ldflags for explicit test package applies to test binary
30+
go test -c -n -gcflags=z1=-N -ldflags=z1=-X=x.y=z z1
31+
stderr 'compile.* -N .*z_test.go'
32+
stderr 'link.* -X=x.y=z'
33+
34+
# -ldflags applies to link of command
35+
go build -n -ldflags=-X=math.pi=3 my/cmd/prog
36+
stderr 'link.* -X=math.pi=3'
37+
38+
# -ldflags applies to link of command even with strange directory name
39+
go build -n -ldflags=-X=math.pi=3 my/cmd/prog/
40+
stderr 'link.* -X=math.pi=3'
41+
42+
# -ldflags applies to current directory
43+
cd my/cmd/prog
44+
go build -n -ldflags=-X=math.pi=3
45+
stderr 'link.* -X=math.pi=3'
46+
47+
# -ldflags applies to current directory even if GOPATH is funny
48+
[windows] cd $WORK/GoPath/src/my/cmd/prog
49+
[darwin] cd $WORK/GoPath/src/my/cmd/prog
50+
go build -n -ldflags=-X=math.pi=3
51+
stderr 'link.* -X=math.pi=3'
52+
53+
-- z1/z.go --
54+
package z1
55+
import _ "y"
56+
import _ "z2"
57+
58+
-- z1/z_test.go --
59+
package z1_test
60+
import "testing"
61+
func Test(t *testing.T) {}
62+
63+
-- z2/z.go --
64+
package z2
65+
66+
-- y/y.go --
67+
package y
68+
69+
-- my/cmd/prog/prog.go --
70+
package main
71+
func main() {}

0 commit comments

Comments
 (0)