aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/cache/cache_fs.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/cache/cache_fs.go')
-rw-r--r--cli/internal/cache/cache_fs.go174
1 files changed, 174 insertions, 0 deletions
diff --git a/cli/internal/cache/cache_fs.go b/cli/internal/cache/cache_fs.go
new file mode 100644
index 0000000..fb15a02
--- /dev/null
+++ b/cli/internal/cache/cache_fs.go
@@ -0,0 +1,174 @@
+// Adapted from https://github.com/thought-machine/please
+// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// Package cache implements our cache abstraction.
+package cache
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/vercel/turbo/cli/internal/analytics"
+ "github.com/vercel/turbo/cli/internal/cacheitem"
+ "github.com/vercel/turbo/cli/internal/turbopath"
+)
+
+// fsCache is a local filesystem cache
+type fsCache struct {
+ cacheDirectory turbopath.AbsoluteSystemPath
+ recorder analytics.Recorder
+}
+
+// newFsCache creates a new filesystem cache
+func newFsCache(opts Opts, recorder analytics.Recorder, repoRoot turbopath.AbsoluteSystemPath) (*fsCache, error) {
+ cacheDir := opts.resolveCacheDir(repoRoot)
+ if err := cacheDir.MkdirAll(0775); err != nil {
+ return nil, err
+ }
+ return &fsCache{
+ cacheDirectory: cacheDir,
+ recorder: recorder,
+ }, nil
+}
+
+// Fetch returns true if items are cached. It moves them into position as a side effect.
+func (f *fsCache) Fetch(anchor turbopath.AbsoluteSystemPath, hash string, _ []string) (ItemStatus, []turbopath.AnchoredSystemPath, int, error) {
+ uncompressedCachePath := f.cacheDirectory.UntypedJoin(hash + ".tar")
+ compressedCachePath := f.cacheDirectory.UntypedJoin(hash + ".tar.zst")
+
+ var actualCachePath turbopath.AbsoluteSystemPath
+ if uncompressedCachePath.FileExists() {
+ actualCachePath = uncompressedCachePath
+ } else if compressedCachePath.FileExists() {
+ actualCachePath = compressedCachePath
+ } else {
+ // It's not in the cache, bail now
+ f.logFetch(false, hash, 0)
+ return ItemStatus{Local: false}, nil, 0, nil
+ }
+
+ cacheItem, openErr := cacheitem.Open(actualCachePath)
+ if openErr != nil {
+ return ItemStatus{Local: false}, nil, 0, openErr
+ }
+
+ restoredFiles, restoreErr := cacheItem.Restore(anchor)
+ if restoreErr != nil {
+ _ = cacheItem.Close()
+ return ItemStatus{Local: false}, nil, 0, restoreErr
+ }
+
+ meta, err := ReadCacheMetaFile(f.cacheDirectory.UntypedJoin(hash + "-meta.json"))
+ if err != nil {
+ _ = cacheItem.Close()
+ return ItemStatus{Local: false}, nil, 0, fmt.Errorf("error reading cache metadata: %w", err)
+ }
+ f.logFetch(true, hash, meta.Duration)
+
+ // Wait to see what happens with close.
+ closeErr := cacheItem.Close()
+ if closeErr != nil {
+ return ItemStatus{Local: false}, restoredFiles, 0, closeErr
+ }
+ return ItemStatus{Local: true}, restoredFiles, meta.Duration, nil
+}
+
+func (f *fsCache) Exists(hash string) ItemStatus {
+ uncompressedCachePath := f.cacheDirectory.UntypedJoin(hash + ".tar")
+ compressedCachePath := f.cacheDirectory.UntypedJoin(hash + ".tar.zst")
+
+ if compressedCachePath.FileExists() || uncompressedCachePath.FileExists() {
+ return ItemStatus{Local: true}
+ }
+
+ return ItemStatus{Local: false}
+}
+
+func (f *fsCache) logFetch(hit bool, hash string, duration int) {
+ var event string
+ if hit {
+ event = CacheEventHit
+ } else {
+ event = CacheEventMiss
+ }
+ payload := &CacheEvent{
+ Source: CacheSourceFS,
+ Event: event,
+ Hash: hash,
+ Duration: duration,
+ }
+ f.recorder.LogEvent(payload)
+}
+
+func (f *fsCache) Put(anchor turbopath.AbsoluteSystemPath, hash string, duration int, files []turbopath.AnchoredSystemPath) error {
+ cachePath := f.cacheDirectory.UntypedJoin(hash + ".tar.zst")
+ cacheItem, err := cacheitem.Create(cachePath)
+ if err != nil {
+ return err
+ }
+
+ for _, file := range files {
+ err := cacheItem.AddFile(anchor, file)
+ if err != nil {
+ _ = cacheItem.Close()
+ return err
+ }
+ }
+
+ writeErr := WriteCacheMetaFile(f.cacheDirectory.UntypedJoin(hash+"-meta.json"), &CacheMetadata{
+ Duration: duration,
+ Hash: hash,
+ })
+
+ if writeErr != nil {
+ _ = cacheItem.Close()
+ return writeErr
+ }
+
+ return cacheItem.Close()
+}
+
+func (f *fsCache) Clean(_ turbopath.AbsoluteSystemPath) {
+ fmt.Println("Not implemented yet")
+}
+
+func (f *fsCache) CleanAll() {
+ fmt.Println("Not implemented yet")
+}
+
+func (f *fsCache) Shutdown() {}
+
+// CacheMetadata stores duration and hash information for a cache entry so that aggregate Time Saved calculations
+// can be made from artifacts from various caches
+type CacheMetadata struct {
+ Hash string `json:"hash"`
+ Duration int `json:"duration"`
+}
+
+// WriteCacheMetaFile writes cache metadata file at a path
+func WriteCacheMetaFile(path turbopath.AbsoluteSystemPath, config *CacheMetadata) error {
+ jsonBytes, marshalErr := json.Marshal(config)
+ if marshalErr != nil {
+ return marshalErr
+ }
+ writeFilErr := path.WriteFile(jsonBytes, 0644)
+ if writeFilErr != nil {
+ return writeFilErr
+ }
+ return nil
+}
+
+// ReadCacheMetaFile reads cache metadata file at a path
+func ReadCacheMetaFile(path turbopath.AbsoluteSystemPath) (*CacheMetadata, error) {
+ jsonBytes, readFileErr := path.ReadFile()
+ if readFileErr != nil {
+ return nil, readFileErr
+ }
+ var config CacheMetadata
+ marshalErr := json.Unmarshal(jsonBytes, &config)
+ if marshalErr != nil {
+ return nil, marshalErr
+ }
+ return &config, nil
+}