// 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 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- that contains the name // of the current index. We believe writing that short file is atomic. // [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-. // ( stands for the CurrentVersion of the index format.) import ( "maps" "os" "path/filepath" "slices" "strings" "time" "golang.org/x/mod/semver" ) // 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) } // 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 nil, err } new, changed, err := build(gomodcache, old) if err != nil { return nil, err } if old == nil || changed { if err := write(gomodcache, new); err != nil { return nil, err } } return new, 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 } 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 nil, false, err } // 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) } } } // 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(x.ImportPath, y.ImportPath) }) return &Index{ GOMODCACHE: gomodcache, ValidAt: now, // time before the directories were scanned Entries: entries, }, len(newEntries) > 0, nil }