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