1// Copyright 2019 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package cache 6 7import ( 8 "context" 9 "fmt" 10 "go/types" 11 12 "golang.org/x/tools/go/packages" 13 "golang.org/x/tools/internal/lsp/source" 14 "golang.org/x/tools/internal/lsp/telemetry" 15 "golang.org/x/tools/internal/span" 16 "golang.org/x/tools/internal/telemetry/log" 17 "golang.org/x/tools/internal/telemetry/tag" 18 "golang.org/x/tools/internal/telemetry/trace" 19 errors "golang.org/x/xerrors" 20) 21 22type metadata struct { 23 id packageID 24 pkgPath packagePath 25 name string 26 goFiles []span.URI 27 compiledGoFiles []span.URI 28 typesSizes types.Sizes 29 errors []packages.Error 30 deps []packageID 31 missingDeps map[packagePath]struct{} 32 33 // config is the *packages.Config associated with the loaded package. 34 config *packages.Config 35} 36 37func (s *snapshot) load(ctx context.Context, scope source.Scope) ([]*metadata, error) { 38 uri := scope.URI() 39 var query string 40 switch scope.(type) { 41 case source.FileURI: 42 query = fmt.Sprintf("file=%s", scope.URI().Filename()) 43 case source.DirectoryURI: 44 query = fmt.Sprintf("%s/...", scope.URI().Filename()) 45 // Simplify the query if it will be run in the requested directory. 46 // This ensures compatibility with Go 1.12 that doesn't allow 47 // <directory>/... in GOPATH mode. 48 if s.view.folder.Filename() == scope.URI().Filename() { 49 query = "./..." 50 } 51 default: 52 panic(fmt.Errorf("unsupported scope type %T", scope)) 53 } 54 ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.URI.Of(uri)) 55 defer done() 56 57 cfg := s.view.Config(ctx) 58 pkgs, err := packages.Load(cfg, query) 59 60 // If the context was canceled, return early. 61 // Otherwise, we might be type-checking an incomplete result. 62 if err == context.Canceled { 63 return nil, errors.Errorf("no metadata for %s: %v", uri, err) 64 } 65 log.Print(ctx, "go/packages.Load", tag.Of("packages", len(pkgs))) 66 if len(pkgs) == 0 { 67 if err == nil { 68 err = errors.Errorf("no packages found for query %s", query) 69 } 70 } 71 if err != nil { 72 return nil, err 73 } 74 return s.updateMetadata(ctx, scope, pkgs, cfg) 75} 76 77// shouldLoad reparses a file's package and import declarations to 78// determine if they have changed. 79func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, currentFH source.FileHandle) bool { 80 if originalFH == nil { 81 return true 82 } 83 // If the file is a mod file, we should always load. 84 if originalFH.Identity().Kind == currentFH.Identity().Kind && currentFH.Identity().Kind == source.Mod { 85 return true 86 } 87 88 // Get the original and current parsed files in order to check package name and imports. 89 original, _, _, originalErr := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx) 90 current, _, _, currentErr := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx) 91 if originalErr != nil || currentErr != nil { 92 return (originalErr == nil) != (currentErr == nil) 93 } 94 95 // Check if the package's metadata has changed. The cases handled are: 96 // 97 // 1. A package's name has changed 98 // 2. A file's imports have changed 99 // 100 if original.Name.Name != current.Name.Name { 101 return true 102 } 103 // If the package's imports have increased, definitely re-run `go list`. 104 if len(original.Imports) < len(current.Imports) { 105 return true 106 } 107 importSet := make(map[string]struct{}) 108 for _, importSpec := range original.Imports { 109 importSet[importSpec.Path.Value] = struct{}{} 110 } 111 // If any of the current imports were not in the original imports. 112 for _, importSpec := range current.Imports { 113 if _, ok := importSet[importSpec.Path.Value]; !ok { 114 return true 115 } 116 } 117 return false 118} 119 120func (s *snapshot) updateMetadata(ctx context.Context, uri source.Scope, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, error) { 121 var results []*metadata 122 for _, pkg := range pkgs { 123 log.Print(ctx, "go/packages.Load", tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles)) 124 125 // Set the metadata for this package. 126 if err := s.updateImports(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{}); err != nil { 127 return nil, err 128 } 129 m := s.getMetadata(packageID(pkg.ID)) 130 if m != nil { 131 results = append(results, m) 132 } 133 } 134 135 // Rebuild the import graph when the metadata is updated. 136 s.clearAndRebuildImportGraph() 137 138 if len(results) == 0 { 139 return nil, errors.Errorf("no metadata for %s", uri) 140 } 141 return results, nil 142} 143 144func (s *snapshot) updateImports(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) error { 145 id := packageID(pkg.ID) 146 if _, ok := seen[id]; ok { 147 return errors.Errorf("import cycle detected: %q", id) 148 } 149 // Recreate the metadata rather than reusing it to avoid locking. 150 m := &metadata{ 151 id: id, 152 pkgPath: pkgPath, 153 name: pkg.Name, 154 typesSizes: pkg.TypesSizes, 155 errors: pkg.Errors, 156 config: cfg, 157 } 158 159 for _, filename := range pkg.CompiledGoFiles { 160 uri := span.FileURI(filename) 161 m.compiledGoFiles = append(m.compiledGoFiles, uri) 162 s.addID(uri, m.id) 163 } 164 for _, filename := range pkg.GoFiles { 165 uri := span.FileURI(filename) 166 m.goFiles = append(m.goFiles, uri) 167 s.addID(uri, m.id) 168 } 169 170 seen[id] = struct{}{} 171 copied := make(map[packageID]struct{}) 172 for k, v := range seen { 173 copied[k] = v 174 } 175 for importPath, importPkg := range pkg.Imports { 176 importPkgPath := packagePath(importPath) 177 importID := packageID(importPkg.ID) 178 179 m.deps = append(m.deps, importID) 180 181 // Don't remember any imports with significant errors. 182 if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 { 183 if m.missingDeps == nil { 184 m.missingDeps = make(map[packagePath]struct{}) 185 } 186 m.missingDeps[importPkgPath] = struct{}{} 187 continue 188 } 189 dep := s.getMetadata(importID) 190 if dep == nil { 191 if err := s.updateImports(ctx, importPkgPath, importPkg, cfg, copied); err != nil { 192 log.Error(ctx, "error in dependency", err) 193 } 194 } 195 } 196 197 // Add the metadata to the cache. 198 s.setMetadata(m) 199 200 return nil 201} 202