github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/cache/load.go (about)

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