src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }