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)