Skip to content

Commit

Permalink
Use strong type for package constraints (#306)
Browse files Browse the repository at this point in the history
This allows us to add more functionality to package constraints,
including support for constraining by arch.

Signed-off-by: Brian Goff <[email protected]>
  • Loading branch information
cpuguy83 authored Jul 12, 2024
1 parent e9d24de commit 00de914
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 69 deletions.
4 changes: 2 additions & 2 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ target "deps-only" {
dockerfile-inline = <<EOT
dependencies:
runtime:
patch: []
bash: []
patch: {}
bash: {}
EOT
args = {
"BUILDKIT_SYNTAX" = FRONTEND_REF
Expand Down
58 changes: 32 additions & 26 deletions docs/spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@
},
"post": {
"$ref": "#/$defs/PostInstall",
"description": "Post is the post install configuration for the image.\nThis allows making additional modifications to the container rootfs after the package(s) are installed.\n\nUse this to perform actions that would otherwise require additional tooling inside the container that is not relavent to\nthe resulting container and makes a post-install script as part of the package unnecessary."
"description": "Post is the post install configuration for the image.\nThis allows making additional modifications to the container rootfs after the package(s) are installed.\n\nUse this to perform actions that would otherwise require additional tooling inside the container that is not relevant to\nthe resulting container and makes a post-install script as part of the package unnecessary."
},
"user": {
"type": "string",
Expand All @@ -441,34 +441,46 @@
"type": "object",
"description": "PackageConfig encapsulates the configuration for artifact targets"
},
"PackageConstraints": {
"properties": {
"version": {
"items": {
"type": "string"
},
"type": "array",
"description": "Version is a list of version constraints for the package.\nThe format of these strings is depenendent on the package manager of the target system.\nExamples:\n [\"\u003e=1.0.0\", \"\u003c2.0.0\"]"
},
"arch": {
"items": {
"type": "string"
},
"type": "array",
"description": "Arch is a list of architecture constraints for the package.\nUse this to specify that a package constraint only applies to certain architectures."
}
},
"additionalProperties": false,
"type": "object",
"description": "PackageConstraints is used to specify complex constraints for a package dependency."
},
"PackageDependencies": {
"properties": {
"build": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
"$ref": "#/$defs/PackageConstraints"
},
"type": "object",
"description": "Build is the list of packagese required to build the package."
},
"runtime": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
"$ref": "#/$defs/PackageConstraints"
},
"type": "object",
"description": "Runtime is the list of packages required to install/run the package."
},
"recommends": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
"$ref": "#/$defs/PackageConstraints"
},
"type": "object",
"description": "Recommends is the list of packages recommended to install with the generated package.\nNote: Not all package managers support this (e.g. rpm)"
Expand Down Expand Up @@ -826,37 +838,31 @@
},
"conflicts": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
"$ref": "#/$defs/PackageConstraints"
},
"type": "object",
"description": "Conflicts is the list of packages that conflict with the generated package.\nThis will prevent the package from being installed if any of these packages are already installed or vice versa."
},
"replaces": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
"$ref": "#/$defs/PackageConstraints"
},
"type": "object",
"description": "Replaces is the list of packages that are replaced by the generated package."
},
"provides": {
"items": {
"type": "string"
"additionalProperties": {
"$ref": "#/$defs/PackageConstraints"
},
"type": "array",
"type": "object",
"description": "Provides is the list of things that the generated package provides.\nThis can be used to satisfy dependencies of other packages.\nAs an example, the moby-runc package provides \"runc\", other packages could depend on \"runc\" and be satisfied by moby-runc.\nThis is an advanced use case and consideration should be taken to ensure that the package actually provides the thing it claims to provide."
},
"sources": {
"additionalProperties": {
"$ref": "#/$defs/Source"
},
"type": "object",
"description": "Sources is the list of sources to use to build the artifact(s).\nThe map key is the name of the source and the value is the source configuration.\nThe source configuration is used to fetch the source and filter the files to include/exclude.\nThis can be mounted into the build using the \"Mounts\" field in the StepGroup.\n\nSources can be embedded in the main spec as here or overriden in a build request."
"description": "Sources is the list of sources to use to build the artifact(s).\nThe map key is the name of the source and the value is the source configuration.\nThe source configuration is used to fetch the source and filter the files to include/exclude.\nThis can be mounted into the build using the \"Mounts\" field in the StepGroup.\n\nSources can be embedded in the main spec as here or overridden in a build request."
},
"patches": {
"additionalProperties": {
Expand Down Expand Up @@ -1022,7 +1028,7 @@
},
"frontend": {
"$ref": "#/$defs/Frontend",
"description": "Frontend is the frontend configuration to use for the target.\nThis is used to forward the build to a different, dalec-compatabile frontend.\nThis can be useful when testing out new distros or using a different version of the frontend for a given distro."
"description": "Frontend is the frontend configuration to use for the target.\nThis is used to forward the build to a different, dalec-compatible frontend.\nThis can be useful when testing out new distros or using a different version of the frontend for a given distro."
},
"tests": {
"items": {
Expand Down
36 changes: 25 additions & 11 deletions frontend/rpm/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"fmt"
"io"
"path/filepath"
"sort"
"strings"
"sync"
"text/template"

"github.com/Azure/dalec"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)

const gomodsName = "__gomods"
Expand Down Expand Up @@ -85,9 +86,11 @@ func (w *specWrapper) Changelog() (fmt.Stringer, error) {
func (w *specWrapper) Provides() fmt.Stringer {
b := &strings.Builder{}

sort.Strings(w.Spec.Provides)
for _, name := range w.Spec.Provides {
fmt.Fprintln(b, "Provides:", name)
ls := maps.Keys(w.Spec.Provides)
slices.Sort(ls)

for _, name := range ls {
writeDep(b, "Provides", name, w.Spec.Replaces[name])
}
b.WriteString("\n")
return b
Expand Down Expand Up @@ -161,7 +164,6 @@ func (w *specWrapper) Requires() fmt.Stringer {
runtimeKeys := dalec.SortMapKeys(deps.Runtime)
for _, name := range runtimeKeys {
constraints := deps.Runtime[name]
// satisifes is only for build deps, not runtime deps
// TODO: consider if it makes sense to support sources satisfying runtime deps
writeDep(b, "Requires", name, constraints)
}
Expand All @@ -170,15 +172,27 @@ func (w *specWrapper) Requires() fmt.Stringer {
return b
}

func writeDep(b *strings.Builder, kind, name string, constraints []string) {
if len(constraints) == 0 {
fmt.Fprintf(b, "%s: %s\n", kind, name)
func writeDep(b *strings.Builder, kind, name string, constraints dalec.PackageConstraints) {
do := func() {
if len(constraints.Version) == 0 {
fmt.Fprintf(b, "%s: %s\n", kind, name)
return
}

for _, c := range constraints.Version {
fmt.Fprintf(b, "%s: %s %s\n", kind, name, c)
}
}

if len(constraints.Arch) == 0 {
do()
return
}

sort.Strings(constraints)
for _, c := range constraints {
fmt.Fprintf(b, "%s: %s %s\n", kind, name, c)
for _, arch := range constraints.Arch {
fmt.Fprintf(b, "%%ifarch %s\n", arch)
do()
fmt.Fprintf(b, "%%endif\n")
}
}

Expand Down
76 changes: 67 additions & 9 deletions frontend/rpm/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ fi
%doc %{_docdir}/test-pkg/docs/README
`

assert.Equal(t, want, got)
})

Expand Down Expand Up @@ -524,24 +525,48 @@ func TestTemplate_Requires(t *testing.T) {
// note: I've prefixed these packages with a/b/c for sorting purposes
// Since the underlying code will sort packages this just makes it
// simpler to read for tests.
Build: map[string][]string{
Build: map[string]dalec.PackageConstraints{
"a-lib-no-constraints": {},
"b-lib-one-constraints": {
"< 2.0",
Version: []string{"< 2.0"},
},
"c-lib-multiple-constraints": {
"< 2.0",
">= 1.0",
Version: []string{
"< 2.0",
">= 1.0",
},
},
"d-lib-single-arch-constraints": {
Arch: []string{"arm64"},
},
"e-lib-multi-arch-constraints": {
Arch: []string{"amd64", "arm64"},
},
"f-lib-multi-arch-multi-version-constraints": {
Arch: []string{"amd64", "arm64"},
Version: []string{"< 2.0", ">= 1.0"},
},
},
Runtime: map[string][]string{
Runtime: map[string]dalec.PackageConstraints{
"a-no-constraints": {},
"b-one-constraints": {
"< 2.0",
Version: []string{"< 2.0"},
},
"c-multiple-constraints": {
"< 2.0",
">= 1.0",
Version: []string{
"< 2.0",
">= 1.0",
},
},
"d-single-arch-constraints": {
Arch: []string{"arm64"},
},
"e-multi-arch-constraints": {
Arch: []string{"amd64", "arm64"},
},
"f-multi-arch-multi-version-constraints": {
Arch: []string{"amd64", "arm64"},
Version: []string{"< 2.0", ">= 1.0"},
},
},
},
Expand All @@ -554,11 +579,45 @@ func TestTemplate_Requires(t *testing.T) {
BuildRequires: b-lib-one-constraints < 2.0
BuildRequires: c-lib-multiple-constraints < 2.0
BuildRequires: c-lib-multiple-constraints >= 1.0
%ifarch arm64
BuildRequires: d-lib-single-arch-constraints
%endif
%ifarch amd64
BuildRequires: e-lib-multi-arch-constraints
%endif
%ifarch arm64
BuildRequires: e-lib-multi-arch-constraints
%endif
%ifarch amd64
BuildRequires: f-lib-multi-arch-multi-version-constraints < 2.0
BuildRequires: f-lib-multi-arch-multi-version-constraints >= 1.0
%endif
%ifarch arm64
BuildRequires: f-lib-multi-arch-multi-version-constraints < 2.0
BuildRequires: f-lib-multi-arch-multi-version-constraints >= 1.0
%endif
Requires: a-no-constraints
Requires: b-one-constraints < 2.0
Requires: c-multiple-constraints < 2.0
Requires: c-multiple-constraints >= 1.0
%ifarch arm64
Requires: d-single-arch-constraints
%endif
%ifarch amd64
Requires: e-multi-arch-constraints
%endif
%ifarch arm64
Requires: e-multi-arch-constraints
%endif
%ifarch amd64
Requires: f-multi-arch-multi-version-constraints < 2.0
Requires: f-multi-arch-multi-version-constraints >= 1.0
%endif
%ifarch arm64
Requires: f-multi-arch-multi-version-constraints < 2.0
Requires: f-multi-arch-multi-version-constraints >= 1.0
%endif
`

Expand Down Expand Up @@ -593,7 +652,6 @@ A helpful tool
%install
%files
`)

assert.Equal(t, expect, actual)
Expand Down
2 changes: 1 addition & 1 deletion image.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type ImageConfig struct {
// Post is the post install configuration for the image.
// This allows making additional modifications to the container rootfs after the package(s) are installed.
//
// Use this to perform actions that would otherwise require additional tooling inside the container that is not relavent to
// Use this to perform actions that would otherwise require additional tooling inside the container that is not relevant to
// the resulting container and makes a post-install script as part of the package unnecessary.
Post *PostInstall `yaml:"post,omitempty" json:"post,omitempty"`

Expand Down
28 changes: 20 additions & 8 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@ type Spec struct {

// Conflicts is the list of packages that conflict with the generated package.
// This will prevent the package from being installed if any of these packages are already installed or vice versa.
Conflicts map[string][]string `yaml:"conflicts,omitempty" json:"conflicts,omitempty"`
Conflicts map[string]PackageConstraints `yaml:"conflicts,omitempty" json:"conflicts,omitempty"`
// Replaces is the list of packages that are replaced by the generated package.
Replaces map[string][]string `yaml:"replaces,omitempty" json:"replaces,omitempty"`
Replaces map[string]PackageConstraints `yaml:"replaces,omitempty" json:"replaces,omitempty"`
// Provides is the list of things that the generated package provides.
// This can be used to satisfy dependencies of other packages.
// As an example, the moby-runc package provides "runc", other packages could depend on "runc" and be satisfied by moby-runc.
// This is an advanced use case and consideration should be taken to ensure that the package actually provides the thing it claims to provide.
Provides []string `yaml:"provides,omitempty" json:"provides,omitempty"`
Provides map[string]PackageConstraints `yaml:"provides,omitempty" json:"provides,omitempty"`

// Sources is the list of sources to use to build the artifact(s).
// The map key is the name of the source and the value is the source configuration.
// The source configuration is used to fetch the source and filter the files to include/exclude.
// This can be mounted into the build using the "Mounts" field in the StepGroup.
//
// Sources can be embedded in the main spec as here or overriden in a build request.
// Sources can be embedded in the main spec as here or overridden in a build request.
Sources map[string]Source `yaml:"sources,omitempty" json:"sources,omitempty"`

// Patches is the list of patches to apply to the sources.
Expand Down Expand Up @@ -426,17 +426,29 @@ type SourceGenerator struct {
Gomod *GeneratorGomod `yaml:"gomod" json:"gomod"`
}

// PackageConstraints is used to specify complex constraints for a package dependency.
type PackageConstraints struct {
// Version is a list of version constraints for the package.
// The format of these strings is depenendent on the package manager of the target system.
// Examples:
// [">=1.0.0", "<2.0.0"]
Version []string `yaml:"version,omitempty" json:"version,omitempty"`
// Arch is a list of architecture constraints for the package.
// Use this to specify that a package constraint only applies to certain architectures.
Arch []string `yaml:"arch,omitempty" json:"arch,omitempty"`
}

// PackageDependencies is a list of dependencies for a package.
// This will be included in the package metadata so that the package manager can install the dependencies.
// It also includes build-time dedendencies, which we'll install before running any build steps.
type PackageDependencies struct {
// Build is the list of packagese required to build the package.
Build map[string][]string `yaml:"build,omitempty" json:"build,omitempty"`
Build map[string]PackageConstraints `yaml:"build,omitempty" json:"build,omitempty"`
// Runtime is the list of packages required to install/run the package.
Runtime map[string][]string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
Runtime map[string]PackageConstraints `yaml:"runtime,omitempty" json:"runtime,omitempty"`
// Recommends is the list of packages recommended to install with the generated package.
// Note: Not all package managers support this (e.g. rpm)
Recommends map[string][]string `yaml:"recommends,omitempty" json:"recommends,omitempty"`
Recommends map[string]PackageConstraints `yaml:"recommends,omitempty" json:"recommends,omitempty"`

// Test lists any extra packages required for running tests
// These packages are only installed for tests which have steps that require
Expand Down Expand Up @@ -518,7 +530,7 @@ type Target struct {
Image *ImageConfig `yaml:"image,omitempty" json:"image,omitempty"`

// Frontend is the frontend configuration to use for the target.
// This is used to forward the build to a different, dalec-compatabile frontend.
// This is used to forward the build to a different, dalec-compatible frontend.
// This can be useful when testing out new distros or using a different version of the frontend for a given distro.
Frontend *Frontend `yaml:"frontend,omitempty" json:"frontend,omitempty"`

Expand Down
Loading

0 comments on commit 00de914

Please sign in to comment.