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.
17 "golang.org/x/tools/go/packages"
18 "golang.org/x/tools/internal/event"
19 "golang.org/x/tools/internal/lsp/debug/tag"
20 "golang.org/x/tools/internal/lsp/source"
21 "golang.org/x/tools/internal/packagesinternal"
22 "golang.org/x/tools/internal/span"
23 errors "golang.org/x/xerrors"
26 // metadata holds package metadata extracted from a call to packages.Load.
27 type metadata struct {
32 compiledGoFiles []span.URI
34 typesSizes types.Sizes
35 errors []packages.Error
37 missingDeps map[packagePath]struct{}
38 module *packages.Module
40 // config is the *packages.Config associated with the loaded package.
41 config *packages.Config
44 // load calls packages.Load for the given scopes, updating package metadata,
45 // import graph, and mapped files with the result.
46 func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
48 var containsDir bool // for logging
49 for _, scope := range scopes {
50 switch scope := scope.(type) {
52 if scope == "command-line-arguments" {
53 panic("attempted to load command-line-arguments")
55 // The only time we pass package paths is when we're doing a
56 // partial workspace load. In those cases, the paths came back from
57 // go list and should already be GOPATH-vendorized when appropriate.
58 query = append(query, string(scope))
60 uri := span.URI(scope)
61 // Don't try to load a file that doesn't exist.
63 if fh == nil || fh.Kind() != source.Go {
66 query = append(query, fmt.Sprintf("file=%s", uri.Filename()))
68 query = append(query, fmt.Sprintf("%s/...", scope))
70 // If we are outside of GOPATH, a module, or some other known
71 // build system, don't load subdirectories.
72 if !s.ValidBuildConfiguration() {
73 query = append(query, "./")
75 query = append(query, "./...")
78 panic(fmt.Sprintf("unknown scope type %T", scope))
88 sort.Strings(query) // for determinism
90 ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
94 wdir := s.view.rootURI.Filename()
100 case s.workspaceMode()&usesWorkspaceModule != 0:
105 tmpDir, cleanup, err = s.tempWorkspaceModule(ctx)
109 wdir = tmpDir.Filename()
110 modURI = span.URIFromPath(filepath.Join(wdir, "go.mod"))
111 modContent, err = ioutil.ReadFile(modURI.Filename())
115 case s.workspaceMode()&tempModfile != 0:
116 // -modfile is unsupported when there are > 1 modules in the workspace.
117 if len(s.modules) != 1 {
118 panic(fmt.Sprintf("unsupported use of -modfile, expected 1 module, got %v", len(s.modules)))
121 for _, m := range s.modules { // range to access the only element
125 modFH, err := s.GetFile(ctx, mod.modURI)
129 modContent, err = modFH.Read()
133 var sumFH source.FileHandle
134 if mod.sumURI != "" {
135 sumFH, err = s.GetFile(ctx, mod.sumURI)
141 tmpURI, cleanup, err = tempModFile(modFH, sumFH)
145 modFile = tmpURI.Filename()
148 cfg := s.config(ctx, wdir)
149 packagesinternal.SetModFile(cfg, modFile)
150 modMod, err := s.needsModEqualsMod(ctx, modURI, modContent)
155 packagesinternal.SetModFlag(cfg, "mod")
158 pkgs, err := packages.Load(cfg, query...)
161 // If the context was canceled, return early. Otherwise, we might be
162 // type-checking an incomplete result. Check the context directly,
163 // because go/packages adds extra information to the error.
164 if ctx.Err() != nil {
168 // Match on common error messages. This is really hacky, but I'm not sure
169 // of any better way. This can be removed when golang/go#39164 is resolved.
170 if strings.Contains(err.Error(), "inconsistent vendoring") {
171 return source.InconsistentVendoring
173 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)))
175 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)))
179 // Try to extract the error into a diagnostic.
180 if srcErrs := s.parseLoadError(ctx, err); srcErrs != nil {
184 err = fmt.Errorf("no packages returned")
186 return errors.Errorf("%v: %w", err, source.PackagesLoadError)
188 for _, pkg := range pkgs {
189 if !containsDir || s.view.Options().VerboseOutput {
190 event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.PackagePath.Of(pkg.PkgPath), tag.Files.Of(pkg.CompiledGoFiles))
192 // Ignore packages with no sources, since we will never be able to
193 // correctly invalidate that metadata.
194 if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
197 // Special case for the builtin package, as it has no dependencies.
198 if pkg.PkgPath == "builtin" {
199 if err := s.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil {
204 // Skip test main packages.
205 if isTestMain(pkg, s.view.gocache) {
208 // Set the metadata for this package.
209 m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{})
213 if _, err := s.buildPackageHandle(ctx, m.id, s.workspaceParseMode(m.id)); err != nil {
217 // Rebuild the import graph when the metadata is updated.
218 s.clearAndRebuildImportGraph()
223 func (s *snapshot) parseLoadError(ctx context.Context, loadErr error) *source.ErrorList {
224 var srcErrs *source.ErrorList
225 for _, uri := range s.ModFiles() {
226 fh, err := s.GetFile(ctx, uri)
230 srcErr := extractGoCommandError(ctx, s, fh, loadErr)
235 srcErrs = &source.ErrorList{}
237 *srcErrs = append(*srcErrs, srcErr)
242 // tempWorkspaceModule creates a temporary directory for use with
243 // packages.Loads that occur from within the workspace module.
244 func (s *snapshot) tempWorkspaceModule(ctx context.Context) (_ span.URI, cleanup func(), err error) {
246 if s.workspaceMode()&usesWorkspaceModule == 0 {
247 return "", cleanup, nil
249 wsModuleHandle, err := s.getWorkspaceModuleHandle(ctx)
253 file, err := wsModuleHandle.build(ctx, s)
257 content, err := file.Format()
259 return "", cleanup, err
261 // Create a temporary working directory for the go command that contains
262 // the workspace module file.
263 name, err := ioutil.TempDir("", "gopls-mod")
265 return "", cleanup, err
270 filename := filepath.Join(name, "go.mod")
271 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
273 return "", cleanup, err
275 return span.URIFromPath(filepath.Dir(filename)), cleanup, nil
278 // setMetadata extracts metadata from pkg and records it in s. It
279 // recurses through pkg.Imports to ensure that metadata exists for all
281 func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) {
282 id := packageID(pkg.ID)
283 if _, ok := seen[id]; ok {
284 return nil, errors.Errorf("import cycle detected: %q", id)
286 // Recreate the metadata rather than reusing it to avoid locking.
290 name: packageName(pkg.Name),
291 forTest: packagePath(packagesinternal.GetForTest(pkg)),
292 typesSizes: pkg.TypesSizes,
298 for _, filename := range pkg.CompiledGoFiles {
299 uri := span.URIFromPath(filename)
300 m.compiledGoFiles = append(m.compiledGoFiles, uri)
303 for _, filename := range pkg.GoFiles {
304 uri := span.URIFromPath(filename)
305 m.goFiles = append(m.goFiles, uri)
309 // TODO(rstambler): is this still necessary?
310 copied := map[packageID]struct{}{
313 for k, v := range seen {
316 for importPath, importPkg := range pkg.Imports {
317 importPkgPath := packagePath(importPath)
318 importID := packageID(importPkg.ID)
320 m.deps = append(m.deps, importID)
322 // Don't remember any imports with significant errors.
323 if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
324 if m.missingDeps == nil {
325 m.missingDeps = make(map[packagePath]struct{})
327 m.missingDeps[importPkgPath] = struct{}{}
330 if s.getMetadata(importID) == nil {
331 if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
332 event.Error(ctx, "error in dependency", err)
337 // Add the metadata to the cache.
341 // TODO: We should make sure not to set duplicate metadata,
342 // and instead panic here. This can be done by making sure not to
343 // reset metadata information for packages we've already seen.
344 if original, ok := s.metadata[m.id]; ok {
350 // Set the workspace packages. If any of the package's files belong to the
351 // view, then the package may be a workspace package.
352 for _, uri := range append(m.compiledGoFiles, m.goFiles...) {
353 if !s.view.contains(uri) {
357 // The package's files are in this view. It may be a workspace package.
358 if strings.Contains(string(uri), "/vendor/") {
359 // Vendored packages are not likely to be interesting to the user.
364 case m.forTest == "":
366 s.workspacePackages[m.id] = pkgPath
367 case m.forTest == m.pkgPath, m.forTest+"_test" == m.pkgPath:
368 // The test variant of some workspace package or its x_test.
369 // To load it, we need to load the non-test variant with -test.
370 s.workspacePackages[m.id] = m.forTest
372 // A test variant of some intermediate package. We don't care about it.
378 func isTestMain(pkg *packages.Package, gocache string) bool {
379 // Test mains must have an import path that ends with ".test".
380 if !strings.HasSuffix(pkg.PkgPath, ".test") {
383 // Test main packages are always named "main".
384 if pkg.Name != "main" {
387 // Test mains always have exactly one GoFile that is in the build cache.
388 if len(pkg.GoFiles) > 1 {
391 if !strings.HasPrefix(pkg.GoFiles[0], gocache) {