go/packages: use "=" instead of ":" for special queries
This change updates the language accepted through the arguments
to packages.Load to make it more consistent. There are now two layers.
A pattern containing an "=" is considered to be a special query, and the
part of the pattern up to the first "=" is considered the query type. All
other patterns are to be interpreted as the build system interprets it.
For now two special queries exist. file= has the behavior that contains:
did: finding packages containing the given file. The query type pattern=
is used to pass through a pattern to be interpreted by the build system.
pattern= is a type of escaping to allow passing through patterns that
contain an "=" to be interpreted by the underlying buildsystem. To allow
for new query types to be introduced, packages.Load will report an
error if the qury type is not understood. We expect name= to be added in
an upcoming change.
"contains:" changes to "file=". A new
Change-Id: I1b208d1c998c67d5556cdc872d7694273cedb7e4
Reviewed-on: https://go-review.googlesource.com/c/141681
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/go/packages/doc.go b/go/packages/doc.go
index 1240974..61933de 100644
--- a/go/packages/doc.go
+++ b/go/packages/doc.go
@@ -15,9 +15,29 @@
structs describing individual packages matched by those patterns.
The LoadMode controls the amount of detail in the loaded packages.
-The patterns are used as arguments to the underlying build tool,
-such as the go command or Bazel, and are interpreted according to
-that tool's conventions.
+Load passes most patterns directly to the underlying build tool,
+but all patterns with the prefix "query=", where query is a
+non-empty string of letters from [a-z], are reserved and may be
+interpreted as query operators.
+
+Only two query operators are currently supported, "file" and "pattern".
+
+The query "file=path/to/file.go" matches the package or packages enclosing
+the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
+might returns the packages "fmt" and "fmt [fmt.test]".
+
+The query "pattern=string" causes "string" to be passed directly to
+the underlying build tool. In most cases this is unnecessary,
+but an application can use Load("pattern=" + x) as an escaping mechanism
+to ensure that x is not interpreted as a query operator if it contains '='.
+
+A third query "name=identifier" will be added soon.
+It will match packages whose package declaration contains the specified identifier.
+For example, "name=rand" would match the packages "math/rand" and "crypto/rand",
+and "name=main" would match all executables.
+
+All other query operators are reserved for future use and currently
+cause Load to report an error.
The Package struct provides basic information about the package, including
diff --git a/go/packages/golist.go b/go/packages/golist.go
index 8f86b42..ec17b8a 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -28,6 +28,38 @@
// Determine files requested in contains patterns
var containFiles []string
restPatterns := make([]string, 0, len(patterns))
+ // Extract file= and other [querytype]= patterns. Report an error if querytype
+ // doesn't exist.
+extractQueries:
+ for _, pattern := range patterns {
+ eqidx := strings.Index(pattern, "=")
+ if eqidx < 0 {
+ restPatterns = append(restPatterns, pattern)
+ } else {
+ query, value := pattern[:eqidx], pattern[eqidx+len("="):]
+ switch query {
+ case "file":
+ containFiles = append(containFiles, value)
+ case "pattern":
+ restPatterns = append(containFiles, value)
+ case "": // not a reserved query
+ restPatterns = append(restPatterns, pattern)
+ default:
+ for _, rune := range query {
+ if rune < 'a' || rune > 'z' { // not a reserved query
+ restPatterns = append(restPatterns, pattern)
+ continue extractQueries
+ }
+ }
+ // Reject all other patterns containing "="
+ return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
+ }
+ }
+ }
+ patterns = restPatterns
+ // Look for the deprecated contains: syntax.
+ // TODO(matloob): delete this around mid-October 2018.
+ restPatterns = restPatterns[:0]
for _, pattern := range patterns {
if strings.HasPrefix(pattern, "contains:") {
containFile := strings.TrimPrefix(pattern, "contains:")
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index f5e99ba..3c996ae 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -1013,7 +1013,7 @@
}
}
-func TestContains(t *testing.T) {
+func TestFile(t *testing.T) {
tmp, cleanup := makeTree(t, map[string]string{
"src/a/a.go": `package a; import "b"`,
"src/b/b.go": `package b; import "c"`,
@@ -1026,7 +1026,7 @@
Dir: tmp,
Env: append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
}
- initial, err := packages.Load(cfg, "contains:src/b/b.go")
+ initial, err := packages.Load(cfg, "file=src/b/b.go")
if err != nil {
t.Fatal(err)
}
@@ -1093,7 +1093,7 @@
Dir: tmp,
Env: append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
}
- initial, err := packages.Load(cfg, "a", "contains:src/b/b.go")
+ initial, err := packages.Load(cfg, "a", "file=src/b/b.go")
if err != nil {
t.Fatal(err)
}
@@ -1261,6 +1261,46 @@
}
}
+func TestRejectInvalidQueries(t *testing.T) {
+ queries := []string{"=", "key=", "key=value", "file/a/b=c/..."}
+ cfg := &packages.Config{
+ Mode: packages.LoadImports,
+ Env: append(os.Environ(), "GO111MODULE=off"),
+ }
+ for _, q := range queries {
+ if _, err := packages.Load(cfg, q); err == nil {
+ t.Errorf("packages.Load(%q) succeeded. Expected \"invalid query type\" error", q)
+ } else if !strings.Contains(err.Error(), "invalid query type") {
+ t.Errorf("packages.Load(%q): got error %v, want \"invalid query type\" error", q, err)
+ }
+ }
+}
+
+func TestPatternPassthrough(t *testing.T) {
+ tmp, cleanup := makeTree(t, map[string]string{
+ "src/a/a.go": `package a;`,
+ })
+ defer cleanup()
+
+ cfg := &packages.Config{
+ Mode: packages.LoadImports,
+ Env: append(os.Environ(), "GOPATH="+tmp, "GO111MODULE=off"),
+ }
+ initial, err := packages.Load(cfg, "pattern=a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, _ := importGraph(initial)
+ wantGraph := `
+* a
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+}
+
func TestConfigDefaultEnv(t *testing.T) {
if runtime.GOOS == "windows" {
// TODO(jayconrod): write an equivalent batch script for windows.