Skip to content

Commit d58eb0c

Browse files
committed
cmd/go: abstract build cache, support implementations via child process
Via setting GOCACHEPROG to a binary which speaks JSON over stdin/stdout. Updates golang#59719 Signed-off-by: Brad Fitzpatrick <[email protected]> Change-Id: I824ff04d5ebdf0ba4d1b5bc2e9fbaee26d34c80f
1 parent 903a25a commit d58eb0c

File tree

8 files changed

+462
-53
lines changed

8 files changed

+462
-53
lines changed

src/cmd/go/internal/cache/cache.go

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,37 @@ type ActionID [HashSize]byte
3131
// An OutputID is a cache output key, the hash of an output of a computation.
3232
type OutputID [HashSize]byte
3333

34+
// Cache is the interface as used by the cmd/go.
35+
type Cache interface {
36+
// Get returns the cache entry for the provided ActionID.
37+
// On miss, the error type should be of type *entryNotFoundError.
38+
Get(ActionID) (Entry, error)
39+
40+
// Put adds an item to the cache.
41+
//
42+
// The seeker is only used to seek to the beginning. After a call to Put,
43+
// the seek position is not guaranteed to be in any particular state.
44+
//
45+
// As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
46+
// the verification from GODEBUG=goverifycache=1 is skipped.
47+
Put(ActionID, io.ReadSeeker) (_ OutputID, size int64, _ error)
48+
49+
// Trim does some optional clean-up at the end of the go tool's action.
50+
Trim() error
51+
52+
// OutputFile returns the path on disk where OutputID is stored.
53+
//
54+
// It's only called after a successful get or put call so it doesn't need
55+
// to return an error; it's assumed that if the previous get or put succeeded,
56+
// it's already on disk.
57+
OutputFile(OutputID) string
58+
59+
// FuzzDir returns where fuzz files are stored.
60+
FuzzDir() string
61+
}
62+
3463
// A Cache is a package cache, backed by a file system directory tree.
35-
type Cache struct {
64+
type DiskCache struct {
3665
dir string
3766
now func() time.Time
3867
}
@@ -48,7 +77,7 @@ type Cache struct {
4877
// to share a cache directory (for example, if the directory were stored
4978
// in a network file system). File locking is notoriously unreliable in
5079
// network file systems and may not suffice to protect the cache.
51-
func Open(dir string) (*Cache, error) {
80+
func Open(dir string) (*DiskCache, error) {
5281
info, err := os.Stat(dir)
5382
if err != nil {
5483
return nil, err
@@ -62,15 +91,15 @@ func Open(dir string) (*Cache, error) {
6291
return nil, err
6392
}
6493
}
65-
c := &Cache{
94+
c := &DiskCache{
6695
dir: dir,
6796
now: time.Now,
6897
}
6998
return c, nil
7099
}
71100

72101
// fileName returns the name of the file corresponding to the given id.
73-
func (c *Cache) fileName(id [HashSize]byte, key string) string {
102+
func (c *DiskCache) fileName(id [HashSize]byte, key string) string {
74103
return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
75104
}
76105

@@ -136,7 +165,7 @@ func initEnv() {
136165
// returning the corresponding output ID and file size, if any.
137166
// Note that finding an output ID does not guarantee that the
138167
// saved file for that output ID is still available.
139-
func (c *Cache) Get(id ActionID) (Entry, error) {
168+
func (c *DiskCache) Get(id ActionID) (Entry, error) {
140169
if verify {
141170
return Entry{}, &entryNotFoundError{Err: errVerifyMode}
142171
}
@@ -150,7 +179,7 @@ type Entry struct {
150179
}
151180

152181
// get is Get but does not respect verify mode, so that Put can use it.
153-
func (c *Cache) get(id ActionID) (Entry, error) {
182+
func (c *DiskCache) get(id ActionID) (Entry, error) {
154183
missing := func(reason error) (Entry, error) {
155184
return Entry{}, &entryNotFoundError{Err: reason}
156185
}
@@ -214,7 +243,7 @@ func (c *Cache) get(id ActionID) (Entry, error) {
214243

215244
// GetFile looks up the action ID in the cache and returns
216245
// the name of the corresponding data file.
217-
func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
246+
func GetFile(c Cache, id ActionID) (file string, entry Entry, err error) {
218247
entry, err = c.Get(id)
219248
if err != nil {
220249
return "", Entry{}, err
@@ -233,7 +262,7 @@ func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
233262
// GetBytes looks up the action ID in the cache and returns
234263
// the corresponding output bytes.
235264
// GetBytes should only be used for data that can be expected to fit in memory.
236-
func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
265+
func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
237266
entry, err := c.Get(id)
238267
if err != nil {
239268
return nil, entry, err
@@ -248,7 +277,7 @@ func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
248277
// GetMmap looks up the action ID in the cache and returns
249278
// the corresponding output bytes.
250279
// GetMmap should only be used for data that can be expected to fit in memory.
251-
func (c *Cache) GetMmap(id ActionID) ([]byte, Entry, error) {
280+
func GetMmap(c Cache, id ActionID) ([]byte, Entry, error) {
252281
entry, err := c.Get(id)
253282
if err != nil {
254283
return nil, entry, err
@@ -264,7 +293,7 @@ func (c *Cache) GetMmap(id ActionID) ([]byte, Entry, error) {
264293
}
265294

266295
// OutputFile returns the name of the cache file storing output with the given OutputID.
267-
func (c *Cache) OutputFile(out OutputID) string {
296+
func (c *DiskCache) OutputFile(out OutputID) string {
268297
file := c.fileName(out, "d")
269298
c.used(file)
270299
return file
@@ -297,7 +326,7 @@ const (
297326
// mtime is more than an hour old. This heuristic eliminates
298327
// nearly all of the mtime updates that would otherwise happen,
299328
// while still keeping the mtimes useful for cache trimming.
300-
func (c *Cache) used(file string) {
329+
func (c *DiskCache) used(file string) {
301330
info, err := os.Stat(file)
302331
if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
303332
return
@@ -306,7 +335,7 @@ func (c *Cache) used(file string) {
306335
}
307336

308337
// Trim removes old cache entries that are likely not to be reused.
309-
func (c *Cache) Trim() error {
338+
func (c *DiskCache) Trim() error {
310339
now := c.now()
311340

312341
// We maintain in dir/trim.txt the time of the last completed cache trim.
@@ -346,7 +375,7 @@ func (c *Cache) Trim() error {
346375
}
347376

348377
// trimSubdir trims a single cache subdirectory.
349-
func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
378+
func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
350379
// Read all directory entries from subdir before removing
351380
// any files, in case removing files invalidates the file offset
352381
// in the directory scan. Also, ignore error from f.Readdirnames,
@@ -374,7 +403,7 @@ func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
374403

375404
// putIndexEntry adds an entry to the cache recording that executing the action
376405
// with the given id produces an output with the given output id (hash) and size.
377-
func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
406+
func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
378407
// Note: We expect that for one reason or another it may happen
379408
// that repeating an action produces a different output hash
380409
// (for example, if the output contains a time stamp or temp dir name).
@@ -428,21 +457,29 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
428457
return nil
429458
}
430459

460+
// noVerifyReadSeeker is a io.ReadSeeker wrapper sentinel type
461+
// that says that Cache.Put should skip the verify check
462+
// (from GODEBUG=goverifycache=1).
463+
type noVerifyReadSeeker struct {
464+
io.ReadSeeker
465+
}
466+
431467
// Put stores the given output in the cache as the output for the action ID.
432468
// It may read file twice. The content of file must not change between the two passes.
433-
func (c *Cache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
434-
return c.put(id, file, true)
469+
func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
470+
_, isNoVerify := file.(noVerifyReadSeeker)
471+
return c.put(id, file, !isNoVerify)
435472
}
436473

437474
// PutNoVerify is like Put but disables the verify check
438475
// when GODEBUG=goverifycache=1 is set.
439476
// It is meant for data that is OK to cache but that we expect to vary slightly from run to run,
440477
// like test output containing times and the like.
441-
func (c *Cache) PutNoVerify(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
442-
return c.put(id, file, false)
478+
func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
479+
return c.Put(id, noVerifyReadSeeker{file})
443480
}
444481

445-
func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
482+
func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
446483
// Compute output ID.
447484
h := sha256.New()
448485
if _, err := file.Seek(0, 0); err != nil {
@@ -465,14 +502,14 @@ func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID
465502
}
466503

467504
// PutBytes stores the given bytes in the cache as the output for the action ID.
468-
func (c *Cache) PutBytes(id ActionID, data []byte) error {
505+
func PutBytes(c Cache, id ActionID, data []byte) error {
469506
_, _, err := c.Put(id, bytes.NewReader(data))
470507
return err
471508
}
472509

473510
// copyFile copies file into the cache, expecting it to have the given
474511
// output ID and size, if that file is not present already.
475-
func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
512+
func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
476513
name := c.fileName(out, "d")
477514
info, err := os.Stat(name)
478515
if err == nil && info.Size() == size {
@@ -562,6 +599,6 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
562599
// They may be removed with 'go clean -fuzzcache'.
563600
//
564601
// TODO(#48526): make Trim remove unused files from this directory.
565-
func (c *Cache) FuzzDir() string {
602+
func (c *DiskCache) FuzzDir() string {
566603
return filepath.Join(c.dir, "fuzz")
567604
}

src/cmd/go/internal/cache/default.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ import (
1616

1717
// Default returns the default cache to use.
1818
// It never returns nil.
19-
func Default() *Cache {
19+
func Default() Cache {
2020
defaultOnce.Do(initDefaultCache)
2121
return defaultCache
2222
}
2323

2424
var (
2525
defaultOnce sync.Once
26-
defaultCache *Cache
26+
defaultCache Cache
2727
)
2828

2929
// cacheREADME is a message stored in a README in the cache directory.
@@ -53,11 +53,17 @@ func initDefaultCache() {
5353
os.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666)
5454
}
5555

56-
c, err := Open(dir)
56+
diskCache, err := Open(dir)
5757
if err != nil {
5858
base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
5959
}
60-
defaultCache = c
60+
61+
if v := cfg.Getenv("GOCACHEPROG"); v != "" {
62+
defaultCache = startCacheProg(v, diskCache)
63+
return
64+
} else {
65+
defaultCache = diskCache
66+
}
6167
}
6268

6369
var (

0 commit comments

Comments
 (0)