cmd/go/internal/vgo: process packages for all build tags in vendor Until now, "vgo vendor" has only considered the packages needed for a build with the current GOOS/GOARCH and build tags. If a module has both linux-specific and windows-specific requirements, then "vgo vendor" on windows will omit the linux code, and vice versa, making it impossible to prepare a full vendor set. This CL introduces a new (for now undocumented) package pattern "ALL" that is like "all" but follows imports in files requiring any plausible build tag, producing a set of files that does not vary from system to system. The only +build lines not satisfied when computing "ALL" packages are those listing only the tag "ignore" or those listing only syntactically invalid build tags. For example, a file containing any of these lines not be processed: // +build ignore // +build ! // +build @#$%^&*() (The first two are commonly used in practice to exclude a file from processing by the go command.) This CL then changes the "vgo vendor" command to process "ALL" packages, not "all" packages. It also changes the vendor/vgo.list output file to list only modules that had files copied out of them and to indicate which packages came from which modules. Fixes golang/go#25376. Fixes golang/go#25624. Change-Id: I6756286fa6bb697819279ef67b4bea68bc0b7f53 Reviewed-on: https://go-review.googlesource.com/117258 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/imports/build.go b/vendor/cmd/go/internal/imports/build.go index bb40f2b..5597870 100644 --- a/vendor/cmd/go/internal/imports/build.go +++ b/vendor/cmd/go/internal/imports/build.go
@@ -27,6 +27,12 @@ // // marks the file as applicable only on Windows and Linux. // +// If tags["*"] is true, then ShouldBuild will consider every +// build tag except "ignore" to be both true and false for +// the purpose of satisfying build tags, in order to estimate +// (conservatively) whether a file could ever possibly be used +// in any build. +// func ShouldBuild(content []byte, tags map[string]bool) bool { // Pass 1. Identify leading run of // comments and blank lines, // which must be followed by a blank line. @@ -87,8 +93,8 @@ // matchTags reports whether the name is one of: // -// tag (if haveTags[tag] is true) -// !tag (if haveTags[tag] is false) +// tag (if tags[tag] is true) +// !tag (if tags[tag] is false) // a comma-separated list of any of these // func matchTags(name string, tags map[string]bool) bool { @@ -105,9 +111,13 @@ return false } if strings.HasPrefix(name, "!") { // negation - return len(name) > 1 && !matchTags(name[1:], tags) + return len(name) > 1 && matchTag(name[1:], tags, false) } + return matchTag(name, tags, true) +} +// matchTag reports whether the tag name is valid and satisfied by tags[name]==want. +func matchTag(name string, tags map[string]bool, want bool) bool { // Tags must be letters, digits, underscores or dots. // Unlike in Go identifiers, all digits are fine (e.g., "386"). for _, c := range name { @@ -116,13 +126,22 @@ } } - if name == "linux" && tags["android"] { + if tags["*"] && name != "" && name != "ignore" { + // Special case for gathering all possible imports: + // if we put * in the tags map then all tags + // except "ignore" are considered both present and not + // (so we return true no matter how 'want' is set). return true } - return tags[name] + + have := tags[name] + if name == "linux" { + have = have || tags["android"] + } + return have == want } -// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH +// MatchFile returns false if the name contains a $GOOS or $GOARCH // suffix which does not match the current system. // The recognized name formats are: // @@ -134,7 +153,14 @@ // name_$(GOOS)_$(GOARCH)_test.* // // An exception: if GOOS=android, then files with GOOS=linux are also matched. +// +// If tags["*"] is true, then MatchFile will consider all possible +// GOOS and GOARCH to be available and will consequently +// always return true. func MatchFile(name string, tags map[string]bool) bool { + if tags["*"] { + return true + } if dot := strings.Index(name, "."); dot != -1 { name = name[:dot] }
diff --git a/vendor/cmd/go/internal/imports/scan_test.go b/vendor/cmd/go/internal/imports/scan_test.go index cb0a46a..dbc7944 100644 --- a/vendor/cmd/go/internal/imports/scan_test.go +++ b/vendor/cmd/go/internal/imports/scan_test.go
@@ -6,6 +6,7 @@ import ( "path/filepath" + "reflect" "runtime" "testing" ) @@ -47,3 +48,15 @@ t.Errorf("json missing test import net/http (%q)", testImports) } } + +func TestScanStar(t *testing.T) { + imports, _, err := ScanDir("testdata/import1", map[string]bool{"*": true}) + if err != nil { + t.Fatal(err) + } + + want := []string{"import1", "import2", "import3", "import4"} + if !reflect.DeepEqual(imports, want) { + t.Errorf("ScanDir testdata/import1:\nhave %v\nwant %v", imports, want) + } +}
diff --git a/vendor/cmd/go/internal/imports/testdata/import1/x.go b/vendor/cmd/go/internal/imports/testdata/import1/x.go new file mode 100644 index 0000000..98f9191 --- /dev/null +++ b/vendor/cmd/go/internal/imports/testdata/import1/x.go
@@ -0,0 +1,3 @@ +package x + +import "import1"
diff --git a/vendor/cmd/go/internal/imports/testdata/import1/x1.go b/vendor/cmd/go/internal/imports/testdata/import1/x1.go new file mode 100644 index 0000000..6a9594a --- /dev/null +++ b/vendor/cmd/go/internal/imports/testdata/import1/x1.go
@@ -0,0 +1,9 @@ +// +build blahblh +// +build linux +// +build !linux +// +build windows +// +build darwin + +package x + +import "import4"
diff --git a/vendor/cmd/go/internal/imports/testdata/import1/x_darwin.go b/vendor/cmd/go/internal/imports/testdata/import1/x_darwin.go new file mode 100644 index 0000000..a0c3fdd --- /dev/null +++ b/vendor/cmd/go/internal/imports/testdata/import1/x_darwin.go
@@ -0,0 +1,3 @@ +package xxxx + +import "import3"
diff --git a/vendor/cmd/go/internal/imports/testdata/import1/x_windows.go b/vendor/cmd/go/internal/imports/testdata/import1/x_windows.go new file mode 100644 index 0000000..63c5082 --- /dev/null +++ b/vendor/cmd/go/internal/imports/testdata/import1/x_windows.go
@@ -0,0 +1,3 @@ +package x + +import "import2"
diff --git a/vendor/cmd/go/internal/vgo/load.go b/vendor/cmd/go/internal/vgo/load.go index 0f11570..1b14e4b 100644 --- a/vendor/cmd/go/internal/vgo/load.go +++ b/vendor/cmd/go/internal/vgo/load.go
@@ -41,6 +41,7 @@ tags map[string]bool importmap map[string]string pkgdir map[string]string + pkgmod map[string]module.Version isGetU bool ) @@ -146,12 +147,15 @@ importmap = ld.importmap pkgdir = ld.pkgdir + pkgmod = ld.pkgmod } type loader struct { imported map[string]importLevel importmap map[string]string pkgdir map[string]string + pkgmod map[string]module.Version + tags map[string]bool missing []missing imports []string stack []string @@ -167,6 +171,8 @@ imported: make(map[string]importLevel), importmap: make(map[string]string), pkgdir: make(map[string]string), + pkgmod: make(map[string]module.Version), + tags: imports.Tags(), } ld.imported["C"] = 100 return ld @@ -214,7 +220,7 @@ ld.pkgdir[realPath] = dir - imports, testImports, err := imports.ScanDir(dir, imports.Tags()) + imports, testImports, err := imports.ScanDir(dir, ld.tags) if err != nil { base.Errorf("vgo: %s [%s]: %v", ld.stackText(), dir, err) return @@ -239,6 +245,7 @@ if len(path) > len(Target.Path) { dir = filepath.Join(dir, path[len(Target.Path)+1:]) } + ld.pkgmod[path] = Target return dir } @@ -276,6 +283,7 @@ mod1 = mod } if dir1 != "" { + ld.pkgmod[path] = mod1 return dir1 } ld.missing = append(ld.missing, missing{path, ld.stackText()})
diff --git a/vendor/cmd/go/internal/vgo/search.go b/vendor/cmd/go/internal/vgo/search.go index 13a426c..c3f7ab1 100644 --- a/vendor/cmd/go/internal/vgo/search.go +++ b/vendor/cmd/go/internal/vgo/search.go
@@ -22,11 +22,13 @@ func expandImportPaths(args []string) []string { var out []string for _, a := range args { - if search.IsMetaPackage(a) { + // TODO(rsc): Move a == "ALL" test into search.IsMetaPackage + // once we officially lock in all the module work (tentatively, Go 1.12). + if search.IsMetaPackage(a) || a == "ALL" { switch a { default: - fmt.Fprintf(os.Stderr, "warning: %q matches no packages when using modules", a) - case "all": + fmt.Fprintf(os.Stderr, "vgo: warning: %q matches no packages when using modules\n", a) + case "all", "ALL": out = append(out, AllPackages(a)...) } continue @@ -65,6 +67,9 @@ if pattern == "all" { return MatchAll() } + if pattern == "ALL" { + return MatchALL() + } return matchPackages(pattern, buildList) } @@ -72,7 +77,7 @@ func matchPackages(pattern string, buildList []module.Version) []string { match := func(string) bool { return true } treeCanMatch := func(string) bool { return true } - if !search.IsMetaPackage(pattern) { + if !search.IsMetaPackage(pattern) && pattern != "ALL" { match = search.MatchPattern(pattern) treeCanMatch = search.TreeCanMatchPattern(pattern) } @@ -162,8 +167,23 @@ // It does not include packages in other modules that are not needed // by builds of this module. func MatchAll() []string { + return matchAll(imports.Tags()) +} + +// MatchALL returns a list of the packages matching the pattern "ALL". +// The pattern "ALL" is like "all" but looks at all source files, +// even ones that would be ignored by current build tag settings. +// That's useful for identifying which packages to include in a vendor directory. +func MatchALL() []string { + return matchAll(map[string]bool{"*": true}) +} + +// matchAll is the common implementation of MatchAll and MatchALL, +// which differ only in the set of tags to apply to select files. +func matchAll(tags map[string]bool) []string { local := matchPackages("all", buildList[:1]) ld := newLoader() + ld.tags = tags ld.importList(local, levelTestRecursive) var all []string for _, pkg := range ld.importmap {
diff --git a/vendor/cmd/go/internal/vgo/vendor.go b/vendor/cmd/go/internal/vgo/vendor.go index e3330f6..acba4af 100644 --- a/vendor/cmd/go/internal/vgo/vendor.go +++ b/vendor/cmd/go/internal/vgo/vendor.go
@@ -14,19 +14,29 @@ "strings" "cmd/go/internal/base" + "cmd/go/internal/module" ) var CmdVendor = &base.Command{ - UsageLine: "vendor", - Run: runVendor, + UsageLine: "vendor [-v]", Short: "vendor dependencies of current module", Long: ` Vendor resets the module's vendor directory to include all -packages needed to builds and test all packages in the module +packages needed to build and test all packages in the module and their dependencies. + +The -v flag causes vendor to print to standard error the +module paths of the modules processed and the import paths +of the packages copied. `, } +var vendorV = CmdVendor.Flag.Bool("v", false, "") + +func init() { + CmdVendor.Run = runVendor // break init cycle +} + func runVendor(cmd *base.Command, args []string) { if Init(); !Enabled() { base.Fatalf("vgo vendor: cannot use -m outside module") @@ -35,29 +45,51 @@ base.Fatalf("vgo vendor: vendor takes no arguments") } InitMod() - // TODO(rsc): This should scan directories with more permissive build tags. - pkgs := ImportPaths([]string{"all"}) + pkgs := ImportPaths([]string{"ALL"}) vdir := filepath.Join(ModRoot, "vendor") if err := os.RemoveAll(vdir); err != nil { base.Fatalf("vgo vendor: %v", err) } + modpkgs := make(map[module.Version][]string) for _, pkg := range pkgs { - vendorPkg(vdir, pkg) + m := pkgmod[pkg] + if m == Target { + continue + } + modpkgs[m] = append(modpkgs[m], pkg) } var buf bytes.Buffer - printListM(&buf) + for _, m := range buildList[1:] { + if pkgs := modpkgs[m]; len(pkgs) > 0 { + repl := "" + if r := replaced(m); r != nil { + repl = " => " + r.New.Path + if r.New.Version != "" { + repl += " " + r.New.Version + } + } + fmt.Fprintf(&buf, "# %s %s%s\n", m.Path, m.Version, repl) + if *vendorV { + fmt.Fprintf(os.Stderr, "# %s %s%s\n", m.Path, m.Version, repl) + } + for _, pkg := range pkgs { + fmt.Fprintf(&buf, "%s\n", pkg) + if *vendorV { + fmt.Fprintf(os.Stderr, "%s\n", pkg) + } + vendorPkg(vdir, pkg) + } + } + } if err := ioutil.WriteFile(filepath.Join(vdir, "vgo.list"), buf.Bytes(), 0666); err != nil { base.Fatalf("vgo vendor: %v", err) } } func vendorPkg(vdir, pkg string) { - if hasPathPrefix(pkg, Target.Path) { - return - } realPath := importmap[pkg] if realPath != pkg && importmap[realPath] != "" { fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
diff --git a/vendor/cmd/go/testdata/vendormod/go.mod b/vendor/cmd/go/testdata/vendormod/go.mod new file mode 100644 index 0000000..6f71634 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/go.mod
@@ -0,0 +1,16 @@ +module m + +replace x v1.0.0 => ./x + +replace y v1.0.0 => ./y + +replace z v1.0.0 => ./z + +replace w v1.0.0 => ./w + +require ( + w v1.0.0 + x v1.0.0 + y v1.0.0 + z v1.0.0 +)
diff --git a/vendor/cmd/go/testdata/vendormod/v1.go b/vendor/cmd/go/testdata/vendormod/v1.go new file mode 100644 index 0000000..6ca04a5 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/v1.go
@@ -0,0 +1,3 @@ +package m + +import _ "x"
diff --git a/vendor/cmd/go/testdata/vendormod/v2.go b/vendor/cmd/go/testdata/vendormod/v2.go new file mode 100644 index 0000000..8b089e4 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/v2.go
@@ -0,0 +1,5 @@ +// +build abc + +package mMmMmMm + +import _ "y"
diff --git a/vendor/cmd/go/testdata/vendormod/v3.go b/vendor/cmd/go/testdata/vendormod/v3.go new file mode 100644 index 0000000..318b5f0 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/v3.go
@@ -0,0 +1,5 @@ +// +build !abc + +package m + +import _ "z"
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/vgo.list b/vendor/cmd/go/testdata/vendormod/vendor/vgo.list new file mode 100644 index 0000000..e8bd2c5 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/vgo.list
@@ -0,0 +1,6 @@ +# x v1.0.0 => ./x +x +# y v1.0.0 => ./y +y +# z v1.0.0 => ./z +z
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/x/go.mod b/vendor/cmd/go/testdata/vendormod/vendor/x/go.mod new file mode 100644 index 0000000..c191435 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/x/go.mod
@@ -0,0 +1 @@ +module x
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/x/x.go b/vendor/cmd/go/testdata/vendormod/vendor/x/x.go new file mode 100644 index 0000000..823aafd --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/x/x.go
@@ -0,0 +1 @@ +package x
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/y/go.mod b/vendor/cmd/go/testdata/vendormod/vendor/y/go.mod new file mode 100644 index 0000000..ac82a48 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/y/go.mod
@@ -0,0 +1 @@ +module y
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/y/y.go b/vendor/cmd/go/testdata/vendormod/vendor/y/y.go new file mode 100644 index 0000000..789ca71 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/y/y.go
@@ -0,0 +1 @@ +package y
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/z/go.mod b/vendor/cmd/go/testdata/vendormod/vendor/z/go.mod new file mode 100644 index 0000000..efc58fe --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/z/go.mod
@@ -0,0 +1 @@ +module z
diff --git a/vendor/cmd/go/testdata/vendormod/vendor/z/z.go b/vendor/cmd/go/testdata/vendormod/vendor/z/z.go new file mode 100644 index 0000000..46458cb --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/vendor/z/z.go
@@ -0,0 +1 @@ +package z
diff --git a/vendor/cmd/go/testdata/vendormod/w/go.mod b/vendor/cmd/go/testdata/vendormod/w/go.mod new file mode 100644 index 0000000..ce2a6c1 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/w/go.mod
@@ -0,0 +1 @@ +module w
diff --git a/vendor/cmd/go/testdata/vendormod/w/w.go b/vendor/cmd/go/testdata/vendormod/w/w.go new file mode 100644 index 0000000..a796c0b --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/w/w.go
@@ -0,0 +1 @@ +package w
diff --git a/vendor/cmd/go/testdata/vendormod/x/go.mod b/vendor/cmd/go/testdata/vendormod/x/go.mod new file mode 100644 index 0000000..c191435 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/x/go.mod
@@ -0,0 +1 @@ +module x
diff --git a/vendor/cmd/go/testdata/vendormod/x/x.go b/vendor/cmd/go/testdata/vendormod/x/x.go new file mode 100644 index 0000000..823aafd --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/x/x.go
@@ -0,0 +1 @@ +package x
diff --git a/vendor/cmd/go/testdata/vendormod/y/go.mod b/vendor/cmd/go/testdata/vendormod/y/go.mod new file mode 100644 index 0000000..ac82a48 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/y/go.mod
@@ -0,0 +1 @@ +module y
diff --git a/vendor/cmd/go/testdata/vendormod/y/y.go b/vendor/cmd/go/testdata/vendormod/y/y.go new file mode 100644 index 0000000..789ca71 --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/y/y.go
@@ -0,0 +1 @@ +package y
diff --git a/vendor/cmd/go/testdata/vendormod/z/go.mod b/vendor/cmd/go/testdata/vendormod/z/go.mod new file mode 100644 index 0000000..efc58fe --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/z/go.mod
@@ -0,0 +1 @@ +module z
diff --git a/vendor/cmd/go/testdata/vendormod/z/z.go b/vendor/cmd/go/testdata/vendormod/z/z.go new file mode 100644 index 0000000..46458cb --- /dev/null +++ b/vendor/cmd/go/testdata/vendormod/z/z.go
@@ -0,0 +1 @@ +package z
diff --git a/vendor/cmd/go/vgo_test.go b/vendor/cmd/go/vgo_test.go index 6d7032a..f559970 100644 --- a/vendor/cmd/go/vgo_test.go +++ b/vendor/cmd/go/vgo_test.go
@@ -221,6 +221,29 @@ tg.grepStderr("tcp.*nonexistent.rsc.io", "expected error for nonexistent.rsc.io") } +func TestVgoVendor(t *testing.T) { + tg := testgo(t) + defer tg.cleanup() + + wd, _ := os.Getwd() + tg.cd(filepath.Join(wd, "testdata/vendormod")) + tg.run("-vgo", "list", "-m") + tg.grepStdout(`^x`, "expected to see module x") + tg.grepStdout(`=> ./x`, "expected to see replacement for module x") + tg.grepStdout(`^w`, "expected to see module w") + + tg.run("-vgo", "vendor", "-v") + tg.grepStderr(`^# x v1.0.0 => ./x`, "expected to see module x with replacement") + tg.grepStderr(`^x`, "expected to see package x") + tg.grepStderr(`^# y v1.0.0 => ./y`, "expected to see module y with replacement") + tg.grepStderr(`^y`, "expected to see package y") + tg.grepStderr(`^# z v1.0.0 => ./z`, "expected to see module z with replacement") + tg.grepStderr(`^z`, "expected to see package z") + tg.grepStderrNot(`w`, "expected NOT to see unused module w") + + tg.must(os.RemoveAll(filepath.Join(wd, "testdata/vendormod/vendor"))) +} + func TestFillGoMod(t *testing.T) { testenv.MustHaveExternalNetwork(t) tg := testgo(t)