2019-08-02 12:05:22 -06:00
|
|
|
package imports
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ModuleResolver implements Resolver for modules using the go command as little
|
|
|
|
// as feasible.
|
|
|
|
//
|
|
|
|
// To find packages to import, the resolver needs to know about all of the
|
|
|
|
// the packages that could be imported. This includes packages that are
|
|
|
|
// already in modules that are in (1) the current module, (2) replace targets,
|
|
|
|
// and (3) packages in the module cache. Packages in (1) and (2) may change over
|
|
|
|
// time, as the client may edit the current module and locally replaced modules.
|
|
|
|
// The module cache (which includes all of the packages in (3)) can only
|
|
|
|
// ever be added to.
|
|
|
|
//
|
|
|
|
// The resolver can thus save state about packages in the module cache
|
|
|
|
// and guarantee that this will not change over time. To obtain information
|
|
|
|
// about new modules added to the module cache, the module cache should be
|
|
|
|
// rescanned.
|
|
|
|
//
|
|
|
|
// It is OK to serve information about modules that have been deleted,
|
|
|
|
// as they do still exist.
|
|
|
|
// TODO(suzmue): can we share information with the caller about
|
|
|
|
// what module needs to be downloaded to import this package?
|
|
|
|
|
|
|
|
type directoryPackageStatus int
|
|
|
|
|
|
|
|
const (
|
|
|
|
_ directoryPackageStatus = iota
|
|
|
|
directoryScanned
|
|
|
|
)
|
|
|
|
|
|
|
|
type directoryPackageInfo struct {
|
|
|
|
// status indicates the extent to which this struct has been filled in.
|
|
|
|
status directoryPackageStatus
|
|
|
|
// err is non-nil when there was an error trying to reach status.
|
|
|
|
err error
|
|
|
|
|
|
|
|
// Set when status > directoryScanned.
|
|
|
|
|
|
|
|
// dir is the absolute directory of this package.
|
|
|
|
dir string
|
|
|
|
// nonCanonicalImportPath is the expected import path for this package.
|
|
|
|
// This may not be an import path that can be used to import this package.
|
|
|
|
nonCanonicalImportPath string
|
|
|
|
// needsReplace is true if the nonCanonicalImportPath does not match the
|
|
|
|
// the modules declared path, making it impossible to import without a
|
|
|
|
// replace directive.
|
|
|
|
needsReplace bool
|
|
|
|
}
|
|
|
|
|
2019-07-19 13:21:23 -06:00
|
|
|
// reachedStatus returns true when info has a status at least target and any error associated with
|
|
|
|
// an attempt to reach target.
|
|
|
|
func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) {
|
|
|
|
if info.err == nil {
|
|
|
|
return info.status >= target, nil
|
|
|
|
}
|
|
|
|
if info.status == target {
|
|
|
|
return true, info.err
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2019-08-02 12:05:22 -06:00
|
|
|
// moduleCacheInfo is a concurrency safe map for storing information about
|
|
|
|
// the directories in the module cache.
|
|
|
|
//
|
|
|
|
// The information in this cache is built incrementally. Entries are initialized in scan.
|
|
|
|
// No new keys should be added in any other functions, as all directories containing
|
|
|
|
// packages are identified in scan.
|
|
|
|
//
|
|
|
|
// Other functions, including loadExports and findPackage, may update entries in this cache
|
|
|
|
// as they discover new things about the directory.
|
|
|
|
//
|
|
|
|
// We do not need to protect the data in the cache for multiple writes, because it only stores
|
|
|
|
// module cache directories, which do not change. If two competing stores take place, there will be
|
|
|
|
// one store that wins. Although this could result in a loss of information it will not be incorrect
|
|
|
|
// and may just result in recomputing the same result later.
|
|
|
|
//
|
|
|
|
// TODO(suzmue): consider other concurrency strategies and data structures (RWLocks, sync.Map, etc)
|
|
|
|
type moduleCacheInfo struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
// modCacheDirInfo stores information about packages in
|
|
|
|
// module cache directories. Keyed by absolute directory.
|
|
|
|
modCacheDirInfo map[string]*directoryPackageInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store stores the package info for dir.
|
|
|
|
func (d *moduleCacheInfo) Store(dir string, info directoryPackageInfo) {
|
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
d.modCacheDirInfo[dir] = &directoryPackageInfo{
|
|
|
|
status: info.status,
|
|
|
|
err: info.err,
|
|
|
|
dir: info.dir,
|
|
|
|
nonCanonicalImportPath: info.nonCanonicalImportPath,
|
|
|
|
needsReplace: info.needsReplace,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load returns a copy of the directoryPackageInfo for absolute directory dir.
|
|
|
|
func (d *moduleCacheInfo) Load(dir string) (directoryPackageInfo, bool) {
|
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
info, ok := d.modCacheDirInfo[dir]
|
|
|
|
if !ok {
|
|
|
|
return directoryPackageInfo{}, false
|
|
|
|
}
|
|
|
|
return *info, true
|
|
|
|
}
|
2019-07-19 10:41:29 -06:00
|
|
|
|
|
|
|
// Keys returns the keys currently present in d.
|
|
|
|
func (d *moduleCacheInfo) Keys() (keys []string) {
|
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
for key := range d.modCacheDirInfo {
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
}
|