modindex.go (3622B)
1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package modindex contains code for building and searching an 6 // [Index] of the Go module cache. 7 package modindex 8 9 // The directory containing the index, returned by 10 // [IndexDir], contains a file index-name-<ver> that contains the name 11 // of the current index. We believe writing that short file is atomic. 12 // [Read] reads that file to get the file name of the index. 13 // WriteIndex writes an index with a unique name and then 14 // writes that name into a new version of index-name-<ver>. 15 // (<ver> stands for the CurrentVersion of the index format.) 16 17 import ( 18 "maps" 19 "os" 20 "path/filepath" 21 "slices" 22 "strings" 23 "time" 24 25 "golang.org/x/mod/semver" 26 ) 27 28 // Update updates the index for the specified Go 29 // module cache directory, creating it as needed. 30 // On success it returns the current index. 31 func Update(gomodcache string) (*Index, error) { 32 prev, err := Read(gomodcache) 33 if err != nil { 34 if !os.IsNotExist(err) { 35 return nil, err 36 } 37 prev = nil 38 } 39 return update(gomodcache, prev) 40 } 41 42 // update builds, writes, and returns the current index. 43 // 44 // If old is nil, the new index is built from all of GOMODCACHE; 45 // otherwise it is built from the old index plus cache updates 46 // since the previous index's time. 47 func update(gomodcache string, old *Index) (*Index, error) { 48 gomodcache, err := filepath.Abs(gomodcache) 49 if err != nil { 50 return nil, err 51 } 52 new, changed, err := build(gomodcache, old) 53 if err != nil { 54 return nil, err 55 } 56 if old == nil || changed { 57 if err := write(gomodcache, new); err != nil { 58 return nil, err 59 } 60 } 61 return new, nil 62 } 63 64 // build returns a new index for the specified Go module cache (an 65 // absolute path). 66 // 67 // If an old index is provided, only directories more recent than it 68 // that it are scanned; older directories are provided by the old 69 // Index. 70 // 71 // The boolean result indicates whether new entries were found. 72 func build(gomodcache string, old *Index) (*Index, bool, error) { 73 // Set the time window. 74 var start time.Time // = dawn of time 75 if old != nil { 76 // -1s to accommodate skew between (fine-grained) time.Now 77 // and (coarse) file system's mtime clock. 78 start = old.ValidAt.Add(-1 * time.Second) 79 } 80 now := time.Now() 81 end := now.Add(24 * time.Hour) // safely in the future 82 83 // Enumerate GOMODCACHE package directories. 84 // Choose the best (latest) package for each import path. 85 pkgDirs := findDirs(gomodcache, start, end) 86 dirByPath, err := bestDirByImportPath(pkgDirs) 87 if err != nil { 88 return nil, false, err 89 } 90 91 // For each import path it might occur only in 92 // dirByPath, only in old, or in both. 93 // If both, use the semantically later one. 94 var entries []Entry 95 if old != nil { 96 for _, entry := range old.Entries { 97 dir, ok := dirByPath[entry.ImportPath] 98 if !ok || semver.Compare(dir.version, entry.Version) <= 0 { 99 // New dir is missing or not more recent; use old entry. 100 entries = append(entries, entry) 101 delete(dirByPath, entry.ImportPath) 102 } 103 } 104 } 105 106 // Extract symbol information for all the new directories. 107 newEntries := extractSymbols(gomodcache, maps.Values(dirByPath)) 108 entries = append(entries, newEntries...) 109 slices.SortFunc(entries, func(x, y Entry) int { 110 if n := strings.Compare(x.PkgName, y.PkgName); n != 0 { 111 return n 112 } 113 return strings.Compare(x.ImportPath, y.ImportPath) 114 }) 115 116 return &Index{ 117 GOMODCACHE: gomodcache, 118 ValidAt: now, // time before the directories were scanned 119 Entries: entries, 120 }, len(newEntries) > 0, nil 121 }