Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 65 additions & 49 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const (
cacheMountUnset = ""

BazelDefaultSocketID = "bazel-default" // Default ID for bazel socket

)

// getSccacheSource returns a Source for downloading and verifying sccache using SourceHTTP
Expand Down Expand Up @@ -73,13 +72,15 @@ type CacheConfig struct {
}

type CacheInfo struct {
Namespace string
DirInfo CacheDirInfo
GoBuild GoBuildCacheInfo
RustSCCache SCCacheInfo
Bazel BazelCacheInfo
}

type CacheDirInfo struct {
Namespace string
// Platform sets the platform used to generate part of the cache key when
// CacheDir.NoAutoNamespace is set to false.
Platform *ocispecs.Platform
Expand All @@ -105,6 +106,12 @@ func (f CacheDirOptionFunc) SetCacheDirOption(info *CacheDirInfo) {
f(info)
}

func WithCacheNamespace(namespace string) CacheConfigOption {
return CacheConfigOptionFunc(func(info *CacheInfo) {
info.Namespace = namespace
})
}

func WithCacheDirConstraints(opts ...llb.ConstraintsOpt) CacheConfigOption {
return CacheConfigOptionFunc(func(info *CacheInfo) {
var c llb.Constraints
Expand All @@ -115,44 +122,48 @@ func WithCacheDirConstraints(opts ...llb.ConstraintsOpt) CacheConfigOption {
})
}

func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...CacheConfigOption) llb.RunOption {
func (c *CacheConfig) ToRunOption(worker llb.State, cacheIdentity string, opts ...CacheConfigOption) llb.RunOption {
if c.Dir != nil {
return c.Dir.ToRunOption(distroKey, CacheDirOptionFunc(func(info *CacheDirInfo) {
return c.Dir.ToRunOption(cacheIdentity, CacheDirOptionFunc(func(info *CacheDirInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
}
*info = cacheInfo.DirInfo
info.Namespace = cacheInfo.Namespace
}))
}

if c.GoBuild != nil {
return c.GoBuild.ToRunOption(distroKey, GoBuildCacheOptionFunc(func(info *GoBuildCacheInfo) {
return c.GoBuild.ToRunOption(cacheIdentity, GoBuildCacheOptionFunc(func(info *GoBuildCacheInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
}
*info = cacheInfo.GoBuild
info.Namespace = cacheInfo.Namespace
}))
}

if c.RustSCCache != nil {
return c.RustSCCache.ToRunOption(distroKey, SCCacheOptionFunc(func(info *SCCacheInfo) {
return c.RustSCCache.ToRunOption(cacheIdentity, SCCacheOptionFunc(func(info *SCCacheInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
}
*info = cacheInfo.RustSCCache
info.Namespace = cacheInfo.Namespace
}))
}

if c.Bazel != nil {
return c.Bazel.ToRunOption(worker, distroKey, BazelCacheOptionFunc(func(info *BazelCacheInfo) {
return c.Bazel.ToRunOption(worker, cacheIdentity, BazelCacheOptionFunc(func(info *BazelCacheInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
}
*info = cacheInfo.Bazel
info.Namespace = cacheInfo.Namespace
}))
}

Expand Down Expand Up @@ -227,12 +238,13 @@ type CacheDir struct {
Sharing string `json:"sharing" yaml:"sharing" jsonschema:"enum=shared,enum=locked,enum=private"`

// NoAutoNamespace disables the automatic prefixing of the cache key with the
// target specific information such as distro and CPU architecture, which may
// be auto-injected to prevent common issues that would cause an invalid cache.
// build environment identity and CPU architecture, which may be auto-injected
// to prevent common issues that would cause an invalid cache.
// It does not disable the global BUILDKIT_CACHE_MOUNT_NS prefix.
NoAutoNamespace bool `json:"no_auto_namespace" yaml:"no_auto_namespace"`
}

func (c *CacheDir) ToRunOption(distroKey string, opts ...CacheDirOption) llb.RunOption {
func (c *CacheDir) ToRunOption(cacheIdentity string, opts ...CacheDirOption) llb.RunOption {
return RunOptFunc(func(ei *llb.ExecInfo) {
var sharing llb.CacheMountSharingMode
switch c.Sharing {
Expand All @@ -259,18 +271,18 @@ func (c *CacheDir) ToRunOption(distroKey string, opts ...CacheDirOption) llb.Run
opt.SetCacheDirOption(&info)
}

if !c.NoAutoNamespace {
platform := ei.Platform

if platform == nil {
platform = info.Platform
}

if platform == nil {
p := platforms.DefaultSpec()
platform = &p
}
key = fmt.Sprintf("%s-%s-%s", distroKey, platforms.Format(*platform), key)
if c.NoAutoNamespace {
key = PersistentCacheID{
Namespace: info.Namespace,
Key: key,
}.String()
} else {
key = PersistentCacheID{
Namespace: info.Namespace,
Environment: cacheIdentity,
Platform: execCacheIDPlatform(ei, info.Platform),
Key: key,
}.String()
}

llb.AddMount(c.Dest, llb.Scratch(), llb.AsPersistentCacheDir(key, sharing)).SetRunOption(ei)
Expand Down Expand Up @@ -320,7 +332,8 @@ func (c *GoBuildCache) validate() error {
}

type GoBuildCacheInfo struct {
Platform *ocispecs.Platform
Namespace string
Platform *ocispecs.Platform
}

type GoBuildCacheOption interface {
Expand All @@ -345,7 +358,7 @@ func WithGoCacheConstraints(opts ...llb.ConstraintsOpt) CacheConfigOption {

const goBuildCacheDir = "/tmp/dalec/gobuild-cache"

func (c *GoBuildCache) ToRunOption(distroKey string, opts ...GoBuildCacheOption) llb.RunOption {
func (c *GoBuildCache) ToRunOption(cacheIdentity string, opts ...GoBuildCacheOption) llb.RunOption {
return RunOptFunc(func(ei *llb.ExecInfo) {
if c.Disabled {
return
Expand All @@ -356,20 +369,13 @@ func (c *GoBuildCache) ToRunOption(distroKey string, opts ...GoBuildCacheOption)
opt.SetGoBuildCacheOption(&info)
}

platform := ei.Platform

if platform == nil {
platform = info.Platform
}
if platform == nil {
p := platforms.DefaultSpec()
platform = &p
}

key := fmt.Sprintf("%s-%s-dalec-gobuildcache", distroKey, platforms.Format(*platform))
if c.Scope != "" {
key = fmt.Sprintf("%s-%s", key, c.Scope)
}
key := PersistentCacheID{
Namespace: info.Namespace,
Environment: cacheIdentity,
Platform: execCacheIDPlatform(ei, info.Platform),
Type: cacheTypeGoBuild,
Key: c.Scope,
}.String()
llb.AddMount(goBuildCacheDir, llb.Scratch(), llb.AsPersistentCacheDir(key, llb.CacheMountShared)).SetRunOption(ei)
llb.AddEnv("GOCACHE", goBuildCacheDir).SetRunOption(ei)
})
Expand Down Expand Up @@ -398,7 +404,8 @@ func (c *SCCache) validate() error {
}

type SCCacheInfo struct {
Platform *ocispecs.Platform
Namespace string
Platform *ocispecs.Platform
}

type SCCacheOption interface {
Expand All @@ -416,7 +423,7 @@ const (
sccacheBinary = "/tmp/internal/dalec/sccache/sccache"
)

func (c *SCCache) ToRunOption(distroKey string, opts ...SCCacheOption) llb.RunOption {
func (c *SCCache) ToRunOption(cacheIdentity string, opts ...SCCacheOption) llb.RunOption {
// TODO: Future improvement - allow pulling sccache from build context instead of GitHub
// This would provide better security and flexibility by allowing users to:
// 1. Bring their own verified sccache binary
Expand All @@ -443,10 +450,13 @@ func (c *SCCache) ToRunOption(distroKey string, opts ...SCCacheOption) llb.RunOp
platform = &p
}

key := fmt.Sprintf("%s-%s-dalec-rustsccache", distroKey, platforms.Format(*platform))
if c.Scope != "" {
key = fmt.Sprintf("%s-%s", key, c.Scope)
}
key := PersistentCacheID{
Namespace: info.Namespace,
Environment: cacheIdentity,
Platform: FormatCacheIDPlatform(*platform),
Type: cacheTypeRustSccache,
Key: c.Scope,
}.String()

// Set up cache mount for sccache compilation cache
llb.AddMount(sccacheCacheDir, llb.Scratch(), llb.AsPersistentCacheDir(key, llb.CacheMountShared)).SetRunOption(ei)
Expand Down Expand Up @@ -485,7 +495,9 @@ fi`),
// Set up RUSTC_WRAPPER to point at the absolute sccache binary path (no PATH update needed)
llb.AddEnv("RUSTC_WRAPPER", sccacheBinary).SetRunOption(ei)
})
} // BazelCache sets up a cache for bazel builds.
}

// BazelCache sets up a cache for bazel builds.
// Currently this only supports setting up a *local* bazel cache.
//
// BazelCache relies on the *system* bazelrc file to configure the default cache location.
Expand All @@ -506,6 +518,7 @@ func (c *BazelCache) validate() error {
}

type BazelCacheInfo struct {
Namespace string
Platform *ocispecs.Platform
constraints *llb.Constraints
}
Expand All @@ -531,7 +544,7 @@ type BazelCacheOption interface {
SetBazelCacheOption(*BazelCacheInfo)
}

func (c *BazelCache) ToRunOption(worker llb.State, distroKey string, opts ...BazelCacheOption) llb.RunOption {
func (c *BazelCache) ToRunOption(worker llb.State, cacheIdentity string, opts ...BazelCacheOption) llb.RunOption {
return RunOptFunc(func(ei *llb.ExecInfo) {
var info BazelCacheInfo

Expand All @@ -549,10 +562,13 @@ func (c *BazelCache) ToRunOption(worker llb.State, distroKey string, opts ...Baz
platform = &p
}

key := fmt.Sprintf("%s-%s-dalec-bazelcache", distroKey, platforms.Format(*platform))
if c.Scope != "" {
key = fmt.Sprintf("%s-%s", key, c.Scope)
}
key := PersistentCacheID{
Namespace: info.Namespace,
Environment: cacheIdentity,
Platform: FormatCacheIDPlatform(*platform),
Type: cacheTypeBazel,
Key: c.Scope,
}.String()

// See bazelrc https://bazel.build/run/bazelrc for more information on the bazelrc file

Expand Down
75 changes: 75 additions & 0 deletions cache_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package dalec

import (
"strings"

"github.com/containerd/platforms"
"github.com/moby/buildkit/client/llb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

const (
cacheTypeGoBuild = "dalec-gobuildcache"
cacheTypeRustSccache = "dalec-rustsccache"
cacheTypeBazel = "dalec-bazelcache"
)

// PersistentCacheID describes a Dalec persistent BuildKit cache mount ID.
type PersistentCacheID struct {
// Namespace is an optional global namespace prepended to the whole cache ID.
Namespace string
// Environment identifies the build environment that owns the cache.
Environment string
// Platform identifies the platform when a cache must be platform-scoped.
Platform string
// Type identifies the Dalec cache type.
Type string
// Key identifies user-provided cache key material, scope, or a sub-cache.
Key string
}

// String format the cache ID from its non-empty parts.
func (id PersistentCacheID) String() string {
parts := make([]string, 0, 4)
for _, part := range []string{id.Environment, id.Platform, id.Type, id.Key} {
if part != "" {
parts = append(parts, part)
}
}

cacheID := strings.Join(parts, "-")
if id.Namespace == "" {
return cacheID
}

ns := strings.TrimRight(id.Namespace, "/")
if cacheID == "" {
return ns
}
return ns + "/" + cacheID
}

// FormatCacheIDPlatform formats a platform for use in cache IDs.
func FormatCacheIDPlatform(p ocispecs.Platform) string {
return platforms.Format(p)
}

// FormatSafeCacheIDPlatform formats a platform without path separators.
func FormatSafeCacheIDPlatform(p ocispecs.Platform) string {
return strings.NewReplacer("/", "_", ":", "_").Replace(FormatCacheIDPlatform(p))
}

func defaultedCacheIDPlatform(p *ocispecs.Platform) string {
if p == nil {
dp := platforms.DefaultSpec()
p = &dp
}
return FormatCacheIDPlatform(*p)
}

func execCacheIDPlatform(ei *llb.ExecInfo, fallback *ocispecs.Platform) string {
if ei.Platform != nil {
return defaultedCacheIDPlatform(ei.Platform)
}
return defaultedCacheIDPlatform(fallback)
}
Loading