Commit fa1d54c2 authored by Russ Cox's avatar Russ Cox

cmd/go: exclude vendored packages from ... matches

By overwhelming popular demand, exclude vendored packages from ... matches,
by making ... never match the "vendor" element above a vendored package.

go help packages now reads:

    An import path is a pattern if it includes one or more "..." wildcards,
    each of which can match any string, including the empty string and
    strings containing slashes.  Such a pattern expands to all package
    directories found in the GOPATH trees with names matching the
    patterns.

    To make common patterns more convenient, there are two special cases.
    First, /... at the end of the pattern can match an empty string,
    so that net/... matches both net and packages in its subdirectories, like net/http.
    Second, any slash-separted pattern element containing a wildcard never
    participates in a match of the "vendor" element in the path of a vendored
    package, so that ./... does not match packages in subdirectories of
    ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
    Note, however, that a directory named vendor that itself contains code
    is not a vendored package: cmd/vendor would be a command named vendor,
    and the pattern cmd/... matches it.

Fixes #19090.

Change-Id: I985bf9571100da316c19fbfd19bb1e534a3c9e5f
Reviewed-on: https://go-review.googlesource.com/38745Reviewed-by: default avatarAlan Donovan <adonovan@google.com>
parent 68da265c
...@@ -316,7 +316,7 @@ ...@@ -316,7 +316,7 @@
// //
// Usage: // Usage:
// //
// go env [var ...] // go env [-json] [var ...]
// //
// Env prints Go environment information. // Env prints Go environment information.
// //
...@@ -325,6 +325,9 @@ ...@@ -325,6 +325,9 @@
// names is given as arguments, env prints the value of // names is given as arguments, env prints the value of
// each named variable on its own line. // each named variable on its own line.
// //
// The -json flag prints the environment in JSON format
// instead of as a shell script.
//
// //
// Start a bug report // Start a bug report
// //
...@@ -1361,8 +1364,19 @@ ...@@ -1361,8 +1364,19 @@
// each of which can match any string, including the empty string and // each of which can match any string, including the empty string and
// strings containing slashes. Such a pattern expands to all package // strings containing slashes. Such a pattern expands to all package
// directories found in the GOPATH trees with names matching the // directories found in the GOPATH trees with names matching the
// patterns. As a special case, x/... matches x as well as x's subdirectories. // patterns.
// For example, net/... expands to net and packages in its subdirectories. //
// To make common patterns more convenient, there are two special cases.
// First, /... at the end of the pattern can match an empty string,
// so that net/... matches both net and packages in its subdirectories, like net/http.
// Second, any slash-separated pattern element containing a wildcard never
// participates in a match of the "vendor" element in the path of a vendored
// package, so that ./... does not match packages in subdirectories of
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
// Note, however, that a directory named vendor that itself contains code
// is not a vendored package: cmd/vendor would be a command named vendor,
// and the pattern cmd/... matches it.
// See golang.org/s/go15vendor for more about vendoring.
// //
// An import path can also name a package to be downloaded from // An import path can also name a package to be downloaded from
// a remote repository. Run 'go help importpath' for details. // a remote repository. Run 'go help importpath' for details.
......
...@@ -71,8 +71,19 @@ An import path is a pattern if it includes one or more "..." wildcards, ...@@ -71,8 +71,19 @@ An import path is a pattern if it includes one or more "..." wildcards,
each of which can match any string, including the empty string and each of which can match any string, including the empty string and
strings containing slashes. Such a pattern expands to all package strings containing slashes. Such a pattern expands to all package
directories found in the GOPATH trees with names matching the directories found in the GOPATH trees with names matching the
patterns. As a special case, x/... matches x as well as x's subdirectories. patterns.
For example, net/... expands to net and packages in its subdirectories.
To make common patterns more convenient, there are two special cases.
First, /... at the end of the pattern can match an empty string,
so that net/... matches both net and packages in its subdirectories, like net/http.
Second, any slash-separated pattern element containing a wildcard never
participates in a match of the "vendor" element in the path of a vendored
package, so that ./... does not match packages in subdirectories of
./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
Note, however, that a directory named vendor that itself contains code
is not a vendored package: cmd/vendor would be a command named vendor,
and the pattern cmd/... matches it.
See golang.org/s/go15vendor for more about vendoring.
An import path can also name a package to be downloaded from An import path can also name a package to be downloaded from
a remote repository. Run 'go help importpath' for details. a remote repository. Run 'go help importpath' for details.
......
...@@ -25,9 +25,43 @@ var matchPatternTests = ` ...@@ -25,9 +25,43 @@ var matchPatternTests = `
match net net/http netchan match net net/http netchan
not not/http not/net/http not not/http not/net/http
# Special cases. Quoting docs:
# First, /... at the end of the pattern can match an empty string,
# so that net/... matches both net and packages in its subdirectories, like net/http.
pattern net/... pattern net/...
match net net/http match net net/http
not not/http not/net/http netchan not not/http not/net/http netchan
# Second, any slash-separted pattern element containing a wildcard never
# participates in a match of the "vendor" element in the path of a vendored
# package, so that ./... does not match packages in subdirectories of
# ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
# Note, however, that a directory named vendor that itself contains code
# is not a vendored package: cmd/vendor would be a command named vendor,
# and the pattern cmd/... matches it.
pattern ./...
match ./vendor ./mycode/vendor
not ./vendor/foo ./mycode/vendor/foo
pattern ./vendor/...
match ./vendor/foo ./vendor/foo/vendor
not ./vendor/foo/vendor/bar
pattern mycode/vendor/...
match mycode/vendor mycode/vendor/foo mycode/vendor/foo/vendor
not mycode/vendor/foo/vendor/bar
pattern x/vendor/y
match x/vendor/y
not x/vendor
pattern x/vendor/y/...
match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor
not x/vendor/y/vendor/z
pattern .../vendor/...
match x/vendor/y x/vendor/y/z x/vendor/y/vendor x/vendor/y/z/vendor
` `
func TestMatchPattern(t *testing.T) { func TestMatchPattern(t *testing.T) {
......
...@@ -202,17 +202,69 @@ func treeCanMatchPattern(pattern string) func(name string) bool { ...@@ -202,17 +202,69 @@ func treeCanMatchPattern(pattern string) func(name string) bool {
// name matches pattern. Pattern is a limited glob // name matches pattern. Pattern is a limited glob
// pattern in which '...' means 'any string' and there // pattern in which '...' means 'any string' and there
// is no other special syntax. // is no other special syntax.
// Unfortunately, there are two special cases. Quoting "go help packages":
//
// First, /... at the end of the pattern can match an empty string,
// so that net/... matches both net and packages in its subdirectories, like net/http.
// Second, any slash-separted pattern element containing a wildcard never
// participates in a match of the "vendor" element in the path of a vendored
// package, so that ./... does not match packages in subdirectories of
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
// Note, however, that a directory named vendor that itself contains code
// is not a vendored package: cmd/vendor would be a command named vendor,
// and the pattern cmd/... matches it.
func matchPattern(pattern string) func(name string) bool { func matchPattern(pattern string) func(name string) bool {
re := regexp.QuoteMeta(pattern) // Convert pattern to regular expression.
re = strings.Replace(re, `\.\.\.`, `.*`, -1) // The strategy for the trailing /... is to nest it in an explicit ? expression.
// Special case: foo/... matches foo too. // The strategy for the vendor exclusion is to change the unmatchable
if strings.HasSuffix(re, `/.*`) { // vendor strings to a disallowed code point (vendorChar) and to use
re = re[:len(re)-len(`/.*`)] + `(/.*)?` // "(anything but that codepoint)*" as the implementation of the ... wildcard.
// This is a bit complicated but the obvious alternative,
// namely a hand-written search like in most shell glob matchers,
// is too easy to make accidentally exponential.
// Using package regexp guarantees linear-time matching.
const vendorChar = "\x00"
if strings.Contains(pattern, vendorChar) {
return func(name string) bool { return false }
} }
re := regexp.QuoteMeta(pattern)
re = replaceVendor(re, vendorChar)
switch {
case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
case re == vendorChar+`/\.\.\.`:
re = `(/vendor|/` + vendorChar + `/\.\.\.)`
case strings.HasSuffix(re, `/\.\.\.`):
re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
}
re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
reg := regexp.MustCompile(`^` + re + `$`) reg := regexp.MustCompile(`^` + re + `$`)
return func(name string) bool { return func(name string) bool {
return reg.MatchString(name) if strings.Contains(name, vendorChar) {
return false
}
return reg.MatchString(replaceVendor(name, vendorChar))
}
}
// replaceVendor returns the result of replacing
// non-trailing vendor path elements in x with repl.
func replaceVendor(x, repl string) string {
if !strings.Contains(x, "vendor") {
return x
}
elem := strings.Split(x, "/")
for i := 0; i < len(elem)-1; i++ {
if elem[i] == "vendor" {
elem[i] = repl
}
} }
return strings.Join(elem, "/")
} }
// ImportPaths returns the import paths to use for the given command line. // ImportPaths returns the import paths to use for the given command line.
......
...@@ -20,18 +20,18 @@ func TestVendorImports(t *testing.T) { ...@@ -20,18 +20,18 @@ func TestVendorImports(t *testing.T) {
tg := testgo(t) tg := testgo(t)
defer tg.cleanup() defer tg.cleanup()
tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata")) tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata"))
tg.run("list", "-f", "{{.ImportPath}} {{.Imports}}", "vend/...") tg.run("list", "-f", "{{.ImportPath}} {{.Imports}}", "vend/...", "vend/vendor/...", "vend/x/vendor/...")
want := ` want := `
vend [vend/vendor/p r] vend [vend/vendor/p r]
vend/dir1 [] vend/dir1 []
vend/hello [fmt vend/vendor/strings] vend/hello [fmt vend/vendor/strings]
vend/subdir [vend/vendor/p r] vend/subdir [vend/vendor/p r]
vend/x [vend/x/vendor/p vend/vendor/q vend/x/vendor/r vend/dir1 vend/vendor/vend/dir1/dir2]
vend/x/invalid [vend/x/invalid/vendor/foo]
vend/vendor/p [] vend/vendor/p []
vend/vendor/q [] vend/vendor/q []
vend/vendor/strings [] vend/vendor/strings []
vend/vendor/vend/dir1/dir2 [] vend/vendor/vend/dir1/dir2 []
vend/x [vend/x/vendor/p vend/vendor/q vend/x/vendor/r vend/dir1 vend/vendor/vend/dir1/dir2]
vend/x/invalid [vend/x/invalid/vendor/foo]
vend/x/vendor/p [] vend/x/vendor/p []
vend/x/vendor/p/p [notfound] vend/x/vendor/p/p [notfound]
vend/x/vendor/r [] vend/x/vendor/r []
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment