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