diff --git a/docs/BUILD-FILE.md b/docs/BUILD-FILE.md index ab72f2b35..4f0b309ff 100644 --- a/docs/BUILD-FILE.md +++ b/docs/BUILD-FILE.md @@ -23,6 +23,8 @@ The following are the high level sections for the build file, with detailed desc ### subpackages List of subpackages that this package also produces. For example, docs. + Subpackages may define their own `target-architecture` entries to limit + which architectures produce that subpackage. ### data @@ -499,4 +501,3 @@ TODO(vaikas): melange config points to apko here: # pipeline Pipeline defines the ordered steps to build the package. - diff --git a/pkg/build/build.go b/pkg/build/build.go index e14f56703..a805460ab 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -296,6 +296,39 @@ func (b *Build) Close(ctx context.Context) error { return errors.Join(errs...) } +func filterSubpackages(ctx context.Context, subpackages []config.Subpackage, arch apko_types.Architecture) []config.Subpackage { + log := clog.FromContext(ctx) + + return slices.DeleteFunc(subpackages, func(sp config.Subpackage) bool { + result, err := shouldRun(sp.If) + if err != nil { + // This shouldn't give an error because we evaluate it in Compile. + panic(err) + } + if !result { + log.Infof("skipping subpackage %s because %s == false", sp.Name, sp.If) + return true + } + + if !subpackageTargetsArch(sp, arch) { + log.Infof("skipping subpackage %s because target-architecture does not include %s", sp.Name, arch.ToAPK()) + return true + } + + return false + }) +} + +func subpackageTargetsArch(sp config.Subpackage, arch apko_types.Architecture) bool { + if len(sp.TargetArchitecture) == 0 { + return true + } + if len(sp.TargetArchitecture) == 1 && sp.TargetArchitecture[0] == "all" { + return true + } + return slices.Contains(sp.TargetArchitecture, arch.ToAPK()) +} + // buildGuest invokes apko to build the guest environment, returning a reference to the image // loaded by the OCI Image loader. // @@ -594,19 +627,7 @@ func (b *Build) BuildPackage(ctx context.Context) error { return fmt.Errorf("compiling %s: %w", b.ConfigFile, err) } - // Filter out any subpackages with false If conditions. - b.Configuration.Subpackages = slices.DeleteFunc(b.Configuration.Subpackages, func(sp config.Subpackage) bool { - result, err := shouldRun(sp.If) - if err != nil { - // This shouldn't give an error because we evaluate it in Compile. - panic(err) - } - if !result { - log.Infof("skipping subpackage %s because %s == false", sp.Name, sp.If) - } - - return !result - }) + b.Configuration.Subpackages = filterSubpackages(ctx, b.Configuration.Subpackages, b.Arch) // Initialize SBOMGroup for the main package and all subpackages pkgNames := []string{b.Configuration.Package.Name} diff --git a/pkg/build/compile_test.go b/pkg/build/compile_test.go index 64f7cdc42..4426722fd 100644 --- a/pkg/build/compile_test.go +++ b/pkg/build/compile_test.go @@ -121,6 +121,28 @@ func TestCompileTest(t *testing.T) { } } +func TestFilterSubpackagesTargetArchitecture(t *testing.T) { + subpackages := []config.Subpackage{ + {Name: "default"}, + {Name: "all", TargetArchitecture: []string{"all"}}, + {Name: "native", TargetArchitecture: []string{"x86_64"}}, + {Name: "other", TargetArchitecture: []string{"aarch64"}}, + } + + got := filterSubpackages(context.Background(), subpackages, apko_types.ParseArchitecture("x86_64")) + if got, want := packageNames(got), []string{"default", "all", "native"}; !slices.Equal(got, want) { + t.Errorf("subpackage names: want %v, got %v", want, got) + } +} + +func packageNames(subpackages []config.Subpackage) []string { + names := make([]string, 0, len(subpackages)) + for _, sp := range subpackages { + names = append(names, sp.Name) + } + return names +} + func Test_stripComments(t *testing.T) { tests := []struct { in, want string diff --git a/pkg/build/test.go b/pkg/build/test.go index 3c2ffeefa..996ad67dc 100644 --- a/pkg/build/test.go +++ b/pkg/build/test.go @@ -22,7 +22,6 @@ import ( "os" "path/filepath" "runtime" - "slices" "time" "chainguard.dev/apko/pkg/apk/apk" @@ -251,19 +250,7 @@ func (t *Test) TestPackage(ctx context.Context) error { return fmt.Errorf("compiling %s tests: %w", t.ConfigFile, err) } - // Filter out any subpackages with false If conditions. - t.Configuration.Subpackages = slices.DeleteFunc(t.Configuration.Subpackages, func(sp config.Subpackage) bool { - result, err := shouldRun(sp.If) - if err != nil { - // This shouldn't give an error because we evaluate it in Compile. - panic(err) - } - if !result { - log.Infof("skipping subpackage %s because %s == false", sp.Name, sp.If) - } - - return !result - }) + t.Configuration.Subpackages = filterSubpackages(ctx, t.Configuration.Subpackages, t.Arch) // Unless a specific architecture is requests, we run the test for all. inarchs := len(pkg.TargetArchitecture) == 0 diff --git a/pkg/config/config.go b/pkg/config/config.go index 16ae61d4d..654531bfe 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -861,6 +861,8 @@ type Subpackage struct { URL string `json:"url,omitempty" yaml:"url,omitempty"` // Optional: The git commit of the subpackage build configuration Commit string `json:"commit,omitempty" yaml:"commit,omitempty"` + // List of target architectures for which this subpackage should be built + TargetArchitecture []string `json:"target-architecture,omitempty" yaml:"target-architecture,omitempty"` // Optional: enabling, disabling, and configuration of build checks Checks Checks `json:"checks" yaml:"checks,omitempty"` // Test section for the subpackage. @@ -1551,17 +1553,18 @@ func replacePackage(r *strings.Replacer, commit string, in Package) Package { func replaceSubpackage(r *strings.Replacer, detectedCommit string, in Subpackage) Subpackage { return Subpackage{ - If: r.Replace(in.If), - Name: r.Replace(in.Name), - Pipeline: replacePipelines(r, in.Pipeline), - Dependencies: replaceDependencies(r, in.Dependencies), - Options: in.Options, - Scriptlets: replaceScriptlets(r, in.Scriptlets), - Description: r.Replace(in.Description), - URL: r.Replace(in.URL), - Commit: replaceCommit(detectedCommit, in.Commit), - Checks: in.Checks, - Test: replaceTest(r, in.Test), + If: r.Replace(in.If), + Name: r.Replace(in.Name), + Pipeline: replacePipelines(r, in.Pipeline), + Dependencies: replaceDependencies(r, in.Dependencies), + Options: in.Options, + Scriptlets: replaceScriptlets(r, in.Scriptlets), + Description: r.Replace(in.Description), + URL: r.Replace(in.URL), + Commit: replaceCommit(detectedCommit, in.Commit), + TargetArchitecture: replaceAll(r, in.TargetArchitecture), + Checks: in.Checks, + Test: replaceTest(r, in.Test), } } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index db8f747d9..994833b7e 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -98,6 +98,7 @@ environment: vars: foo: FOO bar: BAR + arch: x86_64 var-transforms: - from: ${{package.version}} @@ -107,6 +108,8 @@ var-transforms: subpackages: - name: subpackage-${{vars.short-package-version}} + target-architecture: + - ${{vars.arch}} dependencies: runtime: - ${{package.name}}-config-${{package.version}} @@ -181,6 +184,7 @@ test: }, cfg.Test.Environment.Contents.Packages) require.Equal(t, cfg.Subpackages[0].Name, "subpackage-0.0") + require.Equal(t, []string{"x86_64"}, cfg.Subpackages[0].TargetArchitecture) require.Equal(t, "/usr/local/FOO", cfg.Test.Environment.Environment["LD_LIBRARY_PATH"]) } diff --git a/pkg/config/schema.cue b/pkg/config/schema.cue index cd131824e..c67e9e771 100644 --- a/pkg/config/schema.cue +++ b/pkg/config/schema.cue @@ -567,6 +567,10 @@ // Optional: The git commit of the subpackage build configuration commit?: string + // List of target architectures for which this subpackage should be + // built + "target-architecture"?: [...string] + // Optional: enabling, disabling, and configuration of build // checks checks!: #Checks diff --git a/pkg/config/schema.json b/pkg/config/schema.json index 63f79baf6..ff2881412 100644 --- a/pkg/config/schema.json +++ b/pkg/config/schema.json @@ -1033,6 +1033,13 @@ "type": "string", "description": "Optional: The git commit of the subpackage build configuration" }, + "target-architecture": { + "items": { + "type": "string" + }, + "type": "array", + "description": "List of target architectures for which this subpackage should be built" + }, "checks": { "$ref": "#/$defs/Checks", "description": "Optional: enabling, disabling, and configuration of build checks"