cuelang.org/go@v0.13.0/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 "errors" 16 "fmt" 17 "path" 18 "path/filepath" 19 "regexp" 20 "slices" 21 "sort" 22 "strings" 23 "sync" 24 25 "cuelang.org/go/internal/golangorgx/gopls/cache/metadata" 26 "cuelang.org/go/internal/golangorgx/gopls/file" 27 "cuelang.org/go/internal/golangorgx/gopls/protocol" 28 "cuelang.org/go/internal/golangorgx/gopls/settings" 29 "cuelang.org/go/internal/golangorgx/gopls/util/maps" 30 "cuelang.org/go/internal/golangorgx/tools/event" 31 "cuelang.org/go/internal/golangorgx/tools/xcontext" 32 "cuelang.org/go/mod/modfile" 33 ) 34 35 // A Folder represents an LSP workspace folder, together with its per-folder 36 // options and environment variables that affect build configuration. 37 // 38 // Folders (Name and Dir) are specified by the 'initialize' and subsequent 39 // 'didChangeWorkspaceFolders' requests; their options come from 40 // didChangeConfiguration. 41 // 42 // Folders must not be mutated, as they may be shared across multiple views. 43 type Folder struct { 44 Dir protocol.DocumentURI 45 Name string // decorative name for UI; not necessarily unique 46 Options *settings.Options 47 } 48 49 // GoEnv holds the environment variables and data from the Go command that is 50 // required for operating on a workspace folder. 51 type GoEnv struct { 52 // Go environment variables. These correspond directly with the Go env var of 53 // the same name. 54 GOOS string 55 GOARCH string 56 GOCACHE string 57 GOMODCACHE string 58 GOPATH string 59 GOPRIVATE string 60 GOFLAGS string 61 GO111MODULE string 62 63 // Go version output. 64 GoVersion int // The X in Go 1.X 65 GoVersionOutput string // complete go version output 66 67 // OS environment variables (notably not go env). 68 GOWORK string 69 GOPACKAGESDRIVER string 70 } 71 72 // View represents a single build for a workspace. 73 // 74 // A View is a logical build (the viewDefinition) along with a state of that 75 // build (the Snapshot). 76 type View struct { 77 id string // a unique string to identify this View in (e.g.) serialized Commands 78 79 *viewDefinition // build configuration 80 81 // baseCtx is the context handed to NewView. This is the parent of all 82 // background contexts created for this view. 83 baseCtx context.Context 84 85 importsState *importsState 86 87 // parseCache holds an LRU cache of recently parsed files. 88 parseCache *parseCache 89 90 // fs is the file source used to populate this view. 91 fs *overlayFS 92 93 // cancelInitialWorkspaceLoad can be used to terminate the view's first 94 // attempt at initialization. 95 cancelInitialWorkspaceLoad context.CancelFunc 96 97 snapshotMu sync.Mutex 98 snapshot *Snapshot // latest snapshot; nil after shutdown has been called 99 100 // initialWorkspaceLoad is closed when the first workspace initialization has 101 // completed. If we failed to load, we only retry if the go.mod file changes, 102 // to avoid too many go/packages calls. 103 initialWorkspaceLoad chan struct{} 104 105 // initializationSema is used limit concurrent initialization of snapshots in 106 // the view. We use a channel instead of a mutex to avoid blocking when a 107 // context is canceled. 108 // 109 // This field (along with snapshot.initialized) guards against duplicate 110 // initialization of snapshots. Do not change it without adjusting snapshot 111 // accordingly. 112 initializationSema chan struct{} 113 114 // Document filters are constructed once, in View.filterFunc. 115 filterFuncOnce sync.Once 116 _filterFunc func(protocol.DocumentURI) bool // only accessed by View.filterFunc 117 } 118 119 // definition implements the viewDefiner interface. 120 func (v *View) definition() *viewDefinition { return v.viewDefinition } 121 122 // A viewDefinition is a logical build, i.e. configuration (Folder) along with 123 // a build directory and possibly an environment overlay (e.g. GOWORK=off or 124 // GOOS, GOARCH=...) to affect the build. 125 // 126 // This type is immutable, and compared to see if the View needs to be 127 // reconstructed. 128 // 129 // Note: whenever modifying this type, also modify the equivalence relation 130 // implemented by viewDefinitionsEqual. 131 // 132 // TODO(golang/go#57979): viewDefinition should be sufficient for running 133 // go/packages. Enforce this in the API. 134 type viewDefinition struct { 135 folder *Folder // pointer comparison is OK, as any new Folder creates a new def 136 137 typ ViewType 138 139 // root represents the directory root of the CUE module that contains 140 // the WorkspaceFolder folder 141 root protocol.DocumentURI 142 cuemod protocol.DocumentURI // the nearest cue.mod/module.cue file, or "" 143 144 // workspaceModFiles holds the set of cue.mod/module.cue files 145 // active in this snapshot. 146 // 147 // For a go.work workspace, this is the set of workspace modfiles. For a 148 // go.mod workspace, this contains the go.mod file defining the workspace 149 // root, as well as any locally replaced modules (if 150 // "includeReplaceInWorkspace" is set). 151 workspaceModFiles map[protocol.DocumentURI]struct{} 152 workspaceModFilesErr error // error encountered computing workspaceModFiles 153 154 // envOverlay holds additional environment to apply to this viewDefinition. 155 envOverlay map[string]string 156 } 157 158 // definition implements the viewDefiner interface. 159 func (d *viewDefinition) definition() *viewDefinition { return d } 160 161 // Type returns the ViewType type, which determines how go/packages are loaded 162 // for this View. 163 func (d *viewDefinition) Type() ViewType { return d.typ } 164 165 // Root returns the view root, which determines where packages are loaded from. 166 func (d *viewDefinition) Root() protocol.DocumentURI { return d.root } 167 168 // EnvOverlay returns a new sorted slice of environment variables (in the form 169 // "k=v") for this view definition's env overlay. 170 func (d *viewDefinition) EnvOverlay() []string { 171 var env []string 172 for k, v := range d.envOverlay { 173 env = append(env, fmt.Sprintf("%s=%s", k, v)) 174 } 175 sort.Strings(env) 176 return env 177 } 178 179 // ModFiles are the cue.mod/module.cue files enclosed in the 180 // snapshot's view and known to the snapshot. 181 func (d viewDefinition) ModFiles() []protocol.DocumentURI { 182 var uris []protocol.DocumentURI 183 for modURI := range d.workspaceModFiles { 184 uris = append(uris, modURI) 185 } 186 return uris 187 } 188 189 // viewDefinitionsEqual reports whether x and y are equivalent. 190 func viewDefinitionsEqual(x, y *viewDefinition) bool { 191 if (x.workspaceModFilesErr == nil) != (y.workspaceModFilesErr == nil) { 192 return false 193 } 194 if x.workspaceModFilesErr != nil { 195 if x.workspaceModFilesErr.Error() != y.workspaceModFilesErr.Error() { 196 return false 197 } 198 } else if !maps.SameKeys(x.workspaceModFiles, y.workspaceModFiles) { 199 return false 200 } 201 if len(x.envOverlay) != len(y.envOverlay) { 202 return false 203 } 204 for i, xv := range x.envOverlay { 205 if xv != y.envOverlay[i] { 206 return false 207 } 208 } 209 return x.folder == y.folder && 210 x.typ == y.typ && 211 x.root == y.root 212 } 213 214 // A ViewType describes how we load package information for a view. 215 // 216 // This is used for constructing the go/packages.Load query, and for 217 // interpreting missing packages, imports, or errors. 218 // 219 // See the documentation for individual ViewType values for details. 220 type ViewType int 221 222 const ( 223 // An AdHocView is a collection of files in a given directory, not in GOPATH 224 // or a module. 225 // 226 // Load: . from the workspace folder. 227 AdHocView ViewType = iota 228 229 CUEModView 230 ) 231 232 func (t ViewType) String() string { 233 switch t { 234 case AdHocView: 235 return "AdHocView" 236 case CUEModView: 237 return "CUEModView" 238 default: 239 return "Unknown" 240 } 241 } 242 243 // moduleMode reports whether the view uses Go modules. 244 func (w viewDefinition) moduleMode() bool { 245 switch w.typ { 246 case CUEModView: 247 return true 248 default: 249 return false 250 } 251 } 252 253 func (v *View) ID() string { return v.id } 254 255 // Folder returns the folder at the base of this view. 256 func (v *View) Folder() *Folder { 257 return v.folder 258 } 259 260 // UpdateFolders updates the set of views for the new folders. 261 // 262 // Calling this causes each view to be reinitialized. 263 func (s *Session) UpdateFolders(ctx context.Context, newFolders []*Folder) error { 264 s.viewMu.Lock() 265 defer s.viewMu.Unlock() 266 267 overlays := s.Overlays() 268 var openFiles []protocol.DocumentURI 269 for _, o := range overlays { 270 openFiles = append(openFiles, o.URI()) 271 } 272 273 defs, err := selectViewDefs(ctx, s, newFolders, openFiles) 274 if err != nil { 275 return err 276 } 277 var newViews []*View 278 for _, def := range defs { 279 v, _, release := s.createView(ctx, def) 280 release() 281 newViews = append(newViews, v) 282 } 283 for _, v := range s.views { 284 v.shutdown() 285 } 286 s.views = newViews 287 return nil 288 } 289 290 // viewEnv returns a string describing the environment of a newly created view. 291 // 292 // It must not be called concurrently with any other view methods. 293 // TODO(rfindley): rethink this function, or inline sole call. 294 func viewEnv(v *View) string { 295 var buf bytes.Buffer 296 fmt.Fprintf(&buf, `go info for %v 297 (view type %v) 298 (root dir %s) 299 (build flags: %v) 300 (env overlay: %v) 301 `, 302 v.folder.Dir.Path(), 303 v.typ, 304 v.root.Path(), 305 v.folder.Options.BuildFlags, 306 v.envOverlay, 307 ) 308 309 return buf.String() 310 } 311 312 // separated out from its sole use in locateTemplateFiles for testability 313 func fileHasExtension(path string, suffixes []string) bool { 314 ext := filepath.Ext(path) 315 if ext != "" && ext[0] == '.' { 316 ext = ext[1:] 317 } 318 for _, s := range suffixes { 319 if s != "" && ext == s { 320 return true 321 } 322 } 323 return false 324 } 325 326 // filterFunc returns a func that reports whether uri is filtered by the currently configured 327 // directoryFilters. 328 func (v *View) filterFunc() func(protocol.DocumentURI) bool { 329 v.filterFuncOnce.Do(func() { 330 v._filterFunc = func(uri protocol.DocumentURI) bool { 331 return false 332 } 333 }) 334 return v._filterFunc 335 } 336 337 // shutdown releases resources associated with the view. 338 func (v *View) shutdown() { 339 // Cancel the initial workspace load if it is still running. 340 v.cancelInitialWorkspaceLoad() 341 342 v.snapshotMu.Lock() 343 if v.snapshot != nil { 344 v.snapshot.cancel() 345 v.snapshot.decref() 346 v.snapshot = nil 347 } 348 v.snapshotMu.Unlock() 349 } 350 351 // Snapshot returns the current snapshot for the view, and a 352 // release function that must be called when the Snapshot is 353 // no longer needed. 354 // 355 // The resulting error is non-nil if and only if the view is shut down, in 356 // which case the resulting release function will also be nil. 357 func (v *View) Snapshot() (*Snapshot, func(), error) { 358 v.snapshotMu.Lock() 359 defer v.snapshotMu.Unlock() 360 if v.snapshot == nil { 361 return nil, nil, errors.New("view is shutdown") 362 } 363 return v.snapshot, v.snapshot.Acquire(), nil 364 } 365 366 // initialize loads the metadata (and currently, file contents, due to 367 // golang/go#57558) for the main package query of the View, which depends on 368 // the view type (see ViewType). If s.initialized is already true, initialize 369 // is a no op. 370 // 371 // The first attempt--which populates the first snapshot for a new view--must 372 // be allowed to run to completion without being cancelled. 373 // 374 // Subsequent attempts are triggered by conditions where gopls can't enumerate 375 // specific packages that require reloading, such as a change to a go.mod file. 376 // These attempts may be cancelled, and then retried by a later call. 377 // 378 // Postcondition: if ctx was not cancelled, s.initialized is true, s.initialErr 379 // holds the error resulting from initialization, if any, and s.metadata holds 380 // the resulting metadata graph. 381 func (s *Snapshot) initialize(ctx context.Context, firstAttempt bool) { 382 // Acquire initializationSema, which is 383 // (in effect) a mutex with a timeout. 384 select { 385 case <-ctx.Done(): 386 return 387 case s.view.initializationSema <- struct{}{}: 388 } 389 390 defer func() { 391 <-s.view.initializationSema 392 }() 393 394 s.mu.Lock() 395 initialized := s.initialized 396 s.mu.Unlock() 397 398 if initialized { 399 return 400 } 401 402 defer func() { 403 if firstAttempt { 404 close(s.view.initialWorkspaceLoad) 405 } 406 }() 407 408 var scopes []loadScope // scopes to load 409 var modDiagnostics []*Diagnostic // diagnostics for broken cue.mod/module.cue files 410 addError := func(uri protocol.DocumentURI, err error) { 411 modDiagnostics = append(modDiagnostics, &Diagnostic{ 412 URI: uri, 413 Severity: protocol.SeverityError, 414 Source: ListError, 415 Message: err.Error(), 416 }) 417 } 418 419 if len(s.view.workspaceModFiles) > 0 { 420 for modURI := range s.view.workspaceModFiles { 421 fh, err := s.ReadFile(ctx, modURI) 422 if err != nil { 423 if ctx.Err() != nil { 424 return 425 } 426 addError(modURI, err) 427 continue 428 } 429 modContent, err := fh.Content() 430 if err != nil { 431 if ctx.Err() != nil { 432 return 433 } 434 addError(modURI, err) 435 continue 436 } 437 438 parsed, err := modfile.ParseNonStrict(modContent, "module.cue") 439 if err != nil { 440 if ctx.Err() != nil { 441 return 442 } 443 addError(modURI, err) 444 continue 445 } 446 rootDir := filepath.Dir(filepath.Dir(modURI.Path())) 447 scopes = append(scopes, moduleLoadScope{dir: rootDir, modulePath: parsed.ModulePath()}) 448 } 449 } else { 450 scopes = append(scopes, viewLoadScope{}) 451 } 452 453 loadErr := s.load(ctx, true, scopes...) 454 455 // A failure is retryable if it may have been due to context 456 // cancellation, and this is not the initial workspace load 457 // (firstAttempt==true). 458 // 459 // The Initial Workspace Load (IWL) runs on a detached context with 460 // a long (~10m) timeout, so if the context was canceled we 461 // consider loading to have failed permanently. 462 if loadErr != nil && ctx.Err() != nil && !firstAttempt { 463 return 464 } 465 466 var initialErr *InitializationError 467 switch { 468 case loadErr != nil: 469 event.Error(ctx, fmt.Sprintf("initial workspace load: %v", loadErr), loadErr) 470 initialErr = &InitializationError{ 471 MainError: loadErr, 472 } 473 case s.view.workspaceModFilesErr != nil: 474 initialErr = &InitializationError{ 475 MainError: s.view.workspaceModFilesErr, 476 } 477 case len(modDiagnostics) > 0: 478 initialErr = &InitializationError{ 479 MainError: fmt.Errorf(modDiagnostics[0].Message), 480 } 481 } 482 483 s.mu.Lock() 484 defer s.mu.Unlock() 485 486 s.initialized = true 487 s.initialErr = initialErr 488 } 489 490 // A StateChange describes external state changes that may affect a snapshot. 491 // 492 // By far the most common of these is a change to file state, but a query of 493 // module upgrade information or vulnerabilities also affects gopls' behavior. 494 type StateChange struct { 495 Modifications []file.Modification // if set, the raw modifications originating this change 496 Files map[protocol.DocumentURI]file.Handle 497 ModuleUpgrades map[protocol.DocumentURI]map[string]string 498 GCDetails map[metadata.PackageID]bool // package -> whether or not we want details 499 } 500 501 // InvalidateView processes the provided state change, invalidating any derived 502 // results that depend on the changed state. 503 // 504 // The resulting snapshot is non-nil, representing the outcome of the state 505 // change. The second result is a function that must be called to release the 506 // snapshot when the snapshot is no longer needed. 507 // 508 // An error is returned if the given view is no longer active in the session. 509 func (s *Session) InvalidateView(ctx context.Context, view *View, changed StateChange) (*Snapshot, func(), error) { 510 s.viewMu.Lock() 511 defer s.viewMu.Unlock() 512 513 if !slices.Contains(s.views, view) { 514 return nil, nil, fmt.Errorf("view is no longer active") 515 } 516 snapshot, release, _ := s.invalidateViewLocked(ctx, view, changed) 517 return snapshot, release, nil 518 } 519 520 // invalidateViewLocked invalidates the content of the given view. 521 // (See [Session.InvalidateView]). 522 // 523 // The resulting bool reports whether the View needs to be re-diagnosed. 524 // (See [Snapshot.clone]). 525 // 526 // s.viewMu must be held while calling this method. 527 func (s *Session) invalidateViewLocked(ctx context.Context, v *View, changed StateChange) (*Snapshot, func(), bool) { 528 // Detach the context so that content invalidation cannot be canceled. 529 ctx = xcontext.Detach(ctx) 530 531 // This should be the only time we hold the view's snapshot lock for any period of time. 532 v.snapshotMu.Lock() 533 defer v.snapshotMu.Unlock() 534 535 prevSnapshot := v.snapshot 536 537 if prevSnapshot == nil { 538 panic("invalidateContent called after shutdown") 539 } 540 541 // Cancel all still-running previous requests, since they would be 542 // operating on stale data. 543 prevSnapshot.cancel() 544 545 // Do not clone a snapshot until its view has finished initializing. 546 // 547 // TODO(rfindley): shouldn't we do this before canceling? 548 prevSnapshot.AwaitInitialized(ctx) 549 550 var needsDiagnosis bool 551 s.snapshotWG.Add(1) 552 v.snapshot, needsDiagnosis = prevSnapshot.clone(ctx, v.baseCtx, changed, s.snapshotWG.Done) 553 554 // Remove the initial reference created when prevSnapshot was created. 555 prevSnapshot.decref() 556 557 // Return a second lease to the caller. 558 return v.snapshot, v.snapshot.Acquire(), needsDiagnosis 559 } 560 561 // defineView computes the view definition for the provided workspace folder 562 // and URI. 563 // 564 // If forFile is non-empty, this view should be the best view including forFile. 565 // Otherwise, it is the default view for the folder. Per below TODO(myitcv), we 566 // need to better understand when this can happen, and what the preceding sentence 567 // actually means. 568 // 569 // defineView only returns an error in the event of context cancellation. 570 // 571 // gopls note: keep this function in sync with bestView. 572 func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile file.Handle) (*viewDefinition, error) { 573 if err := checkPathValid(folder.Dir.Path()); err != nil { 574 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) 575 } 576 dir := folder.Dir.Path() 577 578 if forFile != nil { 579 // TODO(myitcv): fix the implementation here. forFile != nil when we are trying 580 // to compute the set of views given the set of open files/known folders. This is 581 // part of the zero config approach in gopls, and we don't have anything like that 582 // yet for 'cue lsp'. 583 return nil, fmt.Errorf("defineView with forFile != nil; not yet supported") 584 } 585 586 def := new(viewDefinition) 587 def.folder = folder 588 589 var err error 590 dirURI := protocol.URIFromPath(dir) 591 moduleCue, err := findRootPattern(ctx, dirURI, filepath.FromSlash("cue.mod/module.cue"), fs) 592 if err != nil { 593 return nil, err 594 } 595 if moduleCue == "" { 596 // We found no module, and currently we only support workspaces with modules. 597 return nil, fmt.Errorf("WorkspaceFolder %s does not correspond to a CUE module", folder.Dir.Path()) 598 } 599 def.cuemod = moduleCue 600 601 def.typ = CUEModView 602 def.root = def.cuemod.Dir().Dir() 603 if def.root != dirURI { 604 return nil, fmt.Errorf("WorkspaceFolder %s does not correspond to a CUE module", folder.Dir.Path()) 605 } 606 def.workspaceModFiles = map[protocol.DocumentURI]struct{}{def.cuemod: {}} 607 608 return def, nil 609 } 610 611 // findRootPattern looks for files with the given basename in dir or any parent 612 // directory of dir, using the provided FileSource. It returns the first match, 613 // starting from dir and search parents. 614 // 615 // The resulting string is either the file path of a matching file with the 616 // given basename, or "" if none was found. 617 // 618 // findRootPattern only returns an error in the case of context cancellation. 619 func findRootPattern(ctx context.Context, dirURI protocol.DocumentURI, basename string, fs file.Source) (protocol.DocumentURI, error) { 620 dir := dirURI.Path() 621 for dir != "" { 622 target := filepath.Join(dir, basename) 623 uri := protocol.URIFromPath(target) 624 fh, err := fs.ReadFile(ctx, uri) 625 if err != nil { 626 return "", err // context cancelled 627 } 628 if fileExists(fh) { 629 return uri, nil 630 } 631 // Trailing separators must be trimmed, otherwise filepath.Split is a noop. 632 next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator))) 633 if next == dir { 634 break 635 } 636 dir = next 637 } 638 return "", nil 639 } 640 641 // checkPathValid performs an OS-specific path validity check. The 642 // implementation varies for filesystems that are case-insensitive 643 // (e.g. macOS, Windows), and for those that disallow certain file 644 // names (e.g. path segments ending with a period on Windows, or 645 // reserved names such as "com"; see 646 // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file). 647 var checkPathValid = defaultCheckPathValid 648 649 // CheckPathValid checks whether a directory is suitable as a workspace folder. 650 func CheckPathValid(dir string) error { return checkPathValid(dir) } 651 652 func defaultCheckPathValid(path string) error { 653 return nil 654 } 655 656 // Copied from 657 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a 658 func globsMatchPath(globs, target string) bool { 659 for globs != "" { 660 // Extract next non-empty glob in comma-separated list. 661 var glob string 662 if i := strings.Index(globs, ","); i >= 0 { 663 glob, globs = globs[:i], globs[i+1:] 664 } else { 665 glob, globs = globs, "" 666 } 667 if glob == "" { 668 continue 669 } 670 671 // A glob with N+1 path elements (N slashes) needs to be matched 672 // against the first N+1 path elements of target, 673 // which end just before the N+1'th slash. 674 n := strings.Count(glob, "/") 675 prefix := target 676 // Walk target, counting slashes, truncating at the N+1'th slash. 677 for i := 0; i < len(target); i++ { 678 if target[i] == '/' { 679 if n == 0 { 680 prefix = target[:i] 681 break 682 } 683 n-- 684 } 685 } 686 if n > 0 { 687 // Not enough prefix elements. 688 continue 689 } 690 matched, _ := path.Match(glob, prefix) 691 if matched { 692 return true 693 } 694 } 695 return false 696 } 697 698 var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) 699 700 // TODO(rfindley): clean up the redundancy of allFilesExcluded, 701 // pathExcludedByFilterFunc, pathExcludedByFilter, view.filterFunc... 702 func allFilesExcluded(files []string, filterFunc func(protocol.DocumentURI) bool) bool { 703 for _, f := range files { 704 uri := protocol.URIFromPath(f) 705 if !filterFunc(uri) { 706 return false 707 } 708 } 709 return true 710 } 711 712 func relPathExcludedByFilter(path string, filterer *Filterer) bool { 713 path = strings.TrimPrefix(filepath.ToSlash(path), "/") 714 return filterer.Disallow(path) 715 }