Skip to content

Commit

Permalink
azlinux: Use native dnf to install packages
Browse files Browse the repository at this point in the history
When cross compiling, use dnf from the native platform to install
packages into the target arch rootfs.
This helps speed up package install a bit since dnf won't need to run
under emulation.

Signed-off-by: Brian Goff <[email protected]>
  • Loading branch information
cpuguy83 committed Sep 4, 2024
1 parent c4c36e9 commit 6c9e983
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 69 deletions.
12 changes: 4 additions & 8 deletions frontend/azlinux/azlinux3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package azlinux
import (
"context"
"encoding/json"
"path/filepath"

"github.com/Azure/dalec"
"github.com/moby/buildkit/client/llb"
Expand All @@ -13,8 +12,7 @@ import (
)

const (
AzLinux3TargetKey = "azlinux3"
tdnfCacheNameAzlinux3 = "azlinux3-tdnf-cache"
AzLinux3TargetKey = "azlinux3"

// Azlinux3Ref is the image ref used for the base worker image
Azlinux3Ref = "mcr.microsoft.com/azurelinux/base/core:3.0"
Expand Down Expand Up @@ -49,6 +47,8 @@ func (w azlinux3) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S

img := llb.Image(Azlinux3Ref, llb.WithMetaResolver(sOpt.Resolver), dalec.WithConstraints(opts...))
return img.Run(
w.Install([]string{"dnf"}, installWithConstraints(opts), tdnfOnly),
).Run(
w.Install([]string{"rpm-build", "mariner-rpm-macros", "build-essential", "ca-certificates"}, installWithConstraints(opts)),
dalec.WithConstraints(opts...),
).Root(), nil
Expand All @@ -57,7 +57,7 @@ func (w azlinux3) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S
func (w azlinux3) Install(pkgs []string, opts ...installOpt) llb.RunOption {
var cfg installConfig
setInstallOptions(&cfg, opts)
return dalec.WithRunOptions(tdnfInstall(&cfg, "3.0", pkgs), w.tdnfCacheMount(cfg.root))
return dalec.WithRunOptions(dnfInstall(&cfg, "3.0", pkgs, AzLinux3TargetKey))
}

func (w azlinux3) BasePackages() []string {
Expand Down Expand Up @@ -91,7 +91,3 @@ func (azlinux3) WorkerImageConfig(ctx context.Context, resolver llb.ImageMetaRes

return &cfg, nil
}

func (azlinux3) tdnfCacheMount(root string) llb.RunOption {
return llb.AddMount(filepath.Join(root, tdnfCacheDir), llb.Scratch(), llb.AsPersistentCacheDir(tdnfCacheNameAzlinux3, llb.CacheMountLocked))
}
100 changes: 56 additions & 44 deletions frontend/azlinux/handle_rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,31 @@ func handleRPM(w worker) gwclient.BuildFunc {
}
}

type installFunc func(dalec.SourceOpts) (llb.RunOption, error)
func platformFuzzyMatches(p *ocispecs.Platform) bool {
if p == nil {
return true
}

// Note, this is intentionally not doing a strict match here
// (e.g. [platforms.OnlyStrict])
// This is used to see if we can get some optimizations when building for a
// non-native platformm and in most cases the [platforms.Only] vector handles
// things like building armv7 on an arm64 machine, which should be able to run
// natively.
return platforms.Only(platforms.DefaultSpec()).Match(*p)
}

func installBuildDeps(w worker, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, platform *ocispecs.Platform, opts ...llb.ConstraintsOpt) (llb.StateOption, error) {
deps := spec.GetBuildDeps(targetKey)
if len(deps) == 0 {
return func(in llb.State) llb.State { return in }, nil
}

opts = append(opts, dalec.ProgressGroup("Install build deps"))

// Creates and installs an rpm meta-package that requires the passed in deps as runtime-dependencies
func installBuildDepsPackage(target string, packageName string, w worker, sOpt dalec.SourceOpts, deps map[string]dalec.PackageConstraints, platform *ocispecs.Platform, installOpts ...installOpt) installFunc {
// depsOnly is a simple dalec spec that only includes build dependencies and their constraints
depsOnly := dalec.Spec{
Name: fmt.Sprintf("%s-build-dependencies", packageName),
Name: spec.Name + "-build-dependencies",
Description: "Provides build dependencies for mariner2 and azlinux3",
Version: "1.0",
License: "Apache 2.0",
Expand All @@ -71,46 +89,42 @@ func installBuildDepsPackage(target string, packageName string, w worker, sOpt d
},
}

return func(Opt dalec.SourceOpts) (llb.RunOption, error) {
pg := dalec.ProgressGroup("Building container for build dependencies")
// create an RPM with just the build dependencies, using our same base worker
rpmDir, err := createRPM(w, sOpt, &depsOnly, targetKey, platform, opts...)
if err != nil {
return nil, err
}

rpmMountDir := "/tmp/rpms"
pkg := []string{"/tmp/rpms/*/*.rpm"}

// create an RPM with just the build dependencies, using our same base worker
rpmDir, err := createRPM(w, sOpt, &depsOnly, target, platform, pg)
if !platformFuzzyMatches(platform) {
base, err := w.Base(sOpt, opts...)
if err != nil {
return nil, err
}

var opts []llb.ConstraintsOpt
opts = append(opts, dalec.ProgressGroup("Install build deps"))

rpmMountDir := "/tmp/rpms"

installOpts = append([]installOpt{
noGPGCheck,
withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))),
installWithConstraints(opts),
}, installOpts...)

// install the built RPMs into the worker itself
return w.Install([]string{"/tmp/rpms/*/*.rpm"}, installOpts...), nil
}
}

func installBuildDeps(w worker, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, platform *ocispecs.Platform, opts ...llb.ConstraintsOpt) (llb.StateOption, error) {
deps := spec.GetBuildDeps(targetKey)
if len(deps) == 0 {
return func(in llb.State) llb.State { return in }, nil
}

opts = append(opts, dalec.ProgressGroup("Install build deps"))

installOpt, err := installBuildDepsPackage(targetKey, spec.Name, w, sOpt, deps, platform, installWithConstraints(opts))(sOpt)
if err != nil {
return nil, err
return func(in llb.State) llb.State {
return base.Run(
w.Install(
pkg,
withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))),
atRoot("/tmp/rootfs"),
withPlatform(platform),
),
).AddMount("/tmp/rootfs", in)
}, nil
}

return func(in llb.State) llb.State {
return in.Run(installOpt, dalec.WithConstraints(opts...)).Root()
return in.Run(
w.Install(
[]string{"/tmp/rpms/*/*.rpm"},
withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))),
installWithConstraints(opts),
),
dalec.WithConstraints(opts...),
).Root()
}, nil
}

Expand Down Expand Up @@ -205,15 +219,13 @@ func createRPM(w worker, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey stri
}

var runOpts []llb.RunOption
if platform != nil {
if platforms.Only(platforms.DefaultSpec()).Match(*platform) && hasGolangBuildDep(spec, targetKey) {
native, err := rpmWorker(w, sOpt, spec, targetKey, nil, opts...)
if err != nil {
return llb.Scratch(), err
}

runOpts = append(runOpts, nativeGoMount(native, platform))
if !platformFuzzyMatches(platform) && hasGolangBuildDep(spec, targetKey) {
native, err := rpmWorker(w, sOpt, spec, targetKey, nil, opts...)
if err != nil {
return llb.Scratch(), err
}

runOpts = append(runOpts, nativeGoMount(native, platform))
}

specPath := filepath.Join("SPECS", spec.Name, spec.Name+".spec")
Expand Down
4 changes: 0 additions & 4 deletions frontend/azlinux/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import (
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

const (
tdnfCacheDir = "/var/cache/tdnf"
)

type worker interface {
Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, error)
Install(pkgs []string, opts ...installOpt) llb.RunOption
Expand Down
52 changes: 47 additions & 5 deletions frontend/azlinux/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

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

type installConfig struct {
Expand All @@ -23,7 +24,14 @@ type installConfig struct {
// Additional mounts to add to the tdnf install command (useful if installing RPMS which are mounted to a local directory)
mounts []llb.RunOption

// Instructs the installer to install packages for the specified platform
platform *ocispecs.Platform

constraints []llb.ConstraintsOpt

// This forces the use of tdnf
// Note this will almost certainly not work when platform is set.
tdnfOnly bool
}

type installOpt func(*installConfig)
Expand All @@ -32,6 +40,10 @@ func noGPGCheck(cfg *installConfig) {
cfg.noGPGCheck = true
}

func tdnfOnly(cfg *installConfig) {
cfg.tdnfOnly = true
}

func withMounts(opts ...llb.RunOption) installOpt {
return func(cfg *installConfig) {
cfg.mounts = append(cfg.mounts, opts...)
Expand All @@ -48,13 +60,19 @@ func atRoot(root string) installOpt {
}
}

func withPlatform(p *ocispecs.Platform) installOpt {
return func(cfg *installConfig) {
cfg.platform = p
}
}

func installWithConstraints(opts []llb.ConstraintsOpt) installOpt {
return func(cfg *installConfig) {
cfg.constraints = opts
}
}

func tdnfInstallFlags(cfg *installConfig) string {
func dnfInstallFlags(cfg *installConfig) string {
var cmdOpts string

if cfg.noGPGCheck {
Expand All @@ -63,12 +81,30 @@ func tdnfInstallFlags(cfg *installConfig) string {

if cfg.root != "" {
cmdOpts += " --installroot=" + cfg.root
cmdOpts += " --setopt=reposdir=/etc/yum.repos.d"
cmdOpts += " --setopt reposdir=/etc/yum.repos.d"
}

if cfg.platform != nil {
// cmdOpts += " --ignorearch=true"
cmdOpts += " --forcearch=" + ociArchToOS(cfg.platform)
}

return cmdOpts
}

func ociArchToOS(p *ocispecs.Platform) string {
switch p.Architecture {
case "amd64":
return "x86_64"
case "arm64":
return "aarch64"
// azlinux only supports amd64 and arm64
// We shouldn't need any other arches.
default:
return p.Architecture
}
}

func setInstallOptions(cfg *installConfig, opts []installOpt) {
for _, o := range opts {
o(cfg)
Expand Down Expand Up @@ -111,9 +147,14 @@ rm -rf `+rpmdbDir+`

const manifestSh = "manifest.sh"

func tdnfInstall(cfg *installConfig, relVer string, pkgs []string) llb.RunOption {
cmdFlags := tdnfInstallFlags(cfg)
cmdArgs := fmt.Sprintf("set -ex; tdnf install -y --refresh --releasever=%s %s %s", relVer, cmdFlags, strings.Join(pkgs, " "))
func dnfInstall(cfg *installConfig, relVer string, pkgs []string, cachePrefix string) llb.RunOption {
cmdFlags := dnfInstallFlags(cfg)

cmd := "dnf"
if cfg.tdnfOnly {
cmd = "tdnf"
}
cmdArgs := fmt.Sprintf("set -ex; %s install -y --refresh --releasever=%s %s %s", cmd, relVer, cmdFlags, strings.Join(pkgs, " "))

var runOpts []llb.RunOption

Expand All @@ -128,6 +169,7 @@ func tdnfInstall(cfg *installConfig, relVer string, pkgs []string) llb.RunOption

runOpts = append(runOpts, dalec.ShArgs(cmdArgs))
runOpts = append(runOpts, cfg.mounts...)
runOpts = append(runOpts, llb.AddMount("/var/cache/"+cmd, llb.Scratch(), llb.AsPersistentCacheDir(cachePrefix+"-"+cmd+"-"+"cache", llb.CacheMountLocked)))

return dalec.WithRunOptions(runOpts...)
}
12 changes: 4 additions & 8 deletions frontend/azlinux/mariner2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package azlinux
import (
"context"
"encoding/json"
"path/filepath"

"github.com/Azure/dalec"
"github.com/moby/buildkit/client/llb"
Expand All @@ -13,8 +12,7 @@ import (
)

const (
Mariner2TargetKey = "mariner2"
tdnfCacheNameMariner2 = "mariner2-tdnf-cache"
Mariner2TargetKey = "mariner2"

Mariner2Ref = "mcr.microsoft.com/cbl-mariner/base/core:2.0"
Mariner2WorkerContextName = "dalec-mariner2-worker"
Expand Down Expand Up @@ -46,6 +44,8 @@ func (w mariner2) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S
}

return base.Run(
w.Install([]string{"dnf"}, installWithConstraints(opts), tdnfOnly),
).Run(
w.Install([]string{"rpm-build", "mariner-rpm-macros", "build-essential", "ca-certificates"}, installWithConstraints(opts)),
dalec.WithConstraints(opts...),
).Root(), nil
Expand All @@ -54,7 +54,7 @@ func (w mariner2) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S
func (w mariner2) Install(pkgs []string, opts ...installOpt) llb.RunOption {
var cfg installConfig
setInstallOptions(&cfg, opts)
return dalec.WithRunOptions(tdnfInstall(&cfg, "2.0", pkgs), w.tdnfCacheMount(cfg.root))
return dalec.WithRunOptions(dnfInstall(&cfg, "2.0", pkgs, Mariner2TargetKey))
}

func (w mariner2) BasePackages() []string {
Expand Down Expand Up @@ -90,7 +90,3 @@ func (mariner2) WorkerImageConfig(ctx context.Context, resolver llb.ImageMetaRes

return &cfg, nil
}

func (mariner2) tdnfCacheMount(root string) llb.RunOption {
return llb.AddMount(filepath.Join(root, tdnfCacheDir), llb.Scratch(), llb.AsPersistentCacheDir(tdnfCacheNameMariner2, llb.CacheMountLocked))
}

0 comments on commit 6c9e983

Please sign in to comment.