Skip to content

Commit

Permalink
Merge pull request #58 from bitrise-io/config-cache-command
Browse files Browse the repository at this point in the history
feat: ACI-2928 Gradle save/restore config cache commands
  • Loading branch information
zsolt-marta-bitrise authored Oct 16, 2024
2 parents b9c3a88 + f6edab9 commit ee5eb84
Show file tree
Hide file tree
Showing 20 changed files with 788 additions and 313 deletions.
6 changes: 3 additions & 3 deletions cmd/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
"github.com/google/uuid"

xa "github.com/bitrise-io/bitrise-build-cache-cli/internal/analytics"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/build_cache/kv"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/config/common"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/consts"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/xcode"
"github.com/bitrise-io/go-utils/v2/log"
)

Expand Down Expand Up @@ -76,7 +76,7 @@ func newCacheOperation(startT time.Time, operationType string, envProvider func(
return op
}

func fillCacheOperationWithUploadStats(op *xa.CacheOperation, stats xcode.UploadFilesStats) {
func fillCacheOperationWithUploadStats(op *xa.CacheOperation, stats kv.UploadFilesStats) {
op.TransferSize = stats.UploadSize
op.FileStats = xa.FileStats{
FilesToTransfer: stats.FilesToUpload,
Expand All @@ -87,7 +87,7 @@ func fillCacheOperationWithUploadStats(op *xa.CacheOperation, stats xcode.Upload
}
}

func fillCacheOperationWithDownloadStats(op *xa.CacheOperation, stats xcode.DownloadFilesStats) {
func fillCacheOperationWithDownloadStats(op *xa.CacheOperation, stats kv.DownloadFilesStats) {
op.TransferSize = stats.DownloadSize
op.FileStats = xa.FileStats{
FilesToTransfer: stats.FilesToBeDownloaded,
Expand Down
6 changes: 5 additions & 1 deletion cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import (
"github.com/bitrise-io/go-utils/v2/log"
)

func createKVClient(ctx context.Context, cacheOperationID string, authConfig common.CacheAuthConfig, envProvider common.EnvProviderFunc, logger log.Logger) (*kv.Client, error) {
func createKVClient(ctx context.Context,
cacheOperationID string,
authConfig common.CacheAuthConfig,
envProvider common.EnvProviderFunc,
logger log.Logger) (*kv.Client, error) {
endpointURL := common.SelectEndpointURL("", envProvider)
logger.Infof("(i) Build Cache Endpoint URL: %s", endpointURL)

Expand Down
26 changes: 14 additions & 12 deletions cmd/deleteXcodeDerivedData.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (

"github.com/bitrise-io/bitrise-build-cache-cli/internal/build_cache/kv"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/config/common"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/filegroup"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/hash"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/xcode"
"github.com/bitrise-io/go-utils/v2/log"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -83,11 +85,11 @@ func deleteXcodeDerivedDataCmdFn(ctx context.Context, providedCacheKey string, u
}

func uploadEmptyMetadata(ctx context.Context, providedCacheKey, cacheKey string, envProvider common.EnvProviderFunc, client *kv.Client, logger log.Logger) error {
logger.TInfof("Saving empty metadata file %s", CacheMetadataPath)
logger.TInfof("Saving empty metadata file %s", XCodeCacheMetadataPath)
_, err := xcode.SaveMetadata(&xcode.Metadata{
ProjectFiles: xcode.FileGroupInfo{},
DerivedData: xcode.FileGroupInfo{},
XcodeCacheDir: xcode.FileGroupInfo{},
ProjectFiles: filegroup.Info{},
DerivedData: filegroup.Info{},
XcodeCacheDir: filegroup.Info{},
CacheKey: cacheKey,
CreatedAt: time.Now(),
AppID: envProvider("BITRISE_APP_SLUG"),
Expand All @@ -96,24 +98,24 @@ func uploadEmptyMetadata(ctx context.Context, providedCacheKey, cacheKey string,
GitBranch: envProvider("BITRISE_GIT_BRANCH"),
BuildCacheCLIVersion: envProvider("BITRISE_BUILD_CACHE_CLI_VERSION"),
MetadataVersion: 1,
}, CacheMetadataPath, logger)
}, XCodeCacheMetadataPath, logger)
if err != nil {
return fmt.Errorf("save metadata: %w", err)
}

mdChecksum, err := xcode.ChecksumOfFile(CacheMetadataPath)
mdChecksum, err := hash.ChecksumOfFile(XCodeCacheMetadataPath)
mdChecksumReader := strings.NewReader(mdChecksum)
if err != nil {
return fmt.Errorf("checksum of metadata file: %w", err)
}

logger.TInfof("Uploading metadata checksum of %s (%s) for key %s", CacheMetadataPath, mdChecksum, cacheKey)
if err := xcode.UploadStreamToBuildCache(ctx, mdChecksumReader, cacheKey, mdChecksumReader.Size(), client, logger); err != nil {
logger.TInfof("Uploading metadata checksum of %s (%s) for key %s", XCodeCacheMetadataPath, mdChecksum, cacheKey)
if err := client.UploadStreamToBuildCache(ctx, mdChecksumReader, cacheKey, mdChecksumReader.Size()); err != nil {
return fmt.Errorf("upload metadata checksum to build cache: %w", err)
}

logger.TInfof("Uploading metadata content of %s for key %s", CacheMetadataPath, mdChecksum)
if err := xcode.UploadFileToBuildCache(ctx, CacheMetadataPath, mdChecksum, client, logger); err != nil {
logger.TInfof("Uploading metadata content of %s for key %s", XCodeCacheMetadataPath, mdChecksum)
if err := client.UploadFileToBuildCache(ctx, XCodeCacheMetadataPath, mdChecksum); err != nil {
return fmt.Errorf("upload metadata content to build cache: %w", err)
}

Expand All @@ -124,8 +126,8 @@ func uploadEmptyMetadata(ctx context.Context, providedCacheKey, cacheKey string,
} else if fallbackCacheKey != "" && cacheKey != fallbackCacheKey {
cacheKey = fallbackCacheKey
mdChecksumReader = strings.NewReader(mdChecksum) // reset reader
logger.TInfof("Uploading metadata checksum of %s (%s) for fallback key %s", CacheMetadataPath, mdChecksum, cacheKey)
if err := xcode.UploadStreamToBuildCache(ctx, mdChecksumReader, cacheKey, mdChecksumReader.Size(), client, logger); err != nil {
logger.TInfof("Uploading metadata checksum of %s (%s) for fallback key %s", XCodeCacheMetadataPath, mdChecksum, cacheKey)
if err := client.UploadStreamToBuildCache(ctx, mdChecksumReader, cacheKey, mdChecksumReader.Size()); err != nil {
return fmt.Errorf("upload metadata checksum to build cache: %w", err)
}
}
Expand Down
170 changes: 170 additions & 0 deletions cmd/restoreGradleConfigurationCache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package cmd

import (
"context"
"errors"
"fmt"
"os"
"strings"

"github.com/bitrise-io/bitrise-build-cache-cli/internal/build_cache/kv"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/config/common"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/filegroup"
"github.com/bitrise-io/bitrise-build-cache-cli/internal/gradle"
"github.com/bitrise-io/go-utils/v2/log"
"github.com/google/uuid"
"github.com/spf13/cobra"
)

// nolint: gochecknoglobals
var restoreGradleConfigCacheCmd = &cobra.Command{
Use: "restore-gradle-configuration-cache",
Short: "Restore the Gradle configuration cache directory from Bitrise Build Cache",
Long: `Restore the contents of the Gradle configuration cache folder (used by Gradle to store task graph produced by the configuration phase) from Bitrise Build Cache.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, _ []string) error {
logger := log.NewLogger()
logger.EnableDebugLog(isDebugLogMode)
logCurrentUserInfo(logger)

logger.TInfof("Restore the Gradle configuration cache directory from Bitrise Build Cache")

logger.Infof("(i) Debug mode and verbose logs: %t", isDebugLogMode)

logger.Infof("(i) Checking parameters")
cacheKey, _ := cmd.Flags().GetString("key")

logger.Infof("(i) Check Auth Config")
authConfig, err := common.ReadAuthConfigFromEnvironments(os.Getenv)
if err != nil {
return fmt.Errorf("read auth config from environments: %w", err)
}

err = restoreGradleConfigCacheCmdFn(cmd.Context(),
authConfig,
cacheKey,
logger,
os.Getenv)
if err != nil {
return fmt.Errorf("restore Gradle config cache from Bitrise Build Cache: %w", err)
}

logger.TInfof("✅ Configufation cache restored from Bitrise Build Cache ")

return nil
},
}

func init() {
rootCmd.AddCommand(restoreGradleConfigCacheCmd)

restoreGradleConfigCacheCmd.Flags().String("key", "", "The cache key used for the saved cache item (set to the Bitrise app's slug and current git branch by default)")
}

func restoreGradleConfigCacheCmdFn(ctx context.Context,
authConfig common.CacheAuthConfig,
providedCacheKey string,
logger log.Logger,
envProvider func(string) string) error {
kvClient, err := createKVClient(ctx, uuid.NewString(), authConfig, envProvider, logger)
if err != nil {
return fmt.Errorf("create kv client: %w", err)
}

g := gradle.NewCache(logger, envProvider, kvClient)

logger.TInfof("(i) Restoring Gradle configuration cache")

_, _, err = downloadGradleConfigCacheMetadata(ctx, GradleConfigCacheMetadataPath, providedCacheKey, g, kvClient, logger)
if err != nil {
return fmt.Errorf("download cache metadata: %w", err)
}

logger.TInfof("Loading metadata from %s", GradleConfigCacheMetadataPath)
var metadata *gradle.Metadata
if metadata, _, err = g.LoadMetadata(GradleConfigCacheMetadataPath); err != nil {
return fmt.Errorf("load metadata: %w", err)
}

metadata.Print(logger)

logger.TInfof("Downloading configuration cache files")
_, err = kvClient.DownloadFileGroupFromBuildCache(ctx, metadata.ConfigCacheFiles, isDebugLogMode, true, false, 100)
if err != nil {
logger.Infof("Failed to download DerivedData files, clearing")
// To prevent the build from failing
for _, file := range metadata.ConfigCacheFiles.Files {
if err := os.Remove(file.Path); err != nil {
logger.Infof("Failed to remove file %s: %s", file.Path, err)
}
}

return fmt.Errorf("download config cache files: %w", err)
}

updated := 0

logger.Infof("(i) %d files' info loaded from cache metadata", len(metadata.ConfigCacheFiles.Files))

for _, fi := range metadata.ConfigCacheFiles.Files {
if filegroup.RestoreFileInfo(*fi, "", logger) {
updated++
}
}

logger.Infof("(i) %d files' modification time restored", updated)

return nil
}

func downloadGradleConfigCacheMetadata(ctx context.Context, cacheMetadataPath, providedCacheKey string,
gradleCache *gradle.Cache,
kvClient *kv.Client,
logger log.Logger) (CacheKeyType, string, error) {
var cacheKeyType CacheKeyType = CacheKeyTypeDefault
var cacheKey string
var err error
if providedCacheKey == "" {
if cacheKey, err = gradleCache.GetCacheKey(gradle.CacheKeyParams{}); err != nil {
return "", "", fmt.Errorf("get cache key: %w", err)
}
logger.TInfof("Downloading cache metadata checksum for key %s", cacheKey)
} else {
cacheKeyType = CacheKeyTypeProvided
cacheKey = providedCacheKey
logger.TInfof("Downloading cache metadata checksum for provided key %s", cacheKey)
}

var mdChecksum strings.Builder
err = kvClient.DownloadStreamFromBuildCache(ctx, &mdChecksum, cacheKey)
if err != nil && !errors.Is(err, kv.ErrCacheNotFound) {
return cacheKeyType, cacheKey, fmt.Errorf("download cache metadata checksum: %w", err)
}

if errors.Is(err, kv.ErrCacheNotFound) {
cacheKeyType = CacheKeyTypeFallback
fallbackCacheKey, fallbackErr := gradleCache.GetCacheKey(gradle.CacheKeyParams{IsFallback: true})
if fallbackErr != nil {
return cacheKeyType, fallbackCacheKey, errors.New("cache metadata not found in cache")
}

cacheKey = fallbackCacheKey
logger.Infof("Cache metadata not found for original key, trying fallback key %s", cacheKey)

err = kvClient.DownloadStreamFromBuildCache(ctx, &mdChecksum, cacheKey)
if errors.Is(err, kv.ErrCacheNotFound) {
return cacheKeyType, cacheKey, errors.New("cache metadata not found in cache")
}
if err != nil {
return cacheKeyType, cacheKey, fmt.Errorf("download cache metadata checksum: %w", err)
}
logger.Infof("Cache metadata found for fallback key %s", cacheKey)
}

logger.TInfof("Downloading cache metadata content to %s for key %s", cacheMetadataPath, mdChecksum.String())
if err := kvClient.DownloadFileFromBuildCache(ctx, cacheMetadataPath, mdChecksum.String()); err != nil {
return cacheKeyType, cacheKey, fmt.Errorf("download cache archive: %w", err)
}

return cacheKeyType, cacheKey, nil
}
Loading

0 comments on commit ee5eb84

Please sign in to comment.