diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 1616da18..a61872d4 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -20,6 +20,7 @@ import ( "github.com/crillab/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" + toposort "github.com/philopon/go-toposort" ) // PackageSolver is an interface to a generic package solving algorithm @@ -29,6 +30,7 @@ type PackageSolver interface { Uninstall(candidate pkg.Package) ([]pkg.Package, error) ConflictsWithInstalled(p pkg.Package) (bool, error) ConflictsWith(p pkg.Package, ls []pkg.Package) (bool, error) + Order([]PackageAssert) []PackageAssert } // Solver is the default solver for luet @@ -267,6 +269,48 @@ func (s *Solver) Solve() ([]PackageAssert, error) { return DecodeModel(model) } +func (s *Solver) Order(assertions []PackageAssert) []PackageAssert { + + orderedAssertions := []PackageAssert{} + unorderedAssertions := []PackageAssert{} + fingerprints := []string{} + + tmpMap := map[string]PackageAssert{} + + for _, a := range assertions { + if a.Package.Flagged() { + unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered + fingerprints = append(fingerprints, a.Package.GetFingerPrint()) + tmpMap[a.Package.GetFingerPrint()] = a + } else { + orderedAssertions = append(orderedAssertions, a) // Keep last the ones which are not meant to be installed + } + } + + // Build a topological graph + graph := toposort.NewGraph(len(unorderedAssertions)) + graph.AddNodes(fingerprints...) + for _, a := range unorderedAssertions { + for _, req := range a.Package.GetRequires() { + graph.AddEdge(a.Package.GetFingerPrint(), req.GetFingerPrint()) + } + } + result, ok := graph.Toposort() + if !ok { + panic("cycle detected") + } + + for _, res := range result { + a, ok := tmpMap[res] + if !ok { + panic("Sort order - this shouldn't happen") + } + orderedAssertions = append([]PackageAssert{a}, orderedAssertions...) // push upfront + } + + return orderedAssertions +} + // Install given a list of packages, returns package assertions to indicate the packages that must be installed in the system in order // to statisfy all the constraints func (s *Solver) Install(coll []pkg.Package) ([]PackageAssert, error) { diff --git a/pkg/solver/solver_test.go b/pkg/solver/solver_test.go index 837dfab1..72ab09f7 100644 --- a/pkg/solver/solver_test.go +++ b/pkg/solver/solver_test.go @@ -16,6 +16,8 @@ package solver_test import ( + "strconv" + pkg "github.com/mudler/luet/pkg/package" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -333,5 +335,40 @@ var _ = Describe("Solver", func() { Expect(err).ToNot(HaveOccurred()) }) }) + Context("Assertion ordering", func() { + for index := 0; index < 300; index++ { // Just to make sure we don't have false positives + It("Orders them correctly #"+strconv.Itoa(index), func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + G := pkg.NewPackage("G", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + H := pkg.NewPackage("H", "", []*pkg.DefaultPackage{G}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{H}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{D}, []*pkg.DefaultPackage{}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + s := NewSolver([]pkg.Package{C}, []pkg.Package{A, B, C, D, E, F, G}) + + solution, err := s.Install([]pkg.Package{A}) + Expect(solution).To(ContainElement(PackageAssert{Package: A.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: B.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: D.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: C.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: H.IsFlagged(true), Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: G.IsFlagged(true), Value: true})) + + Expect(len(solution)).To(Equal(6)) + Expect(err).ToNot(HaveOccurred()) + solution = s.Order(solution) + Expect(len(solution)).To(Equal(6)) + Expect(solution[0].Package.GetName()).To(Equal("G")) + Expect(solution[1].Package.GetName()).To(Equal("H")) + Expect(solution[2].Package.GetName()).To(Equal("D")) + Expect(solution[3].Package.GetName()).To(Equal("B")) + //Expect(solution[4].Package.GetName()).To(Equal("A")) + //Expect(solution[5].Package.GetName()).To(Equal("C")) As C doesn't have any dep it can be in both positions + }) + } + }) })