10 "golang.org/x/tools/internal/event"
11 "golang.org/x/tools/internal/event/keys"
12 "golang.org/x/tools/internal/imports"
13 "golang.org/x/tools/internal/lsp/source"
14 "golang.org/x/tools/internal/span"
17 type importsState struct {
21 processEnv *imports.ProcessEnv
22 cleanupProcessEnv func()
23 cacheRefreshDuration time.Duration
24 cacheRefreshTimer *time.Timer
25 cachedModFileIdentifier string
26 cachedBuildFlags []string
29 func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *snapshot, fn func(*imports.Options) error) error {
33 // Use temporary go.mod files, but always go to disk for the contents.
34 // Rebuilding the cache is expensive, and we don't want to do it for
36 var modFH source.FileHandle
38 var modFileIdentifier string
40 // TODO(rfindley): Change the goimports logic to use a persistent workspace
41 // module for workspace module mode.
43 // Get the go.mod file that corresponds to this view's root URI. This is
44 // broken because it assumes that the view's root is a module, but this is
45 // not more broken than the previous state--it is a temporary hack that
46 // should be removed ASAP.
48 for modURI := range snapshot.workspace.activeModFiles() {
49 if dirURI(modURI) == snapshot.view.rootURI {
53 // TODO(rFindley): should it be an error if matchURI is empty?
55 modFH, err = snapshot.GetFile(ctx, matchURI)
59 modFileIdentifier = modFH.FileIdentity().Hash
60 gosum = snapshot.goSum(ctx, matchURI)
62 // v.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 modFileIdentifier != s.cachedModFileIdentifier
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 resolver.(*imports.ModuleResolver).ClearForNewMod()
85 s.cachedModFileIdentifier = modFileIdentifier
86 s.cachedBuildFlags = currentBuildFlags
87 s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot, modFH, gosum)
93 // Run the user function.
94 opts := &imports.Options{
103 LocalPrefix: localPrefix,
106 if err := fn(opts); err != nil {
110 if s.cacheRefreshTimer == nil {
111 // Don't refresh more than twice per minute.
112 delay := 30 * time.Second
113 // Don't spend more than a couple percent of the time refreshing.
114 if adaptive := 50 * s.cacheRefreshDuration; adaptive > delay {
117 s.cacheRefreshTimer = time.AfterFunc(delay, s.refreshProcessEnv)
123 // populateProcessEnv sets the dynamically configurable fields for the view's
124 // process environment. Assumes that the caller is holding the s.view.importsMu.
125 func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot, modFH source.FileHandle, gosum []byte) (cleanup func(), err error) {
129 snapshot.view.optionsMu.Lock()
130 pe.BuildFlags = append([]string(nil), snapshot.view.options.BuildFlags...)
131 if snapshot.view.options.VerboseOutput {
132 pe.Logf = func(format string, args ...interface{}) {
133 event.Log(ctx, fmt.Sprintf(format, args...))
138 snapshot.view.optionsMu.Unlock()
140 pe.Env = map[string]string{}
141 for k, v := range snapshot.view.goEnv {
144 pe.Env["GO111MODULE"] = snapshot.view.go111module
147 var modContent []byte
150 modContent, err = modFH.Read()
155 modmod, err := snapshot.needsModEqualsMod(ctx, modURI, modContent)
163 // Add -modfile to the build flags, if we are using it.
164 if snapshot.workspaceMode()&tempModfile != 0 && modFH != nil {
166 tmpURI, cleanup, err = tempModFile(modFH, gosum)
170 pe.ModFile = tmpURI.Filename()
176 func (s *importsState) refreshProcessEnv() {
181 if resolver, err := s.processEnv.GetResolver(); err == nil {
182 resolver.ClearForNewScan()
186 event.Log(s.ctx, "background imports cache refresh starting")
187 if err := imports.PrimeCache(context.Background(), env); err == nil {
188 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)))
190 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err))
193 s.cacheRefreshDuration = time.Since(start)
194 s.cacheRefreshTimer = nil