go/packages: do not error out for patterns that match no packages
The documentation for Load says: “Load returns an error if any of the patterns was invalid as defined by the underlying build system. It may return an empty list of packages without an error, for instance for an empty expansion of a valid wildcard. Errors associated with a particular package are recorded in the corresponding Package's Errors list, and do not cause Load to return an error.” Therefore, it should not be an error for a pattern to match no packages. If the pattern is a literal package path that does not exist, we should prefer to return a *Package for it with an error in the Errors field. Change-Id: Iaecfb920097e3b520e763bd52c0e326d2e7a4861 Reviewed-on: https://go-review.googlesource.com/137075 Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
e9ca907325
commit
84988e2dba
|
@ -16,7 +16,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// findExternalTool returns the file path of a tool that supplies supplies
|
// findExternalTool returns the file path of a tool that supplies
|
||||||
// the build system package structure, or "" if not found."
|
// the build system package structure, or "" if not found."
|
||||||
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
|
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
|
||||||
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
|
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
|
||||||
|
|
|
@ -136,6 +136,14 @@ type jsonPackage struct {
|
||||||
XTestImports []string
|
XTestImports []string
|
||||||
ForTest string // q in a "p [q.test]" package, else ""
|
ForTest string // q in a "p [q.test]" package, else ""
|
||||||
DepOnly bool
|
DepOnly bool
|
||||||
|
|
||||||
|
Error *jsonPackageError
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonPackageError struct {
|
||||||
|
ImportStack []string
|
||||||
|
Pos string
|
||||||
|
Err string
|
||||||
}
|
}
|
||||||
|
|
||||||
func otherFiles(p *jsonPackage) [][]string {
|
func otherFiles(p *jsonPackage) [][]string {
|
||||||
|
@ -171,49 +179,48 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
|
||||||
return nil, fmt.Errorf("JSON decoding failed: %v", err)
|
return nil, fmt.Errorf("JSON decoding failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bad package?
|
if p.ImportPath == "" {
|
||||||
if p.Name == "" {
|
// The documentation for go list says that “[e]rroneous packages will have
|
||||||
// This could be due to:
|
// a non-empty ImportPath”. If for some reason it comes back empty, we
|
||||||
// - no such package
|
// prefer to error out rather than silently discarding data or handing
|
||||||
// - package directory contains no Go source files
|
// back a package without any way to refer to it.
|
||||||
// - all package declarations are mangled
|
if p.Error != nil {
|
||||||
// - and possibly other things.
|
return nil, Error{
|
||||||
//
|
Pos: p.Error.Pos,
|
||||||
// For now, we throw it away and let later
|
Msg: p.Error.Err,
|
||||||
// stages rediscover the problem, but this
|
}
|
||||||
// discards the error message computed by go list
|
}
|
||||||
// and computes a new one---by different logic:
|
return nil, fmt.Errorf("package missing import path: %+v", p)
|
||||||
// if only one of the package declarations is
|
|
||||||
// bad, for example, should we report an error
|
|
||||||
// in Metadata mode?
|
|
||||||
// Unless we parse and typecheck, we might not
|
|
||||||
// notice there's a problem.
|
|
||||||
//
|
|
||||||
// Perhaps we should save a map of PackageID to
|
|
||||||
// errors for such cases.
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id := p.ImportPath
|
pkg := &Package{
|
||||||
|
Name: p.Name,
|
||||||
|
ID: p.ImportPath,
|
||||||
|
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
|
||||||
|
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
|
||||||
|
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the PkgPath from the package's ID.
|
// Extract the PkgPath from the package's ID.
|
||||||
pkgpath := id
|
if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
|
||||||
if i := strings.IndexByte(id, ' '); i >= 0 {
|
pkg.PkgPath = pkg.ID[:i]
|
||||||
pkgpath = id[:i]
|
} else {
|
||||||
|
pkg.PkgPath = pkg.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkgpath == "unsafe" {
|
if pkg.PkgPath == "unsafe" {
|
||||||
p.GoFiles = nil // ignore fake unsafe.go file
|
pkg.GoFiles = nil // ignore fake unsafe.go file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume go list emits only absolute paths for Dir.
|
// Assume go list emits only absolute paths for Dir.
|
||||||
if !filepath.IsAbs(p.Dir) {
|
if p.Dir != "" && !filepath.IsAbs(p.Dir) {
|
||||||
log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
|
log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
export := p.Export
|
if p.Export != "" && !filepath.IsAbs(p.Export) {
|
||||||
if export != "" && !filepath.IsAbs(export) {
|
pkg.ExportFile = filepath.Join(p.Dir, p.Export)
|
||||||
export = filepath.Join(p.Dir, export)
|
} else {
|
||||||
|
pkg.ExportFile = p.Export
|
||||||
}
|
}
|
||||||
|
|
||||||
// imports
|
// imports
|
||||||
|
@ -224,9 +231,9 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
|
||||||
for _, id := range p.Imports {
|
for _, id := range p.Imports {
|
||||||
ids[id] = true
|
ids[id] = true
|
||||||
}
|
}
|
||||||
imports := make(map[string]*Package)
|
pkg.Imports = make(map[string]*Package)
|
||||||
for path, id := range p.ImportMap {
|
for path, id := range p.ImportMap {
|
||||||
imports[path] = &Package{ID: id} // non-identity import
|
pkg.Imports[path] = &Package{ID: id} // non-identity import
|
||||||
delete(ids, id)
|
delete(ids, id)
|
||||||
}
|
}
|
||||||
for id := range ids {
|
for id := range ids {
|
||||||
|
@ -234,25 +241,24 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
imports[id] = &Package{ID: id} // identity import
|
pkg.Imports[id] = &Package{ID: id} // identity import
|
||||||
}
|
}
|
||||||
if !p.DepOnly {
|
if !p.DepOnly {
|
||||||
response.Roots = append(response.Roots, id)
|
response.Roots = append(response.Roots, pkg.ID)
|
||||||
}
|
|
||||||
pkg := &Package{
|
|
||||||
ID: id,
|
|
||||||
Name: p.Name,
|
|
||||||
PkgPath: pkgpath,
|
|
||||||
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
|
|
||||||
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
|
|
||||||
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
|
|
||||||
Imports: imports,
|
|
||||||
ExportFile: export,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(matloob): Temporary hack since CompiledGoFiles isn't always set.
|
// TODO(matloob): Temporary hack since CompiledGoFiles isn't always set.
|
||||||
if len(pkg.CompiledGoFiles) == 0 {
|
if len(pkg.CompiledGoFiles) == 0 {
|
||||||
pkg.CompiledGoFiles = pkg.GoFiles
|
pkg.CompiledGoFiles = pkg.GoFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.Error != nil {
|
||||||
|
pkg.Errors = append(pkg.Errors, Error{
|
||||||
|
Pos: p.Error.Pos,
|
||||||
|
Msg: p.Error.Err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
response.Packages = append(response.Packages, pkg)
|
response.Packages = append(response.Packages, pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -379,9 +379,6 @@ func newLoader(cfg *Config) *loader {
|
||||||
// refine connects the supplied packages into a graph and then adds type and
|
// refine connects the supplied packages into a graph and then adds type and
|
||||||
// and syntax information as requested by the LoadMode.
|
// and syntax information as requested by the LoadMode.
|
||||||
func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
|
func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
|
||||||
if len(list) == 0 {
|
|
||||||
return nil, fmt.Errorf("packages not found")
|
|
||||||
}
|
|
||||||
isRoot := make(map[string]bool, len(roots))
|
isRoot := make(map[string]bool, len(roots))
|
||||||
for _, root := range roots {
|
for _, root := range roots {
|
||||||
isRoot[root] = true
|
isRoot[root] = true
|
||||||
|
|
|
@ -402,22 +402,23 @@ func TestConfigDir(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
dir string
|
dir string
|
||||||
pattern string
|
pattern string
|
||||||
want string // value of Name constant, or error
|
want string // value of Name constant
|
||||||
|
errContains string
|
||||||
}{
|
}{
|
||||||
{"", "a", `"a"`},
|
{pattern: "a", want: `"a"`},
|
||||||
{"", "b", `"b"`},
|
{pattern: "b", want: `"b"`},
|
||||||
{"", "./a", "packages not found"},
|
{pattern: "./a", errContains: "cannot find"},
|
||||||
{"", "./b", "packages not found"},
|
{pattern: "./b", errContains: "cannot find"},
|
||||||
{filepath.Join(tmp, "/src"), "a", `"a"`},
|
{dir: filepath.Join(tmp, "/src"), pattern: "a", want: `"a"`},
|
||||||
{filepath.Join(tmp, "/src"), "b", `"b"`},
|
{dir: filepath.Join(tmp, "/src"), pattern: "b", want: `"b"`},
|
||||||
{filepath.Join(tmp, "/src"), "./a", `"a"`},
|
{dir: filepath.Join(tmp, "/src"), pattern: "./a", want: `"a"`},
|
||||||
{filepath.Join(tmp, "/src"), "./b", `"b"`},
|
{dir: filepath.Join(tmp, "/src"), pattern: "./b", want: `"b"`},
|
||||||
{filepath.Join(tmp, "/src/a"), "a", `"a"`},
|
{dir: filepath.Join(tmp, "/src/a"), pattern: "a", want: `"a"`},
|
||||||
{filepath.Join(tmp, "/src/a"), "b", `"b"`},
|
{dir: filepath.Join(tmp, "/src/a"), pattern: "b", want: `"b"`},
|
||||||
{filepath.Join(tmp, "/src/a"), "./a", "packages not found"},
|
{dir: filepath.Join(tmp, "/src/a"), pattern: "./a", errContains: "cannot find"},
|
||||||
{filepath.Join(tmp, "/src/a"), "./b", `"a/b"`},
|
{dir: filepath.Join(tmp, "/src/a"), pattern: "./b", want: `"a/b"`},
|
||||||
} {
|
} {
|
||||||
cfg := &packages.Config{
|
cfg := &packages.Config{
|
||||||
Mode: packages.LoadSyntax, // Use LoadSyntax to ensure that files can be opened.
|
Mode: packages.LoadSyntax, // Use LoadSyntax to ensure that files can be opened.
|
||||||
|
@ -426,16 +427,24 @@ func TestConfigDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
initial, err := packages.Load(cfg, test.pattern)
|
initial, err := packages.Load(cfg, test.pattern)
|
||||||
var got string
|
var got, errMsg string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
got = err.Error()
|
errMsg = err.Error()
|
||||||
} else {
|
} else if len(initial) > 0 {
|
||||||
got = constant(initial[0], "Name").Val().String()
|
if len(initial[0].Errors) > 0 {
|
||||||
|
errMsg = initial[0].Errors[0].Error()
|
||||||
|
} else if c := constant(initial[0], "Name"); c != nil {
|
||||||
|
got = c.Val().String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if got != test.want {
|
if got != test.want {
|
||||||
t.Errorf("dir %q, pattern %q: got %s, want %s",
|
t.Errorf("dir %q, pattern %q: got %s, want %s",
|
||||||
test.dir, test.pattern, got, test.want)
|
test.dir, test.pattern, got, test.want)
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(errMsg, test.errContains) {
|
||||||
|
t.Errorf("dir %q, pattern %q: error %s, want %s",
|
||||||
|
test.dir, test.pattern, errMsg, test.errContains)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1427,5 +1436,9 @@ func makeTree(t *testing.T, tree map[string]string) (dir string, cleanup func())
|
||||||
}
|
}
|
||||||
|
|
||||||
func constant(p *packages.Package, name string) *types.Const {
|
func constant(p *packages.Package, name string) *types.Const {
|
||||||
return p.Types.Scope().Lookup(name).(*types.Const)
|
c := p.Types.Scope().Lookup(name)
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.(*types.Const)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue