summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/tools/internal/modindex/index.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/internal/modindex/index.go')
-rw-r--r--vendor/golang.org/x/tools/internal/modindex/index.go262
1 files changed, 262 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/internal/modindex/index.go b/vendor/golang.org/x/tools/internal/modindex/index.go
new file mode 100644
index 000000000..27b6dd832
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/modindex/index.go
@@ -0,0 +1,262 @@
+// 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 (
+ "bufio"
+ "encoding/csv"
+ "errors"
+ "fmt"
+ "hash/crc64"
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+/*
+The on-disk index 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.)
+Following the header are sections of lines, one section for each
+import path. These sections are sorted by package name.
+The first line of each section, marked by a leading :, contains
+the package name, the import path, the name of the directory relative
+to GOMODCACHE, and its semantic version.
+The rest of each section consists of one line per exported symbol.
+The lines are sorted by the symbol's name and contain the name,
+an indication of its lexical type (C, T, V, F), and if it is the
+name of a function, information about the signature.
+
+The fields in the section header lines are separated by commas, and
+in the unlikely event this would be confusing, the csv package is used
+to write (and read) them.
+
+In the lines containing exported names, C=const, V=var, T=type, F=func.
+If it is a func, the next field is the number of returned values,
+followed by pairs consisting of formal parameter names and types.
+All these fields are separated by spaces. Any spaces in a type
+(e.g., chan struct{}) are replaced by $s on the disk. The $s are
+turned back into spaces when read.
+
+Here is an index header (the comments are not part of the index):
+0 // version (of the index format)
+/usr/local/google/home/pjw/go/pkg/mod // GOMODCACHE
+2024-09-11 18:55:09 // validity date of the index
+
+Here is an index section:
+:yaml,gopkg.in/yaml.v1,gopkg.in/yaml.v1@v1.0.0-20140924161607-9f9df34309c0,v1.0.0-20140924161607-9f9df34309c0
+Getter T
+Marshal F 2 in interface{}
+Setter T
+Unmarshal F 1 in []byte out interface{}
+
+The package name is yaml, the import path is gopkg.in/yaml.v1.
+Getter and Setter are types, and Marshal and Unmarshal are functions.
+The latter returns one value and has two arguments, 'in' and 'out'
+whose types are []byte and interface{}.
+*/
+
+// CurrentVersion tells readers about the format of the index.
+const CurrentVersion int = 0
+
+// Index is returned by ReadIndex().
+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
+}
+
+// An Entry contains information for an import path.
+type Entry struct {
+ Dir Relpath // directory in modcache
+ ImportPath string
+ PkgName string
+ Version string
+ //ModTime STime // is this useful?
+ Names []string // exported names and information
+}
+
+// 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)
+ if err != nil {
+ return nil, err
+ }
+ cd := Abspath(cachedir)
+ dir, err := IndexDir()
+ if err != nil {
+ return nil, err
+ }
+ 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)
+ if err != nil {
+ return nil, err
+ }
+ defer fd.Close()
+ r := bufio.NewReader(fd)
+ ix, err := readIndexFrom(cd, r)
+ if err != nil {
+ return nil, err
+ }
+ return ix, nil
+}
+
+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")
+ }
+ l := b.Text()
+ var err error
+ ans.Version, err = strconv.Atoi(l)
+ if err != nil {
+ return nil, err
+ }
+ if ans.Version != CurrentVersion {
+ return nil, fmt.Errorf("got version %d, expected %d", ans.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")
+ }
+ // TODO(pjw): need to check that this is the expected cachedir
+ // so the tag should be passed in to this function
+ ans.Changed, err = time.ParseInLocation(time.DateTime, b.Text(), time.Local)
+ if err != nil {
+ return nil, err
+ }
+ var curEntry *Entry
+ for b.Scan() {
+ v := b.Text()
+ if v[0] == ':' {
+ if curEntry != nil {
+ ans.Entries = append(ans.Entries, *curEntry)
+ }
+ // as directories may contain commas and quotes, they need to be read as csv.
+ rdr := strings.NewReader(v[1:])
+ cs := csv.NewReader(rdr)
+ flds, err := cs.Read()
+ if err != nil {
+ return nil, err
+ }
+ 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]}
+ continue
+ }
+ curEntry.Names = append(curEntry.Names, v)
+ }
+ if curEntry != nil {
+ ans.Entries = append(ans.Entries, *curEntry)
+ }
+ if err := b.Err(); err != nil {
+ return nil, fmt.Errorf("scanner failed %v", err)
+ }
+ return &ans, nil
+}
+
+// write the index as a text file
+func writeIndex(cachedir Abspath, ix *Index) error {
+ dir, err := IndexDir()
+ if err != nil {
+ return err
+ }
+ ipat := fmt.Sprintf("index-%d-*", CurrentVersion)
+ fd, err := os.CreateTemp(dir, ipat)
+ if err != nil {
+ return err // can this happen?
+ }
+ defer fd.Close()
+ if err := writeIndexToFile(ix, fd); err != nil {
+ return err
+ }
+ content := fd.Name()
+ content = filepath.Base(content)
+ base := indexNameBase(cachedir)
+ nm := filepath.Join(dir, base)
+ err = os.WriteFile(nm, []byte(content), 0666)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func writeIndexToFile(x *Index, fd *os.File) error {
+ cnt := 0
+ w := bufio.NewWriter(fd)
+ 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", tm.Format(time.DateTime))
+ for _, e := range x.Entries {
+ if e.ImportPath == "" {
+ continue // shouldn't happen
+ }
+ // 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()
+ } else {
+ fmt.Fprintf(w, ":%s,%s,%s,%s\n", e.PkgName, e.ImportPath, e.Dir, e.Version)
+ }
+ for _, x := range e.Names {
+ fmt.Fprintf(w, "%s\n", x)
+ cnt++
+ }
+ }
+ if err := w.Flush(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// tests can override this
+var IndexDir = indexDir
+
+// IndexDir computes the directory containing the index
+func indexDir() (string, error) {
+ dir, err := os.UserCacheDir()
+ if err != nil {
+ return "", fmt.Errorf("cannot open UserCacheDir, %w", err)
+ }
+ return filepath.Join(dir, "go", "imports"), nil
+}
+
+// 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
+}