diff options
Diffstat (limited to 'vendor/golang.org/x/tools')
8 files changed, 352 insertions, 379 deletions
diff --git a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go index 89f5097be..0fb4e7eea 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go @@ -113,7 +113,7 @@ func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Nod // childrenOf elides the FuncType node beneath FuncDecl. // Add it back here for TypeParams, Params, Results, // all FieldLists). But we don't add it back for the "func" token - // even though it is is the tree at FuncDecl.Type.Func. + // even though it is the tree at FuncDecl.Type.Func. if decl, ok := node.(*ast.FuncDecl); ok { if fields, ok := child.(*ast.FieldList); ok && fields != decl.Recv { path = append(path, decl.Type) diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go index f1931d10e..366aab6b2 100644 --- a/vendor/golang.org/x/tools/go/packages/doc.go +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -76,6 +76,8 @@ uninterpreted to Load, so that it can interpret them according to the conventions of the underlying build system. See the Example function for typical usage. +See also [golang.org/x/tools/go/packages/internal/linecount] +for an example application. # The driver protocol diff --git a/vendor/golang.org/x/tools/internal/imports/source_modindex.go b/vendor/golang.org/x/tools/internal/imports/source_modindex.go index 05229f06c..ca745d4a1 100644 --- a/vendor/golang.org/x/tools/internal/imports/source_modindex.go +++ b/vendor/golang.org/x/tools/internal/imports/source_modindex.go @@ -15,6 +15,10 @@ import ( // This code is here rather than in the modindex package // to avoid import loops +// TODO(adonovan): this code is only used by a test in this package. +// Can we delete it? Or is there a plan to call NewIndexSource from +// cmd/goimports? + // implements Source using modindex, so only for module cache. // // this is perhaps over-engineered. A new Index is read at first use. @@ -22,8 +26,8 @@ import ( // is read if the index changed. It is not clear the Mutex is needed. type IndexSource struct { modcachedir string - mutex sync.Mutex - ix *modindex.Index + mu sync.Mutex + index *modindex.Index // (access via getIndex) expires time.Time } @@ -39,13 +43,14 @@ func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths } func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) { - if err := s.maybeReadIndex(); err != nil { + index, err := s.getIndex() + if err != nil { return nil, err } var cs []modindex.Candidate for pkg, nms := range missing { for nm := range nms { - x := s.ix.Lookup(pkg, nm, false) + x := index.Lookup(pkg, nm, false) cs = append(cs, x...) } } @@ -74,30 +79,22 @@ func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, mi return ans, nil } -func (s *IndexSource) maybeReadIndex() error { - s.mutex.Lock() - defer s.mutex.Unlock() - - var readIndex bool - if time.Now().After(s.expires) { - ok, err := modindex.Update(s.modcachedir) - if err != nil { - return err - } - if ok { - readIndex = true - } - } +func (s *IndexSource) getIndex() (*modindex.Index, error) { + s.mu.Lock() + defer s.mu.Unlock() - if readIndex || s.ix == nil { - ix, err := modindex.ReadIndex(s.modcachedir) + // (s.index = nil => s.expires is zero, + // so the first condition is strictly redundant. + // But it makes the postcondition very clear.) + if s.index == nil || time.Now().After(s.expires) { + index, err := modindex.Update(s.modcachedir) if err != nil { - return err + return nil, err } - s.ix = ix - // for now refresh every 15 minutes - s.expires = time.Now().Add(time.Minute * 15) + s.index = index + s.expires = index.ValidAt.Add(15 * time.Minute) // (refresh period) } + // Inv: s.index != nil - return nil + return s.index, nil } diff --git a/vendor/golang.org/x/tools/internal/modindex/directories.go b/vendor/golang.org/x/tools/internal/modindex/directories.go index 2faa6ce0b..9a963744b 100644 --- a/vendor/golang.org/x/tools/internal/modindex/directories.go +++ b/vendor/golang.org/x/tools/internal/modindex/directories.go @@ -10,7 +10,6 @@ import ( "os" "path/filepath" "regexp" - "slices" "strings" "sync" "time" @@ -20,50 +19,48 @@ import ( ) type directory struct { - path Relpath + path string // relative to GOMODCACHE importPath string version string // semantic version - syms []symbol } -// byImportPath groups the directories by import path, -// sorting the ones with the same import path by semantic version, -// most recent first. -func byImportPath(dirs []Relpath) (map[string][]*directory, error) { - ans := make(map[string][]*directory) // key is import path - for _, d := range dirs { - ip, sv, err := DirToImportPathVersion(d) +// bestDirByImportPath returns the best directory for each import +// path, where "best" means most recent semantic version. These import +// paths are inferred from the GOMODCACHE-relative dir names in dirs. +func bestDirByImportPath(dirs []string) (map[string]directory, error) { + dirsByPath := make(map[string]directory) + for _, dir := range dirs { + importPath, version, err := dirToImportPathVersion(dir) if err != nil { return nil, err } - ans[ip] = append(ans[ip], &directory{ - path: d, - importPath: ip, - version: sv, - }) - } - for k, v := range ans { - semanticSort(v) - ans[k] = v + new := directory{ + path: dir, + importPath: importPath, + version: version, + } + if old, ok := dirsByPath[importPath]; !ok || compareDirectory(new, old) < 0 { + dirsByPath[importPath] = new + } } - return ans, nil + return dirsByPath, nil } -// sort the directories by semantic version, latest first -func semanticSort(v []*directory) { - slices.SortFunc(v, func(l, r *directory) int { - if n := semver.Compare(l.version, r.version); n != 0 { - return -n // latest first - } - return strings.Compare(string(l.path), string(r.path)) - }) +// compareDirectory defines an ordering of path@version directories, +// by descending version, then by ascending path. +func compareDirectory(x, y directory) int { + if sign := -semver.Compare(x.version, y.version); sign != 0 { + return sign // latest first + } + return strings.Compare(string(x.path), string(y.path)) } // modCacheRegexp splits a relpathpath into module, module version, and package. var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) -// DirToImportPathVersion computes import path and semantic version -func DirToImportPathVersion(dir Relpath) (string, string, error) { +// dirToImportPathVersion computes import path and semantic version +// from a GOMODCACHE-relative directory name. +func dirToImportPathVersion(dir string) (string, string, error) { m := modCacheRegexp.FindStringSubmatch(string(dir)) // m[1] is the module path // m[2] is the version major.minor.patch(-<pre release identifier) @@ -74,62 +71,61 @@ func DirToImportPathVersion(dir Relpath) (string, string, error) { if !semver.IsValid(m[2]) { return "", "", fmt.Errorf("bad semantic version %s", m[2]) } - // ToSlash is required for Windows. + // ToSlash is required to convert Windows file paths + // into Go package import paths. return filepath.ToSlash(m[1] + m[3]), m[2], nil } -// a region controls what directories to look at, for -// updating the index incrementally, and for testing that. -// (for testing one builds an index as of A, incrementally -// updates it to B, and compares the result to an index build -// as of B.) -type region struct { - onlyAfter, onlyBefore time.Time - sync.Mutex - ans []Relpath -} +// findDirs returns an unordered list of relevant package directories, +// relative to the specified module cache root. The result includes only +// module dirs whose mtime is within (start, end). +func findDirs(root string, start, end time.Time) []string { + var ( + resMu sync.Mutex + res []string + ) -func findDirs(root string, onlyAfter, onlyBefore time.Time) []Relpath { - roots := []gopathwalk.Root{{Path: root, Type: gopathwalk.RootModuleCache}} - // TODO(PJW): adjust concurrency - opts := gopathwalk.Options{ModulesEnabled: true, Concurrency: 1 /* ,Logf: log.Printf*/} - betw := ®ion{ - onlyAfter: onlyAfter, - onlyBefore: onlyBefore, + addDir := func(root gopathwalk.Root, dir string) { + // TODO(pjw): do we need to check times? + resMu.Lock() + defer resMu.Unlock() + res = append(res, relative(root.Path, dir)) } - gopathwalk.WalkSkip(roots, betw.addDir, betw.skipDir, opts) - return betw.ans -} - -func (r *region) addDir(rt gopathwalk.Root, dir string) { - // do we need to check times? - r.Lock() - defer r.Unlock() - x := filepath.ToSlash(string(toRelpath(Abspath(rt.Path), dir))) - r.ans = append(r.ans, toRelpath(Abspath(rt.Path), x)) -} -func (r *region) skipDir(_ gopathwalk.Root, dir string) bool { - // The cache directory is already ignored in gopathwalk\ - if filepath.Base(dir) == "internal" { - return true - } - if strings.Contains(dir, "toolchain@") { - return true - } - // don't look inside @ directories that are too old - if strings.Contains(filepath.Base(dir), "@") { - st, err := os.Stat(dir) - if err != nil { - log.Printf("can't stat dir %s %v", dir, err) + skipDir := func(_ gopathwalk.Root, dir string) bool { + // The cache directory is already ignored in gopathwalk. + if filepath.Base(dir) == "internal" { return true } - if st.ModTime().Before(r.onlyAfter) { + + // Skip toolchains. + if strings.Contains(dir, "toolchain@") { return true } - if st.ModTime().After(r.onlyBefore) { - return true + + // Don't look inside @ directories that are too old/new. + if strings.Contains(filepath.Base(dir), "@") { + st, err := os.Stat(dir) + if err != nil { + log.Printf("can't stat dir %s %v", dir, err) + return true + } + mtime := st.ModTime() + return mtime.Before(start) || mtime.After(end) } + + return false } - return false + + // TODO(adonovan): parallelize this. Even with a hot buffer cache, + // find $(go env GOMODCACHE) -type d + // can easily take up a minute. + roots := []gopathwalk.Root{{Path: root, Type: gopathwalk.RootModuleCache}} + gopathwalk.WalkSkip(roots, addDir, skipDir, gopathwalk.Options{ + ModulesEnabled: true, + Concurrency: 1, // TODO(pjw): adjust concurrency + // Logf: log.Printf, + }) + + return res } diff --git a/vendor/golang.org/x/tools/internal/modindex/index.go b/vendor/golang.org/x/tools/internal/modindex/index.go index 9665356c0..c41d1dd90 100644 --- a/vendor/golang.org/x/tools/internal/modindex/index.go +++ b/vendor/golang.org/x/tools/internal/modindex/index.go @@ -6,12 +6,10 @@ package modindex import ( "bufio" + "crypto/sha256" "encoding/csv" - "errors" "fmt" - "hash/crc64" "io" - "io/fs" "log" "os" "path/filepath" @@ -22,7 +20,7 @@ import ( ) /* -The on-disk index is a text file. +The on-disk index ("payload") is a text file. The first 3 lines are header information containing CurrentVersion, the value of GOMODCACHE, and the validity date of the index. (This is when the code started building the index.) @@ -68,34 +66,45 @@ whose types are []byte and interface{}. // CurrentVersion tells readers about the format of the index. const CurrentVersion int = 0 -// Index is returned by ReadIndex(). +// Index is returned by [Read]. type Index struct { - Version int - Cachedir Abspath // The directory containing the module cache - Changed time.Time // The index is up to date as of Changed - Entries []Entry + Version int + GOMODCACHE string // absolute path of Go module cache dir + ValidAt time.Time // moment at which the index was up to date + Entries []Entry +} + +func (ix *Index) String() string { + return fmt.Sprintf("Index(%s v%d has %d entries at %v)", + ix.GOMODCACHE, ix.Version, len(ix.Entries), ix.ValidAt) } // An Entry contains information for an import path. type Entry struct { - Dir Relpath // directory in modcache + Dir string // package directory relative to GOMODCACHE; uses OS path separator ImportPath string PkgName string Version string - //ModTime STime // is this useful? - Names []string // exported names and information + Names []string // exported names and information } // IndexDir is where the module index is stored. -var IndexDir string - -// Set IndexDir -func init() { +// Each logical index entry consists of a pair of files: +// +// - the "payload" (index-VERSION-XXX), whose name is +// randomized, holds the actual index; and +// - the "link" (index-name-VERSION-HASH), +// whose name is predictable, contains the +// name of the payload file. +// +// Since the link file is small (<512B), +// reads and writes to it may be assumed atomic. +var IndexDir string = func() string { var dir string - var err error if testing.Testing() { dir = os.TempDir() } else { + var err error dir, err = os.UserCacheDir() // shouldn't happen, but TempDir is better than // creating ./go/imports @@ -103,81 +112,83 @@ func init() { dir = os.TempDir() } } - dir = filepath.Join(dir, "go", "imports") - os.MkdirAll(dir, 0777) - IndexDir = dir -} + dir = filepath.Join(dir, "goimports") + if err := os.MkdirAll(dir, 0777); err != nil { + log.Printf("failed to create modcache index dir: %v", err) + } + return dir +}() -// ReadIndex reads the latest version of the on-disk index -// for the cache directory cd. -// It returns (nil, nil) if there is no index, but returns -// a non-nil error if the index exists but could not be read. -func ReadIndex(cachedir string) (*Index, error) { - cachedir, err := filepath.Abs(cachedir) +// Read reads the latest version of the on-disk index +// for the specified Go module cache directory. +// If there is no index, it returns a nil Index and an fs.ErrNotExist error. +func Read(gomodcache string) (*Index, error) { + gomodcache, err := filepath.Abs(gomodcache) if err != nil { return nil, err } - cd := Abspath(cachedir) - dir := IndexDir - base := indexNameBase(cd) - iname := filepath.Join(dir, base) - buf, err := os.ReadFile(iname) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil, nil - } - return nil, fmt.Errorf("cannot read %s: %w", iname, err) - } - fname := filepath.Join(dir, string(buf)) - fd, err := os.Open(fname) + + // Read the "link" file for the specified gomodcache directory. + // It names the payload file. + content, err := os.ReadFile(filepath.Join(IndexDir, linkFileBasename(gomodcache))) if err != nil { return nil, err } - defer fd.Close() - r := bufio.NewReader(fd) - ix, err := readIndexFrom(cd, r) + payloadFile := filepath.Join(IndexDir, string(content)) + + // Read the index out of the payload file. + f, err := os.Open(payloadFile) if err != nil { return nil, err } - return ix, nil + defer f.Close() + return readIndexFrom(gomodcache, bufio.NewReader(f)) } -func readIndexFrom(cd Abspath, bx io.Reader) (*Index, error) { - b := bufio.NewScanner(bx) - var ans Index - // header - ok := b.Scan() - if !ok { - return nil, fmt.Errorf("unexpected scan error") +func readIndexFrom(gomodcache string, r io.Reader) (*Index, error) { + scan := bufio.NewScanner(r) + + // version + if !scan.Scan() { + return nil, fmt.Errorf("unexpected scan error: %v", scan.Err()) } - l := b.Text() - var err error - ans.Version, err = strconv.Atoi(l) + version, err := strconv.Atoi(scan.Text()) if err != nil { return nil, err } - if ans.Version != CurrentVersion { - return nil, fmt.Errorf("got version %d, expected %d", ans.Version, CurrentVersion) + if version != CurrentVersion { + return nil, fmt.Errorf("got version %d, expected %d", version, CurrentVersion) } - if ok := b.Scan(); !ok { - return nil, fmt.Errorf("scanner error reading cachedir") - } - ans.Cachedir = Abspath(b.Text()) - if ok := b.Scan(); !ok { - return nil, fmt.Errorf("scanner error reading index creation time") + + // gomodcache + if !scan.Scan() { + return nil, fmt.Errorf("scanner error reading module cache dir: %v", scan.Err()) } - // TODO(pjw): need to check that this is the expected cachedir + // TODO(pjw): need to check that this is the expected cache dir // so the tag should be passed in to this function - ans.Changed, err = time.ParseInLocation(time.DateTime, b.Text(), time.Local) + if dir := string(scan.Text()); dir != gomodcache { + return nil, fmt.Errorf("index file GOMODCACHE mismatch: got %q, want %q", dir, gomodcache) + } + + // changed + if !scan.Scan() { + return nil, fmt.Errorf("scanner error reading index creation time: %v", scan.Err()) + } + changed, err := time.ParseInLocation(time.DateTime, scan.Text(), time.Local) if err != nil { return nil, err } - var curEntry *Entry - for b.Scan() { - v := b.Text() + + // entries + var ( + curEntry *Entry + entries []Entry + ) + for scan.Scan() { + v := scan.Text() if v[0] == ':' { if curEntry != nil { - ans.Entries = append(ans.Entries, *curEntry) + entries = append(entries, *curEntry) } // as directories may contain commas and quotes, they need to be read as csv. rdr := strings.NewReader(v[1:]) @@ -189,49 +200,56 @@ func readIndexFrom(cd Abspath, bx io.Reader) (*Index, error) { if len(flds) != 4 { return nil, fmt.Errorf("header contains %d fields, not 4: %q", len(v), v) } - curEntry = &Entry{PkgName: flds[0], ImportPath: flds[1], Dir: toRelpath(cd, flds[2]), Version: flds[3]} + curEntry = &Entry{ + PkgName: flds[0], + ImportPath: flds[1], + Dir: relative(gomodcache, flds[2]), + Version: flds[3], + } continue } curEntry.Names = append(curEntry.Names, v) } - if curEntry != nil { - ans.Entries = append(ans.Entries, *curEntry) + if err := scan.Err(); err != nil { + return nil, fmt.Errorf("scanner failed while reading modindex entry: %v", err) } - if err := b.Err(); err != nil { - return nil, fmt.Errorf("scanner failed %v", err) + if curEntry != nil { + entries = append(entries, *curEntry) } - return &ans, nil + + return &Index{ + Version: version, + GOMODCACHE: gomodcache, + ValidAt: changed, + Entries: entries, + }, nil } -// write the index as a text file -func writeIndex(cachedir Abspath, ix *Index) error { - ipat := fmt.Sprintf("index-%d-*", CurrentVersion) - fd, err := os.CreateTemp(IndexDir, ipat) +// write writes the index file and updates the index directory to refer to it. +func write(gomodcache string, ix *Index) error { + // Write the index into a payload file with a fresh name. + f, err := os.CreateTemp(IndexDir, fmt.Sprintf("index-%d-*", CurrentVersion)) if err != nil { - return err // can this happen? + return err // e.g. disk full, or index dir deleted } - defer fd.Close() - if err := writeIndexToFile(ix, fd); err != nil { + if err := writeIndexToFile(ix, bufio.NewWriter(f)); err != nil { + _ = f.Close() // ignore error return err } - content := fd.Name() - content = filepath.Base(content) - base := indexNameBase(cachedir) - nm := filepath.Join(IndexDir, base) - err = os.WriteFile(nm, []byte(content), 0666) - if err != nil { + if err := f.Close(); err != nil { return err } - return nil + + // Write the name of the payload file into a link file. + indexDirFile := filepath.Join(IndexDir, linkFileBasename(gomodcache)) + content := []byte(filepath.Base(f.Name())) + return os.WriteFile(indexDirFile, content, 0666) } -func writeIndexToFile(x *Index, fd *os.File) error { - cnt := 0 - w := bufio.NewWriter(fd) +func writeIndexToFile(x *Index, w *bufio.Writer) error { fmt.Fprintf(w, "%d\n", x.Version) - fmt.Fprintf(w, "%s\n", x.Cachedir) - // round the time down - tm := x.Changed.Add(-time.Second / 2) + fmt.Fprintf(w, "%s\n", x.GOMODCACHE) + tm := x.ValidAt.Truncate(time.Second) // round the time down fmt.Fprintf(w, "%s\n", tm.Format(time.DateTime)) for _, e := range x.Entries { if e.ImportPath == "" { @@ -239,7 +257,6 @@ func writeIndexToFile(x *Index, fd *os.File) error { } // PJW: maybe always write these headers as csv? if strings.ContainsAny(string(e.Dir), ",\"") { - log.Printf("DIR: %s", e.Dir) cw := csv.NewWriter(w) cw.Write([]string{":" + e.PkgName, e.ImportPath, string(e.Dir), e.Version}) cw.Flush() @@ -248,19 +265,23 @@ func writeIndexToFile(x *Index, fd *os.File) error { } for _, x := range e.Names { fmt.Fprintf(w, "%s\n", x) - cnt++ } } - if err := w.Flush(); err != nil { - return err - } - return nil + return w.Flush() } -// return the base name of the file containing the name of the current index -func indexNameBase(cachedir Abspath) string { - // crc64 is a way to convert path names into 16 hex digits. - h := crc64.Checksum([]byte(cachedir), crc64.MakeTable(crc64.ECMA)) - fname := fmt.Sprintf("index-name-%d-%016x", CurrentVersion, h) - return fname +// linkFileBasename returns the base name of the link file in the +// index directory that holds the name of the payload file for the +// specified (absolute) Go module cache dir. +func linkFileBasename(gomodcache string) string { + // Note: coupled to logic in ./gomodindex/cmd.go. TODO: factor. + h := sha256.Sum256([]byte(gomodcache)) // collision-resistant hash + return fmt.Sprintf("index-name-%d-%032x", CurrentVersion, h) +} + +func relative(base, file string) string { + if rel, err := filepath.Rel(base, file); err == nil { + return rel + } + return file } diff --git a/vendor/golang.org/x/tools/internal/modindex/modindex.go b/vendor/golang.org/x/tools/internal/modindex/modindex.go index 355a53e71..5fa285d98 100644 --- a/vendor/golang.org/x/tools/internal/modindex/modindex.go +++ b/vendor/golang.org/x/tools/internal/modindex/modindex.go @@ -2,17 +2,21 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package modindex contains code for building and searching an index to -// the Go module cache. The directory containing the index, returned by -// IndexDir(), contains a file index-name-<ver> that contains the name +// Package modindex contains code for building and searching an +// [Index] of the Go module cache. +package modindex + +// The directory containing the index, returned by +// [IndexDir], contains a file index-name-<ver> that contains the name // of the current index. We believe writing that short file is atomic. -// ReadIndex reads that file to get the file name of the index. +// [Read] reads that file to get the file name of the index. // WriteIndex writes an index with a unique name and then // writes that name into a new version of index-name-<ver>. // (<ver> stands for the CurrentVersion of the index format.) -package modindex import ( + "maps" + "os" "path/filepath" "slices" "strings" @@ -21,144 +25,95 @@ import ( "golang.org/x/mod/semver" ) -// Create always creates a new index for the go module cache that is in cachedir. -func Create(cachedir string) error { - _, err := indexModCache(cachedir, true) - return err -} - -// Update the index for the go module cache that is in cachedir, -// If there is no existing index it will build one. -// If there are changed directories since the last index, it will -// write a new one and return true. Otherwise it returns false. -func Update(cachedir string) (bool, error) { - return indexModCache(cachedir, false) +// Update updates the index for the specified Go +// module cache directory, creating it as needed. +// On success it returns the current index. +func Update(gomodcache string) (*Index, error) { + prev, err := Read(gomodcache) + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + prev = nil + } + return update(gomodcache, prev) } -// indexModCache writes an index current as of when it is called. -// If clear is true the index is constructed from all of GOMODCACHE -// otherwise the index is constructed from the last previous index -// and the updates to the cache. It returns true if it wrote an index, -// false otherwise. -func indexModCache(cachedir string, clear bool) (bool, error) { - cachedir, err := filepath.Abs(cachedir) +// update builds, writes, and returns the current index. +// +// If old is nil, the new index is built from all of GOMODCACHE; +// otherwise it is built from the old index plus cache updates +// since the previous index's time. +func update(gomodcache string, old *Index) (*Index, error) { + gomodcache, err := filepath.Abs(gomodcache) if err != nil { - return false, err + return nil, err } - cd := Abspath(cachedir) - future := time.Now().Add(24 * time.Hour) // safely in the future - ok, err := modindexTimed(future, cd, clear) + new, changed, err := build(gomodcache, old) if err != nil { - return false, err + return nil, err } - return ok, nil -} - -// modindexTimed writes an index current as of onlyBefore. -// If clear is true the index is constructed from all of GOMODCACHE -// otherwise the index is constructed from the last previous index -// and all the updates to the cache before onlyBefore. -// It returns true if it wrote a new index, false if it wrote nothing. -func modindexTimed(onlyBefore time.Time, cachedir Abspath, clear bool) (bool, error) { - var curIndex *Index - if !clear { - var err error - curIndex, err = ReadIndex(string(cachedir)) - if clear && err != nil { - return false, err + if old == nil || changed { + if err := write(gomodcache, new); err != nil { + return nil, err } - // TODO(pjw): check that most of those directories still exist - } - cfg := &work{ - onlyBefore: onlyBefore, - oldIndex: curIndex, - cacheDir: cachedir, - } - if curIndex != nil { - cfg.onlyAfter = curIndex.Changed - } - if err := cfg.buildIndex(); err != nil { - return false, err } - if len(cfg.newIndex.Entries) == 0 && curIndex != nil { - // no changes from existing curIndex, don't write a new index - return false, nil - } - if err := cfg.writeIndex(); err != nil { - return false, err - } - return true, nil -} - -type work struct { - onlyBefore time.Time // do not use directories later than this - onlyAfter time.Time // only interested in directories after this - // directories from before onlyAfter come from oldIndex - oldIndex *Index - newIndex *Index - cacheDir Abspath + return new, nil } -func (w *work) buildIndex() error { - // The effective date of the new index should be at least - // slightly earlier than when the directories are scanned - // so set it now. - w.newIndex = &Index{Changed: time.Now(), Cachedir: w.cacheDir} - dirs := findDirs(string(w.cacheDir), w.onlyAfter, w.onlyBefore) - if len(dirs) == 0 { - return nil +// build returns a new index for the specified Go module cache (an +// absolute path). +// +// If an old index is provided, only directories more recent than it +// that it are scanned; older directories are provided by the old +// Index. +// +// The boolean result indicates whether new entries were found. +func build(gomodcache string, old *Index) (*Index, bool, error) { + // Set the time window. + var start time.Time // = dawn of time + if old != nil { + start = old.ValidAt } - newdirs, err := byImportPath(dirs) + now := time.Now() + end := now.Add(24 * time.Hour) // safely in the future + + // Enumerate GOMODCACHE package directories. + // Choose the best (latest) package for each import path. + pkgDirs := findDirs(gomodcache, start, end) + dirByPath, err := bestDirByImportPath(pkgDirs) if err != nil { - return err + return nil, false, err } - // for each import path it might occur only in newdirs, - // only in w.oldIndex, or in both. - // If it occurs in both, use the semantically later one - if w.oldIndex != nil { - for _, e := range w.oldIndex.Entries { - found, ok := newdirs[e.ImportPath] - if !ok { - w.newIndex.Entries = append(w.newIndex.Entries, e) - continue // use this one, there is no new one - } - if semver.Compare(found[0].version, e.Version) > 0 { - // use the new one - } else { - // use the old one, forget the new one - w.newIndex.Entries = append(w.newIndex.Entries, e) - delete(newdirs, e.ImportPath) + + // For each import path it might occur only in + // dirByPath, only in old, or in both. + // If both, use the semantically later one. + var entries []Entry + if old != nil { + for _, entry := range old.Entries { + dir, ok := dirByPath[entry.ImportPath] + if !ok || semver.Compare(dir.version, entry.Version) <= 0 { + // New dir is missing or not more recent; use old entry. + entries = append(entries, entry) + delete(dirByPath, entry.ImportPath) } } } - // get symbol information for all the new diredtories - getSymbols(w.cacheDir, newdirs) - // assemble the new index entries - for k, v := range newdirs { - d := v[0] - pkg, names := processSyms(d.syms) - if pkg == "" { - continue // PJW: does this ever happen? - } - entry := Entry{ - PkgName: pkg, - Dir: d.path, - ImportPath: k, - Version: d.version, - Names: names, - } - w.newIndex.Entries = append(w.newIndex.Entries, entry) - } - // sort the entries in the new index - slices.SortFunc(w.newIndex.Entries, func(l, r Entry) int { - if n := strings.Compare(l.PkgName, r.PkgName); n != 0 { + + // Extract symbol information for all the new directories. + newEntries := extractSymbols(gomodcache, maps.Values(dirByPath)) + entries = append(entries, newEntries...) + slices.SortFunc(entries, func(x, y Entry) int { + if n := strings.Compare(x.PkgName, y.PkgName); n != 0 { return n } - return strings.Compare(l.ImportPath, r.ImportPath) + return strings.Compare(x.ImportPath, y.ImportPath) }) - return nil -} -func (w *work) writeIndex() error { - return writeIndex(w.cacheDir, w.newIndex) + return &Index{ + GOMODCACHE: gomodcache, + ValidAt: now, // time before the directories were scanned + Entries: entries, + }, len(newEntries) > 0, nil } diff --git a/vendor/golang.org/x/tools/internal/modindex/symbols.go b/vendor/golang.org/x/tools/internal/modindex/symbols.go index 31a502c58..fe24db9b1 100644 --- a/vendor/golang.org/x/tools/internal/modindex/symbols.go +++ b/vendor/golang.org/x/tools/internal/modindex/symbols.go @@ -10,11 +10,13 @@ import ( "go/parser" "go/token" "go/types" + "iter" "os" "path/filepath" "runtime" "slices" "strings" + "sync" "golang.org/x/sync/errgroup" ) @@ -34,41 +36,65 @@ type symbol struct { sig string // signature information, for F } -// find the symbols for the best directories -func getSymbols(cd Abspath, dirs map[string][]*directory) { +// extractSymbols returns a (new, unordered) array of Entries, one for +// each provided package directory, describing its exported symbols. +func extractSymbols(cwd string, dirs iter.Seq[directory]) []Entry { + var ( + mu sync.Mutex + entries []Entry + ) + var g errgroup.Group g.SetLimit(max(2, runtime.GOMAXPROCS(0)/2)) - for _, vv := range dirs { - // throttling some day? - d := vv[0] + for dir := range dirs { g.Go(func() error { - thedir := filepath.Join(string(cd), string(d.path)) + thedir := filepath.Join(cwd, string(dir.path)) mode := parser.SkipObjectResolution | parser.ParseComments - fi, err := os.ReadDir(thedir) + // Parse all Go files in dir and extract symbols. + dirents, err := os.ReadDir(thedir) if err != nil { return nil // log this someday? } - for _, fx := range fi { - if !strings.HasSuffix(fx.Name(), ".go") || strings.HasSuffix(fx.Name(), "_test.go") { + var syms []symbol + for _, dirent := range dirents { + if !strings.HasSuffix(dirent.Name(), ".go") || + strings.HasSuffix(dirent.Name(), "_test.go") { continue } - fname := filepath.Join(thedir, fx.Name()) + fname := filepath.Join(thedir, dirent.Name()) tr, err := parser.ParseFile(token.NewFileSet(), fname, nil, mode) if err != nil { continue // ignore errors, someday log them? } - d.syms = append(d.syms, getFileExports(tr)...) + syms = append(syms, getFileExports(tr)...) + } + + // Create an entry for the package. + pkg, names := processSyms(syms) + if pkg != "" { + mu.Lock() + defer mu.Unlock() + entries = append(entries, Entry{ + PkgName: pkg, + Dir: dir.path, + ImportPath: dir.importPath, + Version: dir.version, + Names: names, + }) } + return nil }) } - g.Wait() + g.Wait() // ignore error + + return entries } func getFileExports(f *ast.File) []symbol { pkg := f.Name.Name - if pkg == "main" { + if pkg == "main" || pkg == "" { return nil } var ans []symbol @@ -202,17 +228,18 @@ func processSyms(syms []symbol) (string, []string) { pkg := syms[0].pkg var names []string for _, s := range syms { + if s.pkg != pkg { + // Symbols came from two files in same dir + // with different package declarations. + continue + } var nx string - if s.pkg == pkg { - if s.sig != "" { - nx = fmt.Sprintf("%s %s %s", s.name, s.kind, s.sig) - } else { - nx = fmt.Sprintf("%s %s", s.name, s.kind) - } - names = append(names, nx) + if s.sig != "" { + nx = fmt.Sprintf("%s %s %s", s.name, s.kind, s.sig) } else { - continue // PJW: do we want to keep track of these? + nx = fmt.Sprintf("%s %s", s.name, s.kind) } + names = append(names, nx) } return pkg, names } diff --git a/vendor/golang.org/x/tools/internal/modindex/types.go b/vendor/golang.org/x/tools/internal/modindex/types.go deleted file mode 100644 index ece448863..000000000 --- a/vendor/golang.org/x/tools/internal/modindex/types.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2024 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 modindex - -import ( - "strings" -) - -// some special types to avoid confusions - -// distinguish various types of directory names. It's easy to get confused. -type Abspath string // absolute paths -type Relpath string // paths with GOMODCACHE prefix removed - -func toRelpath(cachedir Abspath, s string) Relpath { - if strings.HasPrefix(s, string(cachedir)) { - if s == string(cachedir) { - return Relpath("") - } - return Relpath(s[len(cachedir)+1:]) - } - return Relpath(s) -} |
