From 8e6a6673f529bcff3d9bc49c472d329bf2713a74 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 5 Mar 2025 18:20:19 +0100 Subject: [PATCH] main: auto-cross build for foreign architectures --- cmd/image-builder/main.go | 24 +++++++++------------ cmd/image-builder/main_test.go | 39 ++++++++++++++++++++++------------ cmd/image-builder/manifest.go | 8 ++++--- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index ae98d34..8beda22 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -14,7 +14,6 @@ import ( "github.com/osbuild/bootc-image-builder/bib/pkg/progress" "github.com/osbuild/images/pkg/arch" - "github.com/osbuild/images/pkg/experimentalflags" "github.com/osbuild/images/pkg/imagefilter" "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/ostree" @@ -82,7 +81,7 @@ func ostreeImageOptions(cmd *cobra.Command) (*ostree.ImageOptions, error) { }, nil } -func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []string, w io.Writer, archChecker func(string) error) (*imagefilter.Result, error) { +func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []string, w io.Writer, needBootstrap func(string) bool) (*imagefilter.Result, error) { dataDir, err := cmd.Flags().GetString("data-dir") if err != nil { return nil, err @@ -157,13 +156,6 @@ func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []st if err != nil { return nil, err } - // assume that users with the "bootstrap" flag want cross building - // and skip arch checks then - if archChecker != nil && experimentalflags.String("bootstrap") == "" { - if err := archChecker(img.Arch.Name()); err != nil { - return nil, err - } - } if len(img.ImgType.Exports()) > 1 { return nil, fmt.Errorf("image %q has multiple exports: this is current unsupport: please report this as a bug", basenameFor(img, "")) } @@ -178,6 +170,13 @@ func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []st ForceRepos: forceRepos, } + if needBootstrap != nil { + opts.UseBootstrapContainer = needBootstrap(img.Arch.Name()) + } + if opts.UseBootstrapContainer { + fmt.Fprintf(os.Stderr, "WARNING: using experimental cross-architecture building to build %q\n", img.Arch.Name()) + } + err = generateManifest(dataDir, extraRepos, img, w, opts) return img, err } @@ -244,11 +243,8 @@ func cmdBuild(cmd *cobra.Command, args []string) error { var mf bytes.Buffer // XXX: check env here, i.e. if user is root and osbuild is installed - res, err := cmdManifestWrapper(pbar, cmd, args, &mf, func(archStr string) error { - if archStr != arch.Current().String() { - return fmt.Errorf("cannot build for arch %q from %q", archStr, arch.Current().String()) - } - return nil + res, err := cmdManifestWrapper(pbar, cmd, args, &mf, func(archStr string) bool { + return archStr != arch.Current().String() }) if err != nil { return err diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index 5215b9f..bcb7ac6 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -814,7 +814,7 @@ func TestManifestOverrideRepo(t *testing.T) { // DNF error occurred: RepoError: There was a problem reading a repository: Failed to download metadata for repo '9828718901ab404ac1b600157aec1a8b19f4b2139e7756f347fb0ecc06c92929' [forced repo#0 xxx.abcdefgh-no-such-host.com/repo: http://xxx.abcdefgh-no-such-host.com/repo]: Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried` } -func TestBuildCrossArchCheckSkippedOnExperimentalBuildroot(t *testing.T) { +func TestBuildCrossArchSmoke(t *testing.T) { if testing.Short() { t.Skip("manifest generation takes a while") } @@ -826,31 +826,42 @@ func TestBuildCrossArchCheckSkippedOnExperimentalBuildroot(t *testing.T) { defer restore() tmpdir := t.TempDir() - for _, withExperimentalBootstrapEnv := range []bool{false, true} { - if withExperimentalBootstrapEnv { - t.Setenv("IMAGE_BUILDER_EXPERIMENTAL", "bootstrap=ghcr.io/mvo5/fedora-buildroot:41") - } - restore = main.MockOsArgs([]string{ + for _, withCrossArch := range []bool{false, true} { + cmd := []string{ "build", "qcow2", "--distro", "centos-9", "--cache", tmpdir, - "--arch=s390x", "--output-dir", tmpdir, - }) + } + if withCrossArch { + cmd = append(cmd, "--arch=aarch64") + } + restore = main.MockOsArgs(cmd) defer restore() script := makeFakeOsbuildScript() fakeOsbuildCmd := testutil.MockCommand(t, "osbuild", script) defer fakeOsbuildCmd.Restore() - err := main.Run() - if withExperimentalBootstrapEnv { - assert.NoError(t, err) - require.Equal(t, 1, len(fakeOsbuildCmd.Calls())) + var err error + _, stderr := testutil.CaptureStdio(t, func() { + err = main.Run() + }) + assert.NoError(t, err) + + manifest, err := os.ReadFile(fakeOsbuildCmd.Path() + ".stdin") + assert.NoError(t, err) + pipelines, err := manifesttest.PipelineNamesFrom(manifest) + assert.NoError(t, err) + crossArchPipeline := "bootstrap-buildroot" + crossArchWarning := `WARNING: using experimental cross-architecture building to build "aarch64"` + if withCrossArch { + assert.Contains(t, pipelines, crossArchPipeline) + assert.Contains(t, stderr, crossArchWarning) } else { - assert.EqualError(t, err, `cannot build for arch "s390x" from "x86_64"`) - require.Equal(t, 0, len(fakeOsbuildCmd.Calls())) + assert.NotContains(t, pipelines, crossArchPipeline) + assert.NotContains(t, stderr, crossArchWarning) } } } diff --git a/cmd/image-builder/manifest.go b/cmd/image-builder/manifest.go index 2bfab6e..3c1db7b 100644 --- a/cmd/image-builder/manifest.go +++ b/cmd/image-builder/manifest.go @@ -25,7 +25,8 @@ type manifestOptions struct { RpmDownloader osbuild.RpmDownloader WithSBOM bool - ForceRepos []string + ForceRepos []string + UseBootstrapContainer bool } func sbomWriter(outputDir, filename string, content io.Reader) error { @@ -51,8 +52,9 @@ func generateManifest(dataDir string, extraRepos []string, img *imagefilter.Resu } // XXX: add --rpmmd/cachedir option like bib manifestGenOpts := &manifestgen.Options{ - Output: output, - RpmDownloader: opts.RpmDownloader, + Output: output, + RpmDownloader: opts.RpmDownloader, + UseBootstrapContainer: opts.UseBootstrapContainer, } if opts.WithSBOM { outputDir := basenameFor(img, opts.OutputDir)