Skip to content

Commit df3de6a

Browse files
committed
gopls: prepare for mod cache index
This CL adds the infrastructure for using the module cache index when satisfying missing imports. There is no change in behavior as it invokes the existing imports.Source. There is a new option importsSource whose value can be "goimports" to keep the old behavior, "gopls" to use (in a future CL) the module cache index, and "off" (for use by cider) to avoid looking in the file system at all. The index is kept in memory. Periodically the code checks to see if it needs to be updated. Change-Id: I61e0b5e224a6c26d75932417b26ecb9f432b460f Reviewed-on: https://go-review.googlesource.com/c/tools/+/640495 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent ae303ab commit df3de6a

File tree

8 files changed

+121
-22
lines changed

8 files changed

+121
-22
lines changed

gopls/internal/cache/imports.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"golang.org/x/tools/internal/event"
1616
"golang.org/x/tools/internal/event/keys"
1717
"golang.org/x/tools/internal/imports"
18+
"golang.org/x/tools/internal/modindex"
1819
)
1920

2021
// refreshTimer implements delayed asynchronous refreshing of state.
@@ -59,11 +60,8 @@ func (t *refreshTimer) schedule() {
5960

6061
if t.timer == nil {
6162
// Don't refresh more than twice per minute.
62-
delay := 30 * time.Second
6363
// Don't spend more than ~2% of the time refreshing.
64-
if adaptive := 50 * t.duration; adaptive > delay {
65-
delay = adaptive
66-
}
64+
delay := max(30*time.Second, 50*t.duration)
6765
t.timer = time.AfterFunc(delay, func() {
6866
start := time.Now()
6967
t.mu.Lock()
@@ -149,6 +147,41 @@ func newImportsState(backgroundCtx context.Context, modCache *sharedModCache, en
149147
return s
150148
}
151149

150+
// modcacheState holds a modindex.Index and controls its updates
151+
type modcacheState struct {
152+
dir string // GOMODCACHE
153+
refreshTimer *refreshTimer
154+
mu sync.Mutex
155+
index *modindex.Index
156+
}
157+
158+
// newModcacheState constructs a new modcacheState for goimports.
159+
// The returned state is automatically updated until [modcacheState.stopTimer] is called.
160+
func newModcacheState(dir string) *modcacheState {
161+
s := &modcacheState{
162+
dir: dir,
163+
}
164+
s.index, _ = modindex.ReadIndex(dir)
165+
s.refreshTimer = newRefreshTimer(s.refreshIndex)
166+
go s.refreshIndex()
167+
return s
168+
}
169+
170+
func (s *modcacheState) refreshIndex() {
171+
ok, err := modindex.Update(s.dir)
172+
if err != nil || !ok {
173+
return
174+
}
175+
// read the new index
176+
s.mu.Lock()
177+
defer s.mu.Unlock()
178+
s.index, _ = modindex.ReadIndex(s.dir)
179+
}
180+
181+
func (s *modcacheState) stopTimer() {
182+
s.refreshTimer.stop()
183+
}
184+
152185
// stopTimer stops scheduled refreshes of this imports state.
153186
func (s *importsState) stopTimer() {
154187
s.refreshTimer.stop()

gopls/internal/cache/session.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"context"
99
"errors"
1010
"fmt"
11+
"maps"
1112
"os"
1213
"path/filepath"
1314
"slices"
14-
"sort"
1515
"strconv"
1616
"strings"
1717
"sync"
@@ -218,7 +218,7 @@ func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *
218218
ModCache: s.cache.modCache.dirCache(def.folder.Env.GOMODCACHE),
219219
}
220220
if def.folder.Options.VerboseOutput {
221-
pe.Logf = func(format string, args ...interface{}) {
221+
pe.Logf = func(format string, args ...any) {
222222
event.Log(ctx, fmt.Sprintf(format, args...))
223223
}
224224
}
@@ -237,6 +237,9 @@ func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *
237237
viewDefinition: def,
238238
importsState: newImportsState(backgroundCtx, s.cache.modCache, pe),
239239
}
240+
if def.folder.Options.ImportsSource != "off" {
241+
v.modcacheState = newModcacheState(def.folder.Env.GOMODCACHE)
242+
}
240243

241244
s.snapshotWG.Add(1)
242245
v.snapshot = &Snapshot{
@@ -833,9 +836,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif
833836
openFiles = append(openFiles, o.URI())
834837
}
835838
// Sort for determinism.
836-
sort.Slice(openFiles, func(i, j int) bool {
837-
return openFiles[i] < openFiles[j]
838-
})
839+
slices.Sort(openFiles)
839840

840841
// TODO(rfindley): can we avoid running the go command (go env)
841842
// synchronously to change processing? Can we assume that the env did not
@@ -1124,9 +1125,7 @@ func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.Rel
11241125
if err != nil {
11251126
continue // view is shut down; continue with others
11261127
}
1127-
for k, v := range snapshot.fileWatchingGlobPatterns() {
1128-
patterns[k] = v
1129-
}
1128+
maps.Copy(patterns, snapshot.fileWatchingGlobPatterns())
11301129
release()
11311130
}
11321131
return patterns

gopls/internal/cache/source.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2025 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 cache
6+
7+
import (
8+
"context"
9+
10+
"golang.org/x/tools/internal/imports"
11+
)
12+
13+
// interim code for using the module cache index in imports
14+
// This code just forwards everything to an imports.ProcessEnvSource
15+
16+
// goplsSource is an imports.Source that provides import information using
17+
// gopls and the module cache index.
18+
// TODO(pjw): implement. Right now, this just forwards to the imports.ProcessEnvSource.
19+
type goplsSource struct {
20+
envSource *imports.ProcessEnvSource
21+
}
22+
23+
func (s *Snapshot) NewGoplsSource(is *imports.ProcessEnvSource) *goplsSource {
24+
return &goplsSource{
25+
envSource: is,
26+
}
27+
}
28+
29+
func (s *goplsSource) LoadPackageNames(ctx context.Context, srcDir string, paths []imports.ImportPath) (map[imports.ImportPath]imports.PackageName, error) {
30+
return s.envSource.LoadPackageNames(ctx, srcDir, paths)
31+
}
32+
33+
func (s *goplsSource) ResolveReferences(ctx context.Context, filename string, missing imports.References) ([]*imports.Result, error) {
34+
return s.envSource.ResolveReferences(ctx, filename, missing)
35+
}

gopls/internal/cache/view.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"errors"
1616
"fmt"
1717
"log"
18+
"maps"
1819
"os"
1920
"os/exec"
2021
"path"
@@ -106,8 +107,12 @@ type View struct {
106107
// background contexts created for this view.
107108
baseCtx context.Context
108109

110+
// importsState is for the old imports code
109111
importsState *importsState
110112

113+
// maintain the current module cache index
114+
modcacheState *modcacheState
115+
111116
// pkgIndex is an index of package IDs, for efficient storage of typerefs.
112117
pkgIndex *typerefs.PackageIndex
113118

@@ -1143,9 +1148,7 @@ func (s *Snapshot) ModuleUpgrades(modfile protocol.DocumentURI) map[string]strin
11431148
defer s.mu.Unlock()
11441149
upgrades := map[string]string{}
11451150
orig, _ := s.moduleUpgrades.Get(modfile)
1146-
for mod, ver := range orig {
1147-
upgrades[mod] = ver
1148-
}
1151+
maps.Copy(upgrades, orig)
11491152
return upgrades
11501153
}
11511154

gopls/internal/golang/format.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"golang.org/x/tools/gopls/internal/cache/parsego"
2222
"golang.org/x/tools/gopls/internal/file"
2323
"golang.org/x/tools/gopls/internal/protocol"
24+
"golang.org/x/tools/gopls/internal/settings"
2425
"golang.org/x/tools/gopls/internal/util/safetoken"
2526
"golang.org/x/tools/internal/diff"
2627
"golang.org/x/tools/internal/event"
@@ -120,7 +121,7 @@ func allImportsFixes(ctx context.Context, snapshot *cache.Snapshot, pgf *parsego
120121
defer done()
121122

122123
if err := snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, opts *imports.Options) error {
123-
allFixEdits, editsPerFix, err = computeImportEdits(ctx, pgf, snapshot.View().Folder().Env.GOROOT, opts)
124+
allFixEdits, editsPerFix, err = computeImportEdits(ctx, pgf, snapshot, opts)
124125
return err
125126
}); err != nil {
126127
return nil, nil, fmt.Errorf("allImportsFixes: %v", err)
@@ -130,12 +131,22 @@ func allImportsFixes(ctx context.Context, snapshot *cache.Snapshot, pgf *parsego
130131

131132
// computeImportEdits computes a set of edits that perform one or all of the
132133
// necessary import fixes.
133-
func computeImportEdits(ctx context.Context, pgf *parsego.File, goroot string, options *imports.Options) (allFixEdits []protocol.TextEdit, editsPerFix []*importFix, err error) {
134+
func computeImportEdits(ctx context.Context, pgf *parsego.File, snapshot *cache.Snapshot, options *imports.Options) (allFixEdits []protocol.TextEdit, editsPerFix []*importFix, err error) {
135+
goroot := snapshot.View().Folder().Env.GOROOT
134136
filename := pgf.URI.Path()
135137

136138
// Build up basic information about the original file.
137139
isource, err := imports.NewProcessEnvSource(options.Env, filename, pgf.File.Name.Name)
138-
allFixes, err := imports.FixImports(ctx, filename, pgf.Src, goroot, options.Env.Logf, isource)
140+
var source imports.Source
141+
switch snapshot.Options().ImportsSource {
142+
case settings.ImportsSourceGopls:
143+
source = snapshot.NewGoplsSource(isource)
144+
case settings.ImportsSourceOff: // for cider, which has no file system
145+
source = nil
146+
case settings.ImportsSourceGoimports:
147+
source = isource
148+
}
149+
allFixes, err := imports.FixImports(ctx, filename, pgf.Src, goroot, options.Env.Logf, source)
139150
if err != nil {
140151
return nil, nil, err
141152
}

gopls/internal/settings/default.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func DefaultOptions(overrides ...func(*Options)) *Options {
3939
DynamicWatchedFilesSupported: true,
4040
LineFoldingOnly: false,
4141
HierarchicalDocumentSymbolSupport: true,
42+
ImportsSource: ImportsSourceGopls,
4243
},
4344
ServerOptions: ServerOptions{
4445
SupportedCodeActions: map[file.Kind]map[protocol.CodeActionKind]bool{

gopls/internal/settings/settings.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type ClientOptions struct {
5353
PreferredContentFormat protocol.MarkupKind
5454
LineFoldingOnly bool
5555
HierarchicalDocumentSymbolSupport bool
56+
ImportsSource ImportsSourceEnum `status:"experimental"`
5657
SemanticTypes []string
5758
SemanticMods []string
5859
RelatedInformationSupported bool
@@ -697,6 +698,19 @@ func (s ImportShortcut) ShowDefinition() bool {
697698
return s == BothShortcuts || s == DefinitionShortcut
698699
}
699700

701+
// ImportsSourceEnum has legal values:
702+
//
703+
// - `off` to disable searching the file system for imports
704+
// - `gopls` to use the metadata graph and module cache index
705+
// - `goimports` for the old behavior, to be deprecated
706+
type ImportsSourceEnum string
707+
708+
const (
709+
ImportsSourceOff ImportsSourceEnum = "off"
710+
ImportsSourceGopls = "gopls"
711+
ImportsSourceGoimports = "goimports"
712+
)
713+
700714
type Matcher string
701715

702716
const (
@@ -949,6 +963,11 @@ func (o *Options) setOne(name string, value any) error {
949963
return setBool(&o.CompleteUnimported, value)
950964
case "completionBudget":
951965
return setDuration(&o.CompletionBudget, value)
966+
case "importsSource":
967+
return setEnum(&o.ImportsSource, value,
968+
ImportsSourceOff,
969+
ImportsSourceGopls,
970+
ImportsSourceGoimports)
952971
case "matcher":
953972
return setEnum(&o.Matcher, value,
954973
Fuzzy,
@@ -1033,9 +1052,7 @@ func (o *Options) setOne(name string, value any) error {
10331052
o.Codelenses = make(map[CodeLensSource]bool)
10341053
}
10351054
o.Codelenses = maps.Clone(o.Codelenses)
1036-
for source, enabled := range lensOverrides {
1037-
o.Codelenses[source] = enabled
1038-
}
1055+
maps.Copy(o.Codelenses, lensOverrides)
10391056

10401057
if name == "codelens" {
10411058
return deprecatedError("codelenses")

internal/imports/fix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ type ProcessEnv struct {
927927
WorkingDir string
928928

929929
// If Logf is non-nil, debug logging is enabled through this function.
930-
Logf func(format string, args ...interface{})
930+
Logf func(format string, args ...any)
931931

932932
// If set, ModCache holds a shared cache of directory info to use across
933933
// multiple ProcessEnvs.

0 commit comments

Comments
 (0)