Make fine-grained hashing. (#814)

Speed up golint: don't typecheck packages twice.
Relates: #805
This commit is contained in:
Isaev Denis 2019-10-14 09:50:57 +03:00 committed by GitHub
parent f13436112d
commit 48599c64ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 138 additions and 68 deletions

View file

@ -17,6 +17,14 @@ import (
"github.com/golangci/golangci-lint/pkg/timeutils"
)
type HashMode int
const (
HashModeNeedOnlySelf HashMode = iota
HashModeNeedDirectDeps
HashModeNeedAllDeps
)
// Cache is a per-package data cache. A cached data is invalidated when
// package or it's dependencies change.
type Cache struct {
@ -46,7 +54,7 @@ func (c *Cache) Trim() {
})
}
func (c *Cache) Put(pkg *packages.Package, key string, data interface{}) error {
func (c *Cache) Put(pkg *packages.Package, mode HashMode, key string, data interface{}) error {
var err error
buf := &bytes.Buffer{}
c.sw.TrackStage("gob", func() {
@ -59,7 +67,7 @@ func (c *Cache) Put(pkg *packages.Package, key string, data interface{}) error {
var aID cache.ActionID
c.sw.TrackStage("key build", func() {
aID, err = c.pkgActionID(pkg)
aID, err = c.pkgActionID(pkg, mode)
if err == nil {
subkey, subkeyErr := cache.Subkey(aID, key)
if subkeyErr != nil {
@ -85,11 +93,11 @@ func (c *Cache) Put(pkg *packages.Package, key string, data interface{}) error {
var ErrMissing = errors.New("missing data")
func (c *Cache) Get(pkg *packages.Package, key string, data interface{}) error {
func (c *Cache) Get(pkg *packages.Package, mode HashMode, key string, data interface{}) error {
var aID cache.ActionID
var err error
c.sw.TrackStage("key build", func() {
aID, err = c.pkgActionID(pkg)
aID, err = c.pkgActionID(pkg, mode)
if err == nil {
subkey, subkeyErr := cache.Subkey(aID, key)
if subkeyErr != nil {
@ -125,8 +133,8 @@ func (c *Cache) Get(pkg *packages.Package, key string, data interface{}) error {
return nil
}
func (c *Cache) pkgActionID(pkg *packages.Package) (cache.ActionID, error) {
hash, err := c.packageHash(pkg)
func (c *Cache) pkgActionID(pkg *packages.Package, mode HashMode) (cache.ActionID, error) {
hash, err := c.packageHash(pkg, mode)
if err != nil {
return cache.ActionID{}, errors.Wrap(err, "failed to get package hash")
}
@ -144,12 +152,19 @@ func (c *Cache) pkgActionID(pkg *packages.Package) (cache.ActionID, error) {
// packageHash computes a package's hash. The hash is based on all Go
// files that make up the package, as well as the hashes of imported
// packages.
func (c *Cache) packageHash(pkg *packages.Package) (string, error) {
cachedHash, ok := c.pkgHashes.Load(pkg)
func (c *Cache) packageHash(pkg *packages.Package, mode HashMode) (string, error) {
type hashResults map[HashMode]string
hashResI, ok := c.pkgHashes.Load(pkg)
if ok {
return cachedHash.(string), nil
hashRes := hashResI.(hashResults)
if _, ok := hashRes[mode]; !ok {
return "", fmt.Errorf("no mode %d in hash result", mode)
}
return hashRes[mode], nil
}
hashRes := hashResults{}
key, err := cache.NewHash("package hash")
if err != nil {
return "", errors.Wrap(err, "failed to make a hash")
@ -158,13 +173,15 @@ func (c *Cache) packageHash(pkg *packages.Package) (string, error) {
fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath)
for _, f := range pkg.CompiledGoFiles {
c.ioSem <- struct{}{}
h, err := cache.FileHash(f)
h, fErr := cache.FileHash(f)
<-c.ioSem
if err != nil {
return "", errors.Wrapf(err, "failed to calculate file %s hash", f)
if fErr != nil {
return "", errors.Wrapf(fErr, "failed to calculate file %s hash", f)
}
fmt.Fprintf(key, "file %s %x\n", f, h)
}
curSum := key.Sum()
hashRes[HashModeNeedOnlySelf] = hex.EncodeToString(curSum[:])
imps := make([]*packages.Package, 0, len(pkg.Imports))
for _, imp := range pkg.Imports {
@ -173,20 +190,40 @@ func (c *Cache) packageHash(pkg *packages.Package) (string, error) {
sort.Slice(imps, func(i, j int) bool {
return imps[i].PkgPath < imps[j].PkgPath
})
for _, dep := range imps {
if dep.PkgPath == "unsafe" {
continue
}
depHash, err := c.packageHash(dep)
if err != nil {
return "", errors.Wrapf(err, "failed to calculate hash for dependency %s", dep.Name)
}
calcDepsHash := func(depMode HashMode) error {
for _, dep := range imps {
if dep.PkgPath == "unsafe" {
continue
}
fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, depHash)
depHash, depErr := c.packageHash(dep, depMode)
if depErr != nil {
return errors.Wrapf(depErr, "failed to calculate hash for dependency %s with mode %d", dep.Name, depMode)
}
fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, depHash)
}
return nil
}
h := key.Sum()
ret := hex.EncodeToString(h[:])
c.pkgHashes.Store(pkg, ret)
return ret, nil
if err := calcDepsHash(HashModeNeedOnlySelf); err != nil {
return "", err
}
curSum = key.Sum()
hashRes[HashModeNeedDirectDeps] = hex.EncodeToString(curSum[:])
if err := calcDepsHash(HashModeNeedAllDeps); err != nil {
return "", err
}
curSum = key.Sum()
hashRes[HashModeNeedAllDeps] = hex.EncodeToString(curSum[:])
if _, ok := hashRes[mode]; !ok {
return "", fmt.Errorf("invalid mode %d", mode)
}
c.pkgHashes.Store(pkg, hashRes)
return hashRes[mode], nil
}