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, sumFH source.FileHandle
37 var modFileIdentifier string
39 // TODO(heschik): Change the goimports logic to use a persistent workspace
40 // module for workspace module mode.
42 // Get the go.mod file that corresponds to this view's root URI. This is
43 // broken because it assumes that the view's root is a module, but this is
44 // not more broken than the previous state--it is a temporary hack that
45 // should be removed ASAP.
47 for _, m := range snapshot.modules {
48 if m.rootURI == snapshot.view.rootURI {
53 modFH, err = snapshot.GetFile(ctx, match.modURI)
57 modFileIdentifier = modFH.FileIdentity().Hash
58 if match.sumURI != "" {
59 sumFH, err = snapshot.GetFile(ctx, match.sumURI)
65 // v.goEnv is immutable -- changes make a new view. Options can change.
66 // We can't compare build flags directly because we may add -modfile.
67 snapshot.view.optionsMu.Lock()
68 localPrefix := snapshot.view.options.Local
69 currentBuildFlags := snapshot.view.options.BuildFlags
70 changed := !reflect.DeepEqual(currentBuildFlags, s.cachedBuildFlags) ||
71 snapshot.view.options.VerboseOutput != (s.processEnv.Logf != nil) ||
72 modFileIdentifier != s.cachedModFileIdentifier
73 snapshot.view.optionsMu.Unlock()
75 // If anything relevant to imports has changed, clear caches and
76 // update the processEnv. Clearing caches blocks on any background
79 // As a special case, skip cleanup the first time -- we haven't fully
80 // initialized the environment yet and calling GetResolver will do
81 // unnecessary work and potentially mess up the go.mod file.
82 if s.cleanupProcessEnv != nil {
83 if resolver, err := s.processEnv.GetResolver(); err == nil {
84 resolver.(*imports.ModuleResolver).ClearForNewMod()
88 s.cachedModFileIdentifier = modFileIdentifier
89 s.cachedBuildFlags = currentBuildFlags
90 s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot, modFH, sumFH)
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, modFH, sumFH source.FileHandle) (cleanup func(), err error) {
132 snapshot.view.optionsMu.Lock()
133 pe.BuildFlags = append([]string(nil), snapshot.view.options.BuildFlags...)
134 if snapshot.view.options.VerboseOutput {
135 pe.Logf = func(format string, args ...interface{}) {
136 event.Log(ctx, fmt.Sprintf(format, args...))
141 snapshot.view.optionsMu.Unlock()
143 pe.Env = map[string]string{}
144 for k, v := range snapshot.view.goEnv {
147 pe.Env["GO111MODULE"] = snapshot.view.go111module
150 var modContent []byte
153 modContent, err = modFH.Read()
158 modmod, err := snapshot.needsModEqualsMod(ctx, modURI, modContent)
166 // Add -modfile to the build flags, if we are using it.
167 if snapshot.workspaceMode()&tempModfile != 0 && modFH != nil {
169 tmpURI, cleanup, err = tempModFile(modFH, sumFH)
173 pe.ModFile = tmpURI.Filename()
179 func (s *importsState) refreshProcessEnv() {
184 if resolver, err := s.processEnv.GetResolver(); err == nil {
185 resolver.ClearForNewScan()
189 event.Log(s.ctx, "background imports cache refresh starting")
190 if err := imports.PrimeCache(context.Background(), env); err == nil {
191 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)))
193 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err))
196 s.cacheRefreshDuration = time.Since(start)
197 s.cacheRefreshTimer = nil