From 9dbe528bbb9ffa4b83eb2b813c08f5b07c86c286 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Wed, 25 Jul 2018 16:40:23 -0400 Subject: [PATCH] go/packages: add support for 'contains:' words If a 'word' provided to go/packages' Load function starts with contains, go/packages will interpret that word as the package containing the given file. For example: packages.Load(config, "contains:/usr/local/go/src/fmt/format.go") would load the fmt package from the Go installation at /usr/local/go. This implementation uses "go list ." in the directory the file is contained in to find the package, but this won't work in the module cache. We plan to add support to go list directly to help find the containing package. Then, because we won't need to change directory, go list will have knowledge of the correct vgo root module, and will be able to surface correct results. Change-Id: I6bff62447c12f13dae5e4c0c65f729d9f271c388 Reviewed-on: https://go-review.googlesource.com/126177 Run-TryBot: Michael Matloob TryBot-Result: Gobot Gobot Reviewed-by: Ian Cottrell --- go/packages/packages.go | 70 ++++++++++++++++++++++++++++++++++-- go/packages/packages_test.go | 25 +++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/go/packages/packages.go b/go/packages/packages.go index 5a42be1f..c124f7b6 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -18,6 +18,8 @@ import ( "sync" "golang.org/x/tools/go/gcexportdata" + "path/filepath" + "strings" ) // A LoadMode specifies the amount of detail to return when loading packages. @@ -231,6 +233,12 @@ func newLoader(cfg *Config) *loader { if ld.Context == nil { ld.Context = context.Background() } + // Determine directory to be used for relative contains: paths. + if ld.Dir == "" { + if cwd, err := os.Getwd(); err == nil { + ld.Dir = cwd + } + } if ld.Mode >= LoadSyntax { if ld.Fset == nil { ld.Fset = token.NewFileSet() @@ -257,21 +265,79 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { return nil, fmt.Errorf("no packages to load") } + if ld.Dir == "" { + return nil, fmt.Errorf("failed to get working directory") + } + + // Determine files requested in contains patterns + var containFiles []string + { + restPatterns := patterns[:0] + for _, pattern := range patterns { + if containFile := strings.TrimPrefix(pattern, "contains:"); containFile != pattern { + containFiles = append(containFiles, containFile) + } else { + restPatterns = append(restPatterns, pattern) + } + } + containFiles = absJoin(ld.Dir, containFiles) + patterns = restPatterns + } + // Do the metadata query and partial build. // TODO(adonovan): support alternative build systems at this seam. rawCfg := newRawConfig(&ld.Config) - list, err := golistPackages(rawCfg, patterns...) + listfunc := golistPackages + // TODO(matloob): Patterns may now be empty, if it was solely comprised of contains: patterns. + // See if the extra process invocation can be avoided. + list, err := listfunc(rawCfg, patterns...) if _, ok := err.(GoTooOldError); ok { if ld.Config.Mode >= LoadTypes { // Upgrade to LoadAllSyntax because we can't depend on the existance // of export data. We can remove this once iancottrell's cl is in. ld.Config.Mode = LoadAllSyntax } - list, err = golistPackagesFallback(rawCfg, patterns...) + listfunc = golistPackagesFallback + list, err = listfunc(rawCfg, patterns...) } if err != nil { return nil, err } + + // Run go list for contains: patterns. + seenPkgs := make(map[string]bool) // for deduplication. different containing queries could produce same packages + if len(containFiles) > 0 { + for _, pkg := range list { + seenPkgs[pkg.ID] = true + } + } + for _, f := range containFiles { + // TODO(matloob): Do only one query per directory. + fdir := filepath.Dir(f) + rawCfg.Dir = fdir + cList, err := listfunc(rawCfg, ".") + if err != nil { + return nil, err + } + // Deduplicate and set deplist to set of packages requested files. + dedupedList := cList[:0] // invariant: only packages that haven't been seen before + for _, pkg := range cList { + if seenPkgs[pkg.ID] { + continue + } + seenPkgs[pkg.ID] = true + dedupedList = append(dedupedList, pkg) + pkg.DepOnly = true + for _, pkgFile := range pkg.GoFiles { + if filepath.Base(f) == filepath.Base(pkgFile) { + pkg.DepOnly = false + break + } + } + } + list = append(list, dedupedList...) + } + return ld.loadFrom(list...) } diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index d475f710..3a63908d 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -732,6 +732,31 @@ func TestAbsoluteFilenames(t *testing.T) { } } +func TestContains(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"`, + "src/c/c.go": `package c`, + }) + defer cleanup() + + opts := &packages.Config{Env: append(os.Environ(), "GOPATH="+tmp), Dir: tmp, Mode: packages.LoadImports} + initial, err := packages.Load(opts, "contains:src/b/b.go") + if err != nil { + t.Fatal(err) + } + + graph, _ := importGraph(initial) + wantGraph := ` +* b + c + b -> c +`[1:] + if graph != wantGraph { + t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph) + } +} + func errorMessages(errors []error) []string { var msgs []string for _, err := range errors {