Skip to content

Commit 51eb2e3

Browse files
authored
feat: enable cross-arch image building (#457)
1 parent 6f84fbe commit 51eb2e3

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

internal/app/azldev/cmds/image/build.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ type ImageBuildOptions struct {
3131
// NoRemoteRepoGpgCheck disables GPG checking for all remote repositories
3232
// specified via RemoteRepoPaths.
3333
NoRemoteRepoGpgCheck bool
34+
35+
// TargetArch specifies the target architecture to build for (e.g., "x86_64" or "aarch64").
36+
// If left empty, the host architecture will be used.
37+
TargetArch ImageArch
3438
}
3539

3640
// ImageBuildResult summarizes the results of building an image.
@@ -45,6 +49,45 @@ type ImageBuildResult struct {
4549
ArtifactPaths []string `json:"artifactPaths" table:"Artifact Paths"`
4650
}
4751

52+
// ImageArch represents the architecture of an image, such as "x86_64" or "aarch64".
53+
type ImageArch string
54+
55+
const (
56+
// ImageArchDefault represents the default architecture (i.e., the host architecture).
57+
ImageArchDefault ImageArch = ""
58+
59+
// ImageArchX86_64 represents the x86_64 architecture.
60+
ImageArchX86_64 ImageArch = "x86_64"
61+
62+
// ImageArchAarch64 represents the aarch64 (a.k.a. arm64) architecture.
63+
ImageArchAarch64 ImageArch = "aarch64"
64+
)
65+
66+
func (f *ImageArch) String() string {
67+
return string(*f)
68+
}
69+
70+
// Parses the architecture from a string; used by command-line parser.
71+
func (f *ImageArch) Set(value string) error {
72+
switch value {
73+
case "x86_64":
74+
*f = ImageArchX86_64
75+
case "aarch64":
76+
*f = ImageArchAarch64
77+
case "":
78+
*f = ImageArchDefault
79+
default:
80+
return fmt.Errorf("unsupported architecture: %s", value)
81+
}
82+
83+
return nil
84+
}
85+
86+
// Returns a descriptive string used in command-line help.
87+
func (f *ImageArch) Type() string {
88+
return "arch"
89+
}
90+
4891
func buildOnAppInit(_ *azldev.App, parentCmd *cobra.Command) {
4992
parentCmd.AddCommand(NewImageBuildCmd())
5093
}
@@ -71,6 +114,8 @@ This command invokes kiwi-ng via sudo to build the image.`,
71114
ValidArgsFunction: generateImageNameCompletions,
72115
}
73116

117+
cmd.Flags().Var(&options.TargetArch, "arch",
118+
"Target architecture to build for (e.g., x86_64 or aarch64). Defaults to the host architecture if not specified)")
74119
cmd.Flags().StringArrayVar(&options.LocalRepoPaths, "local-repo", []string{},
75120
"Paths to local repositories to include during build (can be specified multiple times)")
76121
cmd.Flags().StringArrayVar(&options.RemoteRepoPaths, "remote-repo", []string{},
@@ -193,6 +238,10 @@ func createKiwiRunner(
193238
runner.WithProfile(imageConfig.Definition.Profile)
194239
}
195240

241+
if options.TargetArch != "" {
242+
runner.WithTargetArch(string(options.TargetArch))
243+
}
244+
196245
for _, repoURI := range options.RemoteRepoPaths {
197246
if err := runner.AddRemoteRepo(repoURI); err != nil {
198247
return nil, fmt.Errorf("invalid remote repository:\n%w", err)

internal/utils/kiwi/kiwi.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ type Runner struct {
7070

7171
// profile is the optional kiwi profile to use when building the image.
7272
profile string
73+
74+
// targetArch is the optional target architecture to build for (e.g., "x86_64" or "aarch64"). If left
75+
// empty, the host architecture will be used.
76+
targetArch string
7377
}
7478

7579
// NewRunner constructs a new [Runner] that can be used to invoke kiwi-ng.
@@ -82,6 +86,7 @@ func NewRunner(ctx opctx.Ctx, descriptionDir string) *Runner {
8286
descriptionDir: descriptionDir,
8387
remoteRepoGPGCheck: true, // GPG checking enabled by default
8488
profile: "",
89+
targetArch: "",
8590
}
8691
}
8792

@@ -97,6 +102,7 @@ func (r *Runner) Clone() *Runner {
97102
remoteRepoPaths: deep.MustCopy(r.remoteRepoPaths),
98103
remoteRepoGPGCheck: r.remoteRepoGPGCheck,
99104
profile: r.profile,
105+
targetArch: r.targetArch,
100106
}
101107
}
102108

@@ -122,6 +128,14 @@ func (r *Runner) WithProfile(profile string) *Runner {
122128
return r
123129
}
124130

131+
// WithTargetArch sets the target architecture to build for (e.g., "x86_64" or "aarch64").
132+
// If left empty, the host architecture will be used.
133+
func (r *Runner) WithTargetArch(arch string) *Runner {
134+
r.targetArch = arch
135+
136+
return r
137+
}
138+
125139
// RemoteRepoGPGCheck returns whether GPG checking is enabled for remote repositories.
126140
func (r *Runner) RemoteRepoGPGCheck() bool {
127141
return r.remoteRepoGPGCheck
@@ -185,6 +199,11 @@ func (r *Runner) Profile() string {
185199
return r.profile
186200
}
187201

202+
// TargetArch retrieves the target architecture configured for this [Runner].
203+
func (r *Runner) TargetArch() string {
204+
return r.targetArch
205+
}
206+
188207
// Build invokes kiwi-ng via sudo to build an image.
189208
func (r *Runner) Build(ctx context.Context) error {
190209
if r.descriptionDir == "" {
@@ -209,6 +228,10 @@ func (r *Runner) Build(ctx context.Context) error {
209228
"--loglevel", logLevel,
210229
}
211230

231+
if r.targetArch != "" {
232+
kiwiArgs = append(kiwiArgs, "--target-arch", r.targetArch)
233+
}
234+
212235
if r.profile != "" {
213236
kiwiArgs = append(kiwiArgs, "--profile", r.profile)
214237
}

0 commit comments

Comments
 (0)