github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/cache/load.go (about) 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 5 package cache 6 7 import ( 8 "context" 9 "fmt" 10 "go/types" 11 "sort" 12 "strings" 13 14 "github.com/april1989/origin-go-tools/go/packages" 15 "github.com/april1989/origin-go-tools/internal/event" 16 "github.com/april1989/origin-go-tools/internal/lsp/debug/tag" 17 "github.com/april1989/origin-go-tools/internal/lsp/source" 18 "github.com/april1989/origin-go-tools/internal/packagesinternal" 19 "github.com/april1989/origin-go-tools/internal/span" 20 errors "golang.org/x/xerrors" 21 ) 22 23 type metadata struct { 24 id packageID 25 pkgPath packagePath 26 name packageName 27 goFiles []span.URI 28 compiledGoFiles []span.URI 29 forTest packagePath 30 typesSizes types.Sizes 31 errors []packages.Error 32 deps []packageID 33 missingDeps map[packagePath]struct{} 34 module *packages.Module 35 36 // config is the *packages.Config associated with the loaded package. 37 config *packages.Config 38 } 39 40 func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error { 41 var query []string 42 var containsDir bool // for logging 43 for _, scope := range scopes { 44 switch scope := scope.(type) { 45 case packagePath: 46 if scope == "command-line-arguments" { 47 panic("attempted to load command-line-arguments") 48 } 49 // The only time we pass package paths is when we're doing a 50 // partial workspace load. In those cases, the paths came back from 51 // go list and should already be GOPATH-vendorized when appropriate. 52 query = append(query, string(scope)) 53 case fileURI: 54 query = append(query, fmt.Sprintf("file=%s", span.URI(scope).Filename())) 55 case directoryURI: 56 filename := span.URI(scope).Filename() 57 q := fmt.Sprintf("%s/...", filename) 58 // Simplify the query if it will be run in the requested directory. 59 // This ensures compatibility with Go 1.12 that doesn't allow 60 // <directory>/... in GOPATH mode. 61 if s.view.root.Filename() == filename { 62 q = "./..." 63 } 64 query = append(query, q) 65 case viewLoadScope: 66 // If we are outside of GOPATH, a module, or some other known 67 // build system, don't load subdirectories. 68 if !s.view.hasValidBuildConfiguration { 69 query = append(query, "./") 70 } else { 71 query = append(query, "./...") 72 } 73 default: 74 panic(fmt.Sprintf("unknown scope type %T", scope)) 75 } 76 switch scope.(type) { 77 case directoryURI, viewLoadScope: 78 containsDir = true 79 } 80 } 81 sort.Strings(query) // for determinism 82 83 ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query)) 84 defer done() 85 86 cfg := s.config(ctx) 87 cleanup := func() {} 88 if s.view.tmpMod { 89 modFH, err := s.GetFile(ctx, s.view.modURI) 90 if err != nil { 91 return err 92 } 93 var sumFH source.FileHandle 94 if s.view.sumURI != "" { 95 sumFH, err = s.GetFile(ctx, s.view.sumURI) 96 if err != nil { 97 return err 98 } 99 } 100 var tmpURI span.URI 101 tmpURI, cleanup, err = tempModFile(modFH, sumFH) 102 if err != nil { 103 return err 104 } 105 cfg.BuildFlags = append(cfg.BuildFlags, fmt.Sprintf("-modfile=%s", tmpURI.Filename())) 106 } 107 pkgs, err := packages.Load(cfg, query...) 108 cleanup() 109 110 // If the context was canceled, return early. Otherwise, we might be 111 // type-checking an incomplete result. Check the context directly, 112 // because go/packages adds extra information to the error. 113 if ctx.Err() != nil { 114 return ctx.Err() 115 } 116 if err != nil { 117 // Match on common error messages. This is really hacky, but I'm not sure 118 // of any better way. This can be removed when golang/go#39164 is resolved. 119 if strings.Contains(err.Error(), "inconsistent vendoring") { 120 return source.InconsistentVendoring 121 } 122 event.Error(ctx, "go/packages.Load", err, tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs))) 123 } else { 124 err = fmt.Errorf("no packages returned") 125 event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs))) 126 } 127 if len(pkgs) == 0 { 128 return errors.Errorf("%v: %w", err, source.PackagesLoadError) 129 } 130 131 for _, pkg := range pkgs { 132 if !containsDir || s.view.Options().VerboseOutput { 133 event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.PackagePath.Of(pkg.PkgPath), tag.Files.Of(pkg.CompiledGoFiles)) 134 } 135 // Ignore packages with no sources, since we will never be able to 136 // correctly invalidate that metadata. 137 if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 { 138 continue 139 } 140 // Special case for the builtin package, as it has no dependencies. 141 if pkg.PkgPath == "builtin" { 142 if err := s.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil { 143 return err 144 } 145 continue 146 } 147 // Skip test main packages. 148 if isTestMain(pkg, s.view.gocache) { 149 continue 150 } 151 // Set the metadata for this package. 152 m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{}) 153 if err != nil { 154 return err 155 } 156 if _, err := s.buildPackageHandle(ctx, m.id, s.workspaceParseMode(m.id)); err != nil { 157 return err 158 } 159 } 160 // Rebuild the import graph when the metadata is updated. 161 s.clearAndRebuildImportGraph() 162 163 return nil 164 } 165 166 func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) { 167 id := packageID(pkg.ID) 168 if _, ok := seen[id]; ok { 169 return nil, errors.Errorf("import cycle detected: %q", id) 170 } 171 // Recreate the metadata rather than reusing it to avoid locking. 172 m := &metadata{ 173 id: id, 174 pkgPath: pkgPath, 175 name: packageName(pkg.Name), 176 forTest: packagePath(packagesinternal.GetForTest(pkg)), 177 typesSizes: pkg.TypesSizes, 178 errors: pkg.Errors, 179 config: cfg, 180 module: pkg.Module, 181 } 182 183 for _, filename := range pkg.CompiledGoFiles { 184 uri := span.URIFromPath(filename) 185 m.compiledGoFiles = append(m.compiledGoFiles, uri) 186 s.addID(uri, m.id) 187 } 188 for _, filename := range pkg.GoFiles { 189 uri := span.URIFromPath(filename) 190 m.goFiles = append(m.goFiles, uri) 191 s.addID(uri, m.id) 192 } 193 194 copied := map[packageID]struct{}{ 195 id: {}, 196 } 197 for k, v := range seen { 198 copied[k] = v 199 } 200 for importPath, importPkg := range pkg.Imports { 201 importPkgPath := packagePath(importPath) 202 importID := packageID(importPkg.ID) 203 204 m.deps = append(m.deps, importID) 205 206 // Don't remember any imports with significant errors. 207 if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 { 208 if m.missingDeps == nil { 209 m.missingDeps = make(map[packagePath]struct{}) 210 } 211 m.missingDeps[importPkgPath] = struct{}{} 212 continue 213 } 214 if s.getMetadata(importID) == nil { 215 if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil { 216 event.Error(ctx, "error in dependency", err) 217 } 218 } 219 } 220 221 // Add the metadata to the cache. 222 s.mu.Lock() 223 defer s.mu.Unlock() 224 225 // TODO: We should make sure not to set duplicate metadata, 226 // and instead panic here. This can be done by making sure not to 227 // reset metadata information for packages we've already seen. 228 if original, ok := s.metadata[m.id]; ok { 229 m = original 230 } else { 231 s.metadata[m.id] = m 232 } 233 234 // Set the workspace packages. If any of the package's files belong to the 235 // view, then the package may be a workspace package. 236 for _, uri := range append(m.compiledGoFiles, m.goFiles...) { 237 if !s.view.contains(uri) { 238 continue 239 } 240 241 // The package's files are in this view. It may be a workspace package. 242 if strings.Contains(string(uri), "/vendor/") { 243 // Vendored packages are not likely to be interesting to the user. 244 continue 245 } 246 247 switch { 248 case m.forTest == "": 249 // A normal package. 250 s.workspacePackages[m.id] = pkgPath 251 case m.forTest == m.pkgPath, m.forTest+"_test" == m.pkgPath: 252 // The test variant of some workspace package or its x_test. 253 // To load it, we need to load the non-test variant with -test. 254 s.workspacePackages[m.id] = m.forTest 255 default: 256 // A test variant of some intermediate package. We don't care about it. 257 } 258 } 259 return m, nil 260 } 261 262 func isTestMain(pkg *packages.Package, gocache string) bool { 263 // Test mains must have an import path that ends with ".test". 264 if !strings.HasSuffix(pkg.PkgPath, ".test") { 265 return false 266 } 267 // Test main packages are always named "main". 268 if pkg.Name != "main" { 269 return false 270 } 271 // Test mains always have exactly one GoFile that is in the build cache. 272 if len(pkg.GoFiles) > 1 { 273 return false 274 } 275 if !strings.HasPrefix(pkg.GoFiles[0], gocache) { 276 return false 277 } 278 return true 279 }