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 "sort" 12 "strings" 13 14 "golang.org/x/tools/go/packages" 15 "golang.org/x/tools/internal/lsp/source" 16 "golang.org/x/tools/internal/lsp/telemetry" 17 "golang.org/x/tools/internal/packagesinternal" 18 "golang.org/x/tools/internal/span" 19 "golang.org/x/tools/internal/telemetry/log" 20 "golang.org/x/tools/internal/telemetry/tag" 21 "golang.org/x/tools/internal/telemetry/trace" 22 errors "golang.org/x/xerrors" 23) 24 25type metadata struct { 26 id packageID 27 pkgPath packagePath 28 name string 29 goFiles []span.URI 30 compiledGoFiles []span.URI 31 forTest packagePath 32 typesSizes types.Sizes 33 errors []packages.Error 34 deps []packageID 35 missingDeps map[packagePath]struct{} 36 37 // config is the *packages.Config associated with the loaded package. 38 config *packages.Config 39} 40 41func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error { 42 var query []string 43 var containsDir bool // for logging 44 for _, scope := range scopes { 45 switch scope := scope.(type) { 46 case packagePath: 47 // The only time we pass package paths is when we're doing a 48 // partial workspace load. In those cases, the paths came back from 49 // go list and should already be GOPATH-vendorized when appropriate. 50 query = append(query, string(scope)) 51 case fileURI: 52 query = append(query, fmt.Sprintf("file=%s", span.URI(scope).Filename())) 53 case directoryURI: 54 filename := span.URI(scope).Filename() 55 q := fmt.Sprintf("%s/...", filename) 56 // Simplify the query if it will be run in the requested directory. 57 // This ensures compatibility with Go 1.12 that doesn't allow 58 // <directory>/... in GOPATH mode. 59 if s.view.folder.Filename() == filename { 60 q = "./..." 61 } 62 query = append(query, q) 63 case viewLoadScope: 64 // If we are outside of GOPATH, a module, or some other known 65 // build system, don't load subdirectories. 66 if !s.view.hasValidBuildConfiguration { 67 query = append(query, "./") 68 } else { 69 query = append(query, "./...") 70 } 71 default: 72 panic(fmt.Sprintf("unknown scope type %T", scope)) 73 } 74 switch scope.(type) { 75 case directoryURI, viewLoadScope: 76 containsDir = true 77 } 78 } 79 sort.Strings(query) // for determinism 80 81 ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.Query.Of(query)) 82 defer done() 83 84 cfg := s.Config(ctx) 85 pkgs, err := s.view.loadPackages(cfg, query...) 86 87 // If the context was canceled, return early. Otherwise, we might be 88 // type-checking an incomplete result. Check the context directly, 89 // because go/packages adds extra information to the error. 90 if ctx.Err() != nil { 91 return ctx.Err() 92 } 93 94 log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("query", query), tag.Of("packages", len(pkgs))) 95 if len(pkgs) == 0 { 96 return err 97 } 98 for _, pkg := range pkgs { 99 if !containsDir || s.view.Options().VerboseOutput { 100 log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles)) 101 } 102 // Ignore packages with no sources, since we will never be able to 103 // correctly invalidate that metadata. 104 if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 { 105 continue 106 } 107 // Special case for the builtin package, as it has no dependencies. 108 if pkg.PkgPath == "builtin" { 109 if err := s.view.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil { 110 return err 111 } 112 continue 113 } 114 // Skip test main packages. 115 if isTestMain(ctx, pkg, s.view.gocache) { 116 continue 117 } 118 // Set the metadata for this package. 119 m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{}) 120 if err != nil { 121 return err 122 } 123 if _, err := s.buildPackageHandle(ctx, m.id, source.ParseFull); err != nil { 124 return err 125 } 126 } 127 // Rebuild the import graph when the metadata is updated. 128 s.clearAndRebuildImportGraph() 129 130 return nil 131} 132 133func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) { 134 id := packageID(pkg.ID) 135 if _, ok := seen[id]; ok { 136 return nil, errors.Errorf("import cycle detected: %q", id) 137 } 138 // Recreate the metadata rather than reusing it to avoid locking. 139 m := &metadata{ 140 id: id, 141 pkgPath: pkgPath, 142 name: pkg.Name, 143 forTest: packagePath(packagesinternal.GetForTest(pkg)), 144 typesSizes: pkg.TypesSizes, 145 errors: pkg.Errors, 146 config: cfg, 147 } 148 149 for _, filename := range pkg.CompiledGoFiles { 150 uri := span.URIFromPath(filename) 151 m.compiledGoFiles = append(m.compiledGoFiles, uri) 152 s.addID(uri, m.id) 153 } 154 for _, filename := range pkg.GoFiles { 155 uri := span.URIFromPath(filename) 156 m.goFiles = append(m.goFiles, uri) 157 s.addID(uri, m.id) 158 } 159 160 copied := map[packageID]struct{}{ 161 id: struct{}{}, 162 } 163 for k, v := range seen { 164 copied[k] = v 165 } 166 for importPath, importPkg := range pkg.Imports { 167 importPkgPath := packagePath(importPath) 168 importID := packageID(importPkg.ID) 169 170 m.deps = append(m.deps, importID) 171 172 // Don't remember any imports with significant errors. 173 if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 { 174 if m.missingDeps == nil { 175 m.missingDeps = make(map[packagePath]struct{}) 176 } 177 m.missingDeps[importPkgPath] = struct{}{} 178 continue 179 } 180 if s.getMetadata(importID) == nil { 181 if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil { 182 log.Error(ctx, "error in dependency", err) 183 } 184 } 185 } 186 187 // Add the metadata to the cache. 188 s.mu.Lock() 189 defer s.mu.Unlock() 190 191 // TODO: We should make sure not to set duplicate metadata, 192 // and instead panic here. This can be done by making sure not to 193 // reset metadata information for packages we've already seen. 194 if original, ok := s.metadata[m.id]; ok { 195 m = original 196 } else { 197 s.metadata[m.id] = m 198 } 199 200 // Set the workspace packages. If any of the package's files belong to the 201 // view, then the package is considered to be a workspace package. 202 for _, uri := range append(m.compiledGoFiles, m.goFiles...) { 203 // If the package's files are in this view, mark it as a workspace package. 204 if s.view.contains(uri) { 205 // A test variant of a package can only be loaded directly by loading 206 // the non-test variant with -test. Track the import path of the non-test variant. 207 if m.forTest != "" { 208 s.workspacePackages[m.id] = m.forTest 209 } else { 210 s.workspacePackages[m.id] = pkgPath 211 } 212 break 213 } 214 } 215 return m, nil 216} 217 218func isTestMain(ctx context.Context, pkg *packages.Package, gocache string) bool { 219 // Test mains must have an import path that ends with ".test". 220 if !strings.HasSuffix(pkg.PkgPath, ".test") { 221 return false 222 } 223 // Test main packages are always named "main". 224 if pkg.Name != "main" { 225 return false 226 } 227 // Test mains always have exactly one GoFile that is in the build cache. 228 if len(pkg.GoFiles) > 1 { 229 return false 230 } 231 if !strings.HasPrefix(pkg.GoFiles[0], gocache) { 232 return false 233 } 234 return true 235} 236