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