blob: a998fa7fddb20b0dd34396b11325f19880a9dd21 [file] [log] [blame]
package cache
import (
"context"
"go/types"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/tag"
errors "golang.org/x/xerrors"
)
type snapshot struct {
id uint64
packages map[span.URI]map[packageKey]*checkPackageHandle
ids map[span.URI][]packageID
metadata map[packageID]*metadata
}
type metadata struct {
id packageID
pkgPath packagePath
name string
files []span.URI
typesSizes types.Sizes
parents map[packageID]bool
children map[packageID]*metadata
errors []packages.Error
missingDeps map[packagePath]struct{}
}
func (v *view) getSnapshot(uri span.URI) ([]*metadata, []*checkPackageHandle) {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
var m []*metadata
for _, id := range v.snapshot.ids[uri] {
m = append(m, v.snapshot.metadata[id])
}
var cphs []*checkPackageHandle
for _, cph := range v.snapshot.packages[uri] {
cphs = append(cphs, cph)
}
return m, cphs
}
func (v *view) getMetadata(uri span.URI) []*metadata {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
var m []*metadata
for _, id := range v.snapshot.ids[uri] {
m = append(m, v.snapshot.metadata[id])
}
return m
}
func (v *view) getPackages(uri span.URI) map[packageKey]*checkPackageHandle {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
return v.snapshot.packages[uri]
}
func (v *view) updateMetadata(ctx context.Context, uri span.URI, pkgs []*packages.Package) ([]*metadata, map[packageID]map[packagePath]struct{}, error) {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
// Clear metadata since we are re-running go/packages.
prevMissingImports := make(map[packageID]map[packagePath]struct{})
for _, id := range v.snapshot.ids[uri] {
if m, ok := v.snapshot.metadata[id]; ok && len(m.missingDeps) > 0 {
prevMissingImports[id] = m.missingDeps
}
}
without := make(map[span.URI]struct{})
for _, id := range v.snapshot.ids[uri] {
v.remove(id, without, map[packageID]struct{}{})
}
v.snapshot = v.snapshot.cloneMetadata(without)
var results []*metadata
for _, pkg := range pkgs {
log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
// Build the import graph for this package.
if err := v.updateImportGraph(ctx, &importGraph{
pkgPath: packagePath(pkg.PkgPath),
pkg: pkg,
parent: nil,
}); err != nil {
return nil, nil, err
}
results = append(results, v.snapshot.metadata[packageID(pkg.ID)])
}
if len(results) == 0 {
return nil, nil, errors.Errorf("no metadata for %s", uri)
}
return results, prevMissingImports, nil
}
type importGraph struct {
pkgPath packagePath
pkg *packages.Package
parent *metadata
}
func (v *view) updateImportGraph(ctx context.Context, g *importGraph) error {
// Recreate the metadata rather than reusing it to avoid locking.
m := &metadata{
id: packageID(g.pkg.ID),
pkgPath: g.pkgPath,
name: g.pkg.Name,
typesSizes: g.pkg.TypesSizes,
errors: g.pkg.Errors,
}
for _, filename := range g.pkg.CompiledGoFiles {
uri := span.FileURI(filename)
v.snapshot.ids[uri] = append(v.snapshot.ids[uri], m.id)
m.files = append(m.files, uri)
}
// Preserve the import graph.
if original, ok := v.snapshot.metadata[m.id]; ok {
m.children = original.children
m.parents = original.parents
}
if m.children == nil {
m.children = make(map[packageID]*metadata)
}
if m.parents == nil {
m.parents = make(map[packageID]bool)
}
// Add the metadata to the cache.
v.snapshot.metadata[m.id] = m
// Connect the import graph.
if g.parent != nil {
m.parents[g.parent.id] = true
g.parent.children[m.id] = m
}
for importPath, importPkg := range g.pkg.Imports {
importPkgPath := packagePath(importPath)
if importPkgPath == g.pkgPath {
return errors.Errorf("cycle detected in %s", importPath)
}
// 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
}
if _, ok := m.children[packageID(importPkg.ID)]; !ok {
if err := v.updateImportGraph(ctx, &importGraph{
pkgPath: importPkgPath,
pkg: importPkg,
parent: m,
}); err != nil {
log.Error(ctx, "error in dependency", err)
}
}
}
// Clear out any imports that have been removed since the package was last loaded.
for importID := range m.children {
child, ok := v.snapshot.metadata[importID]
if !ok {
continue
}
importPath := string(child.pkgPath)
if _, ok := g.pkg.Imports[importPath]; ok {
continue
}
delete(m.children, importID)
delete(child.parents, m.id)
}
return nil
}
func (v *view) updatePackages(cphs []*checkPackageHandle) {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
for _, cph := range cphs {
for _, ph := range cph.files {
uri := ph.File().Identity().URI
if _, ok := v.snapshot.packages[uri]; !ok {
v.snapshot.packages[uri] = make(map[packageKey]*checkPackageHandle)
}
v.snapshot.packages[uri][packageKey{
id: cph.m.id,
mode: ph.Mode(),
}] = cph
}
}
}
// invalidateContent invalidates the content of a Go file,
// including any position and type information that depends on it.
func (v *view) invalidateContent(ctx context.Context, f *goFile) {
f.handleMu.Lock()
defer f.handleMu.Unlock()
without := make(map[span.URI]struct{})
// Remove the package and all of its reverse dependencies from the cache.
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
for _, id := range v.snapshot.ids[f.URI()] {
f.view.remove(id, without, map[packageID]struct{}{})
}
v.snapshot = v.snapshot.clonePackages(without)
f.handle = nil
}
// invalidateMeta invalidates package metadata for all files in f's
// package. This forces f's package's metadata to be reloaded next
// time the package is checked.
func (v *view) invalidateMetadata(uri span.URI) {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
without := make(map[span.URI]struct{})
for _, id := range v.snapshot.ids[uri] {
v.remove(id, without, map[packageID]struct{}{})
}
v.snapshot = v.snapshot.cloneMetadata(without)
}
// remove invalidates a package and its reverse dependencies in the view's
// package cache. It is assumed that the caller has locked both the mutexes
// of both the mcache and the pcache.
func (v *view) remove(id packageID, toDelete map[span.URI]struct{}, seen map[packageID]struct{}) {
if _, ok := seen[id]; ok {
return
}
m, ok := v.snapshot.metadata[id]
if !ok {
return
}
seen[id] = struct{}{}
for parentID := range m.parents {
v.remove(parentID, toDelete, seen)
}
for _, uri := range m.files {
toDelete[uri] = struct{}{}
}
}
func (s *snapshot) clonePackages(without map[span.URI]struct{}) *snapshot {
result := &snapshot{
id: s.id + 1,
packages: make(map[span.URI]map[packageKey]*checkPackageHandle),
ids: s.ids,
metadata: s.metadata,
}
for k, v := range s.packages {
if _, ok := without[k]; ok {
continue
}
result.packages[k] = v
}
return result
}
func (s *snapshot) cloneMetadata(without map[span.URI]struct{}) *snapshot {
result := &snapshot{
id: s.id + 1,
packages: s.packages,
ids: make(map[span.URI][]packageID),
metadata: make(map[packageID]*metadata),
}
withoutIDs := make(map[packageID]struct{})
for k, ids := range s.ids {
if _, ok := without[k]; ok {
for _, id := range ids {
withoutIDs[id] = struct{}{}
}
continue
}
result.ids[k] = ids
}
for k, v := range s.metadata {
if _, ok := withoutIDs[k]; ok {
continue
}
result.metadata[k] = v
}
return result
}
func (v *view) reverseDependencies(ctx context.Context, uri span.URI) map[span.URI]struct{} {
seen := make(map[packageID]struct{})
uris := make(map[span.URI]struct{})
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
for _, id := range v.snapshot.ids[uri] {
v.rdeps(id, seen, uris, id)
}
return uris
}
func (v *view) rdeps(topID packageID, seen map[packageID]struct{}, results map[span.URI]struct{}, id packageID) {
if _, ok := seen[id]; ok {
return
}
seen[id] = struct{}{}
m, ok := v.snapshot.metadata[id]
if !ok {
return
}
if id != topID {
for _, uri := range m.files {
results[uri] = struct{}{}
}
}
for parentID := range m.parents {
v.rdeps(topID, seen, results, parentID)
}
}