| 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 |
| } |
| |
| func (v *view) getSnapshot(uri span.URI) ([]*metadata, map[packageKey]*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]) |
| } |
| return m, v.snapshot.packages[uri] |
| } |
| |
| 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, error) { |
| v.snapshotMu.Lock() |
| defer v.snapshotMu.Unlock() |
| |
| // Clear metadata since we are re-running go/packages. |
| 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, err |
| } |
| results = append(results, v.snapshot.metadata[packageID(pkg.ID)]) |
| } |
| return results, 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 { |
| 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) |
| } |
| } |