github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/cache/view.go (about)

     1  // Copyright 2018 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 implements the caching layer for gopls.
     6  package cache
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"path"
    16  	"path/filepath"
    17  	"reflect"
    18  	"regexp"
    19  	"sort"
    20  	"strings"
    21  	"sync"
    22  
    23  	"golang.org/x/mod/modfile"
    24  	"golang.org/x/mod/semver"
    25  	exec "golang.org/x/sys/execabs"
    26  	"github.com/jhump/golang-x-tools/go/packages"
    27  	"github.com/jhump/golang-x-tools/internal/event"
    28  	"github.com/jhump/golang-x-tools/internal/gocommand"
    29  	"github.com/jhump/golang-x-tools/internal/imports"
    30  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    31  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    32  	"github.com/jhump/golang-x-tools/internal/memoize"
    33  	"github.com/jhump/golang-x-tools/internal/span"
    34  	"github.com/jhump/golang-x-tools/internal/xcontext"
    35  	errors "golang.org/x/xerrors"
    36  )
    37  
    38  type View struct {
    39  	session *Session
    40  	id      string
    41  
    42  	optionsMu sync.Mutex
    43  	options   *source.Options
    44  
    45  	// mu protects most mutable state of the view.
    46  	mu sync.Mutex
    47  
    48  	// baseCtx is the context handed to NewView. This is the parent of all
    49  	// background contexts created for this view.
    50  	baseCtx context.Context
    51  
    52  	// cancel is called when all action being performed by the current view
    53  	// should be stopped.
    54  	cancel context.CancelFunc
    55  
    56  	// name is the user visible name of this view.
    57  	name string
    58  
    59  	// folder is the folder with which this view was constructed.
    60  	folder span.URI
    61  
    62  	importsState *importsState
    63  
    64  	// moduleUpgrades tracks known upgrades for module paths.
    65  	moduleUpgrades map[string]string
    66  
    67  	// keep track of files by uri and by basename, a single file may be mapped
    68  	// to multiple uris, and the same basename may map to multiple files
    69  	filesByURI  map[span.URI]*fileBase
    70  	filesByBase map[string][]*fileBase
    71  
    72  	// initCancelFirstAttempt can be used to terminate the view's first
    73  	// attempt at initialization.
    74  	initCancelFirstAttempt context.CancelFunc
    75  
    76  	snapshotMu sync.Mutex
    77  	snapshot   *snapshot // nil after shutdown has been called
    78  
    79  	// initialWorkspaceLoad is closed when the first workspace initialization has
    80  	// completed. If we failed to load, we only retry if the go.mod file changes,
    81  	// to avoid too many go/packages calls.
    82  	initialWorkspaceLoad chan struct{}
    83  
    84  	// initializationSema is used limit concurrent initialization of snapshots in
    85  	// the view. We use a channel instead of a mutex to avoid blocking when a
    86  	// context is canceled.
    87  	initializationSema chan struct{}
    88  
    89  	// rootURI is the rootURI directory of this view. If we are in GOPATH mode, this
    90  	// is just the folder. If we are in module mode, this is the module rootURI.
    91  	rootURI span.URI
    92  
    93  	// workspaceInformation tracks various details about this view's
    94  	// environment variables, go version, and use of modules.
    95  	workspaceInformation
    96  }
    97  
    98  type workspaceInformation struct {
    99  	// The Go version in use: X in Go 1.X.
   100  	goversion int
   101  
   102  	// hasGopackagesDriver is true if the user has a value set for the
   103  	// GOPACKAGESDRIVER environment variable or a gopackagesdriver binary on
   104  	// their machine.
   105  	hasGopackagesDriver bool
   106  
   107  	// `go env` variables that need to be tracked by gopls.
   108  	environmentVariables
   109  
   110  	// userGo111Module is the user's value of GO111MODULE.
   111  	userGo111Module go111module
   112  
   113  	// The value of GO111MODULE we want to run with.
   114  	effectiveGo111Module string
   115  
   116  	// goEnv is the `go env` output collected when a view is created.
   117  	// It includes the values of the environment variables above.
   118  	goEnv map[string]string
   119  }
   120  
   121  type go111module int
   122  
   123  const (
   124  	off = go111module(iota)
   125  	auto
   126  	on
   127  )
   128  
   129  type environmentVariables struct {
   130  	gocache, gopath, goroot, goprivate, gomodcache, go111module string
   131  }
   132  
   133  type workspaceMode int
   134  
   135  const (
   136  	moduleMode workspaceMode = 1 << iota
   137  
   138  	// tempModfile indicates whether or not the -modfile flag should be used.
   139  	tempModfile
   140  )
   141  
   142  type builtinPackageHandle struct {
   143  	handle *memoize.Handle
   144  }
   145  
   146  // fileBase holds the common functionality for all files.
   147  // It is intended to be embedded in the file implementations
   148  type fileBase struct {
   149  	uris  []span.URI
   150  	fname string
   151  
   152  	view *View
   153  }
   154  
   155  func (f *fileBase) URI() span.URI {
   156  	return f.uris[0]
   157  }
   158  
   159  func (f *fileBase) filename() string {
   160  	return f.fname
   161  }
   162  
   163  func (f *fileBase) addURI(uri span.URI) int {
   164  	f.uris = append(f.uris, uri)
   165  	return len(f.uris)
   166  }
   167  
   168  func (v *View) ID() string { return v.id }
   169  
   170  // TODO(rfindley): factor this out to use server.tempDir, and consolidate logic with tempModFile.
   171  func tempWorkFile(workFH source.FileHandle) (tmpURI span.URI, cleanup func(), err error) {
   172  	filenameHash := hashContents([]byte(workFH.URI().Filename()))
   173  	tmpMod, err := ioutil.TempFile("", fmt.Sprintf("go.%s.*.work", filenameHash))
   174  	if err != nil {
   175  		return "", nil, err
   176  	}
   177  	defer tmpMod.Close()
   178  
   179  	tmpURI = span.URIFromPath(tmpMod.Name())
   180  
   181  	content, err := workFH.Read()
   182  	if err != nil {
   183  		return "", nil, err
   184  	}
   185  
   186  	if _, err := tmpMod.Write(content); err != nil {
   187  		return "", nil, err
   188  	}
   189  
   190  	cleanup = func() {
   191  		_ = os.Remove(tmpURI.Filename())
   192  	}
   193  
   194  	return tmpURI, cleanup, nil
   195  }
   196  
   197  // tempModFile creates a temporary go.mod file based on the contents of the
   198  // given go.mod file. It is the caller's responsibility to clean up the files
   199  // when they are done using them.
   200  func tempModFile(modFh source.FileHandle, gosum []byte) (tmpURI span.URI, cleanup func(), err error) {
   201  	filenameHash := hashContents([]byte(modFh.URI().Filename()))
   202  	tmpMod, err := ioutil.TempFile("", fmt.Sprintf("go.%s.*.mod", filenameHash))
   203  	if err != nil {
   204  		return "", nil, err
   205  	}
   206  	defer tmpMod.Close()
   207  
   208  	tmpURI = span.URIFromPath(tmpMod.Name())
   209  	tmpSumName := sumFilename(tmpURI)
   210  
   211  	content, err := modFh.Read()
   212  	if err != nil {
   213  		return "", nil, err
   214  	}
   215  
   216  	if _, err := tmpMod.Write(content); err != nil {
   217  		return "", nil, err
   218  	}
   219  
   220  	cleanup = func() {
   221  		_ = os.Remove(tmpSumName)
   222  		_ = os.Remove(tmpURI.Filename())
   223  	}
   224  
   225  	// Be careful to clean up if we return an error from this function.
   226  	defer func() {
   227  		if err != nil {
   228  			cleanup()
   229  			cleanup = nil
   230  		}
   231  	}()
   232  
   233  	// Create an analogous go.sum, if one exists.
   234  	if gosum != nil {
   235  		if err := ioutil.WriteFile(tmpSumName, gosum, 0655); err != nil {
   236  			return "", cleanup, err
   237  		}
   238  	}
   239  
   240  	return tmpURI, cleanup, nil
   241  }
   242  
   243  // Name returns the user visible name of this view.
   244  func (v *View) Name() string {
   245  	return v.name
   246  }
   247  
   248  // Folder returns the folder at the base of this view.
   249  func (v *View) Folder() span.URI {
   250  	return v.folder
   251  }
   252  
   253  func (v *View) Options() *source.Options {
   254  	v.optionsMu.Lock()
   255  	defer v.optionsMu.Unlock()
   256  	return v.options
   257  }
   258  
   259  func (v *View) FileKind(fh source.FileHandle) source.FileKind {
   260  	if o, ok := fh.(source.Overlay); ok {
   261  		if o.Kind() != source.UnknownKind {
   262  			return o.Kind()
   263  		}
   264  	}
   265  	fext := filepath.Ext(fh.URI().Filename())
   266  	switch fext {
   267  	case ".go":
   268  		return source.Go
   269  	case ".mod":
   270  		return source.Mod
   271  	case ".sum":
   272  		return source.Sum
   273  	case ".work":
   274  		return source.Work
   275  	}
   276  	exts := v.Options().TemplateExtensions
   277  	for _, ext := range exts {
   278  		if fext == ext || fext == "."+ext {
   279  			return source.Tmpl
   280  		}
   281  	}
   282  	// and now what? This should never happen, but it does for cgo before go1.15
   283  	return source.Go
   284  }
   285  
   286  func minorOptionsChange(a, b *source.Options) bool {
   287  	// Check if any of the settings that modify our understanding of files have been changed
   288  	if !reflect.DeepEqual(a.Env, b.Env) {
   289  		return false
   290  	}
   291  	if !reflect.DeepEqual(a.DirectoryFilters, b.DirectoryFilters) {
   292  		return false
   293  	}
   294  	if a.MemoryMode != b.MemoryMode {
   295  		return false
   296  	}
   297  	aBuildFlags := make([]string, len(a.BuildFlags))
   298  	bBuildFlags := make([]string, len(b.BuildFlags))
   299  	copy(aBuildFlags, a.BuildFlags)
   300  	copy(bBuildFlags, b.BuildFlags)
   301  	sort.Strings(aBuildFlags)
   302  	sort.Strings(bBuildFlags)
   303  	// the rest of the options are benign
   304  	return reflect.DeepEqual(aBuildFlags, bBuildFlags)
   305  }
   306  
   307  func (v *View) SetOptions(ctx context.Context, options *source.Options) (source.View, error) {
   308  	// no need to rebuild the view if the options were not materially changed
   309  	v.optionsMu.Lock()
   310  	if minorOptionsChange(v.options, options) {
   311  		v.options = options
   312  		v.optionsMu.Unlock()
   313  		return v, nil
   314  	}
   315  	v.optionsMu.Unlock()
   316  	newView, err := v.session.updateView(ctx, v, options)
   317  	return newView, err
   318  }
   319  
   320  func (v *View) Rebuild(ctx context.Context) (source.Snapshot, func(), error) {
   321  	newView, err := v.session.updateView(ctx, v, v.Options())
   322  	if err != nil {
   323  		return nil, func() {}, err
   324  	}
   325  	snapshot, release := newView.Snapshot(ctx)
   326  	return snapshot, release, nil
   327  }
   328  
   329  func (s *snapshot) WriteEnv(ctx context.Context, w io.Writer) error {
   330  	s.view.optionsMu.Lock()
   331  	env := s.view.options.EnvSlice()
   332  	buildFlags := append([]string{}, s.view.options.BuildFlags...)
   333  	s.view.optionsMu.Unlock()
   334  
   335  	fullEnv := make(map[string]string)
   336  	for k, v := range s.view.goEnv {
   337  		fullEnv[k] = v
   338  	}
   339  	for _, v := range env {
   340  		s := strings.SplitN(v, "=", 2)
   341  		if len(s) != 2 {
   342  			continue
   343  		}
   344  		if _, ok := fullEnv[s[0]]; ok {
   345  			fullEnv[s[0]] = s[1]
   346  		}
   347  	}
   348  	goVersion, err := s.view.session.gocmdRunner.Run(ctx, gocommand.Invocation{
   349  		Verb:       "version",
   350  		Env:        env,
   351  		WorkingDir: s.view.rootURI.Filename(),
   352  	})
   353  	if err != nil {
   354  		return err
   355  	}
   356  	fmt.Fprintf(w, `go env for %v
   357  (root %s)
   358  (go version %s)
   359  (valid build configuration = %v)
   360  (build flags: %v)
   361  `,
   362  		s.view.folder.Filename(),
   363  		s.view.rootURI.Filename(),
   364  		strings.TrimRight(goVersion.String(), "\n"),
   365  		s.ValidBuildConfiguration(),
   366  		buildFlags)
   367  	for k, v := range fullEnv {
   368  		fmt.Fprintf(w, "%s=%s\n", k, v)
   369  	}
   370  	return nil
   371  }
   372  
   373  func (s *snapshot) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error {
   374  	return s.view.importsState.runProcessEnvFunc(ctx, s, fn)
   375  }
   376  
   377  // separated out from its sole use in locateTemplateFiles for testability
   378  func fileHasExtension(path string, suffixes []string) bool {
   379  	ext := filepath.Ext(path)
   380  	if ext != "" && ext[0] == '.' {
   381  		ext = ext[1:]
   382  	}
   383  	for _, s := range suffixes {
   384  		if s != "" && ext == s {
   385  			return true
   386  		}
   387  	}
   388  	return false
   389  }
   390  
   391  func (s *snapshot) locateTemplateFiles(ctx context.Context) {
   392  	if len(s.view.Options().TemplateExtensions) == 0 {
   393  		return
   394  	}
   395  	suffixes := s.view.Options().TemplateExtensions
   396  
   397  	// The workspace root may have been expanded to a module, but we should apply
   398  	// directory filters based on the configured workspace folder.
   399  	//
   400  	// TODO(rfindley): we should be more principled about paths outside of the
   401  	// workspace folder: do we even consider them? Do we support absolute
   402  	// exclusions? Relative exclusions starting with ..?
   403  	dir := s.workspace.root.Filename()
   404  	relativeTo := s.view.folder.Filename()
   405  
   406  	searched := 0
   407  	// Change to WalkDir when we move up to 1.16
   408  	err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   409  		if err != nil {
   410  			return err
   411  		}
   412  		relpath := strings.TrimPrefix(path, relativeTo)
   413  		excluded := pathExcludedByFilter(relpath, dir, s.view.gomodcache, s.view.options)
   414  		if fileHasExtension(path, suffixes) && !excluded && !fi.IsDir() {
   415  			k := span.URIFromPath(path)
   416  			_, err := s.GetVersionedFile(ctx, k)
   417  			if err != nil {
   418  				return nil
   419  			}
   420  		}
   421  		searched++
   422  		if fileLimit > 0 && searched > fileLimit {
   423  			return errExhausted
   424  		}
   425  		return nil
   426  	})
   427  	if err != nil {
   428  		event.Error(ctx, "searching for template files failed", err)
   429  	}
   430  }
   431  
   432  func (v *View) contains(uri span.URI) bool {
   433  	inRoot := source.InDir(v.rootURI.Filename(), uri.Filename())
   434  	inFolder := source.InDir(v.folder.Filename(), uri.Filename())
   435  	if !inRoot && !inFolder {
   436  		return false
   437  	}
   438  	// Filters are applied relative to the workspace folder.
   439  	if inFolder {
   440  		return !pathExcludedByFilter(strings.TrimPrefix(uri.Filename(), v.folder.Filename()), v.rootURI.Filename(), v.gomodcache, v.Options())
   441  	}
   442  	return true
   443  }
   444  
   445  func (v *View) mapFile(uri span.URI, f *fileBase) {
   446  	v.filesByURI[uri] = f
   447  	if f.addURI(uri) == 1 {
   448  		basename := basename(f.filename())
   449  		v.filesByBase[basename] = append(v.filesByBase[basename], f)
   450  	}
   451  }
   452  
   453  func basename(filename string) string {
   454  	return strings.ToLower(filepath.Base(filename))
   455  }
   456  
   457  func (v *View) relevantChange(c source.FileModification) bool {
   458  	// If the file is known to the view, the change is relevant.
   459  	if v.knownFile(c.URI) {
   460  		return true
   461  	}
   462  	// The go.work/gopls.mod may not be "known" because we first access it
   463  	// through the session. As a result, treat changes to the view's go.work or
   464  	// gopls.mod file as always relevant, even if they are only on-disk
   465  	// changes.
   466  	// TODO(rstambler): Make sure the go.work/gopls.mod files are always known
   467  	// to the view.
   468  	for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
   469  		if c.URI == uriForSource(v.rootURI, src) {
   470  			return true
   471  		}
   472  	}
   473  	// If the file is not known to the view, and the change is only on-disk,
   474  	// we should not invalidate the snapshot. This is necessary because Emacs
   475  	// sends didChangeWatchedFiles events for temp files.
   476  	if c.OnDisk && (c.Action == source.Change || c.Action == source.Delete) {
   477  		return false
   478  	}
   479  	return v.contains(c.URI)
   480  }
   481  
   482  func (v *View) knownFile(uri span.URI) bool {
   483  	v.mu.Lock()
   484  	defer v.mu.Unlock()
   485  
   486  	f, err := v.findFile(uri)
   487  	return f != nil && err == nil
   488  }
   489  
   490  // getFile returns a file for the given URI.
   491  func (v *View) getFile(uri span.URI) *fileBase {
   492  	v.mu.Lock()
   493  	defer v.mu.Unlock()
   494  
   495  	f, _ := v.findFile(uri)
   496  	if f != nil {
   497  		return f
   498  	}
   499  	f = &fileBase{
   500  		view:  v,
   501  		fname: uri.Filename(),
   502  	}
   503  	v.mapFile(uri, f)
   504  	return f
   505  }
   506  
   507  // findFile checks the cache for any file matching the given uri.
   508  //
   509  // An error is only returned for an irreparable failure, for example, if the
   510  // filename in question does not exist.
   511  func (v *View) findFile(uri span.URI) (*fileBase, error) {
   512  	if f := v.filesByURI[uri]; f != nil {
   513  		// a perfect match
   514  		return f, nil
   515  	}
   516  	// no exact match stored, time to do some real work
   517  	// check for any files with the same basename
   518  	fname := uri.Filename()
   519  	basename := basename(fname)
   520  	if candidates := v.filesByBase[basename]; candidates != nil {
   521  		pathStat, err := os.Stat(fname)
   522  		if os.IsNotExist(err) {
   523  			return nil, err
   524  		}
   525  		if err != nil {
   526  			return nil, nil // the file may exist, return without an error
   527  		}
   528  		for _, c := range candidates {
   529  			if cStat, err := os.Stat(c.filename()); err == nil {
   530  				if os.SameFile(pathStat, cStat) {
   531  					// same file, map it
   532  					v.mapFile(uri, c)
   533  					return c, nil
   534  				}
   535  			}
   536  		}
   537  	}
   538  	// no file with a matching name was found, it wasn't in our cache
   539  	return nil, nil
   540  }
   541  
   542  func (v *View) Shutdown(ctx context.Context) {
   543  	v.session.removeView(ctx, v)
   544  }
   545  
   546  // TODO(rFindley): probably some of this should also be one in View.Shutdown
   547  // above?
   548  func (v *View) shutdown(ctx context.Context) {
   549  	// Cancel the initial workspace load if it is still running.
   550  	v.initCancelFirstAttempt()
   551  
   552  	v.mu.Lock()
   553  	if v.cancel != nil {
   554  		v.cancel()
   555  		v.cancel = nil
   556  	}
   557  	v.mu.Unlock()
   558  	v.snapshotMu.Lock()
   559  	if v.snapshot != nil {
   560  		go v.snapshot.generation.Destroy("View.shutdown")
   561  		v.snapshot = nil
   562  	}
   563  	v.snapshotMu.Unlock()
   564  	v.importsState.destroy()
   565  }
   566  
   567  func (v *View) Session() *Session {
   568  	return v.session
   569  }
   570  
   571  func (s *snapshot) IgnoredFile(uri span.URI) bool {
   572  	filename := uri.Filename()
   573  	var prefixes []string
   574  	if len(s.workspace.getActiveModFiles()) == 0 {
   575  		for _, entry := range filepath.SplitList(s.view.gopath) {
   576  			prefixes = append(prefixes, filepath.Join(entry, "src"))
   577  		}
   578  	} else {
   579  		prefixes = append(prefixes, s.view.gomodcache)
   580  		for m := range s.workspace.getActiveModFiles() {
   581  			prefixes = append(prefixes, dirURI(m).Filename())
   582  		}
   583  	}
   584  	for _, prefix := range prefixes {
   585  		if strings.HasPrefix(filename, prefix) {
   586  			return checkIgnored(filename[len(prefix):])
   587  		}
   588  	}
   589  	return false
   590  }
   591  
   592  // checkIgnored implements go list's exclusion rules. go help list:
   593  // 		Directory and file names that begin with "." or "_" are ignored
   594  // 		by the go tool, as are directories named "testdata".
   595  func checkIgnored(suffix string) bool {
   596  	for _, component := range strings.Split(suffix, string(filepath.Separator)) {
   597  		if len(component) == 0 {
   598  			continue
   599  		}
   600  		if component[0] == '.' || component[0] == '_' || component == "testdata" {
   601  			return true
   602  		}
   603  	}
   604  	return false
   605  }
   606  
   607  func (v *View) Snapshot(ctx context.Context) (source.Snapshot, func()) {
   608  	return v.getSnapshot()
   609  }
   610  
   611  func (v *View) getSnapshot() (*snapshot, func()) {
   612  	v.snapshotMu.Lock()
   613  	defer v.snapshotMu.Unlock()
   614  	if v.snapshot == nil {
   615  		panic("getSnapshot called after shutdown")
   616  	}
   617  	return v.snapshot, v.snapshot.generation.Acquire()
   618  }
   619  
   620  func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
   621  	select {
   622  	case <-ctx.Done():
   623  		return
   624  	case s.view.initializationSema <- struct{}{}:
   625  	}
   626  
   627  	defer func() {
   628  		<-s.view.initializationSema
   629  	}()
   630  
   631  	if s.initializeOnce == nil {
   632  		return
   633  	}
   634  	s.initializeOnce.Do(func() {
   635  		s.loadWorkspace(ctx, firstAttempt)
   636  		s.collectAllKnownSubdirs(ctx)
   637  	})
   638  }
   639  
   640  func (s *snapshot) loadWorkspace(ctx context.Context, firstAttempt bool) {
   641  	defer func() {
   642  		s.initializeOnce = nil
   643  		if firstAttempt {
   644  			close(s.view.initialWorkspaceLoad)
   645  		}
   646  	}()
   647  
   648  	// If we have multiple modules, we need to load them by paths.
   649  	var scopes []interface{}
   650  	var modDiagnostics []*source.Diagnostic
   651  	addError := func(uri span.URI, err error) {
   652  		modDiagnostics = append(modDiagnostics, &source.Diagnostic{
   653  			URI:      uri,
   654  			Severity: protocol.SeverityError,
   655  			Source:   source.ListError,
   656  			Message:  err.Error(),
   657  		})
   658  	}
   659  	s.locateTemplateFiles(ctx)
   660  	if len(s.workspace.getActiveModFiles()) > 0 {
   661  		for modURI := range s.workspace.getActiveModFiles() {
   662  			fh, err := s.GetFile(ctx, modURI)
   663  			if err != nil {
   664  				addError(modURI, err)
   665  				continue
   666  			}
   667  			parsed, err := s.ParseMod(ctx, fh)
   668  			if err != nil {
   669  				addError(modURI, err)
   670  				continue
   671  			}
   672  			if parsed.File == nil || parsed.File.Module == nil {
   673  				addError(modURI, fmt.Errorf("no module path for %s", modURI))
   674  				continue
   675  			}
   676  			path := parsed.File.Module.Mod.Path
   677  			scopes = append(scopes, moduleLoadScope(path))
   678  		}
   679  	} else {
   680  		scopes = append(scopes, viewLoadScope("LOAD_VIEW"))
   681  	}
   682  
   683  	// If we're loading anything, ensure we also load builtin.
   684  	// TODO(rstambler): explain the rationale for this.
   685  	if len(scopes) > 0 {
   686  		scopes = append(scopes, PackagePath("builtin"))
   687  	}
   688  	err := s.load(ctx, firstAttempt, scopes...)
   689  
   690  	// If the context is canceled on the first attempt, loading has failed
   691  	// because the go command has timed out--that should be a critical error.
   692  	if err != nil && !firstAttempt && ctx.Err() != nil {
   693  		return
   694  	}
   695  
   696  	var criticalErr *source.CriticalError
   697  	switch {
   698  	case err != nil && ctx.Err() != nil:
   699  		event.Error(ctx, fmt.Sprintf("initial workspace load: %v", err), err)
   700  		criticalErr = &source.CriticalError{
   701  			MainError: err,
   702  		}
   703  	case err != nil:
   704  		event.Error(ctx, "initial workspace load failed", err)
   705  		extractedDiags, _ := s.extractGoCommandErrors(ctx, err.Error())
   706  		criticalErr = &source.CriticalError{
   707  			MainError: err,
   708  			DiagList:  append(modDiagnostics, extractedDiags...),
   709  		}
   710  	case len(modDiagnostics) == 1:
   711  		criticalErr = &source.CriticalError{
   712  			MainError: fmt.Errorf(modDiagnostics[0].Message),
   713  			DiagList:  modDiagnostics,
   714  		}
   715  	case len(modDiagnostics) > 1:
   716  		criticalErr = &source.CriticalError{
   717  			MainError: fmt.Errorf("error loading module names"),
   718  			DiagList:  modDiagnostics,
   719  		}
   720  	}
   721  
   722  	// Lock the snapshot when setting the initialized error.
   723  	s.mu.Lock()
   724  	defer s.mu.Unlock()
   725  	s.initializedErr = criticalErr
   726  }
   727  
   728  // invalidateContent invalidates the content of a Go file,
   729  // including any position and type information that depends on it.
   730  //
   731  // invalidateContent returns a non-nil snapshot for the new content, along with
   732  // a callback which the caller must invoke to release that snapshot.
   733  func (v *View) invalidateContent(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
   734  	// Detach the context so that content invalidation cannot be canceled.
   735  	ctx = xcontext.Detach(ctx)
   736  
   737  	// This should be the only time we hold the view's snapshot lock for any period of time.
   738  	v.snapshotMu.Lock()
   739  	defer v.snapshotMu.Unlock()
   740  
   741  	if v.snapshot == nil {
   742  		panic("invalidateContent called after shutdown")
   743  	}
   744  
   745  	// Cancel all still-running previous requests, since they would be
   746  	// operating on stale data.
   747  	v.snapshot.cancel()
   748  
   749  	// Do not clone a snapshot until its view has finished initializing.
   750  	v.snapshot.AwaitInitialized(ctx)
   751  
   752  	oldSnapshot := v.snapshot
   753  
   754  	v.snapshot = oldSnapshot.clone(ctx, v.baseCtx, changes, forceReloadMetadata)
   755  	go oldSnapshot.generation.Destroy("View.invalidateContent")
   756  
   757  	return v.snapshot, v.snapshot.generation.Acquire()
   758  }
   759  
   760  func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI, options *source.Options) (*workspaceInformation, error) {
   761  	if err := checkPathCase(folder.Filename()); err != nil {
   762  		return nil, errors.Errorf("invalid workspace folder path: %w; check that the casing of the configured workspace folder path agrees with the casing reported by the operating system", err)
   763  	}
   764  	var err error
   765  	inv := gocommand.Invocation{
   766  		WorkingDir: folder.Filename(),
   767  		Env:        options.EnvSlice(),
   768  	}
   769  	goversion, err := gocommand.GoVersion(ctx, inv, s.gocmdRunner)
   770  	if err != nil {
   771  		return nil, err
   772  	}
   773  
   774  	go111module := os.Getenv("GO111MODULE")
   775  	if v, ok := options.Env["GO111MODULE"]; ok {
   776  		go111module = v
   777  	}
   778  	// Make sure to get the `go env` before continuing with initialization.
   779  	envVars, env, err := s.getGoEnv(ctx, folder.Filename(), goversion, go111module, options.EnvSlice())
   780  	if err != nil {
   781  		return nil, err
   782  	}
   783  	// If using 1.16, change the default back to auto. The primary effect of
   784  	// GO111MODULE=on is to break GOPATH, which we aren't too interested in.
   785  	if goversion >= 16 && go111module == "" {
   786  		go111module = "auto"
   787  	}
   788  	// The value of GOPACKAGESDRIVER is not returned through the go command.
   789  	gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
   790  	for _, s := range env {
   791  		split := strings.SplitN(s, "=", 2)
   792  		if split[0] == "GOPACKAGESDRIVER" {
   793  			gopackagesdriver = split[1]
   794  		}
   795  	}
   796  
   797  	// A user may also have a gopackagesdriver binary on their machine, which
   798  	// works the same way as setting GOPACKAGESDRIVER.
   799  	tool, _ := exec.LookPath("gopackagesdriver")
   800  	hasGopackagesDriver := gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "")
   801  
   802  	return &workspaceInformation{
   803  		hasGopackagesDriver:  hasGopackagesDriver,
   804  		effectiveGo111Module: go111module,
   805  		userGo111Module:      go111moduleForVersion(go111module, goversion),
   806  		goversion:            goversion,
   807  		environmentVariables: envVars,
   808  		goEnv:                env,
   809  	}, nil
   810  }
   811  
   812  func go111moduleForVersion(go111module string, goversion int) go111module {
   813  	// Off by default until Go 1.12.
   814  	if go111module == "off" || (goversion < 12 && go111module == "") {
   815  		return off
   816  	}
   817  	// On by default as of Go 1.16.
   818  	if go111module == "on" || (goversion >= 16 && go111module == "") {
   819  		return on
   820  	}
   821  	return auto
   822  }
   823  
   824  // findWorkspaceRoot searches for the best workspace root according to the
   825  // following heuristics:
   826  //   - First, look for a parent directory containing a gopls.mod file
   827  //     (experimental only).
   828  //   - Then, a parent directory containing a go.mod file.
   829  //   - Then, a child directory containing a go.mod file, if there is exactly
   830  //     one (non-experimental only).
   831  // Otherwise, it returns folder.
   832  // TODO (rFindley): move this to workspace.go
   833  // TODO (rFindley): simplify this once workspace modules are enabled by default.
   834  func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource, excludePath func(string) bool, experimental bool) (span.URI, error) {
   835  	patterns := []string{"go.mod"}
   836  	if experimental {
   837  		patterns = []string{"go.work", "gopls.mod", "go.mod"}
   838  	}
   839  	for _, basename := range patterns {
   840  		dir, err := findRootPattern(ctx, folder, basename, fs)
   841  		if err != nil {
   842  			return "", errors.Errorf("finding %s: %w", basename, err)
   843  		}
   844  		if dir != "" {
   845  			return dir, nil
   846  		}
   847  	}
   848  
   849  	// The experimental workspace can handle nested modules at this point...
   850  	if experimental {
   851  		return folder, nil
   852  	}
   853  
   854  	// ...else we should check if there's exactly one nested module.
   855  	all, err := findModules(folder, excludePath, 2)
   856  	if err == errExhausted {
   857  		// Fall-back behavior: if we don't find any modules after searching 10000
   858  		// files, assume there are none.
   859  		event.Log(ctx, fmt.Sprintf("stopped searching for modules after %d files", fileLimit))
   860  		return folder, nil
   861  	}
   862  	if err != nil {
   863  		return "", err
   864  	}
   865  	if len(all) == 1 {
   866  		// range to access first element.
   867  		for uri := range all {
   868  			return dirURI(uri), nil
   869  		}
   870  	}
   871  	return folder, nil
   872  }
   873  
   874  func findRootPattern(ctx context.Context, folder span.URI, basename string, fs source.FileSource) (span.URI, error) {
   875  	dir := folder.Filename()
   876  	for dir != "" {
   877  		target := filepath.Join(dir, basename)
   878  		exists, err := fileExists(ctx, span.URIFromPath(target), fs)
   879  		if err != nil {
   880  			return "", err
   881  		}
   882  		if exists {
   883  			return span.URIFromPath(dir), nil
   884  		}
   885  		next, _ := filepath.Split(dir)
   886  		if next == dir {
   887  			break
   888  		}
   889  		dir = next
   890  	}
   891  	return "", nil
   892  }
   893  
   894  // OS-specific path case check, for case-insensitive filesystems.
   895  var checkPathCase = defaultCheckPathCase
   896  
   897  func defaultCheckPathCase(path string) error {
   898  	return nil
   899  }
   900  
   901  func validBuildConfiguration(folder span.URI, ws *workspaceInformation, modFiles map[span.URI]struct{}) bool {
   902  	// Since we only really understand the `go` command, if the user has a
   903  	// different GOPACKAGESDRIVER, assume that their configuration is valid.
   904  	if ws.hasGopackagesDriver {
   905  		return true
   906  	}
   907  	// Check if the user is working within a module or if we have found
   908  	// multiple modules in the workspace.
   909  	if len(modFiles) > 0 {
   910  		return true
   911  	}
   912  	// The user may have a multiple directories in their GOPATH.
   913  	// Check if the workspace is within any of them.
   914  	for _, gp := range filepath.SplitList(ws.gopath) {
   915  		if source.InDir(filepath.Join(gp, "src"), folder.Filename()) {
   916  			return true
   917  		}
   918  	}
   919  	return false
   920  }
   921  
   922  // getGoEnv gets the view's various GO* values.
   923  func (s *Session) getGoEnv(ctx context.Context, folder string, goversion int, go111module string, configEnv []string) (environmentVariables, map[string]string, error) {
   924  	envVars := environmentVariables{}
   925  	vars := map[string]*string{
   926  		"GOCACHE":     &envVars.gocache,
   927  		"GOPATH":      &envVars.gopath,
   928  		"GOROOT":      &envVars.goroot,
   929  		"GOPRIVATE":   &envVars.goprivate,
   930  		"GOMODCACHE":  &envVars.gomodcache,
   931  		"GO111MODULE": &envVars.go111module,
   932  	}
   933  
   934  	// We can save ~200 ms by requesting only the variables we care about.
   935  	args := append([]string{"-json"}, imports.RequiredGoEnvVars...)
   936  	for k := range vars {
   937  		args = append(args, k)
   938  	}
   939  	args = append(args, "GOWORK")
   940  
   941  	inv := gocommand.Invocation{
   942  		Verb:       "env",
   943  		Args:       args,
   944  		Env:        configEnv,
   945  		WorkingDir: folder,
   946  	}
   947  	// Don't go through runGoCommand, as we don't need a temporary -modfile to
   948  	// run `go env`.
   949  	stdout, err := s.gocmdRunner.Run(ctx, inv)
   950  	if err != nil {
   951  		return environmentVariables{}, nil, err
   952  	}
   953  	env := make(map[string]string)
   954  	if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
   955  		return environmentVariables{}, nil, err
   956  	}
   957  
   958  	for key, ptr := range vars {
   959  		*ptr = env[key]
   960  	}
   961  
   962  	// Old versions of Go don't have GOMODCACHE, so emulate it.
   963  	if envVars.gomodcache == "" && envVars.gopath != "" {
   964  		envVars.gomodcache = filepath.Join(filepath.SplitList(envVars.gopath)[0], "pkg/mod")
   965  	}
   966  	// GO111MODULE does not appear in `go env` output until Go 1.13.
   967  	if goversion < 13 {
   968  		envVars.go111module = go111module
   969  	}
   970  	return envVars, env, err
   971  }
   972  
   973  func (v *View) IsGoPrivatePath(target string) bool {
   974  	return globsMatchPath(v.goprivate, target)
   975  }
   976  
   977  func (v *View) ModuleUpgrades() map[string]string {
   978  	v.mu.Lock()
   979  	defer v.mu.Unlock()
   980  
   981  	upgrades := map[string]string{}
   982  	for mod, ver := range v.moduleUpgrades {
   983  		upgrades[mod] = ver
   984  	}
   985  	return upgrades
   986  }
   987  
   988  func (v *View) RegisterModuleUpgrades(upgrades map[string]string) {
   989  	v.mu.Lock()
   990  	defer v.mu.Unlock()
   991  
   992  	for mod, ver := range upgrades {
   993  		v.moduleUpgrades[mod] = ver
   994  	}
   995  }
   996  
   997  // Copied from
   998  // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a
   999  func globsMatchPath(globs, target string) bool {
  1000  	for globs != "" {
  1001  		// Extract next non-empty glob in comma-separated list.
  1002  		var glob string
  1003  		if i := strings.Index(globs, ","); i >= 0 {
  1004  			glob, globs = globs[:i], globs[i+1:]
  1005  		} else {
  1006  			glob, globs = globs, ""
  1007  		}
  1008  		if glob == "" {
  1009  			continue
  1010  		}
  1011  
  1012  		// A glob with N+1 path elements (N slashes) needs to be matched
  1013  		// against the first N+1 path elements of target,
  1014  		// which end just before the N+1'th slash.
  1015  		n := strings.Count(glob, "/")
  1016  		prefix := target
  1017  		// Walk target, counting slashes, truncating at the N+1'th slash.
  1018  		for i := 0; i < len(target); i++ {
  1019  			if target[i] == '/' {
  1020  				if n == 0 {
  1021  					prefix = target[:i]
  1022  					break
  1023  				}
  1024  				n--
  1025  			}
  1026  		}
  1027  		if n > 0 {
  1028  			// Not enough prefix elements.
  1029  			continue
  1030  		}
  1031  		matched, _ := path.Match(glob, prefix)
  1032  		if matched {
  1033  			return true
  1034  		}
  1035  	}
  1036  	return false
  1037  }
  1038  
  1039  var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
  1040  
  1041  // TODO(rstambler): Consolidate modURI and modContent back into a FileHandle
  1042  // after we have a version of the workspace go.mod file on disk. Getting a
  1043  // FileHandle from the cache for temporary files is problematic, since we
  1044  // cannot delete it.
  1045  func (s *snapshot) vendorEnabled(ctx context.Context, modURI span.URI, modContent []byte) (bool, error) {
  1046  	if s.workspaceMode()&moduleMode == 0 {
  1047  		return false, nil
  1048  	}
  1049  	matches := modFlagRegexp.FindStringSubmatch(s.view.goEnv["GOFLAGS"])
  1050  	var modFlag string
  1051  	if len(matches) != 0 {
  1052  		modFlag = matches[1]
  1053  	}
  1054  	if modFlag != "" {
  1055  		// Don't override an explicit '-mod=vendor' argument.
  1056  		// We do want to override '-mod=readonly': it would break various module code lenses,
  1057  		// and on 1.16 we know -modfile is available, so we won't mess with go.mod anyway.
  1058  		return modFlag == "vendor", nil
  1059  	}
  1060  
  1061  	modFile, err := modfile.Parse(modURI.Filename(), modContent, nil)
  1062  	if err != nil {
  1063  		return false, err
  1064  	}
  1065  	if fi, err := os.Stat(filepath.Join(s.view.rootURI.Filename(), "vendor")); err != nil || !fi.IsDir() {
  1066  		return false, nil
  1067  	}
  1068  	vendorEnabled := modFile.Go != nil && modFile.Go.Version != "" && semver.Compare("v"+modFile.Go.Version, "v1.14") >= 0
  1069  	return vendorEnabled, nil
  1070  }
  1071  
  1072  func (v *View) allFilesExcluded(pkg *packages.Package) bool {
  1073  	opts := v.Options()
  1074  	folder := filepath.ToSlash(v.folder.Filename())
  1075  	for _, f := range pkg.GoFiles {
  1076  		f = filepath.ToSlash(f)
  1077  		if !strings.HasPrefix(f, folder) {
  1078  			return false
  1079  		}
  1080  		if !pathExcludedByFilter(strings.TrimPrefix(f, folder), v.rootURI.Filename(), v.gomodcache, opts) {
  1081  			return false
  1082  		}
  1083  	}
  1084  	return true
  1085  }
  1086  
  1087  func pathExcludedByFilterFunc(root, gomodcache string, opts *source.Options) func(string) bool {
  1088  	return func(path string) bool {
  1089  		return pathExcludedByFilter(path, root, gomodcache, opts)
  1090  	}
  1091  }
  1092  
  1093  // pathExcludedByFilter reports whether the path (relative to the workspace
  1094  // folder) should be excluded by the configured directory filters.
  1095  //
  1096  // TODO(rfindley): passing root and gomodcache here makes it confusing whether
  1097  // path should be absolute or relative, and has already caused at least one
  1098  // bug.
  1099  func pathExcludedByFilter(path, root, gomodcache string, opts *source.Options) bool {
  1100  	path = strings.TrimPrefix(filepath.ToSlash(path), "/")
  1101  	gomodcache = strings.TrimPrefix(filepath.ToSlash(strings.TrimPrefix(gomodcache, root)), "/")
  1102  	filters := opts.DirectoryFilters
  1103  	if gomodcache != "" {
  1104  		filters = append(filters, "-"+gomodcache)
  1105  	}
  1106  	return source.FiltersDisallow(path, filters)
  1107  }