mirror of
https://github.com/golang/go
synced 2024-10-01 05:18:33 -06:00
67a4523381
Our current invariant is that all workspace packages are parsed in full mode, and all dependencies are parsed in exported mode. We can rely on this, as well as the fact that workspace packages are set during metadata loads, to reduce the amount of plumbing the mode requires.W Change-Id: Ib9406ca3c0dc2c81c7ee3158407f28022924d4d0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217139 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
241 lines
7.1 KiB
Go
241 lines
7.1 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/types"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
"golang.org/x/tools/internal/lsp/telemetry"
|
|
"golang.org/x/tools/internal/packagesinternal"
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
"golang.org/x/tools/internal/telemetry/tag"
|
|
"golang.org/x/tools/internal/telemetry/trace"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
type metadata struct {
|
|
id packageID
|
|
pkgPath packagePath
|
|
name string
|
|
goFiles []span.URI
|
|
compiledGoFiles []span.URI
|
|
forTest packagePath
|
|
typesSizes types.Sizes
|
|
errors []packages.Error
|
|
deps []packageID
|
|
missingDeps map[packagePath]struct{}
|
|
|
|
// config is the *packages.Config associated with the loaded package.
|
|
config *packages.Config
|
|
}
|
|
|
|
func (s *snapshot) load(ctx context.Context, scopes ...interface{}) ([]*metadata, error) {
|
|
var query []string
|
|
var containsDir bool // for logging
|
|
for _, scope := range scopes {
|
|
switch scope := scope.(type) {
|
|
case packagePath:
|
|
// The only time we pass package paths is when we're doing a
|
|
// partial workspace load. In those cases, the paths came back from
|
|
// go list and should already be GOPATH-vendorized when appropriate.
|
|
query = append(query, string(scope))
|
|
case fileURI:
|
|
query = append(query, fmt.Sprintf("file=%s", span.URI(scope).Filename()))
|
|
case directoryURI:
|
|
filename := span.URI(scope).Filename()
|
|
q := fmt.Sprintf("%s/...", filename)
|
|
// Simplify the query if it will be run in the requested directory.
|
|
// This ensures compatibility with Go 1.12 that doesn't allow
|
|
// <directory>/... in GOPATH mode.
|
|
if s.view.folder.Filename() == filename {
|
|
q = "./..."
|
|
}
|
|
query = append(query, q)
|
|
case viewLoadScope:
|
|
// If we are outside of GOPATH, a module, or some other known
|
|
// build system, don't load subdirectories.
|
|
if !s.view.hasValidBuildConfiguration {
|
|
query = append(query, "./")
|
|
} else {
|
|
query = append(query, "./...")
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unknown scope type %T", scope))
|
|
}
|
|
switch scope.(type) {
|
|
case directoryURI, viewLoadScope:
|
|
containsDir = true
|
|
}
|
|
}
|
|
sort.Strings(query) // for determinism
|
|
|
|
ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.Query.Of(query))
|
|
defer done()
|
|
|
|
cfg := s.view.Config(ctx)
|
|
pkgs, err := s.view.loadPackages(cfg, query...)
|
|
|
|
// If the context was canceled, return early. Otherwise, we might be
|
|
// type-checking an incomplete result. Check the context directly,
|
|
// because go/packages adds extra information to the error.
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("query", query), tag.Of("packages", len(pkgs)))
|
|
if len(pkgs) == 0 {
|
|
return nil, err
|
|
}
|
|
var results []*metadata
|
|
for _, pkg := range pkgs {
|
|
if !containsDir || s.view.Options().VerboseOutput {
|
|
log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
|
|
}
|
|
// Ignore packages with no sources, since we will never be able to
|
|
// correctly invalidate that metadata.
|
|
if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
|
|
continue
|
|
}
|
|
// Special case for the builtin package, as it has no dependencies.
|
|
if pkg.PkgPath == "builtin" {
|
|
if err := s.view.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil {
|
|
return nil, err
|
|
}
|
|
continue
|
|
}
|
|
// Skip test main packages.
|
|
if isTestMain(ctx, pkg, s.view.gocache) {
|
|
continue
|
|
}
|
|
// Set the metadata for this package.
|
|
m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := s.packageHandle(ctx, m.id); err != nil {
|
|
return nil, err
|
|
}
|
|
results = append(results, m)
|
|
}
|
|
|
|
// Rebuild the import graph when the metadata is updated.
|
|
s.clearAndRebuildImportGraph()
|
|
|
|
if len(results) == 0 {
|
|
return nil, errors.Errorf("no metadata for %s", scopes)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) {
|
|
id := packageID(pkg.ID)
|
|
if _, ok := seen[id]; ok {
|
|
return nil, errors.Errorf("import cycle detected: %q", id)
|
|
}
|
|
// Recreate the metadata rather than reusing it to avoid locking.
|
|
m := &metadata{
|
|
id: id,
|
|
pkgPath: pkgPath,
|
|
name: pkg.Name,
|
|
forTest: packagePath(packagesinternal.GetForTest(pkg)),
|
|
typesSizes: pkg.TypesSizes,
|
|
errors: pkg.Errors,
|
|
config: cfg,
|
|
}
|
|
|
|
for _, filename := range pkg.CompiledGoFiles {
|
|
uri := span.FileURI(filename)
|
|
m.compiledGoFiles = append(m.compiledGoFiles, uri)
|
|
s.addID(uri, m.id)
|
|
}
|
|
for _, filename := range pkg.GoFiles {
|
|
uri := span.FileURI(filename)
|
|
m.goFiles = append(m.goFiles, uri)
|
|
s.addID(uri, m.id)
|
|
}
|
|
|
|
copied := map[packageID]struct{}{
|
|
id: struct{}{},
|
|
}
|
|
for k, v := range seen {
|
|
copied[k] = v
|
|
}
|
|
for importPath, importPkg := range pkg.Imports {
|
|
importPkgPath := packagePath(importPath)
|
|
importID := packageID(importPkg.ID)
|
|
|
|
m.deps = append(m.deps, importID)
|
|
|
|
// Don't remember any imports with significant errors.
|
|
if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
|
|
if m.missingDeps == nil {
|
|
m.missingDeps = make(map[packagePath]struct{})
|
|
}
|
|
m.missingDeps[importPkgPath] = struct{}{}
|
|
continue
|
|
}
|
|
if s.getMetadata(importID) == nil {
|
|
if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
|
|
log.Error(ctx, "error in dependency", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the metadata to the cache.
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// TODO: We should make sure not to set duplicate metadata,
|
|
// and instead panic here. This can be done by making sure not to
|
|
// reset metadata information for packages we've already seen.
|
|
if original, ok := s.metadata[m.id]; ok {
|
|
m = original
|
|
} else {
|
|
s.metadata[m.id] = m
|
|
}
|
|
|
|
// Set the workspace packages. If any of the package's files belong to the
|
|
// view, then the package is considered to be a workspace package.
|
|
for _, uri := range append(m.compiledGoFiles, m.goFiles...) {
|
|
if !s.view.contains(uri) {
|
|
continue
|
|
}
|
|
// A test variant of a package can only be loaded directly by loading
|
|
// the non-test variant with -test. Track the import path of the non-test variant.
|
|
if m.forTest != "" {
|
|
s.workspacePackages[m.id] = m.forTest
|
|
} else {
|
|
s.workspacePackages[m.id] = pkgPath
|
|
}
|
|
break
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func isTestMain(ctx context.Context, pkg *packages.Package, gocache string) bool {
|
|
// Test mains must have an import path that ends with ".test".
|
|
if !strings.HasSuffix(pkg.PkgPath, ".test") {
|
|
return false
|
|
}
|
|
// Test main packages are always named "main".
|
|
if pkg.Name != "main" {
|
|
return false
|
|
}
|
|
// Test mains always have exactly one GoFile that is in the build cache.
|
|
if len(pkg.GoFiles) > 1 {
|
|
return false
|
|
}
|
|
if !strings.HasPrefix(pkg.GoFiles[0], gocache) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|