mirror of
https://github.com/golang/go
synced 2024-11-19 02:14:43 -07:00
50fa39b762
Even if the packages.Load of the directory the NewView is being created for fails, create and add the view. But also return the error from NewView, just after the new view has been added. Fixes golang/go#35468 Change-Id: I76c2d3cbe1a508ad0794a6fcd3bc67cd48c97e22 Reviewed-on: https://go-review.googlesource.com/c/tools/+/206497 Run-TryBot: Michael Matloob <matloob@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
252 lines
7.1 KiB
Go
252 lines
7.1 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/lsp/telemetry"
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
"golang.org/x/tools/internal/telemetry/tag"
|
|
"golang.org/x/tools/internal/telemetry/trace"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
type metadata struct {
|
|
id packageID
|
|
pkgPath packagePath
|
|
name string
|
|
files []span.URI
|
|
typesSizes types.Sizes
|
|
errors []packages.Error
|
|
deps []packageID
|
|
missingDeps map[packagePath]struct{}
|
|
|
|
// config is the *packages.Config associated with the loaded package.
|
|
config *packages.Config
|
|
}
|
|
|
|
var errNoPackagesFound = errors.New("no packages found for query")
|
|
|
|
func (s *snapshot) load(ctx context.Context, scope source.Scope) ([]*metadata, error) {
|
|
uri := scope.URI()
|
|
var query string
|
|
switch scope.(type) {
|
|
case source.FileURI:
|
|
query = fmt.Sprintf("file=%s", scope.URI().Filename())
|
|
case source.DirectoryURI:
|
|
query = fmt.Sprintf("%s/...", scope.URI().Filename())
|
|
// Simplify the query if it will be run in the requested directory.
|
|
// This ensures compatibility with Go 1.12 that doesn't allow
|
|
// <directory>/... in GOPATH mode.
|
|
if s.view.folder.Filename() == scope.URI().Filename() {
|
|
query = "./..."
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("unsupported scope type %T", scope))
|
|
}
|
|
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri))
|
|
defer done()
|
|
|
|
cfg := s.view.Config(ctx)
|
|
pkgs, err := packages.Load(cfg, query)
|
|
|
|
// If the context was canceled, return early.
|
|
// Otherwise, we might be type-checking an incomplete result.
|
|
if err == context.Canceled {
|
|
return nil, errors.Errorf("no metadata for %s: %v", uri, err)
|
|
}
|
|
log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs)))
|
|
if len(pkgs) == 0 {
|
|
if err == nil {
|
|
err = errNoPackagesFound
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m, prevMissingImports, err := s.updateMetadata(ctx, scope, pkgs, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
meta, err := validateMetadata(ctx, m, prevMissingImports)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return meta, nil
|
|
}
|
|
|
|
func validateMetadata(ctx context.Context, metadata []*metadata, prevMissingImports map[packageID]map[packagePath]struct{}) ([]*metadata, error) {
|
|
// If we saw incorrect metadata for this package previously, don't bother rechecking it.
|
|
for _, m := range metadata {
|
|
if len(m.missingDeps) > 0 {
|
|
prev, ok := prevMissingImports[m.id]
|
|
// There are missing imports that we previously hadn't seen before.
|
|
if !ok {
|
|
return metadata, nil
|
|
}
|
|
// The set of missing imports has changed.
|
|
if !sameSet(prev, m.missingDeps) {
|
|
return metadata, nil
|
|
}
|
|
} else {
|
|
// There are no missing imports.
|
|
return metadata, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func sameSet(x, y map[packagePath]struct{}) bool {
|
|
if len(x) != len(y) {
|
|
return false
|
|
}
|
|
for k := range x {
|
|
if _, ok := y[k]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// shouldLoad reparses a file's package and import declarations to
|
|
// determine if they have changed.
|
|
func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, currentFH source.FileHandle) bool {
|
|
if originalFH == nil {
|
|
return true
|
|
}
|
|
|
|
// Get the original and current parsed files in order to check package name and imports.
|
|
original, _, _, originalErr := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
|
|
current, _, _, currentErr := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
|
|
if originalErr != nil || currentErr != nil {
|
|
return (originalErr == nil) != (currentErr == nil)
|
|
}
|
|
|
|
// Check if the package's metadata has changed. The cases handled are:
|
|
//
|
|
// 1. A package's name has changed
|
|
// 2. A file's imports have changed
|
|
//
|
|
if original.Name.Name != current.Name.Name {
|
|
return true
|
|
}
|
|
// If the package's imports have changed, re-run `go list`.
|
|
if len(original.Imports) != len(current.Imports) {
|
|
return true
|
|
}
|
|
for i, importSpec := range original.Imports {
|
|
// TODO: Handle the case where the imports have just been re-ordered.
|
|
if importSpec.Path.Value != current.Imports[i].Path.Value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *snapshot) updateMetadata(ctx context.Context, uri source.Scope, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
|
|
// Clear metadata since we are re-running go/packages.
|
|
var m []*metadata
|
|
switch uri.(type) {
|
|
case source.FileURI:
|
|
m = s.getMetadataForURI(uri.URI())
|
|
case source.DirectoryURI:
|
|
for _, pkg := range pkgs {
|
|
if pkgMetadata := s.getMetadata(packageID(pkg.ID)); pkgMetadata != nil {
|
|
m = append(m, pkgMetadata)
|
|
}
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("unsupported Scope type %T", uri))
|
|
}
|
|
prevMissingImports := make(map[packageID]map[packagePath]struct{})
|
|
for _, m := range m {
|
|
if len(m.missingDeps) > 0 {
|
|
prevMissingImports[m.id] = m.missingDeps
|
|
}
|
|
}
|
|
|
|
var results []*metadata
|
|
for _, pkg := range pkgs {
|
|
log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
|
|
|
|
// Set the metadata for this package.
|
|
if err := s.updateImports(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{}); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
m := s.getMetadata(packageID(pkg.ID))
|
|
if m != nil {
|
|
results = append(results, m)
|
|
}
|
|
}
|
|
|
|
// Rebuild the import graph when the metadata is updated.
|
|
s.clearAndRebuildImportGraph()
|
|
|
|
if len(results) == 0 {
|
|
return nil, nil, errors.Errorf("no metadata for %s", uri)
|
|
}
|
|
return results, prevMissingImports, nil
|
|
}
|
|
|
|
func (s *snapshot) updateImports(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) error {
|
|
id := packageID(pkg.ID)
|
|
if _, ok := seen[id]; ok {
|
|
return errors.Errorf("import cycle detected: %q", id)
|
|
}
|
|
// Recreate the metadata rather than reusing it to avoid locking.
|
|
m := &metadata{
|
|
id: id,
|
|
pkgPath: pkgPath,
|
|
name: pkg.Name,
|
|
typesSizes: pkg.TypesSizes,
|
|
errors: pkg.Errors,
|
|
config: cfg,
|
|
}
|
|
seen[id] = struct{}{}
|
|
for _, filename := range pkg.CompiledGoFiles {
|
|
uri := span.FileURI(filename)
|
|
m.files = append(m.files, uri)
|
|
|
|
s.addID(uri, m.id)
|
|
}
|
|
|
|
copied := make(map[packageID]struct{})
|
|
for k, v := range seen {
|
|
copied[k] = v
|
|
}
|
|
for importPath, importPkg := range pkg.Imports {
|
|
importPkgPath := packagePath(importPath)
|
|
importID := packageID(importPkg.ID)
|
|
|
|
m.deps = append(m.deps, importID)
|
|
|
|
// Don't remember any imports with significant errors.
|
|
if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
|
|
if m.missingDeps == nil {
|
|
m.missingDeps = make(map[packagePath]struct{})
|
|
}
|
|
m.missingDeps[importPkgPath] = struct{}{}
|
|
continue
|
|
}
|
|
dep := s.getMetadata(importID)
|
|
if dep == nil {
|
|
if err := s.updateImports(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
|
|
log.Error(ctx, "error in dependency", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the metadata to the cache.
|
|
s.setMetadata(m)
|
|
|
|
return nil
|
|
}
|