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.
6package cache
7
8import (
9	"context"
10	"encoding/json"
11	"fmt"
12	"go/ast"
13	"io"
14	"io/ioutil"
15	"os"
16	"path/filepath"
17	"reflect"
18	"regexp"
19	"strings"
20	"sync"
21	"time"
22
23	"golang.org/x/tools/go/packages"
24	"golang.org/x/tools/internal/imports"
25	"golang.org/x/tools/internal/lsp/debug"
26	"golang.org/x/tools/internal/lsp/source"
27	"golang.org/x/tools/internal/lsp/telemetry"
28	"golang.org/x/tools/internal/memoize"
29	"golang.org/x/tools/internal/span"
30	"golang.org/x/tools/internal/telemetry/log"
31	"golang.org/x/tools/internal/telemetry/tag"
32	"golang.org/x/tools/internal/xcontext"
33	errors "golang.org/x/xerrors"
34)
35
36type view struct {
37	session *session
38	id      string
39
40	options source.Options
41
42	// mu protects most mutable state of the view.
43	mu sync.Mutex
44
45	// baseCtx is the context handed to NewView. This is the parent of all
46	// background contexts created for this view.
47	baseCtx context.Context
48
49	// backgroundCtx is the current context used by background tasks initiated
50	// by the view.
51	backgroundCtx context.Context
52
53	// cancel is called when all action being performed by the current view
54	// should be stopped.
55	cancel context.CancelFunc
56
57	// Name is the user visible name of this view.
58	name string
59
60	// Folder is the root of this view.
61	folder span.URI
62
63	// importsMu guards imports-related state, particularly the ProcessEnv.
64	importsMu sync.Mutex
65	// process is the process env for this view.
66	// Note: this contains cached module and filesystem state.
67	//
68	// TODO(suzmue): the state cached in the process env is specific to each view,
69	// however, there is state that can be shared between views that is not currently
70	// cached, like the module cache.
71	processEnv           *imports.ProcessEnv
72	cacheRefreshDuration time.Duration
73	cacheRefreshTimer    *time.Timer
74	cachedModFileVersion source.FileIdentity
75
76	// keep track of files by uri and by basename, a single file may be mapped
77	// to multiple uris, and the same basename may map to multiple files
78	filesByURI  map[span.URI]*fileBase
79	filesByBase map[string][]*fileBase
80
81	snapshotMu sync.Mutex
82	snapshot   *snapshot
83
84	// ignoredURIs is the set of URIs of files that we ignore.
85	ignoredURIsMu sync.Mutex
86	ignoredURIs   map[span.URI]struct{}
87
88	// initialized is closed when the view has been fully initialized.
89	// On initialization, the view's workspace packages are loaded.
90	// All of the fields below are set as part of initialization.
91	// If we failed to load, we don't re-try to avoid too many go/packages calls.
92	initializeOnce sync.Once
93	initialized    chan struct{}
94
95	// builtin pins the AST and package for builtin.go in memory.
96	builtin *builtinPackageHandle
97
98	// True if the view is either in GOPATH, a module, or some other
99	// non go command build system.
100	hasValidBuildConfiguration bool
101
102	// The real and temporary go.mod files that are attributed to a view.
103	// The temporary go.mod is for use with the Go command's -modfile flag.
104	realMod, tempMod span.URI
105
106	// goCommand indicates if the user is using the go command or some other
107	// build system.
108	goCommand bool
109
110	// `go env` variables that need to be tracked.
111	gopath, gocache string
112
113	// LoadMu guards packages.Load calls and associated state.
114	loadMu         sync.Mutex
115	serializeLoads int
116}
117
118type builtinPackageHandle struct {
119	handle *memoize.Handle
120	file   source.ParseGoHandle
121}
122
123type builtinPackageData struct {
124	memoize.NoCopy
125
126	pkg *ast.Package
127	err error
128}
129
130// fileBase holds the common functionality for all files.
131// It is intended to be embedded in the file implementations
132type fileBase struct {
133	uris  []span.URI
134	fname string
135
136	view *view
137}
138
139func (f *fileBase) URI() span.URI {
140	return f.uris[0]
141}
142
143func (f *fileBase) filename() string {
144	return f.fname
145}
146
147func (f *fileBase) addURI(uri span.URI) int {
148	f.uris = append(f.uris, uri)
149	return len(f.uris)
150}
151
152func (v *view) ValidBuildConfiguration() bool {
153	return v.hasValidBuildConfiguration
154}
155
156func (v *view) ModFiles() (span.URI, span.URI) {
157	return v.realMod, v.tempMod
158}
159
160func (v *view) Session() source.Session {
161	return v.session
162}
163
164// Name returns the user visible name of this view.
165func (v *view) Name() string {
166	return v.name
167}
168
169// Folder returns the root of this view.
170func (v *view) Folder() span.URI {
171	return v.folder
172}
173
174func (v *view) Options() source.Options {
175	return v.options
176}
177
178func minorOptionsChange(a, b source.Options) bool {
179	// Check if any of the settings that modify our understanding of files have been changed
180	if !reflect.DeepEqual(a.Env, b.Env) {
181		return false
182	}
183	if !reflect.DeepEqual(a.BuildFlags, b.BuildFlags) {
184		return false
185	}
186	// the rest of the options are benign
187	return true
188}
189
190func (v *view) SetOptions(ctx context.Context, options source.Options) (source.View, error) {
191	// no need to rebuild the view if the options were not materially changed
192	if minorOptionsChange(v.options, options) {
193		v.options = options
194		return v, nil
195	}
196	newView, _, err := v.session.updateView(ctx, v, options)
197	return newView, err
198}
199
200func (v *view) Rebuild(ctx context.Context) (source.Snapshot, error) {
201	_, snapshot, err := v.session.updateView(ctx, v, v.options)
202	return snapshot, err
203}
204
205func (v *view) LookupBuiltin(ctx context.Context, name string) (*ast.Object, error) {
206	v.awaitInitialized(ctx)
207
208	if v.builtin == nil {
209		return nil, errors.Errorf("no builtin package for view %s", v.name)
210	}
211	data := v.builtin.handle.Get(ctx)
212	if ctx.Err() != nil {
213		return nil, ctx.Err()
214	}
215	if data == nil {
216		return nil, errors.Errorf("unexpected nil builtin package")
217	}
218	d, ok := data.(*builtinPackageData)
219	if !ok {
220		return nil, errors.Errorf("unexpected type %T", data)
221	}
222	if d.err != nil {
223		return nil, d.err
224	}
225	if d.pkg == nil || d.pkg.Scope == nil {
226		return nil, errors.Errorf("no builtin package")
227	}
228	astObj := d.pkg.Scope.Lookup(name)
229	if astObj == nil {
230		return nil, errors.Errorf("no builtin object for %s", name)
231	}
232	return astObj, nil
233}
234
235func (v *view) buildBuiltinPackage(ctx context.Context, goFiles []string) error {
236	if len(goFiles) != 1 {
237		return errors.Errorf("only expected 1 file, got %v", len(goFiles))
238	}
239	uri := span.URIFromPath(goFiles[0])
240	v.addIgnoredFile(uri) // to avoid showing diagnostics for builtin.go
241
242	// Get the FileHandle through the cache to avoid adding it to the snapshot
243	// and to get the file content from disk.
244	pgh := v.session.cache.ParseGoHandle(v.session.cache.GetFile(uri), source.ParseFull)
245	fset := v.session.cache.fset
246	h := v.session.cache.store.Bind(pgh.File().Identity(), func(ctx context.Context) interface{} {
247		data := &builtinPackageData{}
248		file, _, _, _, err := pgh.Parse(ctx)
249		if err != nil {
250			data.err = err
251			return data
252		}
253		data.pkg, data.err = ast.NewPackage(fset, map[string]*ast.File{
254			pgh.File().Identity().URI.Filename(): file,
255		}, nil, nil)
256		return data
257	})
258	v.builtin = &builtinPackageHandle{
259		handle: h,
260		file:   pgh,
261	}
262	return nil
263}
264
265func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error {
266	v.importsMu.Lock()
267	defer v.importsMu.Unlock()
268
269	if v.processEnv == nil {
270		var err error
271		if v.processEnv, err = v.buildProcessEnv(ctx); err != nil {
272			return err
273		}
274	}
275
276	// In module mode, check if the mod file has changed.
277	if v.realMod != "" {
278		if mod := v.session.cache.GetFile(v.realMod); mod.Identity() != v.cachedModFileVersion {
279			v.processEnv.GetResolver().(*imports.ModuleResolver).ClearForNewMod()
280			v.cachedModFileVersion = mod.Identity()
281		}
282	}
283
284	// Run the user function.
285	opts := &imports.Options{
286		// Defaults.
287		AllErrors:  true,
288		Comments:   true,
289		Fragment:   true,
290		FormatOnly: false,
291		TabIndent:  true,
292		TabWidth:   8,
293		Env:        v.processEnv,
294	}
295
296	if err := fn(opts); err != nil {
297		return err
298	}
299
300	if v.cacheRefreshTimer == nil {
301		// Don't refresh more than twice per minute.
302		delay := 30 * time.Second
303		// Don't spend more than a couple percent of the time refreshing.
304		if adaptive := 50 * v.cacheRefreshDuration; adaptive > delay {
305			delay = adaptive
306		}
307		v.cacheRefreshTimer = time.AfterFunc(delay, v.refreshProcessEnv)
308	}
309
310	return nil
311}
312
313func (v *view) refreshProcessEnv() {
314	start := time.Now()
315
316	v.importsMu.Lock()
317	env := v.processEnv
318	env.GetResolver().ClearForNewScan()
319	v.importsMu.Unlock()
320
321	// We don't have a context handy to use for logging, so use the stdlib for now.
322	log.Print(v.baseCtx, "background imports cache refresh starting")
323	err := imports.PrimeCache(context.Background(), env)
324	log.Print(v.baseCtx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), tag.Of("Error", err))
325
326	v.importsMu.Lock()
327	v.cacheRefreshDuration = time.Since(start)
328	v.cacheRefreshTimer = nil
329	v.importsMu.Unlock()
330}
331
332func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error) {
333	env, buildFlags := v.env()
334	processEnv := &imports.ProcessEnv{
335		WorkingDir: v.folder.Filename(),
336		BuildFlags: buildFlags,
337		Logf: func(format string, args ...interface{}) {
338			log.Print(ctx, fmt.Sprintf(format, args...))
339		},
340		LocalPrefix: v.options.LocalPrefix,
341		Debug:       v.options.VerboseOutput,
342	}
343	for _, kv := range env {
344		split := strings.Split(kv, "=")
345		if len(split) < 2 {
346			continue
347		}
348		switch split[0] {
349		case "GOPATH":
350			processEnv.GOPATH = split[1]
351		case "GOROOT":
352			processEnv.GOROOT = split[1]
353		case "GO111MODULE":
354			processEnv.GO111MODULE = split[1]
355		case "GOPROXY":
356			processEnv.GOPROXY = split[1]
357		case "GOFLAGS":
358			processEnv.GOFLAGS = split[1]
359		case "GOSUMDB":
360			processEnv.GOSUMDB = split[1]
361		}
362	}
363	return processEnv, nil
364}
365
366func (v *view) env() ([]string, []string) {
367	// We want to run the go commands with the -modfile flag if the version of go
368	// that we are using supports it.
369	buildFlags := v.options.BuildFlags
370	if v.tempMod != "" {
371		buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.tempMod.Filename()))
372	}
373	env := []string{fmt.Sprintf("GOPATH=%s", v.gopath)}
374	env = append(env, v.options.Env...)
375	return env, buildFlags
376}
377
378func (v *view) contains(uri span.URI) bool {
379	return strings.HasPrefix(string(uri), string(v.folder))
380}
381
382func (v *view) mapFile(uri span.URI, f *fileBase) {
383	v.filesByURI[uri] = f
384	if f.addURI(uri) == 1 {
385		basename := basename(f.filename())
386		v.filesByBase[basename] = append(v.filesByBase[basename], f)
387	}
388}
389
390func basename(filename string) string {
391	return strings.ToLower(filepath.Base(filename))
392}
393
394func (v *view) relevantChange(c source.FileModification) bool {
395	// If the file is known to the view, the change is relevant.
396	known := v.knownFile(c.URI)
397
398	// If the file is not known to the view, and the change is only on-disk,
399	// we should not invalidate the snapshot. This is necessary because Emacs
400	// sends didChangeWatchedFiles events for temp files.
401	if !known && c.OnDisk && (c.Action == source.Change || c.Action == source.Delete) {
402		return false
403	}
404	return v.contains(c.URI) || known
405}
406
407func (v *view) knownFile(uri span.URI) bool {
408	v.mu.Lock()
409	defer v.mu.Unlock()
410
411	f, err := v.findFile(uri)
412	return f != nil && err == nil
413}
414
415// getFile returns a file for the given URI. It will always succeed because it
416// adds the file to the managed set if needed.
417func (v *view) getFile(uri span.URI) (*fileBase, error) {
418	v.mu.Lock()
419	defer v.mu.Unlock()
420
421	f, err := v.findFile(uri)
422	if err != nil {
423		return nil, err
424	} else if f != nil {
425		return f, nil
426	}
427	f = &fileBase{
428		view:  v,
429		fname: uri.Filename(),
430	}
431	v.mapFile(uri, f)
432	return f, nil
433}
434
435// findFile checks the cache for any file matching the given uri.
436//
437// An error is only returned for an irreparable failure, for example, if the
438// filename in question does not exist.
439func (v *view) findFile(uri span.URI) (*fileBase, error) {
440	if f := v.filesByURI[uri]; f != nil {
441		// a perfect match
442		return f, nil
443	}
444	// no exact match stored, time to do some real work
445	// check for any files with the same basename
446	fname := uri.Filename()
447	basename := basename(fname)
448	if candidates := v.filesByBase[basename]; candidates != nil {
449		pathStat, err := os.Stat(fname)
450		if os.IsNotExist(err) {
451			return nil, err
452		}
453		if err != nil {
454			return nil, nil // the file may exist, return without an error
455		}
456		for _, c := range candidates {
457			if cStat, err := os.Stat(c.filename()); err == nil {
458				if os.SameFile(pathStat, cStat) {
459					// same file, map it
460					v.mapFile(uri, c)
461					return c, nil
462				}
463			}
464		}
465	}
466	// no file with a matching name was found, it wasn't in our cache
467	return nil, nil
468}
469
470func (v *view) Shutdown(ctx context.Context) {
471	v.session.removeView(ctx, v)
472}
473
474func (v *view) shutdown(context.Context) {
475	// TODO: Cancel the view's initialization.
476	v.mu.Lock()
477	defer v.mu.Unlock()
478	if v.cancel != nil {
479		v.cancel()
480		v.cancel = nil
481	}
482	if v.tempMod != "" {
483		os.Remove(v.tempMod.Filename())
484		os.Remove(tempSumFile(v.tempMod.Filename()))
485	}
486	debug.DropView(debugView{v})
487}
488
489// Ignore checks if the given URI is a URI we ignore.
490// As of right now, we only ignore files in the "builtin" package.
491func (v *view) Ignore(uri span.URI) bool {
492	v.ignoredURIsMu.Lock()
493	defer v.ignoredURIsMu.Unlock()
494
495	_, ok := v.ignoredURIs[uri]
496
497	// Files with _ prefixes are always ignored.
498	if !ok && strings.HasPrefix(filepath.Base(uri.Filename()), "_") {
499		v.ignoredURIs[uri] = struct{}{}
500		return true
501	}
502
503	return ok
504}
505
506func (v *view) addIgnoredFile(uri span.URI) {
507	v.ignoredURIsMu.Lock()
508	defer v.ignoredURIsMu.Unlock()
509
510	v.ignoredURIs[uri] = struct{}{}
511}
512
513func (v *view) BackgroundContext() context.Context {
514	v.mu.Lock()
515	defer v.mu.Unlock()
516
517	return v.backgroundCtx
518}
519
520func (v *view) Snapshot() source.Snapshot {
521	return v.getSnapshot()
522}
523
524func (v *view) getSnapshot() *snapshot {
525	v.snapshotMu.Lock()
526	defer v.snapshotMu.Unlock()
527
528	return v.snapshot
529}
530
531func (v *view) initialize(ctx context.Context, s *snapshot) {
532	v.initializeOnce.Do(func() {
533		defer close(v.initialized)
534
535		if err := s.load(ctx, viewLoadScope("LOAD_VIEW"), packagePath("builtin")); err != nil {
536			log.Error(ctx, "initial workspace load failed", err)
537		}
538	})
539}
540
541func (v *view) awaitInitialized(ctx context.Context) {
542	select {
543	case <-ctx.Done():
544	case <-v.initialized:
545	}
546}
547
548// invalidateContent invalidates the content of a Go file,
549// including any position and type information that depends on it.
550// It returns true if we were already tracking the given file, false otherwise.
551func (v *view) invalidateContent(ctx context.Context, uris map[span.URI]source.FileHandle) source.Snapshot {
552	// Detach the context so that content invalidation cannot be canceled.
553	ctx = xcontext.Detach(ctx)
554
555	// Cancel all still-running previous requests, since they would be
556	// operating on stale data.
557	v.cancelBackground()
558
559	// Do not clone a snapshot until its view has finished initializing.
560	v.awaitInitialized(ctx)
561
562	// This should be the only time we hold the view's snapshot lock for any period of time.
563	v.snapshotMu.Lock()
564	defer v.snapshotMu.Unlock()
565
566	v.snapshot = v.snapshot.clone(ctx, uris)
567	return v.snapshot
568}
569
570func (v *view) cancelBackground() {
571	v.mu.Lock()
572	defer v.mu.Unlock()
573
574	v.cancel()
575	v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
576}
577
578func (v *view) setBuildInformation(ctx context.Context, folder span.URI, env []string, modfileFlagEnabled bool) error {
579	// Make sure to get the `go env` before continuing with initialization.
580	gomod, err := v.getGoEnv(ctx, env)
581	if err != nil {
582		return err
583	}
584	modFile := strings.TrimSpace(gomod)
585	if modFile == os.DevNull {
586		return nil
587	}
588	v.realMod = span.URIFromPath(modFile)
589
590	// Now that we have set all required fields,
591	// check if the view has a valid build configuration.
592	v.hasValidBuildConfiguration = checkBuildConfiguration(v.goCommand, v.realMod, v.folder, v.gopath)
593
594	// The user has disabled the use of the -modfile flag or has no go.mod file.
595	if !modfileFlagEnabled || v.realMod == "" {
596		return nil
597	}
598	if modfileFlag, err := v.modfileFlagExists(ctx, v.Options().Env); err != nil {
599		return err
600	} else if !modfileFlag {
601		return nil
602	}
603	// Copy the current go.mod file into the temporary go.mod file.
604	// The file's name will be of the format go.1234.mod.
605	// It's temporary go.sum file should have the corresponding format of go.1234.sum.
606	tempModFile, err := ioutil.TempFile("", "go.*.mod")
607	if err != nil {
608		return err
609	}
610	defer tempModFile.Close()
611
612	origFile, err := os.Open(modFile)
613	if err != nil {
614		return err
615	}
616	defer origFile.Close()
617
618	if _, err := io.Copy(tempModFile, origFile); err != nil {
619		return err
620	}
621	v.tempMod = span.URIFromPath(tempModFile.Name())
622
623	// Copy go.sum file as well (if there is one).
624	sumFile := filepath.Join(filepath.Dir(modFile), "go.sum")
625	stat, err := os.Stat(sumFile)
626	if err != nil || !stat.Mode().IsRegular() {
627		return nil
628	}
629	contents, err := ioutil.ReadFile(sumFile)
630	if err != nil {
631		return err
632	}
633	if err := ioutil.WriteFile(tempSumFile(tempModFile.Name()), contents, stat.Mode()); err != nil {
634		return err
635	}
636	return nil
637}
638
639func checkBuildConfiguration(goCommand bool, mod, folder span.URI, gopath string) bool {
640	// Since we only really understand the `go` command, if the user is not
641	// using the go command, assume that their configuration is valid.
642	if !goCommand {
643		return true
644	}
645	// Check if the user is working within a module.
646	if mod != "" {
647		return true
648	}
649	// The user may have a multiple directories in their GOPATH.
650	// Check if the workspace is within any of them.
651	for _, gp := range filepath.SplitList(gopath) {
652		if isSubdirectory(filepath.Join(gp, "src"), folder.Filename()) {
653			return true
654		}
655	}
656	return false
657}
658
659func isSubdirectory(root, leaf string) bool {
660	rel, err := filepath.Rel(root, leaf)
661	return err == nil && !strings.HasPrefix(rel, "..")
662}
663
664// getGoEnv sets the view's build information's GOPATH, GOCACHE, and GOPACKAGESDRIVER values.
665// It also returns the view's GOMOD value, which need not be cached.
666func (v *view) getGoEnv(ctx context.Context, env []string) (string, error) {
667	var gocache, gopath, gopackagesdriver bool
668	isGoCommand := func(gopackagesdriver string) bool {
669		return gopackagesdriver == "" || gopackagesdriver == "off"
670	}
671	for _, e := range env {
672		split := strings.Split(e, "=")
673		if len(split) != 2 {
674			continue
675		}
676		switch split[0] {
677		case "GOCACHE":
678			v.gocache = split[1]
679			gocache = true
680		case "GOPATH":
681			v.gopath = split[1]
682			gopath = true
683		case "GOPACKAGESDRIVER":
684			v.goCommand = isGoCommand(split[1])
685			gopackagesdriver = true
686		}
687	}
688	b, err := source.InvokeGo(ctx, v.folder.Filename(), env, "env", "-json")
689	if err != nil {
690		return "", err
691	}
692	envMap := make(map[string]string)
693	decoder := json.NewDecoder(b)
694	if err := decoder.Decode(&envMap); err != nil {
695		return "", err
696	}
697	if !gopath {
698		if gopath, ok := envMap["GOPATH"]; ok {
699			v.gopath = gopath
700		} else {
701			return "", errors.New("unable to determine GOPATH")
702		}
703	}
704	if !gocache {
705		if gocache, ok := envMap["GOCACHE"]; ok {
706			v.gocache = gocache
707		} else {
708			return "", errors.New("unable to determine GOCACHE")
709		}
710	}
711	// The value of GOPACKAGESDRIVER is not returned through the go command.
712	if !gopackagesdriver {
713		v.goCommand = isGoCommand(os.Getenv("GOPACKAGESDRIVER"))
714	}
715	if gomod, ok := envMap["GOMOD"]; ok {
716		return gomod, nil
717	}
718	return "", nil
719}
720
721// 1.13: go: updates to go.mod needed, but contents have changed
722// 1.14: go: updating go.mod: existing contents have changed since last read
723var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
724
725// LoadPackages calls packages.Load, serializing requests if they fight over
726// go.mod changes.
727func (v *view) loadPackages(cfg *packages.Config, patterns ...string) ([]*packages.Package, error) {
728	// We want to run go list calls concurrently as much as possible. However,
729	// if go.mod updates are needed, only one can make them and the others will
730	// fail. We need to retry in those cases, but we don't want to thrash so
731	// badly we never recover. To avoid that, once we've seen one concurrency
732	// error, start serializing everything until the backlog has cleared out.
733	// This could all be avoided on 1.14 by using multiple -modfiles.
734
735	v.loadMu.Lock()
736	var locked bool // If true, we hold the mutex and have incremented.
737	if v.serializeLoads == 0 {
738		v.loadMu.Unlock()
739	} else {
740		locked = true
741		v.serializeLoads++
742	}
743	defer func() {
744		if locked {
745			v.serializeLoads--
746			v.loadMu.Unlock()
747		}
748	}()
749
750	for {
751		pkgs, err := packages.Load(cfg, patterns...)
752		if err == nil || !modConcurrencyError.MatchString(err.Error()) {
753			return pkgs, err
754		}
755
756		log.Error(cfg.Context, "Load concurrency error, will retry serially", err)
757		if !locked {
758			v.loadMu.Lock()
759			v.serializeLoads++
760			locked = true
761		}
762	}
763}
764
765// This function will return the main go.mod file for this folder if it exists and whether the -modfile
766// flag exists for this version of go.
767func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
768	// Check the go version by running "go list" with modules off.
769	// Borrowed from internal/imports/mod.go:620.
770	const format = `{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}`
771	folder := v.folder.Filename()
772	stdout, err := source.InvokeGo(ctx, folder, append(env, "GO111MODULE=off"), "list", "-e", "-f", format)
773	if err != nil {
774		return false, err
775	}
776	// If the output is not go1.14 or an empty string, then it could be an error.
777	lines := strings.Split(stdout.String(), "\n")
778	if len(lines) < 2 && stdout.String() != "" {
779		log.Error(ctx, "unexpected stdout when checking for go1.14", errors.Errorf("%q", stdout), telemetry.Directory.Of(folder))
780		return false, nil
781	}
782	return lines[0] == "go1.14", nil
783}
784
785// tempSumFile returns the path to the copied temporary go.sum file.
786// It simply replaces the extension of the temporary go.mod file with "sum".
787func tempSumFile(filename string) string {
788	if filename == "" {
789		return ""
790	}
791	return filename[:len(filename)-len("mod")] + "sum"
792}
793