-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathpkgcache.go
229 lines (196 loc) · 5.6 KB
/
pkgcache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package pkgcache
import (
"bytes"
"encoding/gob"
"encoding/hex"
"fmt"
"runtime"
"sort"
"sync"
"github.com/pkg/errors"
"golang.org/x/tools/go/packages"
"github.com/golangci/golangci-lint/internal/cache"
"github.com/golangci/golangci-lint/pkg/logutils"
"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 {
lowLevelCache *cache.Cache
pkgHashes sync.Map
sw *timeutils.Stopwatch
log logutils.Log // not used now, but may be needed for future debugging purposes
ioSem chan struct{} // semaphore limiting parallel IO
}
func NewCache(sw *timeutils.Stopwatch, log logutils.Log) (*Cache, error) {
c, err := cache.Default()
if err != nil {
return nil, err
}
return &Cache{
lowLevelCache: c,
sw: sw,
log: log,
ioSem: make(chan struct{}, runtime.GOMAXPROCS(-1)),
}, nil
}
func (c *Cache) Trim() {
c.sw.TrackStage("trim", func() {
c.lowLevelCache.Trim()
})
}
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() {
err = gob.NewEncoder(buf).Encode(data)
})
if err != nil {
return errors.Wrap(err, "failed to gob encode")
}
var aID cache.ActionID
c.sw.TrackStage("key build", func() {
aID, err = c.pkgActionID(pkg, mode)
if err == nil {
subkey, subkeyErr := cache.Subkey(aID, key)
if subkeyErr != nil {
err = errors.Wrap(subkeyErr, "failed to build subkey")
}
aID = subkey
}
})
if err != nil {
return errors.Wrapf(err, "failed to calculate package %s action id", pkg.Name)
}
c.ioSem <- struct{}{}
c.sw.TrackStage("cache io", func() {
err = c.lowLevelCache.PutBytes(aID, buf.Bytes())
})
<-c.ioSem
if err != nil {
return errors.Wrapf(err, "failed to save data to low-level cache by key %s for package %s", key, pkg.Name)
}
return nil
}
var ErrMissing = errors.New("missing data")
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, mode)
if err == nil {
subkey, subkeyErr := cache.Subkey(aID, key)
if subkeyErr != nil {
err = errors.Wrap(subkeyErr, "failed to build subkey")
}
aID = subkey
}
})
if err != nil {
return errors.Wrapf(err, "failed to calculate package %s action id", pkg.Name)
}
var b []byte
c.ioSem <- struct{}{}
c.sw.TrackStage("cache io", func() {
b, _, err = c.lowLevelCache.GetBytes(aID)
})
<-c.ioSem
if err != nil {
if cache.IsErrMissing(err) {
return ErrMissing
}
return errors.Wrapf(err, "failed to get data from low-level cache by key %s for package %s", key, pkg.Name)
}
c.sw.TrackStage("gob", func() {
err = gob.NewDecoder(bytes.NewReader(b)).Decode(data)
})
if err != nil {
return errors.Wrap(err, "failed to gob decode")
}
return nil
}
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")
}
key, err := cache.NewHash("action ID")
if err != nil {
return cache.ActionID{}, errors.Wrap(err, "failed to make a hash")
}
fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath)
fmt.Fprintf(key, "pkghash %s\n", hash)
return key.Sum(), nil
}
// 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, mode HashMode) (string, error) {
type hashResults map[HashMode]string
hashResI, ok := c.pkgHashes.Load(pkg)
if ok {
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")
}
fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath)
for _, f := range pkg.CompiledGoFiles {
c.ioSem <- struct{}{}
h, fErr := cache.FileHash(f)
<-c.ioSem
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 {
imps = append(imps, imp)
}
sort.Slice(imps, func(i, j int) bool {
return imps[i].PkgPath < imps[j].PkgPath
})
calcDepsHash := func(depMode HashMode) error {
for _, dep := range imps {
if dep.PkgPath == "unsafe" {
continue
}
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
}
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
}