X-Git-Url: https://git.josue.xyz/?a=blobdiff_plain;f=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201105173854-bc9fc8d8c4bc%2Finternal%2Flsp%2Fcache%2Fworkspace.go;fp=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201105173854-bc9fc8d8c4bc%2Finternal%2Flsp%2Fcache%2Fworkspace.go;h=4d05adc5ebe2fa08a6630959314db222a9aa59a9;hb=4d07c77cf4d78cab8639e13ddc3c22495e585b0b;hp=0000000000000000000000000000000000000000;hpb=b3950616b54221c40a7dab9099bda675007e5b6e;p=dotfiles%2F.git diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/cache/workspace.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/cache/workspace.go new file mode 100644 index 00000000..4d05adc5 --- /dev/null +++ b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/cache/workspace.go @@ -0,0 +1,409 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cache + +import ( + "context" + "os" + "path/filepath" + "sort" + "strings" + "sync" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/lsp/source" + "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/xcontext" + errors "golang.org/x/xerrors" +) + +type workspaceSource int + +const ( + legacyWorkspace = iota + goplsModWorkspace + fileSystemWorkspace +) + +func (s workspaceSource) String() string { + switch s { + case legacyWorkspace: + return "legacy" + case goplsModWorkspace: + return "gopls.mod" + case fileSystemWorkspace: + return "file system" + default: + return "!(unknown module source)" + } +} + +// workspace tracks go.mod files in the workspace, along with the +// gopls.mod file, to provide support for multi-module workspaces. +// +// Specifically, it provides: +// - the set of modules contained within in the workspace root considered to +// be 'active' +// - the workspace modfile, to be used for the go command `-modfile` flag +// - the set of workspace directories +// +// This type is immutable (or rather, idempotent), so that it may be shared +// across multiple snapshots. +type workspace struct { + root span.URI + moduleSource workspaceSource + + // modFiles holds the active go.mod files. + modFiles map[span.URI]struct{} + + // The workspace module is lazily re-built once after being invalidated. + // buildMu+built guards this reconstruction. + // + // file and wsDirs may be non-nil even if built == false, if they were copied + // from the previous workspace module version. In this case, they will be + // preserved if building fails. + buildMu sync.Mutex + built bool + buildErr error + file *modfile.File + wsDirs map[span.URI]struct{} +} + +func newWorkspace(ctx context.Context, root span.URI, fs source.FileSource, experimental bool) (*workspace, error) { + if !experimental { + modFiles, err := getLegacyModules(ctx, root, fs) + if err != nil { + return nil, err + } + return &workspace{ + root: root, + modFiles: modFiles, + moduleSource: legacyWorkspace, + }, nil + } + goplsModFH, err := fs.GetFile(ctx, goplsModURI(root)) + if err != nil { + return nil, err + } + contents, err := goplsModFH.Read() + if err == nil { + file, modFiles, err := parseGoplsMod(root, goplsModFH.URI(), contents) + if err != nil { + return nil, err + } + return &workspace{ + root: root, + modFiles: modFiles, + file: file, + moduleSource: goplsModWorkspace, + }, nil + } + modFiles, err := findAllModules(ctx, root) + if err != nil { + return nil, err + } + return &workspace{ + root: root, + modFiles: modFiles, + moduleSource: fileSystemWorkspace, + }, nil +} + +func (wm *workspace) activeModFiles() map[span.URI]struct{} { + return wm.modFiles +} + +// modFile gets the workspace modfile associated with this workspace, +// computing it if it doesn't exist. +// +// A fileSource must be passed in to solve a chicken-egg problem: it is not +// correct to pass in the snapshot file source to newWorkspace when +// invalidating, because at the time these are called the snapshot is locked. +// So we must pass it in later on when actually using the modFile. +func (wm *workspace) modFile(ctx context.Context, fs source.FileSource) (*modfile.File, error) { + wm.build(ctx, fs) + return wm.file, wm.buildErr +} + +func (wm *workspace) build(ctx context.Context, fs source.FileSource) { + wm.buildMu.Lock() + defer wm.buildMu.Unlock() + + if wm.built { + return + } + // Building should never be cancelled. Since the workspace module is shared + // across multiple snapshots, doing so would put us in a bad state, and it + // would not be obvious to the user how to recover. + ctx = xcontext.Detach(ctx) + + // If our module source is not gopls.mod, try to build the workspace module + // from modules. Fall back on the pre-existing mod file if parsing fails. + if wm.moduleSource != goplsModWorkspace { + file, err := buildWorkspaceModFile(ctx, wm.modFiles, fs) + switch { + case err == nil: + wm.file = file + case wm.file != nil: + // Parsing failed, but we have a previous file version. + event.Error(ctx, "building workspace mod file", err) + default: + // No file to fall back on. + wm.buildErr = err + } + } + if wm.file != nil { + wm.wsDirs = map[span.URI]struct{}{ + wm.root: {}, + } + for _, r := range wm.file.Replace { + // We may be replacing a module with a different version, not a path + // on disk. + if r.New.Version != "" { + continue + } + wm.wsDirs[span.URIFromPath(r.New.Path)] = struct{}{} + } + } + // Ensure that there is always at least the root dir. + if len(wm.wsDirs) == 0 { + wm.wsDirs = map[span.URI]struct{}{ + wm.root: {}, + } + } + wm.built = true +} + +// dirs returns the workspace directories for the loaded modules. +func (wm *workspace) dirs(ctx context.Context, fs source.FileSource) []span.URI { + wm.build(ctx, fs) + var dirs []span.URI + for d := range wm.wsDirs { + dirs = append(dirs, d) + } + sort.Slice(dirs, func(i, j int) bool { + return span.CompareURI(dirs[i], dirs[j]) < 0 + }) + return dirs +} + +// invalidate returns a (possibly) new workspaceModule after invalidating +// changedURIs. If wm is still valid in the presence of changedURIs, it returns +// itself unmodified. +func (wm *workspace) invalidate(ctx context.Context, changes map[span.URI]*fileChange) (*workspace, bool) { + // Prevent races to wm.modFile or wm.wsDirs below, if wm has not yet been + // built. + wm.buildMu.Lock() + defer wm.buildMu.Unlock() + // Any gopls.mod change is processed first, followed by go.mod changes, as + // changes to gopls.mod may affect the set of active go.mod files. + var ( + // New values. We return a new workspace module if and only if modFiles is + // non-nil. + modFiles map[span.URI]struct{} + moduleSource = wm.moduleSource + modFile = wm.file + err error + ) + if wm.moduleSource == goplsModWorkspace { + // If we are currently reading the modfile from gopls.mod, we default to + // preserving it even if module metadata changes (which may be the case if + // a go.sum file changes). + modFile = wm.file + } + // First handle changes to the gopls.mod file. + if wm.moduleSource != legacyWorkspace { + // If gopls.mod has changed we need to either re-read it if it exists or + // walk the filesystem if it doesn't exist. + gmURI := goplsModURI(wm.root) + if change, ok := changes[gmURI]; ok { + if change.exists { + // Only invalidate if the gopls.mod actually parses. Otherwise, stick with the current gopls.mod + parsedFile, parsedModules, err := parseGoplsMod(wm.root, gmURI, change.content) + if err == nil { + modFile = parsedFile + moduleSource = goplsModWorkspace + modFiles = parsedModules + } else { + // Note that modFile is not invalidated here. + event.Error(ctx, "parsing gopls.mod", err) + } + } else { + // gopls.mod is deleted. search for modules again. + moduleSource = fileSystemWorkspace + modFiles, err = findAllModules(ctx, wm.root) + // the modFile is no longer valid. + if err != nil { + event.Error(ctx, "finding file system modules", err) + } + modFile = nil + } + } + } + + // Next, handle go.mod changes that could affect our set of tracked modules. + // If we're reading our tracked modules from the gopls.mod, there's nothing + // to do here. + if wm.moduleSource != goplsModWorkspace { + for uri, change := range changes { + // If a go.mod file has changed, we may need to update the set of active + // modules. + if !isGoMod(uri) { + continue + } + if wm.moduleSource == legacyWorkspace && !equalURI(modURI(wm.root), uri) { + // Legacy mode only considers a module a workspace root. + continue + } + if !source.InDir(wm.root.Filename(), uri.Filename()) { + // Otherwise, the module must be contained within the workspace root. + continue + } + if modFiles == nil { + modFiles = make(map[span.URI]struct{}) + for k := range wm.modFiles { + modFiles[k] = struct{}{} + } + } + if change.exists { + modFiles[uri] = struct{}{} + } else { + delete(modFiles, uri) + } + } + } + if modFiles != nil { + // Any change to modules triggers a new version. + return &workspace{ + root: wm.root, + moduleSource: moduleSource, + modFiles: modFiles, + file: modFile, + wsDirs: wm.wsDirs, + }, true + } + // No change. Just return wm, since it is immutable. + return wm, false +} + +func equalURI(left, right span.URI) bool { + return span.CompareURI(left, right) == 0 +} + +// goplsModURI returns the URI for the gopls.mod file contained in root. +func goplsModURI(root span.URI) span.URI { + return span.URIFromPath(filepath.Join(root.Filename(), "gopls.mod")) +} + +// modURI returns the URI for the go.mod file contained in root. +func modURI(root span.URI) span.URI { + return span.URIFromPath(filepath.Join(root.Filename(), "go.mod")) +} + +// isGoMod reports if uri is a go.mod file. +func isGoMod(uri span.URI) bool { + return filepath.Base(uri.Filename()) == "go.mod" +} + +// isGoMod reports if uri is a go.sum file. +func isGoSum(uri span.URI) bool { + return filepath.Base(uri.Filename()) == "go.sum" +} + +// fileExists reports if the file uri exists within source. +func fileExists(ctx context.Context, uri span.URI, source source.FileSource) (bool, error) { + fh, err := source.GetFile(ctx, uri) + if err != nil { + return false, err + } + return fileHandleExists(fh) +} + +// fileHandleExists reports if the file underlying fh actually exits. +func fileHandleExists(fh source.FileHandle) (bool, error) { + _, err := fh.Read() + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +// TODO(rFindley): replace this (and similar) with a uripath package analogous +// to filepath. +func dirURI(uri span.URI) span.URI { + return span.URIFromPath(filepath.Dir(uri.Filename())) +} + +// getLegacyModules returns a module set containing at most the root module. +func getLegacyModules(ctx context.Context, root span.URI, fs source.FileSource) (map[span.URI]struct{}, error) { + uri := span.URIFromPath(filepath.Join(root.Filename(), "go.mod")) + modules := make(map[span.URI]struct{}) + exists, err := fileExists(ctx, uri, fs) + if err != nil { + return nil, err + } + if exists { + modules[uri] = struct{}{} + } + return modules, nil +} + +func parseGoplsMod(root, uri span.URI, contents []byte) (*modfile.File, map[span.URI]struct{}, error) { + modFile, err := modfile.Parse(uri.Filename(), contents, nil) + if err != nil { + return nil, nil, errors.Errorf("parsing gopls.mod: %w", err) + } + modFiles := make(map[span.URI]struct{}) + for _, replace := range modFile.Replace { + if replace.New.Version != "" { + return nil, nil, errors.Errorf("gopls.mod: replaced module %q@%q must not have version", replace.New.Path, replace.New.Version) + } + dirFP := filepath.FromSlash(replace.New.Path) + if !filepath.IsAbs(dirFP) { + dirFP = filepath.Join(root.Filename(), dirFP) + // The resulting modfile must use absolute paths, so that it can be + // written to a temp directory. + replace.New.Path = dirFP + } + modURI := span.URIFromPath(filepath.Join(dirFP, "go.mod")) + modFiles[modURI] = struct{}{} + } + return modFile, modFiles, nil +} + +// findAllModules recursively walks the root directory looking for go.mod +// files, returning the set of modules it discovers. +// TODO(rfindley): consider overlays. +func findAllModules(ctx context.Context, root span.URI) (map[span.URI]struct{}, error) { + // Walk the view's folder to find all modules in the view. + modFiles := make(map[span.URI]struct{}) + return modFiles, filepath.Walk(root.Filename(), func(path string, info os.FileInfo, err error) error { + if err != nil { + // Probably a permission error. Keep looking. + return filepath.SkipDir + } + // For any path that is not the workspace folder, check if the path + // would be ignored by the go command. Vendor directories also do not + // contain workspace modules. + if info.IsDir() && path != root.Filename() { + suffix := strings.TrimPrefix(path, root.Filename()) + switch { + case checkIgnored(suffix), + strings.Contains(filepath.ToSlash(suffix), "/vendor/"): + return filepath.SkipDir + } + } + // We're only interested in go.mod files. + uri := span.URIFromPath(path) + if isGoMod(uri) { + modFiles[uri] = struct{}{} + } + return nil + }) +}