summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/tools/internal
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/internal')
-rw-r--r--vendor/golang.org/x/tools/internal/imports/source_modindex.go47
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/directories.go148
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/index.go233
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/modindex.go205
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/symbols.go69
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/types.go25
6 files changed, 349 insertions, 378 deletions
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 := &region{
- 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)
-}