summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/tools/internal/modindex/modindex.go
blob: 5fa285d98e41c159549efda342f712f39138177d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// 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-<ver> 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-<ver>.
// (<ver> 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
}