Skip to content

Commit 71f2e40

Browse files
kartikjoshi21cpuguy83
authored andcommitted
Enable faster cross-arch RPM worker builds (dnf native, tdnf fallback)
This PR speeds up cross-arch RPM worker image builds by running the package manager on the native BuildKit executor while installing packages into a mounted target rootfs via --installroot. For dnf-based distros we use dnf --forcearch=<target>; for tdnf-based distros we prefer dnf (bootstrapped when needed), with a safe fallback to running tdnf inside the target rootfs under QEMU/chroot when cross-install is not possible. Signed-off-by: Kartik Joshi <karikjoshi21@gmail.com>
1 parent b6cfea9 commit 71f2e40

9 files changed

Lines changed: 244 additions & 26 deletions

File tree

targets/linux/rpm/almalinux/v8.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var ConfigV8 = &distro.Config{
2020
ContextRef: v8WorkerContextName,
2121

2222
CacheName: dnfCacheNameV8,
23-
CacheDir: "/var/cache/dnf",
23+
CacheDir: []string{"/var/cache/dnf"},
2424
// Alma's repo configs do not include the $basearch variable in the mirrorlist URL
2525
// This means that the cache key that dnf computes for /var/cache/dnf/<repoid>-<hash>
2626
// is the same across x86_64 and aarch64, which leads to incorrect repo metadata

targets/linux/rpm/almalinux/v9.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var ConfigV9 = &distro.Config{
2020
ContextRef: v9WorkerContextName,
2121

2222
CacheName: dnfCacheNameV9,
23-
CacheDir: "/var/cache/dnf",
23+
CacheDir: []string{"/var/cache/dnf"},
2424
// Alma's repo configs do not include the $basearch variable in the mirrorlist URL
2525
// This means that the cache key that dnf computes for /var/cache/dnf/<repoid>-<hash>
2626
// is the same across x86_64 and aarch64, which leads to incorrect repo metadata

targets/linux/rpm/azlinux/azlinux3.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ var Azlinux3Config = &distro.Config{
1919
ImageRef: Azlinux3Ref,
2020
ContextRef: Azlinux3WorkerContextName,
2121

22-
CacheName: tdnfCacheNameAzlinux3,
23-
CacheDir: "/var/cache/tdnf",
22+
CacheName: tdnfCacheNameAzlinux3,
23+
CacheDir: []string{"/var/cache/tdnf", "/var/cache/dnf"},
24+
CacheAddPlatform: true,
2425

2526
ReleaseVer: "3.0",
26-
BuilderPackages: builderPackages,
27+
BuilderPackages: append(builderPackages, "dnf"),
2728
BasePackages: basePackages(AzLinux3TargetKey),
2829
RepoPlatformConfig: &defaultAzlinuxRepoPlatform,
2930
InstallFunc: distro.TdnfInstall,

targets/linux/rpm/azlinux/mariner2.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ var Mariner2Config = &distro.Config{
1717
ImageRef: "mcr.microsoft.com/cbl-mariner/base/core:2.0",
1818
ContextRef: Mariner2WorkerContextName,
1919

20-
CacheName: tdnfCacheNameMariner2,
21-
CacheDir: "/var/cache/tdnf",
20+
CacheName: tdnfCacheNameMariner2,
21+
CacheDir: []string{"/var/cache/tdnf", "/var/cache/dnf"},
22+
CacheAddPlatform: true,
2223

2324
ReleaseVer: "2.0",
24-
BuilderPackages: builderPackages,
25+
BuilderPackages: append(builderPackages, "dnf"),
2526
BasePackages: basePackages(Mariner2TargetKey),
2627
RepoPlatformConfig: &defaultAzlinuxRepoPlatform,
2728
InstallFunc: distro.TdnfInstall,

targets/linux/rpm/distro/distro.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@ type Config struct {
3737
CacheName string
3838
// Whether to namespace the cache key by platform
3939
// Not all distros need this, hence why it is configurable.
40-
// The cache key is only added when the build platform and target platform differ.
4140
CacheAddPlatform bool
4241

43-
// e.g. /var/cache/tdnf or /var/cache/dnf
44-
CacheDir string
42+
// Cache directories to mount (e.g. ["/var/cache/dnf"] or ["/var/cache/tdnf", "/var/cache/dnf"])
43+
CacheDir []string
4544

4645
// erofs-utils 1.7+ is required for tar support.
4746
SysextSupported bool
@@ -61,7 +60,27 @@ func (cfg *Config) PackageCacheMount(root string) llb.RunOption {
6160
}
6261
cacheKey += "-" + platforms.Format(*p)
6362
}
64-
llb.AddMount(filepath.Join(root, cfg.CacheDir), llb.Scratch(), llb.AsPersistentCacheDir(cacheKey, llb.CacheMountLocked)).SetRunOption(ei)
63+
64+
if len(cfg.CacheDir) == 0 {
65+
return
66+
}
67+
68+
// Mount each cache dir. If there are multiple, suffix key with the dir base.
69+
for _, d := range cfg.CacheDir {
70+
if d == "" {
71+
continue
72+
}
73+
k := cacheKey
74+
if len(cfg.CacheDir) > 1 {
75+
k = cacheKey + "-" + filepath.Base(d)
76+
}
77+
llb.AddMount(
78+
joinUnderRoot(root, d),
79+
llb.Scratch(),
80+
llb.AsPersistentCacheDir(k, llb.CacheMountLocked),
81+
).SetRunOption(ei)
82+
}
83+
6584
})
6685
}
6786

targets/linux/rpm/distro/dnf_install.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package distro
22

33
import (
44
"fmt"
5+
"path"
56
"path/filepath"
67
"strings"
78

89
"github.com/moby/buildkit/client/llb"
10+
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
911
"github.com/project-dalec/dalec"
1012
"github.com/project-dalec/dalec/packaging/linux/rpm"
1113
)
@@ -40,10 +42,21 @@ type dnfInstallConfig struct {
4042

4143
// When true, don't omit docs from the installed RPMs.
4244
includeDocs bool
45+
46+
forceArch string
4347
}
4448

4549
type DnfInstallOpt func(*dnfInstallConfig)
4650

51+
// joinUnderRoot joins a rootfs path with an absolute container path.
52+
// We must not use filepath.Join(root, "/abs") because that drops `root`.
53+
func joinUnderRoot(root, abs string) string {
54+
if root == "" {
55+
return abs
56+
}
57+
return path.Join(root, strings.TrimPrefix(abs, "/"))
58+
}
59+
4760
// see comment in tdnfInstall for why this additional option is needed
4861
func DnfImportKeys(keys []string) DnfInstallOpt {
4962
return func(cfg *dnfInstallConfig) {
@@ -63,6 +76,12 @@ func DnfAtRoot(root string) DnfInstallOpt {
6376
}
6477
}
6578

79+
func DnfForceArch(arch string) DnfInstallOpt {
80+
return func(cfg *dnfInstallConfig) {
81+
cfg.forceArch = arch
82+
}
83+
}
84+
6685
func DnfDownloadAllDeps(dest string) DnfInstallOpt {
6786
return func(cfg *dnfInstallConfig) {
6887
cfg.downloadOnly = true
@@ -145,23 +164,33 @@ func dnfCommand(cfg *dnfInstallConfig, releaseVer string, exe string, dnfSubCmd
145164

146165
cacheDir := "/var/cache/" + exe
147166
if cfg.root != "" {
148-
cacheDir = filepath.Join(cfg.root, cacheDir)
167+
cacheDir = joinUnderRoot(cfg.root, cacheDir)
149168
}
150169
installFlags := dnfInstallFlags(cfg)
151170
installFlags += " -y --setopt varsdir=/etc/dnf/vars --releasever=" + releaseVer + " "
171+
forceArch := cfg.forceArch
152172
installScriptDt := `#!/usr/bin/env bash
153173
set -eux -o pipefail
154174
155175
import_keys_path="` + importKeysPath + `"
156176
cmd="` + exe + `"
157177
install_flags="` + installFlags + `"
178+
force_arch="` + forceArch + `"
158179
dnf_sub_cmd="` + strings.Join(dnfSubCmd, " ") + `"
159180
cache_dir="` + cacheDir + `"
160181
161182
if [ -x "$import_keys_path" ]; then
162183
"$import_keys_path"
163184
fi
164185
186+
if [ -n "$force_arch" ]; then
187+
if [ "$cmd" = "tdnf" ]; then
188+
echo "tdnf does not support --forcearch; cross-arch installs must use dnf" >&2
189+
exit 70
190+
fi
191+
install_flags="$install_flags --forcearch=$force_arch"
192+
fi
193+
165194
$cmd $dnf_sub_cmd $install_flags "${@}"
166195
`
167196
var runOpts []llb.RunOption
@@ -196,6 +225,53 @@ $cmd $dnf_sub_cmd $install_flags "${@}"
196225
return dalec.WithRunOptions(runOpts...)
197226
}
198227

228+
func (cfg *Config) InstallIntoRoot(rootfsPath string, pkgs []string, targetArch string, buildPlat ocispecs.Platform) llb.RunOption {
229+
return dalec.RunOptFunc(func(ei *llb.ExecInfo) {
230+
// Ensure the package manager runs on the build/executor platform (native),
231+
// while installing into the mounted target rootfs via --installroot.
232+
bp := buildPlat
233+
ei.Constraints.Platform = &bp
234+
235+
installOpts := []DnfInstallOpt{
236+
DnfAtRoot(rootfsPath),
237+
DnfForceArch(targetArch),
238+
DnfInstallWithConstraints([]llb.ConstraintsOpt{dalec.WithConstraint(&ei.Constraints)}),
239+
}
240+
241+
var installCfg dnfInstallConfig
242+
dnfInstallOptions(&installCfg, installOpts)
243+
244+
cacheKey := cfg.CacheName
245+
if cfg.CacheAddPlatform {
246+
cacheKey += "-" + targetArch
247+
}
248+
// Cross-arch installs always use dnf --forcearch --installroot
249+
runOpts := []llb.RunOption{
250+
DnfInstall(&installCfg, cfg.ReleaseVer, pkgs),
251+
}
252+
253+
// Mount package manager caches under the target rootfs (may include multiple dirs).
254+
for _, d := range cfg.CacheDir {
255+
if d == "" {
256+
continue
257+
}
258+
k := cacheKey
259+
if len(cfg.CacheDir) > 1 {
260+
k = cacheKey + "-" + filepath.Base(d)
261+
}
262+
runOpts = append(runOpts,
263+
llb.AddMount(
264+
joinUnderRoot(rootfsPath, d),
265+
llb.Scratch(),
266+
llb.AsPersistentCacheDir(k, llb.CacheMountLocked),
267+
),
268+
)
269+
}
270+
271+
dalec.WithRunOptions(runOpts...).SetRunOption(ei)
272+
})
273+
}
274+
199275
func DnfInstall(cfg *dnfInstallConfig, releaseVer string, pkgs []string) llb.RunOption {
200276
return dnfCommand(cfg, releaseVer, "dnf", append([]string{"install"}, pkgs...), nil)
201277
}

0 commit comments

Comments
 (0)