Skip to content

Commit 130b6bd

Browse files
committed
Add caching to leeway exec
1 parent 47e00cd commit 130b6bd

File tree

1 file changed

+89
-7
lines changed

1 file changed

+89
-7
lines changed

cmd/exec.go

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"os"
88
"os/exec"
9+
"path/filepath"
910
"strings"
1011
"sync"
1112
"time"
@@ -58,6 +59,7 @@ Example use:
5859
watch, _ = cmd.Flags().GetBool("watch")
5960
parallel, _ = cmd.Flags().GetBool("parallel")
6061
rawOutput, _ = cmd.Flags().GetBool("raw-output")
62+
cacheKey, _ = cmd.Flags().GetString("cache-key")
6163
)
6264

6365
ws, err := getWorkspace()
@@ -161,7 +163,7 @@ Example use:
161163
}
162164

163165
if watch {
164-
err := executeCommandInLocations(args, locs, parallel, rawOutput)
166+
err := executeCommandInLocations(args, locs, noExecCache{}, parallel, rawOutput)
165167
if err != nil {
166168
log.Error(err)
167169
}
@@ -170,7 +172,7 @@ Example use:
170172
for {
171173
select {
172174
case <-evt:
173-
err := executeCommandInLocations(args, locs, parallel, rawOutput)
175+
err := executeCommandInLocations(args, locs, noExecCache{}, parallel, rawOutput)
174176
if err != nil {
175177
log.Error(err)
176178
}
@@ -179,7 +181,20 @@ Example use:
179181
}
180182
}
181183
}
182-
err = executeCommandInLocations(args, locs, parallel, rawOutput)
184+
185+
var cache execCache = noExecCache{}
186+
if cacheKey != "" {
187+
localCacheLoc := os.Getenv(leeway.EnvvarCacheDir)
188+
if localCacheLoc == "" {
189+
localCacheLoc = filepath.Join(os.TempDir(), "cache")
190+
}
191+
loc := filepath.Join(localCacheLoc, cacheKey)
192+
log.WithField("loc", loc).Debug("using filesystem exec cache")
193+
194+
cache = filesystemExecCache(loc)
195+
}
196+
197+
err = executeCommandInLocations(args, locs, cache, parallel, rawOutput)
183198
if err != nil {
184199
log.WithError(err).Fatal("cannot execut command")
185200
}
@@ -193,9 +208,13 @@ type commandExecLocation struct {
193208
Name string
194209
}
195210

196-
func executeCommandInLocations(rawExecCmd []string, locs []commandExecLocation, parallel, rawOutput bool) error {
211+
func executeCommandInLocations(rawExecCmd []string, locs []commandExecLocation, cache execCache, parallel, rawOutput bool) error {
197212
var wg sync.WaitGroup
198213
for _, loc := range locs {
214+
if ok, _ := cache.NeedsExecution(context.Background(), loc.Package); !ok {
215+
continue
216+
}
217+
199218
execCmd := make([]string, len(rawExecCmd))
200219
for i, c := range rawExecCmd {
201220
if loc.Package == nil {
@@ -233,14 +252,24 @@ func executeCommandInLocations(rawExecCmd []string, locs []commandExecLocation,
233252
go func(loc commandExecLocation) {
234253
defer wg.Done()
235254

236-
err = cmd.Wait()
237-
if err != nil {
255+
err := cmd.Wait()
256+
if err == nil {
257+
err = cache.MarkExecuted(context.Background(), loc.Package)
258+
if err != nil {
259+
log.WithError(err).Warn("cannot mark package as executed")
260+
}
261+
} else {
238262
log.Errorf("execution failed in %s (%s): %v", loc.Name, loc.Dir, err)
239263
}
240264
}(loc)
241265
} else {
242266
err = cmd.Wait()
243-
if err != nil {
267+
if err == nil {
268+
err = cache.MarkExecuted(context.Background(), loc.Package)
269+
if err != nil {
270+
log.WithError(err).Warn("cannot mark package as executed")
271+
}
272+
} else {
244273
return fmt.Errorf("execution failed in %s (%s): %v", loc.Name, loc.Dir, err)
245274
}
246275
}
@@ -265,5 +294,58 @@ func init() {
265294
execCmd.Flags().Bool("watch", false, "Watch source files and re-execute on change")
266295
execCmd.Flags().Bool("parallel", false, "Start all executions in parallel independent of their order")
267296
execCmd.Flags().Bool("raw-output", false, "Produce output without package prefix")
297+
execCmd.Flags().String("cache-key", "", "Specify a cache key to provide package-cache like execution behaviour")
268298
execCmd.Flags().SetInterspersed(true)
269299
}
300+
301+
type execCache interface {
302+
NeedsExecution(ctx context.Context, pkg *leeway.Package) (bool, error)
303+
MarkExecuted(ctx context.Context, pkg *leeway.Package) error
304+
}
305+
306+
type noExecCache struct{}
307+
308+
func (noExecCache) MarkExecuted(ctx context.Context, pkg *leeway.Package) error { return nil }
309+
func (noExecCache) NeedsExecution(ctx context.Context, pkg *leeway.Package) (bool, error) {
310+
return true, nil
311+
}
312+
313+
type filesystemExecCache string
314+
315+
func (c filesystemExecCache) MarkExecuted(ctx context.Context, pkg *leeway.Package) error {
316+
err := os.MkdirAll(string(c), 0755)
317+
if err != nil {
318+
return err
319+
}
320+
fn, err := c.filename(pkg)
321+
if err != nil {
322+
return err
323+
}
324+
f, err := os.Create(string(fn))
325+
if err != nil {
326+
return err
327+
}
328+
f.Close()
329+
log.WithField("name", pkg.FullName()).Debug("marked package as executed")
330+
return nil
331+
}
332+
333+
func (c filesystemExecCache) filename(pkg *leeway.Package) (string, error) {
334+
v, err := pkg.Version()
335+
if err != nil {
336+
return "", err
337+
}
338+
return filepath.Join(string(c), fmt.Sprintf("%s.executed", v)), nil
339+
}
340+
341+
func (c filesystemExecCache) NeedsExecution(ctx context.Context, pkg *leeway.Package) (bool, error) {
342+
fn, err := c.filename(pkg)
343+
if err != nil {
344+
return false, err
345+
}
346+
_, err = os.Stat(fn)
347+
if os.IsNotExist(err) {
348+
return true, nil
349+
}
350+
return false, err
351+
}

0 commit comments

Comments
 (0)