diff --git a/go/buildutil/allpackages.go b/go/buildutil/allpackages.go index bee5fc17..4d4366c1 100644 --- a/go/buildutil/allpackages.go +++ b/go/buildutil/allpackages.go @@ -123,3 +123,73 @@ func allPackages(ctxt *build.Context, root string, ch chan<- item) { walkDir(root) wg.Wait() } + +// ExpandPatterns returns the set of packages matched by patterns, +// which may have the following forms: +// +// golang.org/x/tools/cmd/guru # a single package +// golang.org/x/tools/... # all packages beneath dir +// ... # the entire workspace. +// +// Order is significant: a pattern preceded by '-' removes matching +// packages from the set. For example, these patterns match all encoding +// packages except encoding/xml: +// +// encoding/... -encoding/xml +// +func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool { + // TODO(adonovan): support other features of 'go list': + // - "std"/"cmd"/"all" meta-packages + // - "..." not at the end of a pattern + // - relative patterns using "./" or "../" prefix + + pkgs := make(map[string]bool) + doPkg := func(pkg string, neg bool) { + if neg { + delete(pkgs, pkg) + } else { + pkgs[pkg] = true + } + } + + // Scan entire workspace if wildcards are present. + // TODO(adonovan): opt: scan only the necessary subtrees of the workspace. + var all []string + for _, arg := range patterns { + if strings.HasSuffix(arg, "...") { + all = AllPackages(ctxt) + break + } + } + + for _, arg := range patterns { + if arg == "" { + continue + } + + neg := arg[0] == '-' + if neg { + arg = arg[1:] + } + + if arg == "..." { + // ... matches all packages + for _, pkg := range all { + doPkg(pkg, neg) + } + } else if dir := strings.TrimSuffix(arg, "/..."); dir != arg { + // dir/... matches all packages beneath dir + for _, pkg := range all { + if strings.HasPrefix(pkg, dir) && + (len(pkg) == len(dir) || pkg[len(dir)] == '/') { + doPkg(pkg, neg) + } + } + } else { + // single package + doPkg(arg, neg) + } + } + + return pkgs +} diff --git a/go/buildutil/allpackages_test.go b/go/buildutil/allpackages_test.go index d5bd964b..96bf77a6 100644 --- a/go/buildutil/allpackages_test.go +++ b/go/buildutil/allpackages_test.go @@ -10,6 +10,8 @@ package buildutil_test import ( "go/build" + "sort" + "strings" "testing" "golang.org/x/tools/go/buildutil" @@ -34,3 +36,41 @@ func TestAllPackages(t *testing.T) { } } } + +func TestExpandPatterns(t *testing.T) { + tree := make(map[string]map[string]string) + for _, pkg := range []string{ + "encoding", + "encoding/xml", + "encoding/hex", + "encoding/json", + "fmt", + } { + tree[pkg] = make(map[string]string) + } + ctxt := buildutil.FakeContext(tree) + + for _, test := range []struct { + patterns string + want string + }{ + {"", ""}, + {"fmt", "fmt"}, + {"nosuchpkg", "nosuchpkg"}, + {"nosuchdir/...", ""}, + {"...", "encoding encoding/hex encoding/json encoding/xml fmt"}, + {"encoding/... -encoding/xml", "encoding encoding/hex encoding/json"}, + {"... -encoding/...", "fmt"}, + } { + var pkgs []string + for pkg := range buildutil.ExpandPatterns(ctxt, strings.Fields(test.patterns)) { + pkgs = append(pkgs, pkg) + } + sort.Strings(pkgs) + got := strings.Join(pkgs, " ") + if got != test.want { + t.Errorf("ExpandPatterns(%s) = %s, want %s", + test.patterns, got, test.want) + } + } +}