mirror of
https://github.com/golang/go
synced 2024-11-18 19:54:44 -07:00
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 <matloob@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
bfb5194568
commit
9dbe528bbb
@ -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...)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user