diff --git a/go.mod b/go.mod index 76932c34..df512aae 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/pterm/pterm v0.12.32-0.20211002183613-ada9ef6790c3 github.com/rancher-sandbox/gofilecache v0.0.0-20210330135715-becdeff5df15 + github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 diff --git a/go.sum b/go.sum index c4eeb3c2..c9bf47f8 100644 --- a/go.sum +++ b/go.sum @@ -1300,6 +1300,8 @@ github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1: github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3 h1:i8yAzeysv+UgLJ0O8usGQHvtyM0AbPHlI03l5OnWD4A= +github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3/go.mod h1:FzYqW619hxj8ogwgAr2ENoIELLwatZvaBnWdTquP99U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= diff --git a/pkg/api/core/types/assertion.go b/pkg/api/core/types/assertion.go index 13d33eff..a23159d4 100644 --- a/pkg/api/core/types/assertion.go +++ b/pkg/api/core/types/assertion.go @@ -6,9 +6,10 @@ import ( "sort" "unicode" + "github.com/mudler/luet/pkg/helpers" "github.com/mudler/topsort" - "github.com/philopon/go-toposort" "github.com/pkg/errors" + toposort "github.com/scrohde/go-toposort" ) // PackageAssert represent a package assertion. @@ -30,50 +31,47 @@ func (a *PackageAssert) String() string { return fmt.Sprintf("%s/%s %s %s", a.Package.GetCategory(), a.Package.GetName(), a.Package.GetVersion(), msg) } -func (assertions PackagesAssertions) EnsureOrder() (PackagesAssertions, error) { +func (assertions PackagesAssertions) EnsureOrder(definitiondb PackageDatabase) (PackagesAssertions, error) { + allAssertions := assertions orderedAssertions := PackagesAssertions{} - unorderedAssertions := PackagesAssertions{} - fingerprints := []string{} - tmpMap := map[string]PackageAssert{} - for _, a := range assertions { - tmpMap[a.Package.GetFingerPrint()] = a - fingerprints = append(fingerprints, a.Package.GetFingerPrint()) - unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered + graph := toposort.NewGraph(len(allAssertions)) - if a.Value { - unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered - } else { - orderedAssertions = append(orderedAssertions, a) // Keep last the ones which are not meant to be installed - } + for _, a := range assertions { + tmpMap[a.Package.GetPackageName()] = a + graph.AddNode(a.Package.GetPackageName()) } - sort.Sort(unorderedAssertions) + edges := map[string]string{} // Build a topological graph - graph := toposort.NewGraph(len(unorderedAssertions)) - graph.AddNodes(fingerprints...) - for _, a := range unorderedAssertions { + for _, a := range allAssertions { for _, req := range a.Package.GetRequires() { - graph.AddEdge(a.Package.GetFingerPrint(), req.GetFingerPrint()) + if def, err := definitiondb.FindPackage(req); err == nil { // Provides: Get a chance of being override here + req = def + } + edges[a.Package.GetPackageName()] = req.GetPackageName() + graph.AddNode(req.GetPackageName()) } } - result, ok := graph.Toposort() + + for k, v := range edges { + graph.AddEdge(k, v) + } + + result, ok := graph.ToposortStable() if !ok { return nil, fmt.Errorf("cycle found") } for _, res := range result { a, ok := tmpMap[res] - if !ok { - return nil, fmt.Errorf("cycle found") - + if ok { + orderedAssertions = append(orderedAssertions, a) } - orderedAssertions = append(orderedAssertions, a) - // orderedAssertions = append(PackagesAssertions{a}, orderedAssertions...) // push upfront } - //helpers.ReverseAny(orderedAssertions) + helpers.ReverseAny(orderedAssertions) return orderedAssertions, nil } diff --git a/pkg/api/core/types/assertion_test.go b/pkg/api/core/types/assertion_test.go new file mode 100644 index 00000000..d9432c11 --- /dev/null +++ b/pkg/api/core/types/assertion_test.go @@ -0,0 +1,86 @@ +// Copyright © 2021 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package types_test + +import ( + types "github.com/mudler/luet/pkg/api/core/types" + "github.com/mudler/luet/pkg/database" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Assertions", func() { + Context("Ordering", func() { + It("orders them correctly", func() { + foo := &types.Package{Name: "foo", PackageRequires: []*types.Package{{Name: "bar"}}} + assertions := types.PackagesAssertions{ + {Package: foo}, + {Package: &types.Package{Name: "baz", PackageRequires: []*types.Package{{Name: "bar"}}}}, + {Package: &types.Package{Name: "bar", PackageRequires: []*types.Package{{}}}}, + } + + ordered_old, err := assertions.Order(database.NewInMemoryDatabase(false), foo.GetFingerPrint()) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(ordered_old[0].Package.Name).To(Equal("bar")) + + ordered, err := assertions.EnsureOrder(database.NewInMemoryDatabase(false)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(ordered)).To(Equal(3)) + + Expect(ordered[0].Package.Name).To(Equal("bar")) + }) + + It("errors on cycles", func() { + foo := &types.Package{Name: "foo", PackageRequires: []*types.Package{{Name: "bar"}}} + assertions := types.PackagesAssertions{ + {Package: foo}, + {Package: &types.Package{Name: "baz", PackageRequires: []*types.Package{{Name: "bar"}}}}, + {Package: &types.Package{Name: "bar", PackageRequires: []*types.Package{{Name: "baz"}}}}, + } + + _, err := assertions.Order(database.NewInMemoryDatabase(false), foo.GetFingerPrint()) + Expect(err).Should(HaveOccurred()) + + _, err = assertions.EnsureOrder(database.NewInMemoryDatabase(false)) + Expect(err).Should(HaveOccurred()) + }) + + It("orders them correctly", func() { + foo := &types.Package{Name: "foo", PackageRequires: []*types.Package{{Name: "bar"}}} + assertions := types.PackagesAssertions{ + {Package: foo}, + {Package: &types.Package{Name: "baz2", PackageRequires: []*types.Package{{Name: "foobaz"}}}}, + {Package: &types.Package{Name: "baz", PackageRequires: []*types.Package{{Name: "bar"}}}}, + {Package: &types.Package{Name: "bar", PackageRequires: []*types.Package{{}}}}, + {Package: &types.Package{Name: "foobaz", PackageRequires: []*types.Package{{}}}}, + } + + ordered_old, err := assertions.Order(database.NewInMemoryDatabase(false), foo.GetFingerPrint()) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(ordered_old[0].Package.Name).To(Equal("bar")) + Expect(ordered_old[1].Package.Name).ToNot(Equal("foobaz")) + + ordered, err := assertions.EnsureOrder(database.NewInMemoryDatabase(false)) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(ordered)).To(Equal(5)) + + Expect(ordered[0].Package.Name).To(Equal("bar")) + Expect(ordered[1].Package.Name).To(Equal("foobaz")) + }) + }) +}) diff --git a/pkg/installer/finalizer.go b/pkg/installer/finalizer.go index 6c7b6d78..60b41743 100644 --- a/pkg/installer/finalizer.go +++ b/pkg/installer/finalizer.go @@ -122,7 +122,7 @@ func OrderFinalizers(allRepos types.PackageDatabase, toInstall map[string]Artifa } } } else { - assertions, err := solution.EnsureOrder() + assertions, err := solution.EnsureOrder(allRepos) if err != nil { return toFinalize, err } diff --git a/vendor/github.com/philopon/go-toposort/.travis.yml b/vendor/github.com/scrohde/go-toposort/.travis.yml similarity index 100% rename from vendor/github.com/philopon/go-toposort/.travis.yml rename to vendor/github.com/scrohde/go-toposort/.travis.yml diff --git a/vendor/github.com/philopon/go-toposort/LICENSE b/vendor/github.com/scrohde/go-toposort/LICENSE similarity index 100% rename from vendor/github.com/philopon/go-toposort/LICENSE rename to vendor/github.com/scrohde/go-toposort/LICENSE diff --git a/vendor/github.com/philopon/go-toposort/README.md b/vendor/github.com/scrohde/go-toposort/README.md similarity index 100% rename from vendor/github.com/philopon/go-toposort/README.md rename to vendor/github.com/scrohde/go-toposort/README.md diff --git a/vendor/github.com/philopon/go-toposort/toposort.go b/vendor/github.com/scrohde/go-toposort/toposort.go similarity index 86% rename from vendor/github.com/philopon/go-toposort/toposort.go rename to vendor/github.com/scrohde/go-toposort/toposort.go index 270ca645..c4834c79 100644 --- a/vendor/github.com/philopon/go-toposort/toposort.go +++ b/vendor/github.com/scrohde/go-toposort/toposort.go @@ -1,5 +1,7 @@ package toposort +import "sort" + type Graph struct { nodes []string outputs map[string]map[string]int @@ -59,7 +61,7 @@ func (g *Graph) RemoveEdge(from, to string) bool { return true } -func (g *Graph) Toposort() ([]string, bool) { +func (g *Graph) toposort(stable bool) ([]string, bool) { L := make([]string, 0, len(g.nodes)) S := make([]string, 0, len(g.nodes)) @@ -69,6 +71,10 @@ func (g *Graph) Toposort() ([]string, bool) { } } + if stable { + sort.Strings(S) + } + for len(S) > 0 { var n string n, S = S[0], S[1:] @@ -79,6 +85,10 @@ func (g *Graph) Toposort() ([]string, bool) { ms[i-1] = m } + if stable { + sort.Strings(ms) + } + for _, m := range ms { g.unsafeRemoveEdge(n, m) @@ -99,3 +109,11 @@ func (g *Graph) Toposort() ([]string, bool) { return L, true } + +func (g *Graph) Toposort() ([]string, bool) { + return g.toposort(false) +} + +func (g *Graph) ToposortStable() ([]string, bool) { + return g.toposort(true) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f06ecade..e6a11655 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -370,7 +370,6 @@ github.com/pelletier/go-toml github.com/peterbourgon/diskv # github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f ## explicit -github.com/philopon/go-toposort # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors @@ -402,6 +401,9 @@ github.com/rogpeppe/go-internal/internal/syscall/windows github.com/rogpeppe/go-internal/internal/syscall/windows/sysdll github.com/rogpeppe/go-internal/lockedfile github.com/rogpeppe/go-internal/lockedfile/internal/filelock +# github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3 +## explicit +github.com/scrohde/go-toposort # github.com/shopspring/decimal v1.2.0 github.com/shopspring/decimal # github.com/sirupsen/logrus v1.8.1