Skip to content

Commit 47c6648

Browse files
committed
Adding Support for building Ubuntu24, eLx12 and AZL3 OS images targeting ARM64 architecture
1 parent 6a3d08f commit 47c6648

File tree

11 files changed

+326
-52
lines changed

11 files changed

+326
-52
lines changed

config/osv/edge-microvisor-toolkit/emt3/config.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,11 @@ x86_64:
66
arch: x86_64 # Target architecture
77
pkgType: rpm # Package management system
88
chrootenvConfigFile: chrootenvconfigs/chrootenv_x86_64.yml # Path to chrootenv config
9-
releaseVersion: "3.0" # Distribution release version
9+
releaseVersion: "3.0" # Distribution release version
10+
11+
aarch64:
12+
dist: emt3 # Distribution identifier
13+
arch: aarch64 # Target architecture
14+
pkgType: rpm # Package management system
15+
chrootenvConfigFile: chrootenvconfigs/chrootenv_aarch64.yml # Path to chrootenv config
16+
releaseVersion: "3.0" # Distribution release version

image-templates/ubuntu24-aarch64-minimal-raw.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ disk:
5656
type: xbootldr
5757
start: 101MiB
5858
end: 1024MiB
59-
fsType: vfat
59+
fsType: ext4
6060
mountPoint: /boot
6161
mountOptions: defaults
6262

internal/chroot/chrootenv.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ func CleanDebName(packageName string) string {
572572

573573
func (chrootEnv *ChrootEnv) AptInstallPackage(packageName, installRoot string, repoSrcList []string) error {
574574
packageName = CleanDebName(packageName)
575-
installCmd := fmt.Sprintf("apt-get install -y %s", packageName)
575+
installCmd := fmt.Sprintf("apt-get install -y --no-install-recommends %s", packageName)
576576

577577
if len(repoSrcList) > 0 {
578578
for _, repoSrc := range repoSrcList {
@@ -587,8 +587,11 @@ func (chrootEnv *ChrootEnv) AptInstallPackage(packageName, installRoot string, r
587587
"DEBCONF_NOWARNINGS=yes",
588588
}
589589

590-
if _, err := shell.ExecCmdWithStream(installCmd, true, installRoot, envVars); err != nil {
591-
return fmt.Errorf("failed to install package %s: %w", packageName, err)
590+
output, err := shell.ExecCmdWithStream(installCmd, true, installRoot, envVars)
591+
if err != nil {
592+
log.Errorf("Failed to install package %s: %v", packageName, err)
593+
log.Errorf("Full apt-get output for %s:\n%s", packageName, output)
594+
return fmt.Errorf("failed to install package %s: %w\napt output:\n%s", packageName, err, output)
592595
}
593596

594597
return nil

internal/chroot/deb/installer.go

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"runtime"
78
"strings"
89

910
"github.com/open-edge-platform/os-image-composer/internal/utils/logger"
@@ -19,12 +20,56 @@ type DebInstallerInterface interface {
1920
}
2021

2122
type DebInstaller struct {
23+
targetArch string
2224
}
2325

2426
func NewDebInstaller() *DebInstaller {
2527
return &DebInstaller{}
2628
}
2729

30+
func normalizeDebArch(targetArch string) (string, error) {
31+
switch targetArch {
32+
case "amd64", "x86_64":
33+
return "amd64", nil
34+
case "arm64", "aarch64":
35+
return "arm64", nil
36+
default:
37+
return "", fmt.Errorf("unsupported architecture: %s", targetArch)
38+
}
39+
}
40+
41+
func normalizeRuntimeArch(goArch string) (string, error) {
42+
switch goArch {
43+
case "amd64":
44+
return "amd64", nil
45+
case "arm64":
46+
return "arm64", nil
47+
default:
48+
return "", fmt.Errorf("unsupported host architecture: %s", goArch)
49+
}
50+
}
51+
52+
func (debInstaller *DebInstaller) validateCrossArchDeps(targetArch string) error {
53+
hostArch, err := normalizeRuntimeArch(runtime.GOARCH)
54+
if err != nil {
55+
return err
56+
}
57+
58+
if hostArch == targetArch {
59+
return nil
60+
}
61+
62+
hasArchTest, err := shell.IsCommandExist("arch-test", shell.HostPath)
63+
if err != nil {
64+
return fmt.Errorf("failed to check host dependency 'arch-test' for cross-architecture build (host=%s target=%s): %w", hostArch, targetArch, err)
65+
}
66+
if !hasArchTest {
67+
return fmt.Errorf("cross-architecture build requested (host=%s target=%s) but required host dependency 'arch-test' is missing; install it with: sudo apt-get install -y arch-test", hostArch, targetArch)
68+
}
69+
70+
return nil
71+
}
72+
2873
func (debInstaller *DebInstaller) cleanupOnSuccess(repoPath string, err *error) {
2974
if umountErr := mount.UmountPath(repoPath); umountErr != nil {
3075
log.Errorf("Failed to unmount debian local repository: %v", umountErr)
@@ -50,14 +95,12 @@ func (debInstaller *DebInstaller) UpdateLocalDebRepo(repoPath, targetArch string
5095
return fmt.Errorf("repository path cannot be empty")
5196
}
5297

53-
switch targetArch {
54-
case "amd64", "x86_64":
55-
targetArch = "amd64"
56-
case "arm64", "aarch64":
57-
targetArch = "arm64"
58-
default:
59-
return fmt.Errorf("unsupported architecture: %s", targetArch)
98+
normalizedArch, err := normalizeDebArch(targetArch)
99+
if err != nil {
100+
return err
60101
}
102+
targetArch = normalizedArch
103+
debInstaller.targetArch = normalizedArch
61104

62105
metaDataPath := filepath.Join(repoPath,
63106
fmt.Sprintf("dists/stable/main/binary-%s", targetArch), "Packages.gz")
@@ -92,6 +135,13 @@ func (debInstaller *DebInstaller) InstallDebPkg(targetOsConfigDir, chrootEnvPath
92135
return fmt.Errorf("invalid parameters: chrootEnvPath, chrootPkgCacheDir, and pkgsList cannot be empty")
93136
}
94137

138+
// Prefer the normalized architecture set by UpdateLocalDebRepo.
139+
// Fall back to amd64 for defensive compatibility in unit tests.
140+
debArch := debInstaller.targetArch
141+
if debArch == "" {
142+
debArch = "amd64"
143+
}
144+
95145
// from local.list
96146
repoPath := "/cdrom/cache-repo"
97147
pkgListStr := strings.Join(pkgsList, ",")
@@ -101,6 +151,12 @@ func (debInstaller *DebInstaller) InstallDebPkg(targetOsConfigDir, chrootEnvPath
101151
log.Errorf("Local repository config file does not exist: %s", localRepoConfigPath)
102152
return fmt.Errorf("local repository config file does not exist: %s", localRepoConfigPath)
103153
}
154+
suite := detectDebSuiteFromSourcesList(localRepoConfigPath)
155+
156+
if err := debInstaller.validateCrossArchDeps(debArch); err != nil {
157+
log.Errorf("Missing host dependencies for cross-architecture chroot build: %v", err)
158+
return err
159+
}
104160

105161
if err := mount.MountPath(chrootPkgCacheDir, repoPath, "--bind"); err != nil {
106162
log.Errorf("Failed to mount debian local repository: %v", err)
@@ -129,11 +185,12 @@ func (debInstaller *DebInstaller) InstallDebPkg(targetOsConfigDir, chrootEnvPath
129185
"--aptopt=Dpkg::Options::=--force-confdef "+
130186
"--aptopt=Dpkg::Options::=--force-confold "+
131187
"--aptopt=APT::Get::Assume-Yes=true "+
188+
"--architectures=%s "+
132189
"--hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount "+
133190
"--include=%s "+
134191
"--verbose --debug "+
135-
"-- bookworm %s %s",
136-
pkgListStr, chrootEnvPath, localRepoConfigPath)
192+
"-- %s %s %s",
193+
debArch, pkgListStr, suite, chrootEnvPath, localRepoConfigPath)
137194

138195
// Set environment variables to ensure non-interactive installation
139196
envVars := []string{
@@ -149,3 +206,40 @@ func (debInstaller *DebInstaller) InstallDebPkg(targetOsConfigDir, chrootEnvPath
149206

150207
return nil
151208
}
209+
210+
func detectDebSuiteFromSourcesList(sourcesListPath string) string {
211+
const defaultSuite = "stable"
212+
213+
content, err := os.ReadFile(sourcesListPath)
214+
if err != nil {
215+
log.Warnf("Failed to read local sources list %s, defaulting suite to %s: %v", sourcesListPath, defaultSuite, err)
216+
return defaultSuite
217+
}
218+
219+
for _, rawLine := range strings.Split(string(content), "\n") {
220+
line := strings.TrimSpace(rawLine)
221+
if line == "" || strings.HasPrefix(line, "#") {
222+
continue
223+
}
224+
225+
fields := strings.Fields(line)
226+
if len(fields) < 3 || fields[0] != "deb" {
227+
continue
228+
}
229+
230+
idx := 1
231+
if strings.HasPrefix(fields[idx], "[") {
232+
for idx < len(fields) && !strings.HasSuffix(fields[idx], "]") {
233+
idx++
234+
}
235+
idx++
236+
}
237+
238+
if idx+1 < len(fields) {
239+
return fields[idx+1]
240+
}
241+
}
242+
243+
log.Warnf("Could not determine suite from %s, defaulting to %s", sourcesListPath, defaultSuite)
244+
return defaultSuite
245+
}

internal/chroot/deb/installer_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"runtime"
78
"strings"
89
"testing"
910

@@ -419,3 +420,53 @@ func TestInstallDebPkg_RepoPathConstant(t *testing.T) {
419420
t.Logf("Got error (expected): %v", err)
420421
}
421422
}
423+
424+
func TestInstallDebPkg_CrossArchMissingArchTest(t *testing.T) {
425+
installer := deb.NewDebInstaller()
426+
tempDir := t.TempDir()
427+
428+
configDir := filepath.Join(tempDir, "chrootenvconfigs")
429+
if err := os.MkdirAll(configDir, 0700); err != nil {
430+
t.Fatalf("Failed to create config directory: %v", err)
431+
}
432+
433+
localListPath := filepath.Join(configDir, "local.list")
434+
if err := os.WriteFile(localListPath, []byte("deb file:///cdrom/cache-repo ./"), 0644); err != nil {
435+
t.Fatalf("Failed to create local.list file: %v", err)
436+
}
437+
438+
chrootPkgCacheDir := filepath.Join(tempDir, "cache")
439+
if err := os.MkdirAll(chrootPkgCacheDir, 0700); err != nil {
440+
t.Fatalf("Failed to create cache directory: %v", err)
441+
}
442+
443+
// Pick a target architecture that is different from the runtime architecture.
444+
targetArch := "arm64"
445+
if runtime.GOARCH == "arm64" {
446+
targetArch = "amd64"
447+
}
448+
449+
originalExecutor := shell.Default
450+
defer func() { shell.Default = originalExecutor }()
451+
mockExpectedOutput := []shell.MockCommand{
452+
{Pattern: "dpkg-scanpackages", Output: "override-test\n", Error: nil},
453+
{Pattern: "command -v arch-test", Output: "", Error: nil},
454+
}
455+
shell.Default = shell.NewMockExecutor(mockExpectedOutput)
456+
457+
if err := installer.UpdateLocalDebRepo(tempDir, targetArch, false); err != nil {
458+
t.Fatalf("Failed to set target architecture via UpdateLocalDebRepo: %v", err)
459+
}
460+
461+
err := installer.InstallDebPkg(tempDir, filepath.Join(tempDir, "chroot"), chrootPkgCacheDir, []string{"test-package"})
462+
if err == nil {
463+
t.Fatal("Expected cross-architecture preflight error for missing arch-test")
464+
}
465+
466+
if !strings.Contains(err.Error(), "arch-test") {
467+
t.Fatalf("Expected error to mention arch-test, got: %v", err)
468+
}
469+
if !strings.Contains(err.Error(), "cross-architecture") {
470+
t.Fatalf("Expected cross-architecture error context, got: %v", err)
471+
}
472+
}

internal/chroot/rpm/installer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (rpmInstaller *RpmInstaller) InstallRpmPkg(targetOs, chrootEnvPath, chrootP
8383
return fmt.Errorf("package %s does not exist in cache directory: %w", pkg, err)
8484
}
8585
log.Infof("Installing package %s in chroot environment", pkg)
86-
cmdStr := fmt.Sprintf("rpm -i -v --nodeps --force --root %s --define '_dbpath /var/lib/rpm' %s",
86+
cmdStr := fmt.Sprintf("rpm -i -v --nodeps --force --ignorearch --root %s --define '_dbpath /var/lib/rpm' %s",
8787
chrootEnvPath, pkgPath)
8888
var output string
8989
output, err = shell.ExecCmd(cmdStr, true, shell.HostPath, nil)
@@ -157,7 +157,7 @@ func (rpmInstaller *RpmInstaller) updateRpmDB(chrootEnvBuildPath, chrootPkgCache
157157

158158
for _, rpm := range rpmList {
159159
rpmChrootPath := filepath.Join("/packages", rpm)
160-
cmdStr := "rpm -i -v --nodeps --force --justdb " + rpmChrootPath
160+
cmdStr := "rpm -i -v --nodeps --force --ignorearch --justdb " + rpmChrootPath
161161
if _, err := shell.ExecCmdWithStream(cmdStr, true, chrootEnvBuildPath, nil); err != nil {
162162
log.Errorf("Failed to update RPM Database for %s in chroot environment: %v", rpm, err)
163163
return fmt.Errorf("failed to update RPM Database for %s in chroot environment: %w", rpm, err)

0 commit comments

Comments
 (0)