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.
19 "golang.org/x/tools/go/packages"
20 "golang.org/x/tools/internal/event"
21 "golang.org/x/tools/internal/gocommand"
22 "golang.org/x/tools/internal/lsp/debug/tag"
23 "golang.org/x/tools/internal/lsp/protocol"
24 "golang.org/x/tools/internal/lsp/source"
25 "golang.org/x/tools/internal/memoize"
26 "golang.org/x/tools/internal/packagesinternal"
27 "golang.org/x/tools/internal/span"
28 errors "golang.org/x/xerrors"
31 // metadata holds package metadata extracted from a call to packages.Load.
32 type metadata struct {
37 compiledGoFiles []span.URI
39 typesSizes types.Sizes
40 errors []packages.Error
42 missingDeps map[packagePath]struct{}
43 module *packages.Module
44 depsErrors []*packagesinternal.PackageError
46 // config is the *packages.Config associated with the loaded package.
47 config *packages.Config
50 // load calls packages.Load for the given scopes, updating package metadata,
51 // import graph, and mapped files with the result.
52 func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interface{}) error {
54 var containsDir bool // for logging
55 for _, scope := range scopes {
56 switch scope := scope.(type) {
58 if isCommandLineArguments(string(scope)) {
59 panic("attempted to load command-line-arguments")
61 // The only time we pass package paths is when we're doing a
62 // partial workspace load. In those cases, the paths came back from
63 // go list and should already be GOPATH-vendorized when appropriate.
64 query = append(query, string(scope))
66 uri := span.URI(scope)
67 // Don't try to load a file that doesn't exist.
69 if fh == nil || fh.Kind() != source.Go {
72 query = append(query, fmt.Sprintf("file=%s", uri.Filename()))
74 query = append(query, fmt.Sprintf("%s/...", scope))
76 // If we are outside of GOPATH, a module, or some other known
77 // build system, don't load subdirectories.
78 if !s.ValidBuildConfiguration() {
79 query = append(query, "./")
81 query = append(query, "./...")
84 panic(fmt.Sprintf("unknown scope type %T", scope))
87 case viewLoadScope, moduleLoadScope:
94 sort.Strings(query) // for determinism
96 ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
99 flags := source.LoadWorkspace
101 flags |= source.AllowNetwork
103 _, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{
104 WorkingDir: s.view.rootURI.Filename(),
110 // Set a last resort deadline on packages.Load since it calls the go
111 // command, which may hang indefinitely if it has a bug. golang/go#42132
112 // and golang/go#42255 have more context.
113 ctx, cancel := context.WithTimeout(ctx, 15*time.Minute)
116 cfg := s.config(ctx, inv)
117 pkgs, err := packages.Load(cfg, query...)
120 // If the context was canceled, return early. Otherwise, we might be
121 // type-checking an incomplete result. Check the context directly,
122 // because go/packages adds extra information to the error.
123 if ctx.Err() != nil {
127 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)))
129 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)))
133 err = fmt.Errorf("no packages returned")
135 return errors.Errorf("%v: %w", err, source.PackagesLoadError)
137 for _, pkg := range pkgs {
138 if !containsDir || s.view.Options().VerboseOutput {
139 event.Log(ctx, "go/packages.Load",
140 tag.Snapshot.Of(s.ID()),
141 tag.Package.Of(pkg.ID),
142 tag.Files.Of(pkg.CompiledGoFiles))
144 // Ignore packages with no sources, since we will never be able to
145 // correctly invalidate that metadata.
146 if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
149 // Special case for the builtin package, as it has no dependencies.
150 if pkg.PkgPath == "builtin" {
151 if err := s.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil {
156 // Skip test main packages.
157 if isTestMain(pkg, s.view.gocache) {
160 // Skip filtered packages. They may be added anyway if they're
161 // dependencies of non-filtered packages.
162 if s.view.allFilesExcluded(pkg) {
165 // Set the metadata for this package.
166 m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{})
170 if _, err := s.buildPackageHandle(ctx, m.id, s.workspaceParseMode(m.id)); err != nil {
174 // Rebuild the import graph when the metadata is updated.
175 s.clearAndRebuildImportGraph()
180 // workspaceLayoutErrors returns a diagnostic for every open file, as well as
181 // an error message if there are no open files.
182 func (s *snapshot) workspaceLayoutError(ctx context.Context) *source.CriticalError {
183 if len(s.workspace.getKnownModFiles()) == 0 {
186 if s.view.userGo111Module == off {
189 if s.workspace.moduleSource != legacyWorkspace {
192 // If the user has one module per view, there is nothing to warn about.
193 if s.ValidBuildConfiguration() && len(s.workspace.getKnownModFiles()) == 1 {
197 // Apply diagnostics about the workspace configuration to relevant open
199 openFiles := s.openFiles()
201 // If the snapshot does not have a valid build configuration, it may be
202 // that the user has opened a directory that contains multiple modules.
203 // Check for that an warn about it.
204 if !s.ValidBuildConfiguration() {
205 msg := `gopls requires a module at the root of your workspace.
206 You can work with multiple modules by opening each one as a workspace folder.
207 Improvements to this workflow will be coming soon, and you can learn more here:
208 https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`
209 return &source.CriticalError{
210 MainError: errors.Errorf(msg),
211 DiagList: s.applyCriticalErrorToFiles(ctx, msg, openFiles),
215 // If the user has one active go.mod file, they may still be editing files
216 // in nested modules. Check the module of each open file and add warnings
217 // that the nested module must be opened as a workspace folder.
218 if len(s.workspace.getActiveModFiles()) == 1 {
219 // Get the active root go.mod file to compare against.
220 var rootModURI span.URI
221 for uri := range s.workspace.getActiveModFiles() {
224 nestedModules := map[string][]source.VersionedFileHandle{}
225 for _, fh := range openFiles {
226 modURI := moduleForURI(s.workspace.knownModFiles, fh.URI())
227 if modURI != rootModURI {
228 modDir := filepath.Dir(modURI.Filename())
229 nestedModules[modDir] = append(nestedModules[modDir], fh)
232 // Add a diagnostic to each file in a nested module to mark it as
233 // "orphaned". Don't show a general diagnostic in the progress bar,
234 // because the user may still want to edit a file in a nested module.
235 var srcDiags []*source.Diagnostic
236 for modDir, uris := range nestedModules {
237 msg := fmt.Sprintf(`This file is in %s, which is a nested module in the %s module.
238 gopls currently requires one module per workspace folder.
239 Please open %s as a separate workspace folder.
240 You can learn more here: https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.
241 `, modDir, filepath.Dir(rootModURI.Filename()), modDir)
242 srcDiags = append(srcDiags, s.applyCriticalErrorToFiles(ctx, msg, uris)...)
244 if len(srcDiags) != 0 {
245 return &source.CriticalError{
246 MainError: errors.Errorf(`You are working in a nested module.
247 Please open it as a separate workspace folder. Learn more:
248 https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`),
256 func (s *snapshot) applyCriticalErrorToFiles(ctx context.Context, msg string, files []source.VersionedFileHandle) []*source.Diagnostic {
257 var srcDiags []*source.Diagnostic
258 for _, fh := range files {
259 // Place the diagnostics on the package or module declarations.
260 var rng protocol.Range
263 if pgf, err := s.ParseGo(ctx, fh, source.ParseHeader); err == nil {
264 pkgDecl := span.NewRange(s.FileSet(), pgf.File.Package, pgf.File.Name.End())
265 if spn, err := pkgDecl.Span(); err == nil {
266 rng, _ = pgf.Mapper.Range(spn)
270 if pmf, err := s.ParseMod(ctx, fh); err == nil {
271 if pmf.File.Module != nil && pmf.File.Module.Syntax != nil {
272 rng, _ = rangeFromPositions(pmf.Mapper, pmf.File.Module.Syntax.Start, pmf.File.Module.Syntax.End)
276 srcDiags = append(srcDiags, &source.Diagnostic{
279 Severity: protocol.SeverityError,
280 Source: source.ListError,
287 type workspaceDirKey string
289 type workspaceDirData struct {
294 // getWorkspaceDir gets the URI for the workspace directory associated with
295 // this snapshot. The workspace directory is a temp directory containing the
296 // go.mod file computed from all active modules.
297 func (s *snapshot) getWorkspaceDir(ctx context.Context) (span.URI, error) {
299 h := s.workspaceDirHandle
302 return getWorkspaceDir(ctx, h, s.generation)
304 file, err := s.workspace.modFile(ctx, s)
309 modContent, err := file.Format()
313 sumContent, err := s.workspace.sumFile(ctx, s)
317 hash.Write(modContent)
318 hash.Write(sumContent)
319 key := workspaceDirKey(hash.Sum(nil))
321 h = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
322 tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
324 return &workspaceDirData{err: err}
327 for name, content := range map[string][]byte{
328 "go.mod": modContent,
329 "go.sum": sumContent,
331 filename := filepath.Join(tmpdir, name)
332 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
334 return &workspaceDirData{err: err}
338 return &workspaceDirData{dir: tmpdir}
339 }, func(v interface{}) {
340 d := v.(*workspaceDirData)
342 if err := os.RemoveAll(d.dir); err != nil {
343 event.Error(context.Background(), "cleaning workspace dir", err)
347 s.workspaceDirHandle = h
349 return getWorkspaceDir(ctx, h, s.generation)
352 func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
353 v, err := h.Get(ctx, g, nil)
357 return span.URIFromPath(v.(*workspaceDirData).dir), nil
360 // setMetadata extracts metadata from pkg and records it in s. It
361 // recurses through pkg.Imports to ensure that metadata exists for all
363 func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) {
364 id := packageID(pkg.ID)
365 if _, ok := seen[id]; ok {
366 return nil, errors.Errorf("import cycle detected: %q", id)
368 // Recreate the metadata rather than reusing it to avoid locking.
372 name: packageName(pkg.Name),
373 forTest: packagePath(packagesinternal.GetForTest(pkg)),
374 typesSizes: pkg.TypesSizes,
377 depsErrors: packagesinternal.GetDepsErrors(pkg),
380 for _, err := range pkg.Errors {
381 // Filter out parse errors from go list. We'll get them when we
382 // actually parse, and buggy overlay support may generate spurious
383 // errors. (See TestNewModule_Issue38207.)
384 if strings.Contains(err.Msg, "expected '") {
387 m.errors = append(m.errors, err)
390 for _, filename := range pkg.CompiledGoFiles {
391 uri := span.URIFromPath(filename)
392 m.compiledGoFiles = append(m.compiledGoFiles, uri)
395 for _, filename := range pkg.GoFiles {
396 uri := span.URIFromPath(filename)
397 m.goFiles = append(m.goFiles, uri)
401 // TODO(rstambler): is this still necessary?
402 copied := map[packageID]struct{}{
405 for k, v := range seen {
408 for importPath, importPkg := range pkg.Imports {
409 importPkgPath := packagePath(importPath)
410 importID := packageID(importPkg.ID)
412 m.deps = append(m.deps, importID)
414 // Don't remember any imports with significant errors.
415 if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
416 if m.missingDeps == nil {
417 m.missingDeps = make(map[packagePath]struct{})
419 m.missingDeps[importPkgPath] = struct{}{}
422 if s.getMetadata(importID) == nil {
423 if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
424 event.Error(ctx, "error in dependency", err)
429 // Add the metadata to the cache.
433 // TODO: We should make sure not to set duplicate metadata,
434 // and instead panic here. This can be done by making sure not to
435 // reset metadata information for packages we've already seen.
436 if original, ok := s.metadata[m.id]; ok {
442 // Set the workspace packages. If any of the package's files belong to the
443 // view, then the package may be a workspace package.
444 for _, uri := range append(m.compiledGoFiles, m.goFiles...) {
445 if !s.view.contains(uri) {
449 // The package's files are in this view. It may be a workspace package.
450 if strings.Contains(string(uri), "/vendor/") {
451 // Vendored packages are not likely to be interesting to the user.
456 case m.forTest == "":
458 s.workspacePackages[m.id] = pkgPath
459 case m.forTest == m.pkgPath, m.forTest+"_test" == m.pkgPath:
460 // The test variant of some workspace package or its x_test.
461 // To load it, we need to load the non-test variant with -test.
462 s.workspacePackages[m.id] = m.forTest
464 // A test variant of some intermediate package. We don't care about it.
470 func isTestMain(pkg *packages.Package, gocache string) bool {
471 // Test mains must have an import path that ends with ".test".
472 if !strings.HasSuffix(pkg.PkgPath, ".test") {
475 // Test main packages are always named "main".
476 if pkg.Name != "main" {
479 // Test mains always have exactly one GoFile that is in the build cache.
480 if len(pkg.GoFiles) > 1 {
483 if !source.InDir(gocache, pkg.GoFiles[0]) {