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
2122type DebInstaller struct {
23+ targetArch string
2224}
2325
2426func 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+
2873func (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+ }
0 commit comments