diff --git a/cache.go b/cache.go index fc48a93d2..5c058bdb6 100644 --- a/cache.go +++ b/cache.go @@ -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 @@ -115,9 +114,9 @@ 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) @@ -127,7 +126,7 @@ func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...Ca } 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) @@ -137,7 +136,7 @@ func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...Ca } 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) @@ -147,7 +146,7 @@ func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...Ca } 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) @@ -227,12 +226,12 @@ 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. 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 { @@ -260,17 +259,11 @@ func (c *CacheDir) ToRunOption(distroKey string, opts ...CacheDirOption) llb.Run } 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) + key = PersistentCacheID{ + Environment: cacheIdentity, + Platform: execCacheIDPlatform(ei, info.Platform), + Key: key, + }.String() } llb.AddMount(c.Dest, llb.Scratch(), llb.AsPersistentCacheDir(key, sharing)).SetRunOption(ei) @@ -345,7 +338,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 @@ -356,20 +349,12 @@ 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{ + 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) }) @@ -416,7 +401,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 @@ -443,10 +428,12 @@ 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{ + 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) @@ -531,7 +518,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 @@ -549,10 +536,12 @@ 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{ + 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 diff --git a/cache_id.go b/cache_id.go new file mode 100644 index 000000000..8f49593b7 --- /dev/null +++ b/cache_id.go @@ -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) +} diff --git a/cache_id_test.go b/cache_id_test.go new file mode 100644 index 000000000..07ff162cd --- /dev/null +++ b/cache_id_test.go @@ -0,0 +1,75 @@ +package dalec + +import ( + "testing" + + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" +) + +func TestPersistentCacheIDString(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + id PersistentCacheID + want string + }{ + { + name: "all parts", + id: PersistentCacheID{ + Namespace: "tenant", + Environment: "ubuntu22.04", + Platform: "linux/amd64", + Type: "dalec-gobuildcache", + Key: "scope", + }, + want: "tenant/ubuntu22.04-linux/amd64-dalec-gobuildcache-scope", + }, + { + name: "empty parts omitted", + id: PersistentCacheID{ + Environment: "azlinux3.0", + Type: "dalec-bazelcache", + }, + want: "azlinux3.0-dalec-bazelcache", + }, + { + name: "trailing namespace slash trimmed", + id: PersistentCacheID{ + Namespace: "ci/", + Type: "dalec-gomod-proxy-cache", + }, + want: "ci/dalec-gomod-proxy-cache", + }, + { + name: "user key preserved", + id: PersistentCacheID{ + Key: "/tmp/cache", + }, + want: "/tmp/cache", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := tt.id.String(); got != tt.want { + t.Fatalf("expected %q, got %q", tt.want, got) + } + }) + } +} + +func TestFormatSafeCacheIDPlatform(t *testing.T) { + t.Parallel() + + p := ocispecs.Platform{ + OS: "linux", + Architecture: "arm64", + } + + if got, want := FormatSafeCacheIDPlatform(p), "linux_arm64"; got != want { + t.Fatalf("expected %q, got %q", want, got) + } +} diff --git a/cmd/website/main.go b/cmd/website/main.go index 0c351707b..a91ba029f 100644 --- a/cmd/website/main.go +++ b/cmd/website/main.go @@ -149,8 +149,18 @@ func generateSite(toolchain llb.State) llb.StateOption { hugoCacheID = "dalec-website-hugo" ) cacheMounts := dalec.RunOptFunc(func(ei *llb.ExecInfo) { - llb.AddMount("/go/pkg/mod", llb.Scratch(), llb.AsPersistentCacheDir(modsCacheID, llb.CacheMountLocked)).SetRunOption(ei) - llb.AddMount("/cache", llb.Scratch(), llb.AsPersistentCacheDir(hugoCacheID, llb.CacheMountLocked)).SetRunOption(ei) + goModPersistentCacheID := dalec.PersistentCacheID{Type: modsCacheID}.String() + hugoPersistentCacheID := dalec.PersistentCacheID{Type: hugoCacheID}.String() + llb.AddMount( + "/go/pkg/mod", + llb.Scratch(), + llb.AsPersistentCacheDir(goModPersistentCacheID, llb.CacheMountLocked), + ).SetRunOption(ei) + llb.AddMount( + "/cache", + llb.Scratch(), + llb.AsPersistentCacheDir(hugoPersistentCacheID, llb.CacheMountLocked), + ).SetRunOption(ei) }) generated := toolchain.Run( cacheMounts, diff --git a/docs/spec.schema.json b/docs/spec.schema.json index b9029be09..48d470190 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -592,7 +592,7 @@ "type": [ "boolean" ], - "description": "NoAutoNamespace disables the automatic prefixing of the cache key with the\ntarget specific information such as distro and CPU architecture, which may\nbe auto-injected to prevent common issues that would cause an invalid cache." + "description": "NoAutoNamespace disables the automatic prefixing of the cache key with the\nbuild environment identity and CPU architecture, which may be auto-injected\nto prevent common issues that would cause an invalid cache." }, "sharing": { "enum": [ diff --git a/generator_gomod.go b/generator_gomod.go index dc4c5eb9d..cdb25e499 100644 --- a/generator_gomod.go +++ b/generator_gomod.go @@ -24,6 +24,10 @@ const ( BuildArgDalecGomodProxy = "DALEC_GOMOD_PROXY" ) +func gomodProxyCacheID() string { + return PersistentCacheID{Type: GomodCacheKey}.String() +} + func (g *GeneratorGomod) processBuildArgs(args map[string]string, allowArg func(key string) bool) error { var errs []error lex := shell.NewLex('\\') @@ -122,7 +126,7 @@ func withGomod(gomodOpts gomodGeneratorOpts) func(llb.State) llb.State { llb.AddEnv("GIT_SSH_COMMAND", "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"), llb.Dir(filepath.Join(joinedWorkDir, path)), srcMount, - llb.AddMount(proxyPath, llb.Scratch(), llb.AsPersistentCacheDir(GomodCacheKey, llb.CacheMountShared)), + llb.AddMount(proxyPath, llb.Scratch(), llb.AsPersistentCacheDir(gomodProxyCacheID(), llb.CacheMountShared)), WithConstraints(opts...), g.Gomod._sourceMap.GetLocation(in), } diff --git a/helpers.go b/helpers.go index 787a4b649..050dd6f84 100644 --- a/helpers.go +++ b/helpers.go @@ -82,10 +82,23 @@ func (f runOptionFunc) SetRunOption(i *llb.ExecInfo) { f(i) } +const ( + cacheTypeAptVarCache = "dalec-var-cache-apt" + cacheTypeAptVarLib = "dalec-var-lib-apt" +) + // WithMountedAptCache gives an [llb.RunOption] that mounts the apt cache directories. -// It uses the given namePrefix as the prefix for the cache keys. -// namePrefix should be distinct per distro version. -func WithMountedAptCache(namePrefix string, opts ...llb.ConstraintsOpt) llb.RunOption { +// cacheIdentity should be distinct per distro version. +func WithMountedAptCache(cacheIdentity string, opts ...llb.ConstraintsOpt) llb.RunOption { + return withMountedAptCache(cacheIdentity, "", opts...) +} + +// WithMountedAptCacheForPlatform mounts platform-scoped apt cache directories. +func WithMountedAptCacheForPlatform(cacheIdentity string, platform ocispecs.Platform, opts ...llb.ConstraintsOpt) llb.RunOption { + return withMountedAptCache(cacheIdentity, FormatSafeCacheIDPlatform(platform), opts...) +} + +func withMountedAptCache(cacheIdentity, platform string, opts ...llb.ConstraintsOpt) llb.RunOption { return runOptionFunc(func(ei *llb.ExecInfo) { // This is in the "official" docker image for ubuntu/debian. // This file prevents us from actually caching anything. @@ -98,13 +111,21 @@ func WithMountedAptCache(namePrefix string, opts ...llb.ConstraintsOpt) llb.RunO llb.AddMount( "/var/cache/apt", llb.Scratch(), - llb.AsPersistentCacheDir(namePrefix+"dalec-var-cache-apt", llb.CacheMountLocked), + llb.AsPersistentCacheDir(PersistentCacheID{ + Environment: cacheIdentity, + Platform: platform, + Type: cacheTypeAptVarCache, + }.String(), llb.CacheMountLocked), ).SetRunOption(ei) llb.AddMount( "/var/lib/apt", llb.Scratch(), - llb.AsPersistentCacheDir(namePrefix+"dalec-var-lib-apt", llb.CacheMountLocked), + llb.AsPersistentCacheDir(PersistentCacheID{ + Environment: cacheIdentity, + Platform: platform, + Type: cacheTypeAptVarLib, + }.String(), llb.CacheMountLocked), ).SetRunOption(ei) }) } diff --git a/packaging/linux/deb/pkg.go b/packaging/linux/deb/pkg.go index 7ea270f82..8a60b873b 100644 --- a/packaging/linux/deb/pkg.go +++ b/packaging/linux/deb/pkg.go @@ -172,7 +172,7 @@ func BuildDebBinaryOnly(worker llb.State, spec *dalec.Spec, debroot llb.State, d return st } -func BuildDeb(worker llb.State, spec *dalec.Spec, srcPkg llb.State, distroVersionID string, opts ...llb.ConstraintsOpt) llb.State { +func BuildDeb(worker llb.State, spec *dalec.Spec, srcPkg llb.State, cacheIdentity string, opts ...llb.ConstraintsOpt) llb.State { dirName := filepath.Join("/work", spec.Name+"_"+spec.Version+"-"+spec.Revision) buildRootRel := spec.Name + "-" + spec.Version st := worker. @@ -186,7 +186,7 @@ func BuildDeb(worker llb.State, spec *dalec.Spec, srcPkg llb.State, distroVersio dalec.WithCacheDirConstraints(opts...), } for _, cache := range spec.Build.Caches { - cache.ToRunOption(worker, distroVersionID, opts...).SetRunOption(ei) + cache.ToRunOption(worker, cacheIdentity, opts...).SetRunOption(ei) } }), ).AddMount("/tmp/out", llb.Scratch()) diff --git a/packaging/linux/rpm/rpmbuild.go b/packaging/linux/rpm/rpmbuild.go index ebdb7202c..7fcacc6f2 100644 --- a/packaging/linux/rpm/rpmbuild.go +++ b/packaging/linux/rpm/rpmbuild.go @@ -9,8 +9,8 @@ import ( ) type CacheInfo struct { - TargetKey string - Caches []dalec.CacheConfig + CacheIdentity string + Caches []dalec.CacheConfig } // Builds an RPM and source RPM from a spec @@ -44,7 +44,7 @@ func Build(topDir, workerImg llb.State, specPath string, caches CacheInfo, opts dalec.WithCacheDirConstraints(opts...), } for _, cache := range caches.Caches { - cache.ToRunOption(workerImg, caches.TargetKey, opts...).SetRunOption(ei) + cache.ToRunOption(workerImg, caches.CacheIdentity, opts...).SetRunOption(ei) } }), ). diff --git a/preprocess.go b/preprocess.go index a2bb8b22e..24da62077 100644 --- a/preprocess.go +++ b/preprocess.go @@ -292,7 +292,7 @@ func (s *Spec) generateGomodPatchStateForSource(gomodOpts gomodGeneratorOpts) (* llb.AddMount("/gomod-patch.sh", scriptState, llb.SourcePath("/gomod-patch.sh")), llb.AddMount(workDir, gomodOpts.sourceState), llb.AddMount(origWorkDir, gomodOpts.sourceState, llb.Readonly), // Read-only mount for diffing - llb.AddMount(proxyPath, llb.Scratch(), llb.AsPersistentCacheDir(GomodCacheKey, llb.CacheMountShared)), + llb.AddMount(proxyPath, llb.Scratch(), llb.AsPersistentCacheDir(gomodProxyCacheID(), llb.CacheMountShared)), llb.AddMount(patchOutputDir, patchOutput), // Mount scratch state to capture patch file llb.AddEnv("GOPATH", "/go"), llb.AddEnv("TMP_GOMODCACHE", proxyPath), diff --git a/targets/linux/deb/debian/bookworm.go b/targets/linux/deb/debian/bookworm.go index d55326e81..ddc6e4319 100644 --- a/targets/linux/deb/debian/bookworm.go +++ b/targets/linux/deb/debian/bookworm.go @@ -18,6 +18,7 @@ var ( ImageRef: bookwormRef, AptCachePrefix: BookwormAptCachePrefix, VersionID: bookwormVersionID, + CacheIdentity: bookwormVersionID, ContextRef: BookwormWorkerContextName, DefaultOutputImage: bookwormRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/debian/bullseye.go b/targets/linux/deb/debian/bullseye.go index 712ba154d..de36e729f 100644 --- a/targets/linux/deb/debian/bullseye.go +++ b/targets/linux/deb/debian/bullseye.go @@ -19,6 +19,7 @@ var ( ImageRef: bullseyeRef, AptCachePrefix: BullseyeAptCachePrefix, VersionID: bullseyeVersionID, + CacheIdentity: bullseyeVersionID, ContextRef: BullseyeWorkerContextName, DefaultOutputImage: bullseyeRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/debian/trixie.go b/targets/linux/deb/debian/trixie.go index 2d514735c..c9a1405d7 100644 --- a/targets/linux/deb/debian/trixie.go +++ b/targets/linux/deb/debian/trixie.go @@ -18,6 +18,7 @@ var ( ImageRef: trixieRef, AptCachePrefix: TrixieAptCachePrefix, VersionID: trixieVersionID, + CacheIdentity: trixieVersionID, ContextRef: TrixieWorkerContextName, DefaultOutputImage: trixieRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/distro/distro.go b/targets/linux/deb/distro/distro.go index c57d50802..623f417c7 100644 --- a/targets/linux/deb/distro/distro.go +++ b/targets/linux/deb/distro/distro.go @@ -21,9 +21,12 @@ var defaultRepoConfig = &dalec.RepoPlatformConfig{ } type Config struct { - ImageRef string - ContextRef string - VersionID string + ImageRef string + ContextRef string + VersionID string + // CacheIdentity identifies the build environment for user build-cache + // namespacing. When unset, VersionID is used. + CacheIdentity string AptCachePrefix string BuilderPackages []string @@ -41,6 +44,17 @@ type Config struct { SysextSupported bool } +func (cfg *Config) BuildCacheIdentity() string { + if cfg.CacheIdentity != "" { + return cfg.CacheIdentity + } + return cfg.VersionID +} + +func (cfg *Config) SetBuildCacheIdentity(identity string) { + cfg.CacheIdentity = identity +} + func (cfg *Config) BuildImageConfig(ctx context.Context, sOpt dalec.SourceOpts, spec *dalec.Spec, platform *ocispecs.Platform, targetKey string) (*dalec.DockerImageSpec, error) { img, err := resolveConfig(ctx, sOpt, spec, platform, targetKey) if err != nil { diff --git a/targets/linux/deb/distro/pkg.go b/targets/linux/deb/distro/pkg.go index 997d35938..c269ffe84 100644 --- a/targets/linux/deb/distro/pkg.go +++ b/targets/linux/deb/distro/pkg.go @@ -34,6 +34,10 @@ func (d *Config) BuildPkg(ctx context.Context, client gwclient.Client, sOpt dale return dalec.ErrorState(worker, err) } } + cacheIdentity := d.BuildCacheIdentity() + if cacheIdentity == "" { + cacheIdentity = versionID + } worker = worker.With(d.InstallBuildDeps(ctx, sOpt, spec, targetKey, append(opts, frontend.IgnoreCache(client))...)) @@ -50,7 +54,7 @@ func (d *Config) BuildPkg(ctx context.Context, client gwclient.Client, sOpt dale builder := worker.With(dalec.SetBuildNetworkMode(spec)) buildOpts := append(opts, spec.Build.Steps.GetSourceLocation(builder)) - st := deb.BuildDeb(builder, spec, srcPkg, versionID, append(buildOpts, frontend.IgnoreCache(client, targets.IgnoreCacheKeyPkg))...) + st := deb.BuildDeb(builder, spec, srcPkg, cacheIdentity, append(buildOpts, frontend.IgnoreCache(client, targets.IgnoreCacheKeyPkg))...) // Filter out everything except the .deb files filtered := llb.Scratch().File( diff --git a/targets/linux/deb/distro/worker.go b/targets/linux/deb/distro/worker.go index d7085ba7f..80a7122e3 100644 --- a/targets/linux/deb/distro/worker.go +++ b/targets/linux/deb/distro/worker.go @@ -3,7 +3,6 @@ package distro import ( "context" "encoding/json" - "strings" "github.com/containerd/platforms" "github.com/moby/buildkit/client/llb" @@ -65,19 +64,6 @@ func debArchFromPlatform(p ocispecs.Platform) (string, error) { } } -// aptCacheKeyForCross returns a platform-scoped apt cache key for cross-architecture builds. -// When building a target image on a different build platform, the shared apt cache must be -// separated by target platform to avoid mixing build-arch and target-arch .deb artifacts. -func aptCacheKeyForCross(prefix string, target ocispecs.Platform) string { - if prefix == "" { - return prefix - } - // platforms.Format => e.g. "linux/arm64" - s := platforms.Format(target) - s = strings.NewReplacer("/", "_", ":", "_").Replace(s) - return prefix + "-" + s -} - func (cfg *Config) HandleWorker(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { return frontend.BuildWithPlatform(ctx, client, func(ctx context.Context, client gwclient.Client, platform *ocispecs.Platform, spec *dalec.Spec, targetKey string) (gwclient.Reference, *dalec.DockerImageSpec, error) { // Normalize the platform early so: @@ -200,14 +186,13 @@ func (cfg *Config) workerWithBuildPlatform(sOpt dalec.SourceOpts, buildPlat ocis // Build platform container (tools run here), pinned to build platform. buildBase := frontend.GetBaseImage(buildSOpt, cfg.ImageRef, buildOpts...).Platform(buildPlat) const rootfsMount = "/tmp/dalec/rootfs" - cacheKey := aptCacheKeyForCross(cfg.AptCachePrefix, targetPlat) es := buildBase.Run( dalec.WithConstraints(append(opts, llb.Platform(buildPlat))...), llb.AddMount(rootfsMount, targetBase), AptInstallIntoRoot(rootfsMount, cfg.BuilderPackages, targetArch, buildPlat), aptProxyConfig(sOpt), - dalec.WithMountedAptCache(cacheKey), + dalec.WithMountedAptCacheForPlatform(cfg.AptCachePrefix, targetPlat), ) return es.GetMount(rootfsMount).Platform(targetPlat) @@ -247,7 +232,6 @@ func (cfg *Config) SysextWorker(sOpts dalec.SourceOpts, opts ...llb.ConstraintsO buildOpts := append(append([]llb.ConstraintsOpt{}, opts...), llb.Platform(buildPlat)) const rootfsMount = "/tmp/dalec/rootfs" - cacheKey := aptCacheKeyForCross(cfg.AptCachePrefix, targetPlat) buildBase := frontend.GetBaseImage(buildSOpt, cfg.ImageRef, buildOpts...).Platform(buildPlat) es := buildBase.Run( @@ -255,7 +239,7 @@ func (cfg *Config) SysextWorker(sOpts dalec.SourceOpts, opts ...llb.ConstraintsO llb.AddMount(rootfsMount, worker), AptInstallIntoRoot(rootfsMount, []string{"erofs-utils"}, targetArch, buildPlat), aptProxyConfig(sOpts), - dalec.WithMountedAptCache(cacheKey), + dalec.WithMountedAptCacheForPlatform(cfg.AptCachePrefix, targetPlat), ) return es.GetMount(rootfsMount).Platform(targetPlat) diff --git a/targets/linux/deb/ubuntu/bionic.go b/targets/linux/deb/ubuntu/bionic.go index 3140ce316..5b4fec060 100644 --- a/targets/linux/deb/ubuntu/bionic.go +++ b/targets/linux/deb/ubuntu/bionic.go @@ -18,6 +18,7 @@ var ( ImageRef: bionicRef, AptCachePrefix: BionicAptCachePrefix, VersionID: bionicVersionID, + CacheIdentity: bionicVersionID, ContextRef: BionicWorkerContextName, DefaultOutputImage: bionicRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/ubuntu/focal.go b/targets/linux/deb/ubuntu/focal.go index a2e610de8..75db55f1e 100644 --- a/targets/linux/deb/ubuntu/focal.go +++ b/targets/linux/deb/ubuntu/focal.go @@ -18,6 +18,7 @@ var ( ImageRef: focalRef, AptCachePrefix: FocalAptCachePrefix, VersionID: focalVersionID, + CacheIdentity: focalVersionID, ContextRef: FocalWorkerContextName, DefaultOutputImage: focalRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/ubuntu/jammy.go b/targets/linux/deb/ubuntu/jammy.go index 82a5f87ae..219cb8df5 100644 --- a/targets/linux/deb/ubuntu/jammy.go +++ b/targets/linux/deb/ubuntu/jammy.go @@ -18,6 +18,7 @@ var ( ImageRef: jammyRef, AptCachePrefix: JammyAptCachePrefix, VersionID: JammyVersionID, + CacheIdentity: JammyVersionID, ContextRef: JammyWorkerContextName, DefaultOutputImage: jammyRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/ubuntu/noble.go b/targets/linux/deb/ubuntu/noble.go index d3c0ca251..7a90d1e03 100644 --- a/targets/linux/deb/ubuntu/noble.go +++ b/targets/linux/deb/ubuntu/noble.go @@ -18,6 +18,7 @@ var ( ImageRef: nobleRef, AptCachePrefix: NobleAptCachePrefix, VersionID: nobleVersionID, + CacheIdentity: nobleVersionID, ContextRef: NobleWorkerContextName, DefaultOutputImage: nobleRef, BuilderPackages: builderPackages, diff --git a/targets/linux/deb/ubuntu/resolute.go b/targets/linux/deb/ubuntu/resolute.go index c5fdcd702..cbc12df3e 100644 --- a/targets/linux/deb/ubuntu/resolute.go +++ b/targets/linux/deb/ubuntu/resolute.go @@ -18,6 +18,7 @@ var ( ImageRef: resoluteRef, AptCachePrefix: ResoluteAptCachePrefix, VersionID: resoluteVersionID, + CacheIdentity: resoluteVersionID, ContextRef: ResoluteWorkerContextName, DefaultOutputImage: resoluteRef, BuilderPackages: builderPackages, diff --git a/targets/linux/rpm/almalinux/v8.go b/targets/linux/rpm/almalinux/v8.go index b1e210d11..49a74e461 100644 --- a/targets/linux/rpm/almalinux/v8.go +++ b/targets/linux/rpm/almalinux/v8.go @@ -5,8 +5,9 @@ import ( ) const ( - V8TargetKey = "almalinux8" - dnfCacheNameV8 = "almalinux8-dnf-cache" + V8TargetKey = "almalinux8" + cacheIdentityV8 = "almalinux8" + dnfCacheNameV8 = "almalinux8-dnf-cache" // v8Ref is the image ref used for the base worker image v8Ref = "docker.io/library/almalinux:8" @@ -19,8 +20,9 @@ var ConfigV8 = &distro.Config{ ImageRef: v8Ref, ContextRef: v8WorkerContextName, - CacheName: dnfCacheNameV8, - CacheDir: []string{"/var/cache/dnf"}, + CacheIdentity: cacheIdentityV8, + CacheName: dnfCacheNameV8, + CacheDir: []string{"/var/cache/dnf"}, // Alma's repo configs do not include the $basearch variable in the mirrorlist URL // This means that the cache key that dnf computes for /var/cache/dnf/- // is the same across x86_64 and aarch64, which leads to incorrect repo metadata diff --git a/targets/linux/rpm/almalinux/v9.go b/targets/linux/rpm/almalinux/v9.go index 14360e9c1..623a674ec 100644 --- a/targets/linux/rpm/almalinux/v9.go +++ b/targets/linux/rpm/almalinux/v9.go @@ -5,8 +5,9 @@ import ( ) const ( - V9TargetKey = "almalinux9" - dnfCacheNameV9 = "almalinux9-dnf-cache" + V9TargetKey = "almalinux9" + cacheIdentityV9 = "almalinux9" + dnfCacheNameV9 = "almalinux9-dnf-cache" // v9Ref is the image ref used for the base worker image v9Ref = "docker.io/library/almalinux:9" @@ -19,8 +20,9 @@ var ConfigV9 = &distro.Config{ ImageRef: v9Ref, ContextRef: v9WorkerContextName, - CacheName: dnfCacheNameV9, - CacheDir: []string{"/var/cache/dnf"}, + CacheIdentity: cacheIdentityV9, + CacheName: dnfCacheNameV9, + CacheDir: []string{"/var/cache/dnf"}, // Alma's repo configs do not include the $basearch variable in the mirrorlist URL // This means that the cache key that dnf computes for /var/cache/dnf/- // is the same across x86_64 and aarch64, which leads to incorrect repo metadata diff --git a/targets/linux/rpm/azlinux/azlinux3.go b/targets/linux/rpm/azlinux/azlinux3.go index a7f409ef2..a85474bef 100644 --- a/targets/linux/rpm/azlinux/azlinux3.go +++ b/targets/linux/rpm/azlinux/azlinux3.go @@ -7,6 +7,7 @@ import ( const ( AzLinux3TargetKey = "azlinux3" + azlinux3CacheIdentity = "azlinux3.0" tdnfCacheNameAzlinux3 = "azlinux3-tdnf-cache" // Azlinux3Ref is the image ref used for the base worker image @@ -61,6 +62,7 @@ var Azlinux3Config = &distro.Config{ ImageRef: Azlinux3Ref, ContextRef: Azlinux3WorkerContextName, + CacheIdentity: azlinux3CacheIdentity, CacheName: tdnfCacheNameAzlinux3, CacheDir: []string{"/var/cache/tdnf", "/var/cache/dnf"}, CacheAddPlatform: true, diff --git a/targets/linux/rpm/azlinux/azlinux4.go b/targets/linux/rpm/azlinux/azlinux4.go index 50d07ef32..7645209c6 100644 --- a/targets/linux/rpm/azlinux/azlinux4.go +++ b/targets/linux/rpm/azlinux/azlinux4.go @@ -6,8 +6,9 @@ import ( ) const ( - AzLinux4TargetKey = "azlinux4" - dnfCacheNameAzlinux4 = "azlinux4-dnf-cache" + AzLinux4TargetKey = "azlinux4" + azlinux4CacheIdentity = "azlinux4.0" + dnfCacheNameAzlinux4 = "azlinux4-dnf-cache" // Azlinux4Ref is the image ref used for the base worker image. // @@ -87,6 +88,7 @@ var Azlinux4Config = &distro.Config{ ImageRef: Azlinux4Ref, ContextRef: Azlinux4WorkerContextName, + CacheIdentity: azlinux4CacheIdentity, CacheName: dnfCacheNameAzlinux4, CacheDir: []string{"/var/cache/libdnf5"}, CacheAddPlatform: true, diff --git a/targets/linux/rpm/distro/distro.go b/targets/linux/rpm/distro/distro.go index f8e187355..966b5ff96 100644 --- a/targets/linux/rpm/distro/distro.go +++ b/targets/linux/rpm/distro/distro.go @@ -17,6 +17,11 @@ type Config struct { ImageRef string ContextRef string + // CacheIdentity identifies the build environment for user build-cache + // namespacing. This is separate from CacheName, which is used for package + // manager metadata caches. + CacheIdentity string + // The release version of the distro ReleaseVer string @@ -62,9 +67,30 @@ type Config struct { RPMMacros []rpm.SpecMacro } +func (cfg *Config) BuildCacheIdentity() string { + return cfg.CacheIdentity +} + +func (cfg *Config) SetBuildCacheIdentity(identity string) { + cfg.CacheIdentity = identity +} + +func (cfg *Config) packageCacheID(platform, dir string) string { + key := "" + if len(cfg.CacheDir) > 1 { + key = filepath.Base(dir) + } + + return dalec.PersistentCacheID{ + Environment: cfg.CacheName, + Platform: platform, + Key: key, + }.String() +} + func (cfg *Config) PackageCacheMount(root string) llb.RunOption { return dalec.RunOptFunc(func(ei *llb.ExecInfo) { - cacheKey := cfg.CacheName + var platform string if cfg.CacheAddPlatform { p := ei.Constraints.Platform if p == nil { @@ -74,7 +100,7 @@ func (cfg *Config) PackageCacheMount(root string) llb.RunOption { dp := platforms.DefaultSpec() p = &dp } - cacheKey += "-" + platforms.Format(*p) + platform = dalec.FormatCacheIDPlatform(*p) } if len(cfg.CacheDir) == 0 { @@ -86,14 +112,10 @@ func (cfg *Config) PackageCacheMount(root string) llb.RunOption { if d == "" { continue } - k := cacheKey - if len(cfg.CacheDir) > 1 { - k = cacheKey + "-" + filepath.Base(d) - } llb.AddMount( joinUnderRoot(root, d), llb.Scratch(), - llb.AsPersistentCacheDir(k, llb.CacheMountLocked), + llb.AsPersistentCacheDir(cfg.packageCacheID(platform, d), llb.CacheMountLocked), ).SetRunOption(ei) } diff --git a/targets/linux/rpm/distro/dnf_install.go b/targets/linux/rpm/distro/dnf_install.go index 08771e137..dc1cbd18b 100644 --- a/targets/linux/rpm/distro/dnf_install.go +++ b/targets/linux/rpm/distro/dnf_install.go @@ -331,9 +331,9 @@ func (cfg *Config) InstallIntoRoot(rootfsPath string, pkgs []string, targetArch var installCfg dnfInstallConfig dnfInstallOptions(&installCfg, installOpts) - cacheKey := cfg.CacheName + var cachePlatform string if cfg.CacheAddPlatform { - cacheKey += "-" + targetArch + cachePlatform = targetArch } // Cross-arch installs always use dnf --forcearch --installroot runOpts := []llb.RunOption{ @@ -345,15 +345,11 @@ func (cfg *Config) InstallIntoRoot(rootfsPath string, pkgs []string, targetArch if d == "" { continue } - k := cacheKey - if len(cfg.CacheDir) > 1 { - k = cacheKey + "-" + filepath.Base(d) - } runOpts = append(runOpts, llb.AddMount( joinUnderRoot(rootfsPath, d), llb.Scratch(), - llb.AsPersistentCacheDir(k, llb.CacheMountLocked), + llb.AsPersistentCacheDir(cfg.packageCacheID(cachePlatform, d), llb.CacheMountLocked), ), ) } @@ -404,7 +400,7 @@ func (cfg *Config) WithDeps(sOpt dalec.SourceOpts, targetKey, pkgName string, de rpmSpec := rpm.RPMSpecWithMacros(spec, in, targetKey, "", dalec.SourceFilterConfig{}, cfg.RPMMacros, opts...) specPath := filepath.Join("SPECS", spec.Name, spec.Name+".spec") - cacheInfo := rpm.CacheInfo{TargetKey: targetKey, Caches: spec.Build.Caches} + cacheInfo := rpm.CacheInfo{CacheIdentity: cfg.BuildCacheIdentity(), Caches: spec.Build.Caches} rpmDir := rpm.Build(rpmSpec, in, specPath, cacheInfo, opts...) const rpmMountDir = "/tmp/internal/dalec/deps/install/rpms" diff --git a/targets/linux/rpm/distro/pkg.go b/targets/linux/rpm/distro/pkg.go index 25969988c..61ee730c7 100644 --- a/targets/linux/rpm/distro/pkg.go +++ b/targets/linux/rpm/distro/pkg.go @@ -2,6 +2,7 @@ package distro import ( "context" + "fmt" "path/filepath" "github.com/moby/buildkit/client/llb" @@ -60,11 +61,14 @@ func (c *Config) BuildPkg(ctx context.Context, client gwclient.Client, sOpt dale specPath := filepath.Join("SPECS", spec.Name, spec.Name+".spec") builder := worker.With(dalec.SetBuildNetworkMode(spec)) - cacheInfo := rpm.CacheInfo{TargetKey: targetKey, Caches: spec.Build.Caches} + cacheInfo := rpm.CacheInfo{CacheIdentity: c.BuildCacheIdentity(), Caches: spec.Build.Caches} if needsAutoGocache(spec, targetKey) { addGoCache(&cacheInfo) } + if len(cacheInfo.Caches) > 0 && cacheInfo.CacheIdentity == "" { + return dalec.ErrorState(builder, fmt.Errorf("rpm distro cache identity is not set")) + } buildOpts := append(opts, spec.Build.Steps.GetSourceLocation(builder), frontend.IgnoreCache(client, targets.IgnoreCacheKeyPkg)) st := rpm.Build(br, builder, specPath, cacheInfo, buildOpts...) diff --git a/targets/linux/rpm/rockylinux/v8.go b/targets/linux/rpm/rockylinux/v8.go index 4a278b3aa..bfa903d30 100644 --- a/targets/linux/rpm/rockylinux/v8.go +++ b/targets/linux/rpm/rockylinux/v8.go @@ -5,8 +5,9 @@ import ( ) const ( - V8TargetKey = "rockylinux8" - dnfCacheNameV8 = "rockylinux8-dnf-cache" + V8TargetKey = "rockylinux8" + cacheIdentityV8 = "rockylinux8" + dnfCacheNameV8 = "rockylinux8-dnf-cache" // v8Ref is the image ref used for the base worker image v8Ref = "docker.io/library/rockylinux:8" @@ -19,6 +20,7 @@ var ConfigV8 = &distro.Config{ ImageRef: v8Ref, ContextRef: v8WorkerContextName, + CacheIdentity: cacheIdentityV8, CacheName: dnfCacheNameV8, CacheDir: []string{"/var/cache/dnf"}, CacheAddPlatform: true, diff --git a/targets/linux/rpm/rockylinux/v9.go b/targets/linux/rpm/rockylinux/v9.go index 5fe328fb4..5fd57945e 100644 --- a/targets/linux/rpm/rockylinux/v9.go +++ b/targets/linux/rpm/rockylinux/v9.go @@ -5,8 +5,9 @@ import ( ) const ( - V9TargetKey = "rockylinux9" - dnfCacheNameV9 = "rockylinux9-dnf-cache" + V9TargetKey = "rockylinux9" + cacheIdentityV9 = "rockylinux9" + dnfCacheNameV9 = "rockylinux9-dnf-cache" // v9Ref is the image ref used for the base worker image v9Ref = "docker.io/library/rockylinux:9" @@ -19,6 +20,7 @@ var ConfigV9 = &distro.Config{ ImageRef: v9Ref, ContextRef: v9WorkerContextName, + CacheIdentity: cacheIdentityV9, CacheName: dnfCacheNameV9, CacheDir: []string{"/var/cache/dnf"}, CacheAddPlatform: true, diff --git a/targets/linux/rpm/suse/sles15.go b/targets/linux/rpm/suse/sles15.go index d4080d933..5e65a9095 100644 --- a/targets/linux/rpm/suse/sles15.go +++ b/targets/linux/rpm/suse/sles15.go @@ -7,8 +7,9 @@ import ( const ( // SLES15TargetKey is the target name recipes use, e.g. "sles15/rpm". - SLES15TargetKey = "sles15" - zypperCacheSLES15 = "sles15-zypper-cache" + SLES15TargetKey = "sles15" + sles15CacheIdentity = "sles15.7" + zypperCacheSLES15 = "sles15-zypper-cache" // sles15Ref is the image ref used for the base worker image. Pin to 15.7: // SP6 (15.6) reached end of general support on 2025-12-31, so builds should @@ -29,8 +30,9 @@ var ConfigSLES15 = &distro.Config{ ImageRef: sles15Ref, ContextRef: sles15WorkerContextName, - CacheName: zypperCacheSLES15, - CacheDir: []string{"/var/cache/zypp"}, + CacheIdentity: sles15CacheIdentity, + CacheName: zypperCacheSLES15, + CacheDir: []string{"/var/cache/zypp"}, ReleaseVer: "15", BuilderPackages: builderPackages, diff --git a/targets/plugin/init.go b/targets/plugin/init.go index 89709ce15..2ae072615 100644 --- a/targets/plugin/init.go +++ b/targets/plugin/init.go @@ -12,14 +12,16 @@ import ( "github.com/project-dalec/dalec/targets/linux/flatcar" "github.com/project-dalec/dalec/targets/linux/rpm/almalinux" "github.com/project-dalec/dalec/targets/linux/rpm/azlinux" + rpmdistro "github.com/project-dalec/dalec/targets/linux/rpm/distro" "github.com/project-dalec/dalec/targets/linux/rpm/rockylinux" "github.com/project-dalec/dalec/targets/linux/rpm/suse" "github.com/project-dalec/dalec/targets/windows" ) -const testingAltVersionIDSuffix = "testingalt" +const testingAltCacheIdentitySuffix = "testingalt" type routeFunc func(prefix string, spec *dalec.Spec) ([]frontend.Route, error) +type testingAltCacheIdentityRouteFunc func(cacheIdentity string) routeFunc func init() { registerDebRoutes(debian.TrixieDefaultTargetKey, debian.TrixieConfig) @@ -32,20 +34,20 @@ func init() { registerDebRoutes(ubuntu.NobleDefaultTargetKey, ubuntu.NobleConfig) registerDebRoutes(ubuntu.ResoluteDefaultTargetKey, ubuntu.ResoluteConfig) - registerRoutes(almalinux.V8TargetKey, almalinux.ConfigV8.Routes) - registerRoutes(almalinux.V9TargetKey, almalinux.ConfigV9.Routes) + registerRpmRoutes(almalinux.V8TargetKey, almalinux.ConfigV8) + registerRpmRoutes(almalinux.V9TargetKey, almalinux.ConfigV9) - registerRoutes(rockylinux.V8TargetKey, rockylinux.ConfigV8.Routes) - registerRoutes(rockylinux.V9TargetKey, rockylinux.ConfigV9.Routes) + registerRpmRoutes(rockylinux.V8TargetKey, rockylinux.ConfigV8) + registerRpmRoutes(rockylinux.V9TargetKey, rockylinux.ConfigV9) - registerRoutes(azlinux.AzLinux3TargetKey, azlinux.Azlinux3Config.Routes) - registerRoutes(azlinux.AzLinux4TargetKey, azlinux.Azlinux4Config.Routes) + registerRpmRoutes(azlinux.AzLinux3TargetKey, azlinux.Azlinux3Config) + registerRpmRoutes(azlinux.AzLinux4TargetKey, azlinux.Azlinux4Config) - registerRoutes(suse.SLES15TargetKey, suse.ConfigSLES15.Routes) + registerRpmRoutes(suse.SLES15TargetKey, suse.ConfigSLES15) registerRoutes(flatcar.TargetKey, flatcar.DefaultConfig.Routes) - registerRoutes(windows.DefaultTargetKey, windows.Routes) + registerWindowsRoutes(windows.DefaultTargetKey) } func registerRoutes(name string, routes routeFunc) { @@ -63,9 +65,9 @@ func registerRoutes(name string, routes routeFunc) { }) } -func registerDebRoutes(name string, cfg *debdistro.Config) { +func registerRoutesWithTestingAltCacheIdentity(name string, routes routeFunc, cacheIdentity string, altRoutes testingAltCacheIdentityRouteFunc) { targets.RegisterRouteProvider(name, func(_ context.Context, spec *dalec.Spec) ([]frontend.Route, error) { - return cfg.Routes(name, spec) + return routes(name, spec) }) if !includeAltTestingTargets { @@ -73,10 +75,30 @@ func registerDebRoutes(name string, cfg *debdistro.Config) { } altName := targets.TestingAltTargetKey(name) - altCfg := *cfg - altCfg.VersionID += testingAltVersionIDSuffix - + testingAltRoutes := altRoutes(cacheIdentity + testingAltCacheIdentitySuffix) targets.RegisterRouteProvider(altName, func(_ context.Context, spec *dalec.Spec) ([]frontend.Route, error) { - return altCfg.Routes(altName, spec) + return testingAltRoutes(altName, spec) + }) +} + +func registerDebRoutes(name string, cfg *debdistro.Config) { + registerRoutesWithTestingAltCacheIdentity(name, cfg.Routes, cfg.BuildCacheIdentity(), func(cacheIdentity string) routeFunc { + altCfg := *cfg + altCfg.SetBuildCacheIdentity(cacheIdentity) + return altCfg.Routes + }) +} + +func registerRpmRoutes(name string, cfg *rpmdistro.Config) { + registerRoutesWithTestingAltCacheIdentity(name, cfg.Routes, cfg.BuildCacheIdentity(), func(cacheIdentity string) routeFunc { + altCfg := *cfg + altCfg.SetBuildCacheIdentity(cacheIdentity) + return altCfg.Routes + }) +} + +func registerWindowsRoutes(name string) { + registerRoutesWithTestingAltCacheIdentity(name, windows.Routes, windows.BuildCacheIdentity(), func(cacheIdentity string) routeFunc { + return windows.RoutesWithCacheIdentity(cacheIdentity) }) } diff --git a/targets/windows/handle_container.go b/targets/windows/handle_container.go index 49836d6c1..13b443c54 100644 --- a/targets/windows/handle_container.go +++ b/targets/windows/handle_container.go @@ -35,7 +35,7 @@ var defaultPlatform = ocispecs.Platform{ Architecture: runtime.GOARCH, } -func handleContainer(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { +func (h routeHandlers) handleContainer(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { dc, err := dockerui.NewClient(client) if err != nil { return nil, err @@ -130,9 +130,9 @@ func handleContainer(ctx context.Context, client gwclient.Client) (*gwclient.Res } pg := dalec.ProgressGroup("Build windows container: " + spec.Name) - worker := distroConfig.Worker(sOpt, pg) + worker := h.distro.Worker(sOpt, pg) - bin := buildBinaries(ctx, spec, worker, client, sOpt, targetKey, pg) + bin := h.buildBinaries(ctx, spec, worker, client, sOpt, targetKey, pg) bi := bases[idx] diff --git a/targets/windows/handle_zip.go b/targets/windows/handle_zip.go index a5a591029..ca357ebf5 100644 --- a/targets/windows/handle_zip.go +++ b/targets/windows/handle_zip.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "path" "path/filepath" "strings" @@ -14,17 +13,15 @@ import ( "github.com/project-dalec/dalec" "github.com/project-dalec/dalec/frontend" "github.com/project-dalec/dalec/targets" - "github.com/project-dalec/dalec/targets/linux/deb/ubuntu" ) const ( outputDir = "/tmp/output" buildScriptName = "_build.sh" aptCachePrefix = "jammy-windowscross" - distroVersionID = ubuntu.JammyVersionID ) -func handleZip(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { +func (h routeHandlers) handleZip(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { return frontend.BuildWithPlatform(ctx, client, func(ctx context.Context, client gwclient.Client, platform *ocispecs.Platform, spec *dalec.Spec, targetKey string) (gwclient.Reference, *dalec.DockerImageSpec, error) { sOpt, err := frontend.SourceOptFromClient(ctx, client, nil) if err != nil { @@ -32,9 +29,9 @@ func handleZip(ctx context.Context, client gwclient.Client) (*gwclient.Result, e } pg := dalec.ProgressGroup("Build windows container: " + spec.Name) - worker := distroConfig.Worker(sOpt, pg) + worker := h.distro.Worker(sOpt, pg) - bin := buildBinaries(ctx, spec, worker, client, sOpt, targetKey, pg) + bin := h.buildBinaries(ctx, spec, worker, client, sOpt, targetKey, pg) st := getZipLLB(worker, platform, spec, bin, pg) @@ -152,13 +149,13 @@ func addGoCache(spec *dalec.Spec, targetKey string) { }) } -func buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, client gwclient.Client, sOpt dalec.SourceOpts, targetKey string, opts ...llb.ConstraintsOpt) llb.State { +func (h routeHandlers) buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, client gwclient.Client, sOpt dalec.SourceOpts, targetKey string, opts ...llb.ConstraintsOpt) llb.State { opts = append(opts, frontend.IgnoreCache(client, targets.IgnoreCacheKeyPkg)) deps := spec.GetPackageDeps(targetKey).GetBuild() if len(deps) > 0 { opts := append(opts, deps.GetSourceLocation(worker)) - worker = worker.With(distroConfig.InstallBuildDeps(ctx, sOpt, spec, targetKey, opts...)) + worker = worker.With(h.distro.InstallBuildDeps(ctx, sOpt, spec, targetKey, opts...)) } // Preprocess the spec to generate patches for gomod edits and other generators @@ -195,7 +192,7 @@ func buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, clie llb.AddEnv("GOOS", "windows"), dalec.RunOptFunc(func(ei *llb.ExecInfo) { for _, c := range spec.Build.Caches { - c.ToRunOption(worker, path.Join(distroVersionID, targetKey), dalec.WithCacheDirConstraints(opts...)).SetRunOption(ei) + c.ToRunOption(worker, h.distro.BuildCacheIdentity(), dalec.WithCacheDirConstraints(opts...)).SetRunOption(ei) } }), dalec.RunOptFunc(func(ei *llb.ExecInfo) { diff --git a/targets/windows/handler.go b/targets/windows/handler.go index 91f86ccf1..5f18398e1 100644 --- a/targets/windows/handler.go +++ b/targets/windows/handler.go @@ -12,19 +12,22 @@ import ( "github.com/project-dalec/dalec" "github.com/project-dalec/dalec/frontend" "github.com/project-dalec/dalec/targets/linux/deb/distro" + "github.com/project-dalec/dalec/targets/linux/deb/ubuntu" ) const ( DefaultTargetKey = "windowscross" outputKey = "windows" workerImgRef = "docker.io/library/ubuntu:jammy" + windowsCacheIdentity = ubuntu.JammyVersionID + "-mingw-w64" WindowscrossWorkerContextName = "dalec-windowscross-worker" ) var distroConfig = &distro.Config{ ImageRef: workerImgRef, AptCachePrefix: aptCachePrefix, - VersionID: "ubuntu22.04", + VersionID: ubuntu.JammyVersionID, + CacheIdentity: windowsCacheIdentity, ContextRef: WindowscrossWorkerContextName, BuilderPackages: []string{ "aptitude", @@ -42,18 +45,39 @@ var distroConfig = &distro.Config{ }, } +type routeHandlers struct { + distro *distro.Config +} + // Routes returns the flat routes for the Windows target, prefixed with the given prefix. func Routes(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { + return RoutesWithConfig(prefix, spec, distroConfig) +} + +func BuildCacheIdentity() string { + return distroConfig.BuildCacheIdentity() +} + +func RoutesWithCacheIdentity(cacheIdentity string) func(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { + cfg := *distroConfig + cfg.SetBuildCacheIdentity(cacheIdentity) + return func(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { + return RoutesWithConfig(prefix, spec, &cfg) + } +} + +func RoutesWithConfig(prefix string, spec *dalec.Spec, cfg *distro.Config) ([]frontend.Route, error) { _, specDefined := spec.Targets[prefix] specDefined = specDefined && len(spec.Targets) > 0 defaultPlatform := platforms.DefaultSpec() defaultPlatform.OS = "windows" + handlers := routeHandlers{distro: cfg} return []frontend.Route{ { FullPath: prefix, - Handler: frontend.WithDefaultPlatform(defaultPlatform, handleContainer), + Handler: frontend.WithDefaultPlatform(defaultPlatform, handlers.handleContainer), Info: frontend.Target{ Target: bktargets.Target{ Name: prefix, @@ -64,7 +88,7 @@ func Routes(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { }, { FullPath: prefix + "/zip", - Handler: frontend.WithDefaultPlatform(defaultPlatform, handleZip), + Handler: frontend.WithDefaultPlatform(defaultPlatform, handlers.handleZip), Info: frontend.Target{ Target: bktargets.Target{ Name: prefix + "/zip", @@ -75,7 +99,7 @@ func Routes(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { }, { FullPath: prefix + "/container", - Handler: frontend.WithDefaultPlatform(defaultPlatform, handleContainer), + Handler: frontend.WithDefaultPlatform(defaultPlatform, handlers.handleContainer), Info: frontend.Target{ Target: bktargets.Target{ Name: prefix + "/container", @@ -87,7 +111,7 @@ func Routes(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { }, { FullPath: prefix + "/worker", - Handler: handleWorker, + Handler: handlers.handleWorker, Info: frontend.Target{ Target: bktargets.Target{ Name: prefix + "/worker", @@ -99,7 +123,7 @@ func Routes(prefix string, spec *dalec.Spec) ([]frontend.Route, error) { }, nil } -func handleWorker(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { +func (h routeHandlers) handleWorker(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { return frontend.BuildWithPlatform(ctx, client, func(ctx context.Context, client gwclient.Client, platform *ocispecs.Platform, spec *dalec.Spec, targetKey string) (gwclient.Reference, *dalec.DockerImageSpec, error) { sOpt, err := frontend.SourceOptFromClient(ctx, client, nil) if err != nil { @@ -108,7 +132,7 @@ func handleWorker(ctx context.Context, client gwclient.Client) (*gwclient.Result pg := dalec.ProgressGroup("Handle windows worker") - st := distroConfig.Worker(sOpt, pg) + st := h.distro.Worker(sOpt, pg) def, err := st.Marshal(ctx) if err != nil { return nil, nil, err