| // 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" |
| "errors" |
| "fmt" |
| "path/filepath" |
| |
| "golang.org/x/mod/modfile" |
| "golang.org/x/tools/gopls/internal/file" |
| "golang.org/x/tools/gopls/internal/protocol" |
| ) |
| |
| // isGoWork reports if uri is a go.work file. |
| func isGoWork(uri protocol.DocumentURI) bool { |
| return filepath.Base(uri.Path()) == "go.work" |
| } |
| |
| // goWorkModules returns the URIs of go.mod files named by the go.work file. |
| func goWorkModules(ctx context.Context, gowork protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) { |
| fh, err := fs.ReadFile(ctx, gowork) |
| if err != nil { |
| return nil, err // canceled |
| } |
| content, err := fh.Content() |
| if err != nil { |
| return nil, err |
| } |
| filename := gowork.Path() |
| dir := filepath.Dir(filename) |
| workFile, err := modfile.ParseWork(filename, content, nil) |
| if err != nil { |
| return nil, fmt.Errorf("parsing go.work: %w", err) |
| } |
| var usedDirs []string |
| for _, use := range workFile.Use { |
| usedDirs = append(usedDirs, use.Path) |
| } |
| return localModFiles(dir, usedDirs), nil |
| } |
| |
| // localModFiles builds a set of local go.mod files referenced by |
| // goWorkOrModPaths, which is a slice of paths as contained in a go.work 'use' |
| // directive or go.mod 'replace' directive (and which therefore may use either |
| // '/' or '\' as a path separator). |
| func localModFiles(relativeTo string, goWorkOrModPaths []string) map[protocol.DocumentURI]unit { |
| modFiles := make(map[protocol.DocumentURI]unit) |
| for _, path := range goWorkOrModPaths { |
| modDir := filepath.FromSlash(path) |
| if !filepath.IsAbs(modDir) { |
| modDir = filepath.Join(relativeTo, modDir) |
| } |
| modURI := protocol.URIFromPath(filepath.Join(modDir, "go.mod")) |
| modFiles[modURI] = unit{} |
| } |
| return modFiles |
| } |
| |
| // isGoMod reports if uri is a go.mod file. |
| func isGoMod(uri protocol.DocumentURI) bool { |
| return filepath.Base(uri.Path()) == "go.mod" |
| } |
| |
| // goModModules returns the URIs of "workspace" go.mod files defined by a |
| // go.mod file. This set is defined to be the given go.mod file itself, as well |
| // as the modfiles of any locally replaced modules in the go.mod file. |
| func goModModules(ctx context.Context, gomod protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) { |
| fh, err := fs.ReadFile(ctx, gomod) |
| if err != nil { |
| return nil, err // canceled |
| } |
| content, err := fh.Content() |
| if err != nil { |
| return nil, err |
| } |
| filename := gomod.Path() |
| dir := filepath.Dir(filename) |
| modFile, err := modfile.Parse(filename, content, nil) |
| if err != nil { |
| return nil, err |
| } |
| var localReplaces []string |
| for _, replace := range modFile.Replace { |
| if modfile.IsDirectoryPath(replace.New.Path) { |
| localReplaces = append(localReplaces, replace.New.Path) |
| } |
| } |
| modFiles := localModFiles(dir, localReplaces) |
| modFiles[gomod] = unit{} |
| return modFiles, nil |
| } |
| |
| // fileExists reports whether the file has a Content (which may be empty). |
| // An overlay exists even if it is not reflected in the file system. |
| func fileExists(fh file.Handle) bool { |
| _, err := fh.Content() |
| return err == nil |
| } |
| |
| // errExhausted is returned by findModules if the file scan limit is reached. |
| var errExhausted = errors.New("exhausted") |
| |
| // Limit go.mod search to 1 million files. As a point of reference, |
| // Kubernetes has 22K files (as of 2020-11-24). |
| // |
| // Note: per golang/go#56496, the previous limit of 1M files was too slow, at |
| // which point this limit was decreased to 100K. |
| const fileLimit = 100_000 |