Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / cache / load.go
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         "io/ioutil"
12         "os"
13         "path/filepath"
14         "sort"
15         "strings"
16         "time"
17
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"
27 )
28
29 // metadata holds package metadata extracted from a call to packages.Load.
30 type metadata struct {
31         id              packageID
32         pkgPath         packagePath
33         name            packageName
34         goFiles         []span.URI
35         compiledGoFiles []span.URI
36         forTest         packagePath
37         typesSizes      types.Sizes
38         errors          []packages.Error
39         deps            []packageID
40         missingDeps     map[packagePath]struct{}
41         module          *packages.Module
42
43         // config is the *packages.Config associated with the loaded package.
44         config *packages.Config
45 }
46
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 {
50         var query []string
51         var containsDir bool // for logging
52         for _, scope := range scopes {
53                 switch scope := scope.(type) {
54                 case packagePath:
55                         if scope == "command-line-arguments" {
56                                 panic("attempted to load command-line-arguments")
57                         }
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))
62                 case fileURI:
63                         uri := span.URI(scope)
64                         // Don't try to load a file that doesn't exist.
65                         fh := s.FindFile(uri)
66                         if fh == nil || fh.Kind() != source.Go {
67                                 continue
68                         }
69                         query = append(query, fmt.Sprintf("file=%s", uri.Filename()))
70                 case moduleLoadScope:
71                         query = append(query, fmt.Sprintf("%s/...", scope))
72                 case viewLoadScope:
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, "./")
77                         } else {
78                                 query = append(query, "./...")
79                         }
80                 default:
81                         panic(fmt.Sprintf("unknown scope type %T", scope))
82                 }
83                 switch scope.(type) {
84                 case viewLoadScope:
85                         containsDir = true
86                 }
87         }
88         if len(query) == 0 {
89                 return nil
90         }
91         sort.Strings(query) // for determinism
92
93         ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
94         defer done()
95
96         _, inv, cleanup, err := s.goCommandInvocation(ctx, source.ForTypeChecking, &gocommand.Invocation{
97                 WorkingDir: s.view.rootURI.Filename(),
98         })
99         if err != nil {
100                 return err
101         }
102
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)
107         defer cancel()
108
109         cfg := s.config(ctx, inv)
110         pkgs, err := packages.Load(cfg, query...)
111         cleanup()
112
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 {
117                 return ctx.Err()
118         }
119         if 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)))
121         } else {
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)))
123         }
124         if len(pkgs) == 0 {
125                 if err != nil {
126                         // Try to extract the error into a diagnostic.
127                         if srcErrs := s.parseLoadError(ctx, err); srcErrs != nil {
128                                 return srcErrs
129                         }
130                 } else {
131                         err = fmt.Errorf("no packages returned")
132                 }
133                 return errors.Errorf("%v: %w", err, source.PackagesLoadError)
134         }
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))
138                 }
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 {
142                         continue
143                 }
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 {
147                                 return err
148                         }
149                         continue
150                 }
151                 // Skip test main packages.
152                 if isTestMain(pkg, s.view.gocache) {
153                         continue
154                 }
155                 // Set the metadata for this package.
156                 m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{})
157                 if err != nil {
158                         return err
159                 }
160                 if _, err := s.buildPackageHandle(ctx, m.id, s.workspaceParseMode(m.id)); err != nil {
161                         return err
162                 }
163         }
164         // Rebuild the import graph when the metadata is updated.
165         s.clearAndRebuildImportGraph()
166
167         return nil
168 }
169
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)
174                 if err != nil {
175                         continue
176                 }
177                 srcErr := extractGoCommandError(ctx, s, fh, loadErr)
178                 if srcErr == nil {
179                         continue
180                 }
181                 if srcErrs == nil {
182                         srcErrs = &source.ErrorList{}
183                 }
184                 *srcErrs = append(*srcErrs, srcErr)
185         }
186         return srcErrs
187 }
188
189 type workspaceDirKey string
190
191 type workspaceDirData struct {
192         dir string
193         err error
194 }
195
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) {
200         s.mu.Lock()
201         h := s.workspaceDirHandle
202         s.mu.Unlock()
203         if h != nil {
204                 return getWorkspaceDir(ctx, h, s.generation)
205         }
206         file, err := s.workspace.modFile(ctx, s)
207         if err != nil {
208                 return "", err
209         }
210         content, err := file.Format()
211         if err != nil {
212                 return "", err
213         }
214         key := workspaceDirKey(hashContents(content))
215         s.mu.Lock()
216         s.workspaceDirHandle = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
217                 tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
218                 if err != nil {
219                         return &workspaceDirData{err: err}
220                 }
221                 filename := filepath.Join(tmpdir, "go.mod")
222                 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
223                         os.RemoveAll(tmpdir)
224                         return &workspaceDirData{err: err}
225                 }
226                 return &workspaceDirData{dir: tmpdir}
227         }, func(v interface{}) {
228                 d := v.(*workspaceDirData)
229                 if d.dir != "" {
230                         if err := os.RemoveAll(d.dir); err != nil {
231                                 event.Error(context.Background(), "cleaning workspace dir", err)
232                         }
233                 }
234         })
235         s.mu.Unlock()
236         return getWorkspaceDir(ctx, s.workspaceDirHandle, s.generation)
237 }
238
239 func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
240         v, err := h.Get(ctx, g, nil)
241         if err != nil {
242                 return "", err
243         }
244         return span.URIFromPath(v.(*workspaceDirData).dir), nil
245 }
246
247 // setMetadata extracts metadata from pkg and records it in s. It
248 // recurses through pkg.Imports to ensure that metadata exists for all
249 // dependencies.
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)
254         }
255         // Recreate the metadata rather than reusing it to avoid locking.
256         m := &metadata{
257                 id:         id,
258                 pkgPath:    pkgPath,
259                 name:       packageName(pkg.Name),
260                 forTest:    packagePath(packagesinternal.GetForTest(pkg)),
261                 typesSizes: pkg.TypesSizes,
262                 errors:     pkg.Errors,
263                 config:     cfg,
264                 module:     pkg.Module,
265         }
266
267         for _, filename := range pkg.CompiledGoFiles {
268                 uri := span.URIFromPath(filename)
269                 m.compiledGoFiles = append(m.compiledGoFiles, uri)
270                 s.addID(uri, m.id)
271         }
272         for _, filename := range pkg.GoFiles {
273                 uri := span.URIFromPath(filename)
274                 m.goFiles = append(m.goFiles, uri)
275                 s.addID(uri, m.id)
276         }
277
278         // TODO(rstambler): is this still necessary?
279         copied := map[packageID]struct{}{
280                 id: {},
281         }
282         for k, v := range seen {
283                 copied[k] = v
284         }
285         for importPath, importPkg := range pkg.Imports {
286                 importPkgPath := packagePath(importPath)
287                 importID := packageID(importPkg.ID)
288
289                 m.deps = append(m.deps, importID)
290
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{})
295                         }
296                         m.missingDeps[importPkgPath] = struct{}{}
297                         continue
298                 }
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)
302                         }
303                 }
304         }
305
306         // Add the metadata to the cache.
307         s.mu.Lock()
308         defer s.mu.Unlock()
309
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 {
314                 m = original
315         } else {
316                 s.metadata[m.id] = m
317         }
318
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) {
323                         continue
324                 }
325
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.
329                         continue
330                 }
331
332                 switch {
333                 case m.forTest == "":
334                         // A normal package.
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
340                 default:
341                         // A test variant of some intermediate package. We don't care about it.
342                 }
343         }
344         return m, nil
345 }
346
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") {
350                 return false
351         }
352         // Test main packages are always named "main".
353         if pkg.Name != "main" {
354                 return false
355         }
356         // Test mains always have exactly one GoFile that is in the build cache.
357         if len(pkg.GoFiles) > 1 {
358                 return false
359         }
360         if !strings.HasPrefix(pkg.GoFiles[0], gocache) {
361                 return false
362         }
363         return true
364 }