Skip to content

Commit 3b13754

Browse files
committed
feat(cli): add support for caching using watchman (#8398)
This adds a watchman based implementation of the cache used by `configure`. When using watchman caching we can invalidate the cache based on the watchman `GetDiff` since the last cache was written, instead of invalidating the cache based on the file content. With one large repo `configure` is 50+s, file caching 20s, watchman caching gets it ~8s. --- ### Changes are visible to end-users: yes - Searched for relevant documentation and updated as needed: yes - Breaking change (forces users to change their own code or config): no - Suggested release notes appear below: yes Support `configure` use of `watchman` for file caching. Set the `ASPECT_CONFIGURE_CACHE` env var to location to read+persist the cache, pass the `--watchman` flag to `configure`. For example: ``` ASPECT_CONFIGURE_CACHE=configure.cache aspect configure --watchman ``` NOTE: `ASPECT_CONFIGURE_CACHE` without `--watchman` uses basic file caching and the file format is not compatible, different cache files must be used for watchman vs disk based caching. ### Test plan - Manual testing; run `configure` with `ASPECT_CONFIGURE_CACHE` + `--watchman` GitOrigin-RevId: 83385109e8894f9d96996ebe817a3d650618bf1c
1 parent bbbd970 commit 3b13754

File tree

7 files changed

+86
-56
lines changed

7 files changed

+86
-56
lines changed

cmd/aspect/configure/configure.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func NewDefaultCmd() *cobra.Command {
3838
func NewCmd(streams ioutils.Streams) *cobra.Command {
3939
return NewCmdWithConfigure(streams, configure.New(streams))
4040
}
41-
func NewCmdWithConfigure(streams ioutils.Streams, v *configure.Configure) *cobra.Command {
41+
func NewCmdWithConfigure(streams ioutils.Streams, v configure.ConfigureRunner) *cobra.Command {
4242
cmd := &cobra.Command{
4343
Use: "configure",
4444
Short: "Auto-configure Bazel by updating BUILD files",
@@ -91,14 +91,14 @@ Run 'aspect help directives' or see https://docs.aspect.build/cli/help/directive
9191
return cmd
9292
}
9393

94-
func run(streams ioutils.Streams, v *configure.Configure, mode string, exclude []string, args []string) error {
94+
func run(streams ioutils.Streams, v configure.ConfigureRunner, mode string, exclude []string, args []string) error {
9595
if buildinfo.Current().OpenSource {
9696
if configurePlugins := viper.GetStringSlice("configure.plugins"); len(configurePlugins) > 0 {
9797
fmt.Fprintln(streams.Stderr, "WARNING: Aspect CLI configure.plugins are not supported in Aspect OSS CLI.")
9898
}
9999
}
100100

101-
err := v.Run(mode, exclude, args)
101+
err := v.Generate(mode, exclude, args)
102102
if aspectError, isAError := err.(*aspecterrors.ExitError); isAError && aspectError.ExitCode == aspecterrors.ConfigureNoConfig {
103103
fmt.Fprintln(streams.Stderr, `No languages enabled for BUILD file generation.
104104
@@ -117,7 +117,7 @@ configure:
117117
return err
118118
}
119119

120-
func addCliEnabledLanguages(c *configure.Configure) {
120+
func addCliEnabledLanguages(c configure.ConfigureRunner) {
121121
// Order matters for gazelle languages. Proto should be run before golang.
122122
viper.SetDefault("configure.languages.protobuf", false)
123123
if viper.GetBool("configure.languages.protobuf") {

gazelle/common/cache/cache.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package cache
22

3+
import "github.com/bazelbuild/bazel-gazelle/config"
4+
35
type Cache interface {
46
/** Load a key from the cache. */
57
Load(key string) (any, bool)
68
/** Store a key+value in the ache */
79
Store(key string, value any)
10+
/** Store a key+value in the cache, or return the existing value */
11+
LoadOrStore(key string, value any) (any, bool)
812

913
/** Persist any changes to the cache */
1014
Persist()
@@ -20,5 +24,9 @@ type Cache interface {
2024
* The path 'root' is not part of the cache key, but is used to resolve
2125
* relative paths in the cache.
2226
*/
23-
LoadAndStoreFile(root, path string, loader func(path string, content []byte) (any, error)) (any, bool, error)
27+
LoadOrStoreFile(root, path, key string, loader FileCompute) (any, bool, error)
2428
}
29+
30+
type FileCompute = func(path string, content []byte) (any, error)
31+
32+
type CacheFactory = func(c *config.Config) Cache

gazelle/common/cache/configurer.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,27 @@ import (
1212
const gazelleExtensionKey = "__aspect:cache"
1313

1414
func NewConfigurer() config.Configurer {
15-
return &cacheConfigurer{}
15+
return &cacheConfigurer{
16+
cache: noop,
17+
}
1618
}
1719

1820
// Fetch the shared cache for a given config
19-
func Get[T any](config *config.Config) Cache {
21+
func Get(config *config.Config) Cache {
2022
if v, ok := config.Exts[gazelleExtensionKey]; ok {
2123
return v.(Cache)
2224
}
23-
return nil
25+
return noop
26+
}
27+
28+
var cacheFactory CacheFactory
29+
30+
func init() {
31+
if diskCachePath := os.Getenv("ASPECT_CONFIGURE_CACHE"); diskCachePath != "" {
32+
cacheFactory = func(c *config.Config) Cache {
33+
return NewDiskCache(diskCachePath)
34+
}
35+
}
2436
}
2537

2638
var _ config.Configurer = (*cacheConfigurer)(nil)
@@ -30,22 +42,26 @@ type cacheConfigurer struct {
3042
cache Cache
3143
}
3244

45+
func SetCacheFactory(c CacheFactory) {
46+
cacheFactory = c
47+
}
48+
3349
// Load + store the cache
34-
func (cc *cacheConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
35-
if diskCachePath := os.Getenv("ASPECT_CONFIGURE_CACHE"); diskCachePath != "" {
36-
cc.cache = NewDiskCache(diskCachePath)
50+
func (cc *cacheConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
51+
if cacheFactory == nil {
52+
cc.cache = noop
3753
} else {
38-
cc.cache = Noop()
54+
cc.cache = cacheFactory(c)
3955
}
40-
4156
c.Exts[gazelleExtensionKey] = cc.cache
57+
return nil
4258
}
4359

4460
// Persist the cache
4561
func (cc *cacheConfigurer) DoneGeneratingRules() {
4662
cc.cache.Persist()
4763
}
4864

49-
func (*cacheConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil }
50-
func (*cacheConfigurer) KnownDirectives() []string { return nil }
51-
func (*cacheConfigurer) Configure(c *config.Config, rel string, f *rule.File) {}
65+
func (cc *cacheConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {}
66+
func (*cacheConfigurer) KnownDirectives() []string { return nil }
67+
func (*cacheConfigurer) Configure(c *config.Config, rel string, f *rule.File) {}

gazelle/common/cache/disk.go

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,13 @@ type diskCache struct {
3838
new *sync.Map
3939
}
4040

41-
func computeCacheKey(content []byte) (string, bool) {
41+
func computeCacheKey(content []byte, key string) string {
4242
cacheDigest := crypto.MD5.New()
43-
4443
if buildinfo.IsStamped() {
45-
if _, err := cacheDigest.Write([]byte(buildinfo.GitCommit)); err != nil {
46-
BazelLog.Errorf("Failed to write GitCommit to cache digest: %v", err)
47-
return "", false
48-
}
44+
cacheDigest.Write([]byte(buildinfo.GitCommit))
4945
}
50-
51-
if _, err := cacheDigest.Write(content); err != nil {
52-
BazelLog.Errorf("Failed to write source to cache digest: %v", err)
53-
return "", false
54-
}
55-
56-
return hex.EncodeToString(cacheDigest.Sum(nil)), true
46+
cacheDigest.Write([]byte(key))
47+
return hex.EncodeToString(cacheDigest.Sum(content))
5748
}
5849

5950
func (c *diskCache) read() {
@@ -91,46 +82,50 @@ func (c *diskCache) write() {
9182
}
9283

9384
func (c *diskCache) Load(key string) (any, bool) {
85+
// Already written to new cache.
9486
if v, found := c.new.Load(key); found {
9587
return v, true
9688
}
9789

90+
// Exists in old cache and can transfer to new.
9891
if v, ok := c.old[key]; ok {
99-
c.new.LoadOrStore(key, v)
92+
v, _ = c.LoadOrStore(key, v)
10093
return v, true
10194
}
10295

96+
// Cache miss
10397
return nil, false
10498
}
10599

106100
func (c *diskCache) Store(key string, value any) {
107101
c.new.Store(key, value)
108102
}
109103

110-
func (c *diskCache) LoadAndStoreFile(root, p string, loader func(path string, content []byte) (any, error)) (any, bool, error) {
104+
func (c *diskCache) LoadOrStore(key string, value any) (any, bool) {
105+
return c.new.LoadOrStore(key, value)
106+
}
107+
108+
func (c *diskCache) LoadOrStoreFile(root, p, key string, loader FileCompute) (any, bool, error) {
111109
content, err := os.ReadFile(path.Join(root, p))
112110
if err != nil {
113111
return nil, false, err
114112
}
115113

116-
if parserCacheKey, parsingCacheable := computeCacheKey(content); parsingCacheable {
117-
// Try loading from the cache.
118-
if v, found := c.Load(parserCacheKey); found {
119-
return v, true, nil
120-
}
114+
// Include the file content in the cache key
115+
key = computeCacheKey(content, key)
121116

122-
// Compute and persist in cache.
123-
v, err := loader(p, content)
117+
// Try loading from the cache.
118+
v, found := c.Load(key)
119+
120+
// Compute and persist in cache.
121+
if !found {
122+
v, err = loader(p, content)
124123
if err != nil {
125-
c.Store(parserCacheKey, v)
124+
v, found = c.LoadOrStore(key, v)
126125
}
127-
128-
return v, false, err
129126
}
130127

131-
// Not cacheable, simply recompute each time.
132-
v, err := loader(p, content)
133-
return v, false, err
128+
return v, found, err
134129
}
135130

136131
func (c *diskCache) Persist() {

gazelle/common/cache/noop.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ import (
55
"path"
66
)
77

8-
func Noop() Cache {
9-
return &noopCache{}
10-
}
11-
128
var _ Cache = (*noopCache)(nil)
139

10+
var noop Cache = &noopCache{}
11+
1412
type noopCache struct{}
1513

1614
func (c *noopCache) Load(key string) (any, bool) { return nil, false }
1715
func (c *noopCache) Store(key string, value any) {}
18-
func (c *noopCache) LoadAndStoreFile(root, p string, loader func(p string, content []byte) (any, error)) (any, bool, error) {
16+
func (c *noopCache) LoadOrStore(key string, value any) (any, bool) {
17+
return value, false
18+
}
19+
func (c *noopCache) LoadOrStoreFile(root, p, key string, loader FileCompute) (any, bool, error) {
1920
content, err := os.ReadFile(path.Join(root, p))
2021
if err != nil {
2122
return nil, false, err

gazelle/js/generate.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -832,16 +832,18 @@ func (ts *typeScriptLang) collectImports(cfg *JsGazelleConfig, config *config.Co
832832
func parseSourceFile(config *config.Config, rootDir, filePath string) (parser.ParseResult, error) {
833833
BazelLog.Tracef("ParseImports(%s): %s", LanguageName, filePath)
834834

835-
parserCache := cache.Get[parser.ParseResult](config)
836-
if parserCache == nil {
837-
parserCache = cache.Noop()
838-
}
835+
parserCache := cache.Get(config)
839836

840-
r, _, err := parserCache.LoadAndStoreFile(rootDir, filePath, func(filePath string, content []byte) (any, error) {
837+
var p parser.ParseResult
838+
r, _, err := parserCache.LoadOrStoreFile(rootDir, filePath, "js.ParseSource", func(filePath string, content []byte) (any, error) {
841839
return parser.ParseSource(filePath, content)
842840
})
843841

844-
return r.(parser.ParseResult), err
842+
if r != nil {
843+
p = r.(parser.ParseResult)
844+
}
845+
846+
return p, err
845847
}
846848

847849
func init() {

pkg/aspect/configure/configure.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,21 @@ import (
3535
"golang.org/x/term"
3636
)
3737

38+
type ConfigureRunner interface {
39+
AddLanguage(lang ConfigureLanguage)
40+
AddLanguageFactory(lang string, langFactory func() language.Language)
41+
Generate(mode ConfigureMode, excludes []string, args []string) error
42+
}
43+
3844
type Configure struct {
3945
ioutils.Streams
4046

4147
languageKeys []string
4248
languages []func() language.Language
4349
}
4450

51+
var _ ConfigureRunner = (*Configure)(nil)
52+
4553
// Builtin Gazelle languages
4654
type ConfigureLanguage = string
4755

@@ -117,7 +125,7 @@ func (c *Configure) AddLanguage(lang ConfigureLanguage) {
117125
}
118126
}
119127

120-
func (runner *Configure) Run(mode ConfigureMode, excludes []string, args []string) error {
128+
func (runner *Configure) Generate(mode ConfigureMode, excludes []string, args []string) error {
121129
if len(runner.languageKeys) == 0 {
122130
return &aspecterrors.ExitError{
123131
ExitCode: aspecterrors.ConfigureNoConfig,

0 commit comments

Comments
 (0)