mirror of
https://github.com/golang/go
synced 2024-11-19 01:14:39 -07:00
57610eddc9
This change does not complete the work to handle snapshots correctly, but it does implement the behavior of re-building the snapshot on each file invalidation. It also moves to the approach of caching the FileHandles on the snapshot, rather than in the goFile object, which is now not necessary. Finally, this change shifts the logic of metadata invalidation into the content invalidation step, so there is less logic to decide if we should re-load a package or not. Change-Id: I18387c385fb070da4db1302bf97035ce6328b5c3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/197799 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
215 lines
5.8 KiB
Go
215 lines
5.8 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 packageKey struct {
|
|
mode source.ParseMode
|
|
id packageID
|
|
}
|
|
|
|
type metadata struct {
|
|
id packageID
|
|
pkgPath packagePath
|
|
name string
|
|
files []span.URI
|
|
typesSizes types.Sizes
|
|
errors []packages.Error
|
|
deps []packageID
|
|
missingDeps map[packagePath]struct{}
|
|
}
|
|
|
|
func (s *snapshot) load(ctx context.Context, uri span.URI, cfg *packages.Config) ([]*metadata, error) {
|
|
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri))
|
|
defer done()
|
|
|
|
pkgs, err := packages.Load(cfg, fmt.Sprintf("file=%s", uri.Filename()))
|
|
log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs)))
|
|
|
|
if len(pkgs) == 0 {
|
|
if err == nil {
|
|
err = errors.Errorf("go/packages.Load: no packages found for %s", uri)
|
|
}
|
|
// Return this error as a diagnostic to the user.
|
|
return nil, err
|
|
}
|
|
m, prevMissingImports, err := s.updateMetadata(ctx, uri, pkgs)
|
|
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 both 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 parsed file in order to check package name and imports.
|
|
original, _, _, err := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Get the current parsed file in order to check package name and imports.
|
|
current, _, _, err := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// 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 span.URI, pkgs []*packages.Package) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
|
|
// Clear metadata since we are re-running go/packages.
|
|
prevMissingImports := make(map[packageID]map[packagePath]struct{})
|
|
m := s.getMetadataForURI(uri)
|
|
|
|
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); 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) error {
|
|
// Recreate the metadata rather than reusing it to avoid locking.
|
|
m := &metadata{
|
|
id: packageID(pkg.ID),
|
|
pkgPath: pkgPath,
|
|
name: pkg.Name,
|
|
typesSizes: pkg.TypesSizes,
|
|
errors: pkg.Errors,
|
|
}
|
|
for _, filename := range pkg.CompiledGoFiles {
|
|
uri := span.FileURI(filename)
|
|
m.files = append(m.files, uri)
|
|
|
|
s.addID(uri, m.id)
|
|
}
|
|
|
|
// Add the metadata to the cache.
|
|
s.setMetadata(m)
|
|
|
|
for importPath, importPkg := range pkg.Imports {
|
|
importPkgPath := packagePath(importPath)
|
|
importID := packageID(importPkg.ID)
|
|
|
|
if importPkgPath == pkgPath {
|
|
return errors.Errorf("cycle detected in %s", importPath)
|
|
}
|
|
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); err != nil {
|
|
log.Error(ctx, "error in dependency", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|