--- /dev/null
+package cache
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "sync"
+ "time"
+
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/event/keys"
+ "golang.org/x/tools/internal/imports"
+ "golang.org/x/tools/internal/lsp/source"
+ "golang.org/x/tools/internal/span"
+)
+
+type importsState struct {
+ ctx context.Context
+
+ mu sync.Mutex
+ processEnv *imports.ProcessEnv
+ cleanupProcessEnv func()
+ cacheRefreshDuration time.Duration
+ cacheRefreshTimer *time.Timer
+ cachedModFileIdentifier string
+ cachedBuildFlags []string
+}
+
+func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *snapshot, fn func(*imports.Options) error) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ // Use temporary go.mod files, but always go to disk for the contents.
+ // Rebuilding the cache is expensive, and we don't want to do it for
+ // transient changes.
+ var modFH, sumFH source.FileHandle
+ var modFileIdentifier string
+ var err error
+ // TODO(heschik): Change the goimports logic to use a persistent workspace
+ // module for workspace module mode.
+ //
+ // Get the go.mod file that corresponds to this view's root URI. This is
+ // broken because it assumes that the view's root is a module, but this is
+ // not more broken than the previous state--it is a temporary hack that
+ // should be removed ASAP.
+ var match *moduleRoot
+ for _, m := range snapshot.modules {
+ if m.rootURI == snapshot.view.rootURI {
+ match = m
+ }
+ }
+ if match != nil {
+ modFH, err = snapshot.GetFile(ctx, match.modURI)
+ if err != nil {
+ return err
+ }
+ modFileIdentifier = modFH.FileIdentity().Hash
+ if match.sumURI != "" {
+ sumFH, err = snapshot.GetFile(ctx, match.sumURI)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // v.goEnv is immutable -- changes make a new view. Options can change.
+ // We can't compare build flags directly because we may add -modfile.
+ snapshot.view.optionsMu.Lock()
+ localPrefix := snapshot.view.options.Local
+ currentBuildFlags := snapshot.view.options.BuildFlags
+ changed := !reflect.DeepEqual(currentBuildFlags, s.cachedBuildFlags) ||
+ snapshot.view.options.VerboseOutput != (s.processEnv.Logf != nil) ||
+ modFileIdentifier != s.cachedModFileIdentifier
+ snapshot.view.optionsMu.Unlock()
+
+ // If anything relevant to imports has changed, clear caches and
+ // update the processEnv. Clearing caches blocks on any background
+ // scans.
+ if changed {
+ // As a special case, skip cleanup the first time -- we haven't fully
+ // initialized the environment yet and calling GetResolver will do
+ // unnecessary work and potentially mess up the go.mod file.
+ if s.cleanupProcessEnv != nil {
+ if resolver, err := s.processEnv.GetResolver(); err == nil {
+ resolver.(*imports.ModuleResolver).ClearForNewMod()
+ }
+ s.cleanupProcessEnv()
+ }
+ s.cachedModFileIdentifier = modFileIdentifier
+ s.cachedBuildFlags = currentBuildFlags
+ s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot, modFH, sumFH)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Run the user function.
+ opts := &imports.Options{
+ // Defaults.
+ AllErrors: true,
+ Comments: true,
+ Fragment: true,
+ FormatOnly: false,
+ TabIndent: true,
+ TabWidth: 8,
+ Env: s.processEnv,
+ LocalPrefix: localPrefix,
+ }
+
+ if err := fn(opts); err != nil {
+ return err
+ }
+
+ if s.cacheRefreshTimer == nil {
+ // Don't refresh more than twice per minute.
+ delay := 30 * time.Second
+ // Don't spend more than a couple percent of the time refreshing.
+ if adaptive := 50 * s.cacheRefreshDuration; adaptive > delay {
+ delay = adaptive
+ }
+ s.cacheRefreshTimer = time.AfterFunc(delay, s.refreshProcessEnv)
+ }
+
+ return nil
+}
+
+// populateProcessEnv sets the dynamically configurable fields for the view's
+// process environment. Assumes that the caller is holding the s.view.importsMu.
+func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot, modFH, sumFH source.FileHandle) (cleanup func(), err error) {
+ cleanup = func() {}
+ pe := s.processEnv
+
+ snapshot.view.optionsMu.Lock()
+ pe.BuildFlags = append([]string(nil), snapshot.view.options.BuildFlags...)
+ if snapshot.view.options.VerboseOutput {
+ pe.Logf = func(format string, args ...interface{}) {
+ event.Log(ctx, fmt.Sprintf(format, args...))
+ }
+ } else {
+ pe.Logf = nil
+ }
+ snapshot.view.optionsMu.Unlock()
+
+ pe.Env = map[string]string{}
+ for k, v := range snapshot.view.goEnv {
+ pe.Env[k] = v
+ }
+ pe.Env["GO111MODULE"] = snapshot.view.go111module
+
+ var modURI span.URI
+ var modContent []byte
+ if modFH != nil {
+ modURI = modFH.URI()
+ modContent, err = modFH.Read()
+ if err != nil {
+ return nil, err
+ }
+ }
+ modmod, err := snapshot.needsModEqualsMod(ctx, modURI, modContent)
+ if err != nil {
+ return cleanup, err
+ }
+ if modmod {
+ pe.ModFlag = "mod"
+ }
+
+ // Add -modfile to the build flags, if we are using it.
+ if snapshot.workspaceMode()&tempModfile != 0 && modFH != nil {
+ var tmpURI span.URI
+ tmpURI, cleanup, err = tempModFile(modFH, sumFH)
+ if err != nil {
+ return nil, err
+ }
+ pe.ModFile = tmpURI.Filename()
+ }
+
+ return cleanup, nil
+}
+
+func (s *importsState) refreshProcessEnv() {
+ start := time.Now()
+
+ s.mu.Lock()
+ env := s.processEnv
+ if resolver, err := s.processEnv.GetResolver(); err == nil {
+ resolver.ClearForNewScan()
+ }
+ s.mu.Unlock()
+
+ event.Log(s.ctx, "background imports cache refresh starting")
+ if err := imports.PrimeCache(context.Background(), env); err == nil {
+ event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)))
+ } else {
+ event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err))
+ }
+ s.mu.Lock()
+ s.cacheRefreshDuration = time.Since(start)
+ s.cacheRefreshTimer = nil
+ s.mu.Unlock()
+}