packaging/linux/deb/template_control.go (168 lines of code) (raw):
package deb
import (
_ "embed"
"fmt"
"io"
"maps"
"strconv"
"strings"
"text/template"
"github.com/Azure/dalec"
"golang.org/x/exp/slices"
)
func WriteControl(spec *dalec.Spec, target string, w io.Writer) error {
return controlTmpl.Execute(w, &controlWrapper{spec, target})
}
type controlWrapper struct {
*dalec.Spec
Target string
}
func (w *controlWrapper) Architecture() string {
if w.NoArch {
return "all"
}
return "linux-any"
}
// NOTE: This is very basic and does not handle things like grouped constraints
// Given this is just trying to shim things to allow either the rpm format or the deb format
// in its basic form, this is sufficient for now.
func formatVersionConstraint(v string) string {
prefix, suffix, ok := strings.Cut(v, " ")
if !ok {
if len(prefix) >= 1 {
_, err := strconv.Atoi(prefix[:1])
if err == nil {
// This is just a version number, assume it should use the equal symbol
return "= " + v
}
}
return v
}
switch prefix {
case "<":
return "<< " + suffix
case ">":
return ">> " + suffix
case "==":
return "= " + suffix
default:
return v
}
}
// appendConstraints takes an input list of packages and returns a new list of
// packages with the constraints appended for use in a debian/control file.
// The output list is sorted lexicographically.
func appendConstraints(deps map[string]dalec.PackageConstraints) []string {
if deps == nil {
return nil
}
out := dalec.SortMapKeys(deps)
for i, dep := range out {
constraints := deps[dep]
var versionConstraints []string
// Format is specified in https://www.debian.org/doc/debian-policy/ch-relationships.html#syntax-of-relationship-fields
if len(constraints.Version) > 0 {
ls := constraints.Version
slices.Sort(ls)
for _, v := range ls {
versionConstraints = append(versionConstraints, fmt.Sprintf("%s (%s)", dep, formatVersionConstraint(v)))
}
} else {
versionConstraints = append(versionConstraints, dep)
}
if len(constraints.Arch) > 0 {
ls := constraints.Arch
slices.Sort(ls)
for j, vc := range versionConstraints {
versionConstraints[j] = fmt.Sprintf("%s [%s]", vc, strings.Join(ls, " "))
}
}
out[i] = strings.Join(versionConstraints, " | ")
}
return out
}
func (w *controlWrapper) depends(buf io.Writer, depsSpec *dalec.PackageDependencies) {
var (
needsClone bool
rtDeps map[string]dalec.PackageConstraints
)
if depsSpec == nil || depsSpec.Runtime == nil {
rtDeps = make(map[string]dalec.PackageConstraints)
} else {
rtDeps = depsSpec.Runtime
needsClone = true
}
// Add in deps vars that will get resolved by debbuild
// In some cases these are not necessary (maybe even most), but when they are
// it is important.
// When not needed lintian may throw warnings but that's ok.
// If these aren't actually needed they'll resolve to nothing and don't cause
// any changes.
const (
shlibsDeps = "${shlibs:Depends}"
miscDeps = "${misc:Depends}"
)
if _, exists := rtDeps[shlibsDeps]; !exists {
if needsClone {
rtDeps = maps.Clone(rtDeps)
needsClone = false
}
rtDeps[shlibsDeps] = dalec.PackageConstraints{}
}
if _, exists := rtDeps[miscDeps]; !exists {
if needsClone {
rtDeps = maps.Clone(rtDeps)
}
rtDeps[miscDeps] = dalec.PackageConstraints{}
}
deps := appendConstraints(rtDeps)
fmt.Fprintln(buf, multiline("Depends", deps))
}
// multiline attempts to format a field with multiple values in a way that is more human readable
// with line breaks and indentation.
func multiline(field string, values []string) string {
return fmt.Sprintf("%s: %s", field, strings.Join(values, ",\n"+strings.Repeat(" ", len(field)+2)))
}
func (w *controlWrapper) recommends(buf io.Writer, depsSpec *dalec.PackageDependencies) {
if len(depsSpec.Recommends) == 0 {
return
}
deps := appendConstraints(depsSpec.Recommends)
fmt.Fprintln(buf, multiline("Recommends", deps))
}
func (w *controlWrapper) BuildDeps() fmt.Stringer {
b := &strings.Builder{}
depsSpec := w.Spec.GetPackageDeps(w.Target)
var deps []string
if depsSpec != nil {
deps = appendConstraints(depsSpec.Build)
}
deps = append(deps, fmt.Sprintf("debhelper-compat (= %s)", DebHelperCompat))
fmt.Fprintln(b, multiline("Build-Depends", deps))
return b
}
func (w *controlWrapper) AllRuntimeDeps() fmt.Stringer {
b := &strings.Builder{}
deps := w.Spec.GetPackageDeps(w.Target)
if deps == nil {
return b
}
w.depends(b, deps)
w.recommends(b, deps)
return b
}
func (w *controlWrapper) Replaces() fmt.Stringer {
b := &strings.Builder{}
if len(w.Spec.Replaces) == 0 {
return b
}
ls := appendConstraints(w.Spec.Replaces)
fmt.Fprintln(b, multiline("Replaces", ls))
return b
}
func (w *controlWrapper) Conflicts() fmt.Stringer {
b := &strings.Builder{}
if len(w.Spec.Conflicts) == 0 {
return b
}
ls := appendConstraints(w.Spec.Conflicts)
fmt.Fprintln(b, multiline("Conflicts", ls))
return b
}
func (w *controlWrapper) Provides() fmt.Stringer {
b := &strings.Builder{}
if len(w.Spec.Provides) == 0 {
return b
}
ls := appendConstraints(w.Spec.Provides)
fmt.Fprintln(b, multiline("Provides", ls))
return b
}
var (
//go:embed templates/debian_control.tmpl
controlTmplContent []byte
controlTmpl = template.Must(template.New("control").Parse(string(controlTmplContent)))
)