Skip to content

Commit

Permalink
sat: sort inputs to the sat solver for determinism
Browse files Browse the repository at this point in the history
Make sure the sat solver output is deterministic and reproducible by
making the input order stable
  • Loading branch information
manuelnaranjo committed Jan 13, 2025
1 parent 73c4e4a commit ed0a589
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ common --incompatible_enable_proto_toolchain_resolution
# inside the rules project we build from source
common --extra_toolchains=//cmd:bazeldnf-host-toolchain

# make sure bazel doesn't complain on hosts without java
common --java_runtime_version=remotejdk_21

# Load any settings & overrides specific to the current user from `.bazelrc.user`.
# This file should appear in `.gitignore` so that settings are not shared with team members. This
# should be last statement in this config so the user configuration is able to overwrite flags from
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use_repo(
"com_github_spf13_cobra",
"io_k8s_sigs_yaml",
"org_golang_x_crypto",
"org_golang_x_exp",
)

# deps only needed for the repo internals
Expand Down
1 change: 1 addition & 0 deletions pkg/sat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ go_library(
"@com_github_crillab_gophersat//explain",
"@com_github_crillab_gophersat//maxsat",
"@com_github_sirupsen_logrus//:logrus",
"@org_golang_x_exp//slices",
],
)

Expand Down
60 changes: 53 additions & 7 deletions pkg/sat/sat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sat

import (
"bufio"
"cmp"
"fmt"
"io"
"regexp"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/rmohr/bazeldnf/pkg/reducer"
"github.com/rmohr/bazeldnf/pkg/rpm"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
)

type VarType string
Expand All @@ -34,6 +36,13 @@ type VarContext struct {
Version api.Version
}

func varContextSort(a VarContext, b VarContext) int {
return cmp.Or(
cmp.Compare(a.Package, b.Package),
rpm.Compare(a.Version, b.Version),
)
}

type Var struct {
satVarName string
varType VarType
Expand Down Expand Up @@ -129,8 +138,15 @@ func (r *Resolver) LoadInvolvedPackages(packages []*api.Package, ignoreRegex []s
deduplicated[pkg.String()] = packages[i]
}
}

deduplicatedKeys := make([]string, 0, len(deduplicated))
for k := range deduplicated {
deduplicatedKeys = append(deduplicatedKeys, k)
}
slices.Sort(deduplicatedKeys)

packages = nil
for k, _ := range deduplicated {
for _, k := range deduplicatedKeys {
reducer.FixPackages(deduplicated[k])
packages = append(packages, deduplicated[k])
}
Expand All @@ -146,8 +162,13 @@ func (r *Resolver) LoadInvolvedPackages(packages []*api.Package, ignoreRegex []s

if !r.nobest {
packages = nil
for _, v := range r.bestPackages {
packages = append(packages, v)
bestPackagesKeys := make([]string, 0, len(r.bestPackages))
for k := range r.bestPackages {
bestPackagesKeys = append(bestPackagesKeys, k)
}
slices.Sort(bestPackagesKeys)
for _, v := range bestPackagesKeys {
packages = append(packages, r.bestPackages[v])
}
}
// Generate variables
Expand All @@ -161,17 +182,31 @@ func (r *Resolver) LoadInvolvedPackages(packages []*api.Package, ignoreRegex []s
}
}

for x, _ := range r.packages {
packagesKeys := make([]string, 0, len(r.packages))
for k := range r.packages {
packagesKeys = append(packagesKeys, k)
}
slices.Sort(packagesKeys)

for _, x := range packagesKeys {
sort.SliceStable(r.packages[x], func(i, j int) bool {
return rpm.Compare(r.packages[x][i].Package.Version, r.packages[x][j].Package.Version) < 0
})
}

logrus.Infof("Loaded %v packages.", len(r.pkgProvides))

pkgProvideKeys := make([]VarContext, 0, len(r.pkgProvides))
for vc := range r.pkgProvides {
pkgProvideKeys = append(pkgProvideKeys, vc)
}
slices.SortFunc(pkgProvideKeys, varContextSort)

// Generate imply rules
for _, resourceVars := range r.pkgProvides {
for _, provided := range pkgProvideKeys {
// Create imply rules for every package and add them to the formula
// one provided dependency implies all dependencies from that package
resourceVars := r.pkgProvides[provided]
bfVar := bf.And(toBFVars(resourceVars)...)
var ands []bf.Formula
for _, res := range resourceVars {
Expand Down Expand Up @@ -250,8 +285,14 @@ func (res *Resolver) Resolve() (install []*api.Package, excluded []*api.Package,
}
}
// write soft rules. We don't want to install any package
for _, pkgs := range res.packages {
weight := 1901
packageKeys := make([]string, 0, len(res.packages))
for k := range res.packages {
packageKeys = append(packageKeys, k)
}

for _, name := range packageKeys {
pkgs := res.packages[name]
weight := 2999
fmt.Fprintf(pwMaxSatWriter, "c prefer %s\n", pkgs[len(pkgs)-1].Package.String())
if len(pkgs) > 1 {
for _, pkg := range pkgs[0 : len(pkgs)-1] {
Expand Down Expand Up @@ -533,6 +574,11 @@ func (r *Resolver) explodeSingleRequires(entry api.Entry, provides []*Var) (acce
return nil, fmt.Errorf("Nothing can satisfy %s", entry.Name)
}

slices.SortFunc(accepts, func(a *Var, b *Var) int {
// there's a single var context for a given package
return varContextSort(a.Context, b.Context)
})

return accepts, nil
}

Expand Down

0 comments on commit ed0a589

Please sign in to comment.