1 // Copyright 2020 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.
15 "golang.org/x/tools/internal/event"
16 "golang.org/x/tools/internal/event/keys"
17 "golang.org/x/tools/internal/gocommand"
18 "golang.org/x/tools/internal/imports"
19 "golang.org/x/tools/internal/lsp/source"
22 type importsState struct {
26 processEnv *imports.ProcessEnv
27 cleanupProcessEnv func()
28 cacheRefreshDuration time.Duration
29 cacheRefreshTimer *time.Timer
30 cachedModFileHash string
31 cachedBuildFlags []string
34 func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *snapshot, fn func(*imports.Options) error) error {
38 // Find the hash of the active mod file, if any. Using the unsaved content
39 // is slightly wasteful, since we'll drop caches a little too often, but
40 // the mod file shouldn't be changing while people are autocompleting.
41 var modFileHash string
42 if snapshot.workspaceMode()&usesWorkspaceModule == 0 {
43 for m := range snapshot.workspace.getActiveModFiles() { // range to access the only element
44 modFH, err := snapshot.GetFile(ctx, m)
48 modFileHash = modFH.FileIdentity().Hash
51 modFile, err := snapshot.workspace.modFile(ctx, snapshot)
55 modBytes, err := modFile.Format()
59 modFileHash = hashContents(modBytes)
62 // view.goEnv is immutable -- changes make a new view. Options can change.
63 // We can't compare build flags directly because we may add -modfile.
64 snapshot.view.optionsMu.Lock()
65 localPrefix := snapshot.view.options.Local
66 currentBuildFlags := snapshot.view.options.BuildFlags
67 changed := !reflect.DeepEqual(currentBuildFlags, s.cachedBuildFlags) ||
68 snapshot.view.options.VerboseOutput != (s.processEnv.Logf != nil) ||
69 modFileHash != s.cachedModFileHash
70 snapshot.view.optionsMu.Unlock()
72 // If anything relevant to imports has changed, clear caches and
73 // update the processEnv. Clearing caches blocks on any background
76 // As a special case, skip cleanup the first time -- we haven't fully
77 // initialized the environment yet and calling GetResolver will do
78 // unnecessary work and potentially mess up the go.mod file.
79 if s.cleanupProcessEnv != nil {
80 if resolver, err := s.processEnv.GetResolver(); err == nil {
81 if modResolver, ok := resolver.(*imports.ModuleResolver); ok {
82 modResolver.ClearForNewMod()
87 s.cachedModFileHash = modFileHash
88 s.cachedBuildFlags = currentBuildFlags
90 s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot)
96 // Run the user function.
97 opts := &imports.Options{
106 LocalPrefix: localPrefix,
109 if err := fn(opts); err != nil {
113 if s.cacheRefreshTimer == nil {
114 // Don't refresh more than twice per minute.
115 delay := 30 * time.Second
116 // Don't spend more than a couple percent of the time refreshing.
117 if adaptive := 50 * s.cacheRefreshDuration; adaptive > delay {
120 s.cacheRefreshTimer = time.AfterFunc(delay, s.refreshProcessEnv)
126 // populateProcessEnv sets the dynamically configurable fields for the view's
127 // process environment. Assumes that the caller is holding the s.view.importsMu.
128 func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot) (cleanup func(), err error) {
131 if snapshot.view.Options().VerboseOutput {
132 pe.Logf = func(format string, args ...interface{}) {
133 event.Log(ctx, fmt.Sprintf(format, args...))
139 // Take an extra reference to the snapshot so that its workspace directory
140 // (if any) isn't destroyed while we're using it.
141 release := snapshot.generation.Acquire(ctx)
142 _, inv, cleanupInvocation, err := snapshot.goCommandInvocation(ctx, source.LoadWorkspace, &gocommand.Invocation{
143 WorkingDir: snapshot.view.rootURI.Filename(),
148 pe.WorkingDir = inv.WorkingDir
149 pe.BuildFlags = inv.BuildFlags
150 pe.WorkingDir = inv.WorkingDir
151 pe.ModFile = inv.ModFile
152 pe.ModFlag = inv.ModFlag
153 pe.Env = map[string]string{}
154 for _, kv := range inv.Env {
155 split := strings.SplitN(kv, "=", 2)
159 pe.Env[split[0]] = split[1]
168 func (s *importsState) refreshProcessEnv() {
173 if resolver, err := s.processEnv.GetResolver(); err == nil {
174 resolver.ClearForNewScan()
178 event.Log(s.ctx, "background imports cache refresh starting")
179 if err := imports.PrimeCache(context.Background(), env); err == nil {
180 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)))
182 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err))
185 s.cacheRefreshDuration = time.Since(start)
186 s.cacheRefreshTimer = nil
190 func (s *importsState) destroy() {
192 if s.cleanupProcessEnv != nil {
193 s.cleanupProcessEnv()