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.
18 "golang.org/x/tools/go/packages"
19 "golang.org/x/tools/internal/event"
20 "golang.org/x/tools/internal/gocommand"
21 "golang.org/x/tools/internal/lsp/debug/tag"
22 "golang.org/x/tools/internal/lsp/source"
23 "golang.org/x/tools/internal/memoize"
24 "golang.org/x/tools/internal/packagesinternal"
25 "golang.org/x/tools/internal/span"
26 errors "golang.org/x/xerrors"
29 // metadata holds package metadata extracted from a call to packages.Load.
30 type metadata struct {
35 compiledGoFiles []span.URI
37 typesSizes types.Sizes
38 errors []packages.Error
40 missingDeps map[packagePath]struct{}
41 module *packages.Module
43 // config is the *packages.Config associated with the loaded package.
44 config *packages.Config
47 // load calls packages.Load for the given scopes, updating package metadata,
48 // import graph, and mapped files with the result.
49 func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
51 var containsDir bool // for logging
52 for _, scope := range scopes {
53 switch scope := scope.(type) {
55 if scope == "command-line-arguments" {
56 panic("attempted to load command-line-arguments")
58 // The only time we pass package paths is when we're doing a
59 // partial workspace load. In those cases, the paths came back from
60 // go list and should already be GOPATH-vendorized when appropriate.
61 query = append(query, string(scope))
63 uri := span.URI(scope)
64 // Don't try to load a file that doesn't exist.
66 if fh == nil || fh.Kind() != source.Go {
69 query = append(query, fmt.Sprintf("file=%s", uri.Filename()))
71 query = append(query, fmt.Sprintf("%s/...", scope))
73 // If we are outside of GOPATH, a module, or some other known
74 // build system, don't load subdirectories.
75 if !s.ValidBuildConfiguration() {
76 query = append(query, "./")
78 query = append(query, "./...")
81 panic(fmt.Sprintf("unknown scope type %T", scope))
91 sort.Strings(query) // for determinism
93 ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
96 _, inv, cleanup, err := s.goCommandInvocation(ctx, source.ForTypeChecking, &gocommand.Invocation{
97 WorkingDir: s.view.rootURI.Filename(),
103 // Set a last resort deadline on packages.Load since it calls the go
104 // command, which may hang indefinitely if it has a bug. golang/go#42132
105 // and golang/go#42255 have more context.
106 ctx, cancel := context.WithTimeout(ctx, 15*time.Minute)
109 cfg := s.config(ctx, inv)
110 pkgs, err := packages.Load(cfg, query...)
113 // If the context was canceled, return early. Otherwise, we might be
114 // type-checking an incomplete result. Check the context directly,
115 // because go/packages adds extra information to the error.
116 if ctx.Err() != nil {
120 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)))
122 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 // Try to extract the error into a diagnostic.
127 if srcErrs := s.parseLoadError(ctx, err); srcErrs != nil {
131 err = fmt.Errorf("no packages returned")
133 return errors.Errorf("%v: %w", err, source.PackagesLoadError)
135 for _, pkg := range pkgs {
136 if !containsDir || s.view.Options().VerboseOutput {
137 event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.PackagePath.Of(pkg.PkgPath), tag.Files.Of(pkg.CompiledGoFiles))
139 // Ignore packages with no sources, since we will never be able to
140 // correctly invalidate that metadata.
141 if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
144 // Special case for the builtin package, as it has no dependencies.
145 if pkg.PkgPath == "builtin" {
146 if err := s.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil {
151 // Skip test main packages.
152 if isTestMain(pkg, s.view.gocache) {
155 // Set the metadata for this package.
156 m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{})
160 if _, err := s.buildPackageHandle(ctx, m.id, s.workspaceParseMode(m.id)); err != nil {
164 // Rebuild the import graph when the metadata is updated.
165 s.clearAndRebuildImportGraph()
170 func (s *snapshot) parseLoadError(ctx context.Context, loadErr error) *source.ErrorList {
171 var srcErrs *source.ErrorList
172 for _, uri := range s.ModFiles() {
173 fh, err := s.GetFile(ctx, uri)
177 srcErr := extractGoCommandError(ctx, s, fh, loadErr)
182 srcErrs = &source.ErrorList{}
184 *srcErrs = append(*srcErrs, srcErr)
189 type workspaceDirKey string
191 type workspaceDirData struct {
196 // getWorkspaceDir gets the URI for the workspace directory associated with
197 // this snapshot. The workspace directory is a temp directory containing the
198 // go.mod file computed from all active modules.
199 func (s *snapshot) getWorkspaceDir(ctx context.Context) (span.URI, error) {
201 h := s.workspaceDirHandle
204 return getWorkspaceDir(ctx, h, s.generation)
206 file, err := s.workspace.modFile(ctx, s)
210 content, err := file.Format()
214 key := workspaceDirKey(hashContents(content))
216 s.workspaceDirHandle = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
217 tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
219 return &workspaceDirData{err: err}
221 filename := filepath.Join(tmpdir, "go.mod")
222 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
224 return &workspaceDirData{err: err}
226 return &workspaceDirData{dir: tmpdir}
227 }, func(v interface{}) {
228 d := v.(*workspaceDirData)
230 if err := os.RemoveAll(d.dir); err != nil {
231 event.Error(context.Background(), "cleaning workspace dir", err)
236 return getWorkspaceDir(ctx, s.workspaceDirHandle, s.generation)
239 func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
240 v, err := h.Get(ctx, g, nil)
244 return span.URIFromPath(v.(*workspaceDirData).dir), nil
247 // setMetadata extracts metadata from pkg and records it in s. It
248 // recurses through pkg.Imports to ensure that metadata exists for all
250 func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) {
251 id := packageID(pkg.ID)
252 if _, ok := seen[id]; ok {
253 return nil, errors.Errorf("import cycle detected: %q", id)
255 // Recreate the metadata rather than reusing it to avoid locking.
259 name: packageName(pkg.Name),
260 forTest: packagePath(packagesinternal.GetForTest(pkg)),
261 typesSizes: pkg.TypesSizes,
267 for _, filename := range pkg.CompiledGoFiles {
268 uri := span.URIFromPath(filename)
269 m.compiledGoFiles = append(m.compiledGoFiles, uri)
272 for _, filename := range pkg.GoFiles {
273 uri := span.URIFromPath(filename)
274 m.goFiles = append(m.goFiles, uri)
278 // TODO(rstambler): is this still necessary?
279 copied := map[packageID]struct{}{
282 for k, v := range seen {
285 for importPath, importPkg := range pkg.Imports {
286 importPkgPath := packagePath(importPath)
287 importID := packageID(importPkg.ID)
289 m.deps = append(m.deps, importID)
291 // Don't remember any imports with significant errors.
292 if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
293 if m.missingDeps == nil {
294 m.missingDeps = make(map[packagePath]struct{})
296 m.missingDeps[importPkgPath] = struct{}{}
299 if s.getMetadata(importID) == nil {
300 if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
301 event.Error(ctx, "error in dependency", err)
306 // Add the metadata to the cache.
310 // TODO: We should make sure not to set duplicate metadata,
311 // and instead panic here. This can be done by making sure not to
312 // reset metadata information for packages we've already seen.
313 if original, ok := s.metadata[m.id]; ok {
319 // Set the workspace packages. If any of the package's files belong to the
320 // view, then the package may be a workspace package.
321 for _, uri := range append(m.compiledGoFiles, m.goFiles...) {
322 if !s.view.contains(uri) {
326 // The package's files are in this view. It may be a workspace package.
327 if strings.Contains(string(uri), "/vendor/") {
328 // Vendored packages are not likely to be interesting to the user.
333 case m.forTest == "":
335 s.workspacePackages[m.id] = pkgPath
336 case m.forTest == m.pkgPath, m.forTest+"_test" == m.pkgPath:
337 // The test variant of some workspace package or its x_test.
338 // To load it, we need to load the non-test variant with -test.
339 s.workspacePackages[m.id] = m.forTest
341 // A test variant of some intermediate package. We don't care about it.
347 func isTestMain(pkg *packages.Package, gocache string) bool {
348 // Test mains must have an import path that ends with ".test".
349 if !strings.HasSuffix(pkg.PkgPath, ".test") {
352 // Test main packages are always named "main".
353 if pkg.Name != "main" {
356 // Test mains always have exactly one GoFile that is in the build cache.
357 if len(pkg.GoFiles) > 1 {
360 if !strings.HasPrefix(pkg.GoFiles[0], gocache) {