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