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 }