cuelang.org/go@v0.10.1/internal/golangorgx/gopls/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 is the core of gopls: it is concerned with state 6 // management, dependency analysis, and invalidation; and it holds the 7 // machinery of type checking and modular static analysis. Its 8 // principal types are [Session], [Folder], [View], [Snapshot], 9 // [Cache], and [Package]. 10 package cache 11 12 import ( 13 "bytes" 14 "context" 15 "encoding/json" 16 "errors" 17 "fmt" 18 "os" 19 "os/exec" 20 "path" 21 "path/filepath" 22 "regexp" 23 "sort" 24 "strings" 25 "sync" 26 27 "cuelang.org/go/internal/golangorgx/gopls/cache/metadata" 28 "cuelang.org/go/internal/golangorgx/gopls/file" 29 "cuelang.org/go/internal/golangorgx/gopls/protocol" 30 "cuelang.org/go/internal/golangorgx/gopls/settings" 31 "cuelang.org/go/internal/golangorgx/gopls/util/maps" 32 "cuelang.org/go/internal/golangorgx/gopls/util/pathutil" 33 "cuelang.org/go/internal/golangorgx/gopls/util/slices" 34 "cuelang.org/go/internal/golangorgx/tools/event" 35 "cuelang.org/go/internal/golangorgx/tools/gocommand" 36 "cuelang.org/go/internal/golangorgx/tools/imports" 37 "cuelang.org/go/internal/golangorgx/tools/xcontext" 38 ) 39 40 // A Folder represents an LSP workspace folder, together with its per-folder 41 // options and environment variables that affect build configuration. 42 // 43 // Folders (Name and Dir) are specified by the 'initialize' and subsequent 44 // 'didChangeWorkspaceFolders' requests; their options come from 45 // didChangeConfiguration. 46 // 47 // Folders must not be mutated, as they may be shared across multiple views. 48 type Folder struct { 49 Dir protocol.DocumentURI 50 Name string // decorative name for UI; not necessarily unique 51 Options *settings.Options 52 Env *GoEnv 53 } 54 55 // GoEnv holds the environment variables and data from the Go command that is 56 // required for operating on a workspace folder. 57 type GoEnv struct { 58 // Go environment variables. These correspond directly with the Go env var of 59 // the same name. 60 GOOS string 61 GOARCH string 62 GOCACHE string 63 GOMODCACHE string 64 GOPATH string 65 GOPRIVATE string 66 GOFLAGS string 67 GO111MODULE string 68 69 // Go version output. 70 GoVersion int // The X in Go 1.X 71 GoVersionOutput string // complete go version output 72 73 // OS environment variables (notably not go env). 74 GOWORK string 75 GOPACKAGESDRIVER string 76 } 77 78 // View represents a single build for a workspace. 79 // 80 // A View is a logical build (the viewDefinition) along with a state of that 81 // build (the Snapshot). 82 type View struct { 83 id string // a unique string to identify this View in (e.g.) serialized Commands 84 85 *viewDefinition // build configuration 86 87 gocmdRunner *gocommand.Runner // limits go command concurrency 88 89 // baseCtx is the context handed to NewView. This is the parent of all 90 // background contexts created for this view. 91 baseCtx context.Context 92 93 importsState *importsState 94 95 // parseCache holds an LRU cache of recently parsed files. 96 parseCache *parseCache 97 98 // fs is the file source used to populate this view. 99 fs *overlayFS 100 101 // ignoreFilter is used for fast checking of ignored files. 102 ignoreFilter *ignoreFilter 103 104 // cancelInitialWorkspaceLoad can be used to terminate the view's first 105 // attempt at initialization. 106 cancelInitialWorkspaceLoad context.CancelFunc 107 108 snapshotMu sync.Mutex 109 snapshot *Snapshot // latest snapshot; nil after shutdown has been called 110 111 // initialWorkspaceLoad is closed when the first workspace initialization has 112 // completed. If we failed to load, we only retry if the go.mod file changes, 113 // to avoid too many go/packages calls. 114 initialWorkspaceLoad chan struct{} 115 116 // initializationSema is used limit concurrent initialization of snapshots in 117 // the view. We use a channel instead of a mutex to avoid blocking when a 118 // context is canceled. 119 // 120 // This field (along with snapshot.initialized) guards against duplicate 121 // initialization of snapshots. Do not change it without adjusting snapshot 122 // accordingly. 123 initializationSema chan struct{} 124 125 // Document filters are constructed once, in View.filterFunc. 126 filterFuncOnce sync.Once 127 _filterFunc func(protocol.DocumentURI) bool // only accessed by View.filterFunc 128 } 129 130 // definition implements the viewDefiner interface. 131 func (v *View) definition() *viewDefinition { return v.viewDefinition } 132 133 // A viewDefinition is a logical build, i.e. configuration (Folder) along with 134 // a build directory and possibly an environment overlay (e.g. GOWORK=off or 135 // GOOS, GOARCH=...) to affect the build. 136 // 137 // This type is immutable, and compared to see if the View needs to be 138 // reconstructed. 139 // 140 // Note: whenever modifying this type, also modify the equivalence relation 141 // implemented by viewDefinitionsEqual. 142 // 143 // TODO(golang/go#57979): viewDefinition should be sufficient for running 144 // go/packages. Enforce this in the API. 145 type viewDefinition struct { 146 folder *Folder // pointer comparison is OK, as any new Folder creates a new def 147 148 typ ViewType 149 root protocol.DocumentURI // root directory; where to run the Go command 150 gomod protocol.DocumentURI // the nearest go.mod file, or "" 151 gowork protocol.DocumentURI // the nearest go.work file, or "" 152 153 // workspaceModFiles holds the set of mod files active in this snapshot. 154 // 155 // For a go.work workspace, this is the set of workspace modfiles. For a 156 // go.mod workspace, this contains the go.mod file defining the workspace 157 // root, as well as any locally replaced modules (if 158 // "includeReplaceInWorkspace" is set). 159 // 160 // TODO(rfindley): should we just run `go list -m` to compute this set? 161 workspaceModFiles map[protocol.DocumentURI]struct{} 162 workspaceModFilesErr error // error encountered computing workspaceModFiles 163 164 // envOverlay holds additional environment to apply to this viewDefinition. 165 envOverlay map[string]string 166 } 167 168 // definition implements the viewDefiner interface. 169 func (d *viewDefinition) definition() *viewDefinition { return d } 170 171 // Type returns the ViewType type, which determines how go/packages are loaded 172 // for this View. 173 func (d *viewDefinition) Type() ViewType { return d.typ } 174 175 // Root returns the view root, which determines where packages are loaded from. 176 func (d *viewDefinition) Root() protocol.DocumentURI { return d.root } 177 178 // GoMod returns the nearest go.mod file for this view's root, or "". 179 func (d *viewDefinition) GoMod() protocol.DocumentURI { return d.gomod } 180 181 // GoWork returns the nearest go.work file for this view's root, or "". 182 func (d *viewDefinition) GoWork() protocol.DocumentURI { return d.gowork } 183 184 // EnvOverlay returns a new sorted slice of environment variables (in the form 185 // "k=v") for this view definition's env overlay. 186 func (d *viewDefinition) EnvOverlay() []string { 187 var env []string 188 for k, v := range d.envOverlay { 189 env = append(env, fmt.Sprintf("%s=%s", k, v)) 190 } 191 sort.Strings(env) 192 return env 193 } 194 195 // GOOS returns the effective GOOS value for this view definition, accounting 196 // for its env overlay. 197 func (d *viewDefinition) GOOS() string { 198 if goos, ok := d.envOverlay["GOOS"]; ok { 199 return goos 200 } 201 return d.folder.Env.GOOS 202 } 203 204 // GOOS returns the effective GOARCH value for this view definition, accounting 205 // for its env overlay. 206 func (d *viewDefinition) GOARCH() string { 207 if goarch, ok := d.envOverlay["GOARCH"]; ok { 208 return goarch 209 } 210 return d.folder.Env.GOARCH 211 } 212 213 // adjustedGO111MODULE is the value of GO111MODULE to use for loading packages. 214 // It is adjusted to default to "auto" rather than "on", since if we are in 215 // GOPATH and have no module, we may as well allow a GOPATH view to work. 216 func (d viewDefinition) adjustedGO111MODULE() string { 217 if d.folder.Env.GO111MODULE != "" { 218 return d.folder.Env.GO111MODULE 219 } 220 return "auto" 221 } 222 223 // ModFiles are the go.mod files enclosed in the snapshot's view and known 224 // to the snapshot. 225 func (d viewDefinition) ModFiles() []protocol.DocumentURI { 226 var uris []protocol.DocumentURI 227 for modURI := range d.workspaceModFiles { 228 uris = append(uris, modURI) 229 } 230 return uris 231 } 232 233 // viewDefinitionsEqual reports whether x and y are equivalent. 234 func viewDefinitionsEqual(x, y *viewDefinition) bool { 235 if (x.workspaceModFilesErr == nil) != (y.workspaceModFilesErr == nil) { 236 return false 237 } 238 if x.workspaceModFilesErr != nil { 239 if x.workspaceModFilesErr.Error() != y.workspaceModFilesErr.Error() { 240 return false 241 } 242 } else if !maps.SameKeys(x.workspaceModFiles, y.workspaceModFiles) { 243 return false 244 } 245 if len(x.envOverlay) != len(y.envOverlay) { 246 return false 247 } 248 for i, xv := range x.envOverlay { 249 if xv != y.envOverlay[i] { 250 return false 251 } 252 } 253 return x.folder == y.folder && 254 x.typ == y.typ && 255 x.root == y.root && 256 x.gomod == y.gomod && 257 x.gowork == y.gowork 258 } 259 260 // A ViewType describes how we load package information for a view. 261 // 262 // This is used for constructing the go/packages.Load query, and for 263 // interpreting missing packages, imports, or errors. 264 // 265 // See the documentation for individual ViewType values for details. 266 type ViewType int 267 268 const ( 269 // GoPackagesDriverView is a view with a non-empty GOPACKAGESDRIVER 270 // environment variable. 271 // 272 // Load: ./... from the workspace folder. 273 GoPackagesDriverView ViewType = iota 274 275 // GOPATHView is a view in GOPATH mode. 276 // 277 // I.e. in GOPATH, with GO111MODULE=off, or GO111MODULE=auto with no 278 // go.mod file. 279 // 280 // Load: ./... from the workspace folder. 281 GOPATHView 282 283 // GoModView is a view in module mode with a single Go module. 284 // 285 // Load: <modulePath>/... from the module root. 286 GoModView 287 288 // GoWorkView is a view in module mode with a go.work file. 289 // 290 // Load: <modulePath>/... from the workspace folder, for each module. 291 GoWorkView 292 293 // An AdHocView is a collection of files in a given directory, not in GOPATH 294 // or a module. 295 // 296 // Load: . from the workspace folder. 297 AdHocView 298 ) 299 300 func (t ViewType) String() string { 301 switch t { 302 case GoPackagesDriverView: 303 return "GoPackagesDriverView" 304 case GOPATHView: 305 return "GOPATHView" 306 case GoModView: 307 return "GoModView" 308 case GoWorkView: 309 return "GoWorkView" 310 case AdHocView: 311 return "AdHocView" 312 default: 313 return "Unknown" 314 } 315 } 316 317 // moduleMode reports whether the view uses Go modules. 318 func (w viewDefinition) moduleMode() bool { 319 switch w.typ { 320 case GoModView, GoWorkView: 321 return true 322 default: 323 return false 324 } 325 } 326 327 func (v *View) ID() string { return v.id } 328 329 // tempModFile creates a temporary go.mod file based on the contents 330 // of the given go.mod file. On success, it is the caller's 331 // responsibility to call the cleanup function when the file is no 332 // longer needed. 333 func tempModFile(modURI protocol.DocumentURI, gomod, gosum []byte) (tmpURI protocol.DocumentURI, cleanup func(), err error) { 334 filenameHash := file.HashOf([]byte(modURI.Path())) 335 tmpMod, err := os.CreateTemp("", fmt.Sprintf("go.%s.*.mod", filenameHash)) 336 if err != nil { 337 return "", nil, err 338 } 339 defer tmpMod.Close() 340 341 tmpURI = protocol.URIFromPath(tmpMod.Name()) 342 tmpSumName := sumFilename(tmpURI) 343 344 if _, err := tmpMod.Write(gomod); err != nil { 345 return "", nil, err 346 } 347 348 // We use a distinct name here to avoid subtlety around the fact 349 // that both 'return' and 'defer' update the "cleanup" variable. 350 doCleanup := func() { 351 _ = os.Remove(tmpSumName) 352 _ = os.Remove(tmpURI.Path()) 353 } 354 355 // Be careful to clean up if we return an error from this function. 356 defer func() { 357 if err != nil { 358 doCleanup() 359 cleanup = nil 360 } 361 }() 362 363 // Create an analogous go.sum, if one exists. 364 if gosum != nil { 365 if err := os.WriteFile(tmpSumName, gosum, 0655); err != nil { 366 return "", nil, err 367 } 368 } 369 370 return tmpURI, doCleanup, nil 371 } 372 373 // Folder returns the folder at the base of this view. 374 func (v *View) Folder() *Folder { 375 return v.folder 376 } 377 378 // UpdateFolders updates the set of views for the new folders. 379 // 380 // Calling this causes each view to be reinitialized. 381 func (s *Session) UpdateFolders(ctx context.Context, newFolders []*Folder) error { 382 s.viewMu.Lock() 383 defer s.viewMu.Unlock() 384 385 overlays := s.Overlays() 386 var openFiles []protocol.DocumentURI 387 for _, o := range overlays { 388 openFiles = append(openFiles, o.URI()) 389 } 390 391 defs, err := selectViewDefs(ctx, s, newFolders, openFiles) 392 if err != nil { 393 return err 394 } 395 var newViews []*View 396 for _, def := range defs { 397 v, _, release := s.createView(ctx, def) 398 release() 399 newViews = append(newViews, v) 400 } 401 for _, v := range s.views { 402 v.shutdown() 403 } 404 s.views = newViews 405 return nil 406 } 407 408 // viewEnv returns a string describing the environment of a newly created view. 409 // 410 // It must not be called concurrently with any other view methods. 411 // TODO(rfindley): rethink this function, or inline sole call. 412 func viewEnv(v *View) string { 413 var buf bytes.Buffer 414 fmt.Fprintf(&buf, `go info for %v 415 (view type %v) 416 (root dir %s) 417 (go version %s) 418 (build flags: %v) 419 (go env: %+v) 420 (env overlay: %v) 421 `, 422 v.folder.Dir.Path(), 423 v.typ, 424 v.root.Path(), 425 strings.TrimRight(v.folder.Env.GoVersionOutput, "\n"), 426 v.folder.Options.BuildFlags, 427 *v.folder.Env, 428 v.envOverlay, 429 ) 430 431 return buf.String() 432 } 433 434 // RunProcessEnvFunc runs fn with the process env for this snapshot's view. 435 // Note: the process env contains cached module and filesystem state. 436 func (s *Snapshot) RunProcessEnvFunc(ctx context.Context, fn func(context.Context, *imports.Options) error) error { 437 return s.view.importsState.runProcessEnvFunc(ctx, s, fn) 438 } 439 440 // separated out from its sole use in locateTemplateFiles for testability 441 func fileHasExtension(path string, suffixes []string) bool { 442 ext := filepath.Ext(path) 443 if ext != "" && ext[0] == '.' { 444 ext = ext[1:] 445 } 446 for _, s := range suffixes { 447 if s != "" && ext == s { 448 return true 449 } 450 } 451 return false 452 } 453 454 // locateTemplateFiles ensures that the snapshot has mapped template files 455 // within the workspace folder. 456 func (s *Snapshot) locateTemplateFiles(ctx context.Context) { 457 suffixes := s.Options().TemplateExtensions 458 if len(suffixes) == 0 { 459 return 460 } 461 462 searched := 0 463 filterFunc := s.view.filterFunc() 464 err := filepath.WalkDir(s.view.folder.Dir.Path(), func(path string, entry os.DirEntry, err error) error { 465 if err != nil { 466 return err 467 } 468 if entry.IsDir() { 469 return nil 470 } 471 if fileLimit > 0 && searched > fileLimit { 472 return errExhausted 473 } 474 searched++ 475 if !fileHasExtension(path, suffixes) { 476 return nil 477 } 478 uri := protocol.URIFromPath(path) 479 if filterFunc(uri) { 480 return nil 481 } 482 // Get the file in order to include it in the snapshot. 483 // TODO(golang/go#57558): it is fundamentally broken to track files in this 484 // way; we may lose them if configuration or layout changes cause a view to 485 // be recreated. 486 // 487 // Furthermore, this operation must ignore errors, including context 488 // cancellation, or risk leaving the snapshot in an undefined state. 489 s.ReadFile(ctx, uri) 490 return nil 491 }) 492 if err != nil { 493 event.Error(ctx, "searching for template files failed", err) 494 } 495 } 496 497 // filterFunc returns a func that reports whether uri is filtered by the currently configured 498 // directoryFilters. 499 func (v *View) filterFunc() func(protocol.DocumentURI) bool { 500 v.filterFuncOnce.Do(func() { 501 folderDir := v.folder.Dir.Path() 502 gomodcache := v.folder.Env.GOMODCACHE 503 var filters []string 504 filters = append(filters, v.folder.Options.DirectoryFilters...) 505 if pref := strings.TrimPrefix(gomodcache, folderDir); pref != gomodcache { 506 modcacheFilter := "-" + strings.TrimPrefix(filepath.ToSlash(pref), "/") 507 filters = append(filters, modcacheFilter) 508 } 509 filterer := NewFilterer(filters) 510 v._filterFunc = func(uri protocol.DocumentURI) bool { 511 // Only filter relative to the configured root directory. 512 if pathutil.InDir(folderDir, uri.Path()) { 513 return relPathExcludedByFilter(strings.TrimPrefix(uri.Path(), folderDir), filterer) 514 } 515 return false 516 } 517 }) 518 return v._filterFunc 519 } 520 521 // shutdown releases resources associated with the view. 522 func (v *View) shutdown() { 523 // Cancel the initial workspace load if it is still running. 524 v.cancelInitialWorkspaceLoad() 525 526 v.snapshotMu.Lock() 527 if v.snapshot != nil { 528 v.snapshot.cancel() 529 v.snapshot.decref() 530 v.snapshot = nil 531 } 532 v.snapshotMu.Unlock() 533 } 534 535 // IgnoredFile reports if a file would be ignored by a `go list` of the whole 536 // workspace. 537 // 538 // While go list ./... skips directories starting with '.', '_', or 'testdata', 539 // gopls may still load them via file queries. Explicitly filter them out. 540 func (s *Snapshot) IgnoredFile(uri protocol.DocumentURI) bool { 541 // Fast path: if uri doesn't contain '.', '_', or 'testdata', it is not 542 // possible that it is ignored. 543 { 544 uriStr := string(uri) 545 if !strings.Contains(uriStr, ".") && !strings.Contains(uriStr, "_") && !strings.Contains(uriStr, "testdata") { 546 return false 547 } 548 } 549 550 return s.view.ignoreFilter.ignored(uri.Path()) 551 } 552 553 // An ignoreFilter implements go list's exclusion rules via its 'ignored' method. 554 type ignoreFilter struct { 555 prefixes []string // root dirs, ending in filepath.Separator 556 } 557 558 // newIgnoreFilter returns a new ignoreFilter implementing exclusion rules 559 // relative to the provided directories. 560 func newIgnoreFilter(dirs []string) *ignoreFilter { 561 f := new(ignoreFilter) 562 for _, d := range dirs { 563 f.prefixes = append(f.prefixes, filepath.Clean(d)+string(filepath.Separator)) 564 } 565 return f 566 } 567 568 func (f *ignoreFilter) ignored(filename string) bool { 569 for _, prefix := range f.prefixes { 570 if suffix := strings.TrimPrefix(filename, prefix); suffix != filename { 571 if checkIgnored(suffix) { 572 return true 573 } 574 } 575 } 576 return false 577 } 578 579 // checkIgnored implements go list's exclusion rules. 580 // Quoting “go help list”: 581 // 582 // Directory and file names that begin with "." or "_" are ignored 583 // by the go tool, as are directories named "testdata". 584 func checkIgnored(suffix string) bool { 585 // Note: this could be further optimized by writing a HasSegment helper, a 586 // segment-boundary respecting variant of strings.Contains. 587 for _, component := range strings.Split(suffix, string(filepath.Separator)) { 588 if len(component) == 0 { 589 continue 590 } 591 if component[0] == '.' || component[0] == '_' || component == "testdata" { 592 return true 593 } 594 } 595 return false 596 } 597 598 // Snapshot returns the current snapshot for the view, and a 599 // release function that must be called when the Snapshot is 600 // no longer needed. 601 // 602 // The resulting error is non-nil if and only if the view is shut down, in 603 // which case the resulting release function will also be nil. 604 func (v *View) Snapshot() (*Snapshot, func(), error) { 605 v.snapshotMu.Lock() 606 defer v.snapshotMu.Unlock() 607 if v.snapshot == nil { 608 return nil, nil, errors.New("view is shutdown") 609 } 610 return v.snapshot, v.snapshot.Acquire(), nil 611 } 612 613 // initialize loads the metadata (and currently, file contents, due to 614 // golang/go#57558) for the main package query of the View, which depends on 615 // the view type (see ViewType). If s.initialized is already true, initialize 616 // is a no op. 617 // 618 // The first attempt--which populates the first snapshot for a new view--must 619 // be allowed to run to completion without being cancelled. 620 // 621 // Subsequent attempts are triggered by conditions where gopls can't enumerate 622 // specific packages that require reloading, such as a change to a go.mod file. 623 // These attempts may be cancelled, and then retried by a later call. 624 // 625 // Postcondition: if ctx was not cancelled, s.initialized is true, s.initialErr 626 // holds the error resulting from initialization, if any, and s.metadata holds 627 // the resulting metadata graph. 628 func (s *Snapshot) initialize(ctx context.Context, firstAttempt bool) { 629 // Acquire initializationSema, which is 630 // (in effect) a mutex with a timeout. 631 select { 632 case <-ctx.Done(): 633 return 634 case s.view.initializationSema <- struct{}{}: 635 } 636 637 defer func() { 638 <-s.view.initializationSema 639 }() 640 641 s.mu.Lock() 642 initialized := s.initialized 643 s.mu.Unlock() 644 645 if initialized { 646 return 647 } 648 649 defer func() { 650 if firstAttempt { 651 close(s.view.initialWorkspaceLoad) 652 } 653 }() 654 655 // TODO(rFindley): we should only locate template files on the first attempt, 656 // or guard it via a different mechanism. 657 s.locateTemplateFiles(ctx) 658 659 // Collect module paths to load by parsing go.mod files. If a module fails to 660 // parse, capture the parsing failure as a critical diagnostic. 661 var scopes []loadScope // scopes to load 662 var modDiagnostics []*Diagnostic // diagnostics for broken go.mod files 663 addError := func(uri protocol.DocumentURI, err error) { 664 modDiagnostics = append(modDiagnostics, &Diagnostic{ 665 URI: uri, 666 Severity: protocol.SeverityError, 667 Source: ListError, 668 Message: err.Error(), 669 }) 670 } 671 672 if len(s.view.workspaceModFiles) > 0 { 673 for modURI := range s.view.workspaceModFiles { 674 // Verify that the modfile is valid before trying to load it. 675 // 676 // TODO(rfindley): now that we no longer need to parse the modfile in 677 // order to load scope, we could move these diagnostics to a more general 678 // location where we diagnose problems with modfiles or the workspace. 679 // 680 // Be careful not to add context cancellation errors as critical module 681 // errors. 682 fh, err := s.ReadFile(ctx, modURI) 683 if err != nil { 684 if ctx.Err() != nil { 685 return 686 } 687 addError(modURI, err) 688 continue 689 } 690 parsed, err := s.ParseMod(ctx, fh) 691 if err != nil { 692 if ctx.Err() != nil { 693 return 694 } 695 addError(modURI, err) 696 continue 697 } 698 if parsed.File == nil || parsed.File.Module == nil { 699 addError(modURI, fmt.Errorf("no module path for %s", modURI)) 700 continue 701 } 702 moduleDir := filepath.Dir(modURI.Path()) 703 // Previously, we loaded <modulepath>/... for each module path, but that 704 // is actually incorrect when the pattern may match packages in more than 705 // one module. See golang/go#59458 for more details. 706 scopes = append(scopes, moduleLoadScope{dir: moduleDir, modulePath: parsed.File.Module.Mod.Path}) 707 } 708 } else { 709 scopes = append(scopes, viewLoadScope{}) 710 } 711 712 // If we're loading anything, ensure we also load builtin, 713 // since it provides fake definitions (and documentation) 714 // for types like int that are used everywhere. 715 if len(scopes) > 0 { 716 scopes = append(scopes, packageLoadScope("builtin")) 717 } 718 loadErr := s.load(ctx, true, scopes...) 719 720 // A failure is retryable if it may have been due to context cancellation, 721 // and this is not the initial workspace load (firstAttempt==true). 722 // 723 // The IWL runs on a detached context with a long (~10m) timeout, so 724 // if the context was canceled we consider loading to have failed 725 // permanently. 726 if loadErr != nil && ctx.Err() != nil && !firstAttempt { 727 return 728 } 729 730 var initialErr *InitializationError 731 switch { 732 case loadErr != nil && ctx.Err() != nil: 733 event.Error(ctx, fmt.Sprintf("initial workspace load: %v", loadErr), loadErr) 734 initialErr = &InitializationError{ 735 MainError: loadErr, 736 } 737 case loadErr != nil: 738 event.Error(ctx, "initial workspace load failed", loadErr) 739 extractedDiags := s.extractGoCommandErrors(ctx, loadErr) 740 initialErr = &InitializationError{ 741 MainError: loadErr, 742 Diagnostics: maps.Group(extractedDiags, byURI), 743 } 744 case s.view.workspaceModFilesErr != nil: 745 initialErr = &InitializationError{ 746 MainError: s.view.workspaceModFilesErr, 747 } 748 case len(modDiagnostics) > 0: 749 initialErr = &InitializationError{ 750 MainError: fmt.Errorf(modDiagnostics[0].Message), 751 } 752 } 753 754 s.mu.Lock() 755 defer s.mu.Unlock() 756 757 s.initialized = true 758 s.initialErr = initialErr 759 } 760 761 // A StateChange describes external state changes that may affect a snapshot. 762 // 763 // By far the most common of these is a change to file state, but a query of 764 // module upgrade information or vulnerabilities also affects gopls' behavior. 765 type StateChange struct { 766 Modifications []file.Modification // if set, the raw modifications originating this change 767 Files map[protocol.DocumentURI]file.Handle 768 ModuleUpgrades map[protocol.DocumentURI]map[string]string 769 GCDetails map[metadata.PackageID]bool // package -> whether or not we want details 770 } 771 772 // InvalidateView processes the provided state change, invalidating any derived 773 // results that depend on the changed state. 774 // 775 // The resulting snapshot is non-nil, representing the outcome of the state 776 // change. The second result is a function that must be called to release the 777 // snapshot when the snapshot is no longer needed. 778 // 779 // An error is returned if the given view is no longer active in the session. 780 func (s *Session) InvalidateView(ctx context.Context, view *View, changed StateChange) (*Snapshot, func(), error) { 781 s.viewMu.Lock() 782 defer s.viewMu.Unlock() 783 784 if !slices.Contains(s.views, view) { 785 return nil, nil, fmt.Errorf("view is no longer active") 786 } 787 snapshot, release, _ := s.invalidateViewLocked(ctx, view, changed) 788 return snapshot, release, nil 789 } 790 791 // invalidateViewLocked invalidates the content of the given view. 792 // (See [Session.InvalidateView]). 793 // 794 // The resulting bool reports whether the View needs to be re-diagnosed. 795 // (See [Snapshot.clone]). 796 // 797 // s.viewMu must be held while calling this method. 798 func (s *Session) invalidateViewLocked(ctx context.Context, v *View, changed StateChange) (*Snapshot, func(), bool) { 799 // Detach the context so that content invalidation cannot be canceled. 800 ctx = xcontext.Detach(ctx) 801 802 // This should be the only time we hold the view's snapshot lock for any period of time. 803 v.snapshotMu.Lock() 804 defer v.snapshotMu.Unlock() 805 806 prevSnapshot := v.snapshot 807 808 if prevSnapshot == nil { 809 panic("invalidateContent called after shutdown") 810 } 811 812 // Cancel all still-running previous requests, since they would be 813 // operating on stale data. 814 prevSnapshot.cancel() 815 816 // Do not clone a snapshot until its view has finished initializing. 817 // 818 // TODO(rfindley): shouldn't we do this before canceling? 819 prevSnapshot.AwaitInitialized(ctx) 820 821 var needsDiagnosis bool 822 s.snapshotWG.Add(1) 823 v.snapshot, needsDiagnosis = prevSnapshot.clone(ctx, v.baseCtx, changed, s.snapshotWG.Done) 824 825 // Remove the initial reference created when prevSnapshot was created. 826 prevSnapshot.decref() 827 828 // Return a second lease to the caller. 829 return v.snapshot, v.snapshot.Acquire(), needsDiagnosis 830 } 831 832 // defineView computes the view definition for the provided workspace folder 833 // and URI. 834 // 835 // If forURI is non-empty, this view should be the best view including forURI. 836 // Otherwise, it is the default view for the folder. 837 // 838 // defineView only returns an error in the event of context cancellation. 839 // 840 // Note: keep this function in sync with bestView. 841 // 842 // TODO(rfindley): we should be able to remove the error return, as 843 // findModules is going away, and all other I/O is memoized. 844 // 845 // TODO(rfindley): pass in a narrower interface for the file.Source 846 // (e.g. fileExists func(DocumentURI) bool) to make clear that this 847 // process depends only on directory information, not file contents. 848 func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile file.Handle) (*viewDefinition, error) { 849 if err := checkPathValid(folder.Dir.Path()); err != nil { 850 return nil, fmt.Errorf("invalid workspace folder path: %w; check that the spelling of the configured workspace folder path agrees with the spelling reported by the operating system", err) 851 } 852 dir := folder.Dir.Path() 853 if forFile != nil { 854 dir = filepath.Dir(forFile.URI().Path()) 855 } 856 857 def := new(viewDefinition) 858 def.folder = folder 859 860 if forFile != nil && fileKind(forFile) == file.Go { 861 // If the file has GOOS/GOARCH build constraints that 862 // don't match the folder's environment (which comes from 863 // 'go env' in the folder, plus user options), 864 // add those constraints to the viewDefinition's environment. 865 866 // Content trimming is nontrivial, so do this outside of the loop below. 867 // Keep this in sync with bestView. 868 path := forFile.URI().Path() 869 if content, err := forFile.Content(); err == nil { 870 // Note the err == nil condition above: by convention a non-existent file 871 // does not have any constraints. See the related note in bestView: this 872 // choice of behavior shouldn't actually matter. In this case, we should 873 // only call defineView with Overlays, which always have content. 874 content = trimContentForPortMatch(content) 875 viewPort := port{def.folder.Env.GOOS, def.folder.Env.GOARCH} 876 if !viewPort.matches(path, content) { 877 for _, p := range preferredPorts { 878 if p.matches(path, content) { 879 if def.envOverlay == nil { 880 def.envOverlay = make(map[string]string) 881 } 882 def.envOverlay["GOOS"] = p.GOOS 883 def.envOverlay["GOARCH"] = p.GOARCH 884 break 885 } 886 } 887 } 888 } 889 } 890 891 var err error 892 dirURI := protocol.URIFromPath(dir) 893 goworkFromEnv := false 894 if folder.Env.GOWORK != "off" && folder.Env.GOWORK != "" { 895 goworkFromEnv = true 896 def.gowork = protocol.URIFromPath(folder.Env.GOWORK) 897 } else { 898 def.gowork, err = findRootPattern(ctx, dirURI, "go.work", fs) 899 if err != nil { 900 return nil, err 901 } 902 } 903 904 // When deriving the best view for a given file, we only want to search 905 // up the directory hierarchy for modfiles. 906 def.gomod, err = findRootPattern(ctx, dirURI, "go.mod", fs) 907 if err != nil { 908 return nil, err 909 } 910 911 // Determine how we load and where to load package information for this view 912 // 913 // Specifically, set 914 // - def.typ 915 // - def.root 916 // - def.workspaceModFiles, and 917 // - def.envOverlay. 918 919 // If GOPACKAGESDRIVER is set it takes precedence. 920 { 921 // The value of GOPACKAGESDRIVER is not returned through the go command. 922 gopackagesdriver := os.Getenv("GOPACKAGESDRIVER") 923 // A user may also have a gopackagesdriver binary on their machine, which 924 // works the same way as setting GOPACKAGESDRIVER. 925 // 926 // TODO(rfindley): remove this call to LookPath. We should not support this 927 // undocumented method of setting GOPACKAGESDRIVER. 928 tool, err := exec.LookPath("gopackagesdriver") 929 if gopackagesdriver != "off" && (gopackagesdriver != "" || (err == nil && tool != "")) { 930 def.typ = GoPackagesDriverView 931 def.root = dirURI 932 return def, nil 933 } 934 } 935 936 // From go.dev/ref/mod, module mode is active if GO111MODULE=on, or 937 // GO111MODULE=auto or "" and we are inside a module or have a GOWORK value. 938 // But gopls is less strict, allowing GOPATH mode if GO111MODULE="", and 939 // AdHoc views if no module is found. 940 941 // gomodWorkspace is a helper to compute the correct set of workspace 942 // modfiles for a go.mod file, based on folder options. 943 gomodWorkspace := func() map[protocol.DocumentURI]unit { 944 modFiles := map[protocol.DocumentURI]struct{}{def.gomod: {}} 945 if folder.Options.IncludeReplaceInWorkspace { 946 includingReplace, err := goModModules(ctx, def.gomod, fs) 947 if err == nil { 948 modFiles = includingReplace 949 } else { 950 // If the go.mod file fails to parse, we don't know anything about 951 // replace directives, so fall back to a view of just the root module. 952 } 953 } 954 return modFiles 955 } 956 957 // Prefer a go.work file if it is available and contains the module relevant 958 // to forURI. 959 if def.adjustedGO111MODULE() != "off" && folder.Env.GOWORK != "off" && def.gowork != "" { 960 def.typ = GoWorkView 961 if goworkFromEnv { 962 // The go.work file could be anywhere, which can lead to confusing error 963 // messages. 964 def.root = dirURI 965 } else { 966 // The go.work file could be anywhere, which can lead to confusing error 967 def.root = def.gowork.Dir() 968 } 969 def.workspaceModFiles, def.workspaceModFilesErr = goWorkModules(ctx, def.gowork, fs) 970 971 // If forURI is in a module but that module is not 972 // included in the go.work file, use a go.mod view with GOWORK=off. 973 if forFile != nil && def.workspaceModFilesErr == nil && def.gomod != "" { 974 if _, ok := def.workspaceModFiles[def.gomod]; !ok { 975 def.typ = GoModView 976 def.root = def.gomod.Dir() 977 def.workspaceModFiles = gomodWorkspace() 978 if def.envOverlay == nil { 979 def.envOverlay = make(map[string]string) 980 } 981 def.envOverlay["GOWORK"] = "off" 982 } 983 } 984 return def, nil 985 } 986 987 // Otherwise, use the active module, if in module mode. 988 // 989 // Note, we could override GO111MODULE here via envOverlay if we wanted to 990 // support the case where someone opens a module with GO111MODULE=off. But 991 // that is probably not worth worrying about (at this point, folks probably 992 // shouldn't be setting GO111MODULE). 993 if def.adjustedGO111MODULE() != "off" && def.gomod != "" { 994 def.typ = GoModView 995 def.root = def.gomod.Dir() 996 def.workspaceModFiles = gomodWorkspace() 997 return def, nil 998 } 999 1000 // Check if the workspace is within any GOPATH directory. 1001 inGOPATH := false 1002 for _, gp := range filepath.SplitList(folder.Env.GOPATH) { 1003 if pathutil.InDir(filepath.Join(gp, "src"), dir) { 1004 inGOPATH = true 1005 break 1006 } 1007 } 1008 if def.adjustedGO111MODULE() != "on" && inGOPATH { 1009 def.typ = GOPATHView 1010 def.root = dirURI 1011 return def, nil 1012 } 1013 1014 // We're not in a workspace, module, or GOPATH, so have no better choice than 1015 // an ad-hoc view. 1016 def.typ = AdHocView 1017 def.root = dirURI 1018 return def, nil 1019 } 1020 1021 // FetchGoEnv queries the environment and Go command to collect environment 1022 // variables necessary for the workspace folder. 1023 func FetchGoEnv(ctx context.Context, folder protocol.DocumentURI, opts *settings.Options) (*GoEnv, error) { 1024 dir := folder.Path() 1025 // All of the go commands invoked here should be fast. No need to share a 1026 // runner with other operations. 1027 runner := new(gocommand.Runner) 1028 inv := gocommand.Invocation{ 1029 WorkingDir: dir, 1030 Env: opts.EnvSlice(), 1031 } 1032 1033 var ( 1034 env = new(GoEnv) 1035 err error 1036 ) 1037 envvars := map[string]*string{ 1038 "GOOS": &env.GOOS, 1039 "GOARCH": &env.GOARCH, 1040 "GOCACHE": &env.GOCACHE, 1041 "GOPATH": &env.GOPATH, 1042 "GOPRIVATE": &env.GOPRIVATE, 1043 "GOMODCACHE": &env.GOMODCACHE, 1044 "GOFLAGS": &env.GOFLAGS, 1045 "GO111MODULE": &env.GO111MODULE, 1046 } 1047 if err := loadGoEnv(ctx, dir, opts.EnvSlice(), runner, envvars); err != nil { 1048 return nil, err 1049 } 1050 1051 env.GoVersion, err = gocommand.GoVersion(ctx, inv, runner) 1052 if err != nil { 1053 return nil, err 1054 } 1055 env.GoVersionOutput, err = gocommand.GoVersionOutput(ctx, inv, runner) 1056 if err != nil { 1057 return nil, err 1058 } 1059 1060 // The value of GOPACKAGESDRIVER is not returned through the go command. 1061 if driver, ok := opts.Env["GOPACKAGESDRIVER"]; ok { 1062 env.GOPACKAGESDRIVER = driver 1063 } else { 1064 env.GOPACKAGESDRIVER = os.Getenv("GOPACKAGESDRIVER") 1065 // A user may also have a gopackagesdriver binary on their machine, which 1066 // works the same way as setting GOPACKAGESDRIVER. 1067 // 1068 // TODO(rfindley): remove this call to LookPath. We should not support this 1069 // undocumented method of setting GOPACKAGESDRIVER. 1070 if env.GOPACKAGESDRIVER == "" { 1071 tool, err := exec.LookPath("gopackagesdriver") 1072 if err == nil && tool != "" { 1073 env.GOPACKAGESDRIVER = tool 1074 } 1075 } 1076 } 1077 1078 // While GOWORK is available through the Go command, we want to differentiate 1079 // between an explicit GOWORK value and one which is implicit from the file 1080 // system. The former doesn't change unless the environment changes. 1081 if gowork, ok := opts.Env["GOWORK"]; ok { 1082 env.GOWORK = gowork 1083 } else { 1084 env.GOWORK = os.Getenv("GOWORK") 1085 } 1086 return env, nil 1087 } 1088 1089 // loadGoEnv loads `go env` values into the provided map, keyed by Go variable 1090 // name. 1091 func loadGoEnv(ctx context.Context, dir string, configEnv []string, runner *gocommand.Runner, vars map[string]*string) error { 1092 // We can save ~200 ms by requesting only the variables we care about. 1093 args := []string{"-json"} 1094 for k := range vars { 1095 args = append(args, k) 1096 } 1097 1098 inv := gocommand.Invocation{ 1099 Verb: "env", 1100 Args: args, 1101 Env: configEnv, 1102 WorkingDir: dir, 1103 } 1104 stdout, err := runner.Run(ctx, inv) 1105 if err != nil { 1106 return err 1107 } 1108 envMap := make(map[string]string) 1109 if err := json.Unmarshal(stdout.Bytes(), &envMap); err != nil { 1110 return fmt.Errorf("internal error unmarshaling JSON from 'go env': %w", err) 1111 } 1112 for key, ptr := range vars { 1113 *ptr = envMap[key] 1114 } 1115 1116 return nil 1117 } 1118 1119 // findRootPattern looks for files with the given basename in dir or any parent 1120 // directory of dir, using the provided FileSource. It returns the first match, 1121 // starting from dir and search parents. 1122 // 1123 // The resulting string is either the file path of a matching file with the 1124 // given basename, or "" if none was found. 1125 // 1126 // findRootPattern only returns an error in the case of context cancellation. 1127 func findRootPattern(ctx context.Context, dirURI protocol.DocumentURI, basename string, fs file.Source) (protocol.DocumentURI, error) { 1128 dir := dirURI.Path() 1129 for dir != "" { 1130 target := filepath.Join(dir, basename) 1131 uri := protocol.URIFromPath(target) 1132 fh, err := fs.ReadFile(ctx, uri) 1133 if err != nil { 1134 return "", err // context cancelled 1135 } 1136 if fileExists(fh) { 1137 return uri, nil 1138 } 1139 // Trailing separators must be trimmed, otherwise filepath.Split is a noop. 1140 next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator))) 1141 if next == dir { 1142 break 1143 } 1144 dir = next 1145 } 1146 return "", nil 1147 } 1148 1149 // checkPathValid performs an OS-specific path validity check. The 1150 // implementation varies for filesystems that are case-insensitive 1151 // (e.g. macOS, Windows), and for those that disallow certain file 1152 // names (e.g. path segments ending with a period on Windows, or 1153 // reserved names such as "com"; see 1154 // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file). 1155 var checkPathValid = defaultCheckPathValid 1156 1157 // CheckPathValid checks whether a directory is suitable as a workspace folder. 1158 func CheckPathValid(dir string) error { return checkPathValid(dir) } 1159 1160 func defaultCheckPathValid(path string) error { 1161 return nil 1162 } 1163 1164 // IsGoPrivatePath reports whether target is a private import path, as identified 1165 // by the GOPRIVATE environment variable. 1166 func (s *Snapshot) IsGoPrivatePath(target string) bool { 1167 return globsMatchPath(s.view.folder.Env.GOPRIVATE, target) 1168 } 1169 1170 // ModuleUpgrades returns known module upgrades for the dependencies of 1171 // modfile. 1172 func (s *Snapshot) ModuleUpgrades(modfile protocol.DocumentURI) map[string]string { 1173 s.mu.Lock() 1174 defer s.mu.Unlock() 1175 upgrades := map[string]string{} 1176 orig, _ := s.moduleUpgrades.Get(modfile) 1177 for mod, ver := range orig { 1178 upgrades[mod] = ver 1179 } 1180 return upgrades 1181 } 1182 1183 // GoVersion returns the effective release Go version (the X in go1.X) for this 1184 // view. 1185 func (v *View) GoVersion() int { 1186 return v.folder.Env.GoVersion 1187 } 1188 1189 // GoVersionString returns the effective Go version string for this view. 1190 // 1191 // Unlike [GoVersion], this encodes the minor version and commit hash information. 1192 func (v *View) GoVersionString() string { 1193 return gocommand.ParseGoVersionOutput(v.folder.Env.GoVersionOutput) 1194 } 1195 1196 // GoVersionString is temporarily available from the snapshot. 1197 // 1198 // TODO(rfindley): refactor so that this method is not necessary. 1199 func (s *Snapshot) GoVersionString() string { 1200 return s.view.GoVersionString() 1201 } 1202 1203 // Copied from 1204 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a 1205 func globsMatchPath(globs, target string) bool { 1206 for globs != "" { 1207 // Extract next non-empty glob in comma-separated list. 1208 var glob string 1209 if i := strings.Index(globs, ","); i >= 0 { 1210 glob, globs = globs[:i], globs[i+1:] 1211 } else { 1212 glob, globs = globs, "" 1213 } 1214 if glob == "" { 1215 continue 1216 } 1217 1218 // A glob with N+1 path elements (N slashes) needs to be matched 1219 // against the first N+1 path elements of target, 1220 // which end just before the N+1'th slash. 1221 n := strings.Count(glob, "/") 1222 prefix := target 1223 // Walk target, counting slashes, truncating at the N+1'th slash. 1224 for i := 0; i < len(target); i++ { 1225 if target[i] == '/' { 1226 if n == 0 { 1227 prefix = target[:i] 1228 break 1229 } 1230 n-- 1231 } 1232 } 1233 if n > 0 { 1234 // Not enough prefix elements. 1235 continue 1236 } 1237 matched, _ := path.Match(glob, prefix) 1238 if matched { 1239 return true 1240 } 1241 } 1242 return false 1243 } 1244 1245 var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) 1246 1247 // TODO(rfindley): clean up the redundancy of allFilesExcluded, 1248 // pathExcludedByFilterFunc, pathExcludedByFilter, view.filterFunc... 1249 func allFilesExcluded(files []string, filterFunc func(protocol.DocumentURI) bool) bool { 1250 for _, f := range files { 1251 uri := protocol.URIFromPath(f) 1252 if !filterFunc(uri) { 1253 return false 1254 } 1255 } 1256 return true 1257 } 1258 1259 func relPathExcludedByFilter(path string, filterer *Filterer) bool { 1260 path = strings.TrimPrefix(filepath.ToSlash(path), "/") 1261 return filterer.Disallow(path) 1262 }