topo sorted map

This commit is contained in:
jguer committed 2022-09-09 17:38:48 +02:00
1 parent be036c39ff
commit 4a73dfb0ca
3 files changed
+233 -46

No files matched your search

+54 -3
View File
@@ -52,10 +52,61 @@ func installLocalPKGBUILD(
return err
}
fmt.Println(graph)
topoSorted := graph.TopoSortedLayers()
topoSorted := graph.TopoSortedLayerMap()
fmt.Println(topoSorted, len(topoSorted))
installer := &Installer{dbExecutor: dbExecutor}
installer.Install(ctx, topoSorted)
return nil
}
type Installer struct {
dbExecutor db.Executor
}
func (installer *Installer) Install(ctx context.Context, targets []map[string]*dep.InstallInfo) error {
// Reorganize targets into layers of dependencies
for i := len(targets) - 1; i >= 0; i-- {
err := installer.handleLayer(ctx, targets[i])
if err != nil {
// rollback
return err
}
}
return nil
}
type MapBySourceAndType map[dep.Source]map[dep.Reason][]string
func (m *MapBySourceAndType) String() string {
var s string
for source, reasons := range *m {
s += fmt.Sprintf("%s: [", source)
for reason, names := range reasons {
s += fmt.Sprintf(" %d: [%v] ", reason, names)
}
s += "], "
}
return s
}
func (installer *Installer) handleLayer(ctx context.Context, layer map[string]*dep.InstallInfo) error {
// Install layer
depByTypeAndReason := make(MapBySourceAndType)
for name, info := range layer {
if _, ok := depByTypeAndReason[info.Source]; !ok {
depByTypeAndReason[info.Source] = make(map[dep.Reason][]string)
}
depByTypeAndReason[info.Source][info.Reason] = append(depByTypeAndReason[info.Source][info.Reason], name)
}
fmt.Printf("%v\n", depByTypeAndReason)
return nil
}
+115 -24
View File
@@ -16,33 +16,66 @@ import (
"github.com/leonelquinteros/gotext"
)
type InstallInfo struct {
Source Source
Reason Reason
}
func (i *InstallInfo) String() string {
return fmt.Sprintf("InstallInfo{Source: %v, Reason: %v}", i.Source, i.Reason)
}
type (
packageType int
sourceType int
Reason int
Source int
)
const (
Explicit packageType = iota
Dep
MakeDep
CheckDep
)
func (r Reason) String() string {
return reasonNames[r]
}
func (s Source) String() string {
return sourceNames[s]
}
const (
AUR sourceType = iota
Explicit Reason = iota // 0
Dep // 1
MakeDep // 2
CheckDep // 3
)
var reasonNames = map[Reason]string{
Explicit: gotext.Get("explicit"),
Dep: gotext.Get("dep"),
MakeDep: gotext.Get("makedep"),
CheckDep: gotext.Get("checkdep"),
}
const (
AUR Source = iota
Sync
Local
SrcInfo
Missing
)
var bgColorMap = map[sourceType]string{
var sourceNames = map[Source]string{
AUR: gotext.Get("aur"),
Sync: gotext.Get("sync"),
Local: gotext.Get("local"),
SrcInfo: gotext.Get("srcinfo"),
Missing: gotext.Get("missing"),
}
var bgColorMap = map[Source]string{
AUR: "lightblue",
Sync: "lemonchiffon",
Local: "darkolivegreen1",
Missing: "tomato",
}
var colorMap = map[packageType]string{
var colorMap = map[Reason]string{
Explicit: "black",
Dep: "deeppink",
MakeDep: "navyblue",
@@ -67,8 +100,8 @@ func NewGrapher(dbExecutor db.Executor, aurCache *metadata.AURCache, fullGraph,
}
}
func (g *Grapher) GraphFromSrcInfo(pkgbuild *gosrc.Srcinfo) (*topo.Graph[string, int], error) {
graph := topo.New[string, int]()
func (g *Grapher) GraphFromSrcInfo(pkgbuild *gosrc.Srcinfo) (*topo.Graph[string, *InstallInfo], error) {
graph := topo.New[string, *InstallInfo]()
aurPkgs, err := makeAURPKGFromSrcinfo(g.dbExecutor, pkgbuild)
if err != nil {
@@ -77,13 +110,27 @@ func (g *Grapher) GraphFromSrcInfo(pkgbuild *gosrc.Srcinfo) (*topo.Graph[string,
for _, pkg := range aurPkgs {
pkg := pkg
if err := graph.Alias(pkg.PackageBase, pkg.Name); err != nil {
text.Warnln("aur target alias warn:", pkg.PackageBase, pkg.Name, err)
}
g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
Color: colorMap[Explicit],
Background: bgColorMap[AUR],
Value: &InstallInfo{
Source: SrcInfo,
Reason: Explicit,
},
})
g.addDepNodes(&pkg, graph)
}
return graph, nil
}
func (g *Grapher) addDepNodes(pkg *aur.Pkg, graph *topo.Graph[string, int]) {
func (g *Grapher) addDepNodes(pkg *aur.Pkg, graph *topo.Graph[string, *InstallInfo]) {
if len(pkg.MakeDepends) > 0 {
g.addNodes(graph, pkg.Name, pkg.MakeDepends, MakeDep)
}
@@ -97,8 +144,8 @@ func (g *Grapher) addDepNodes(pkg *aur.Pkg, graph *topo.Graph[string, int]) {
}
}
func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string, int], error) {
graph := topo.New[string, int]()
func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string, *InstallInfo], error) {
graph := topo.New[string, *InstallInfo]()
for _, target := range targets {
aurPkgs, _ := g.aurCache.FindPackage(target)
@@ -108,25 +155,50 @@ func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string, int],
text.Warnln("aur target alias warn:", pkg.PackageBase, pkg.Name, err)
}
graph.SetNodeInfo(pkg.Name, &topo.NodeInfo[int]{Color: colorMap[Explicit], Background: bgColorMap[AUR]})
g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
Color: colorMap[Explicit],
Background: bgColorMap[AUR],
Value: &InstallInfo{
Source: AUR,
Reason: Explicit,
},
})
g.addDepNodes(pkg, graph)
}
return graph, nil
}
func (g *Grapher) ValidateAndSetNodeInfo(graph *topo.Graph[string, *InstallInfo],
node string, nodeInfo *topo.NodeInfo[*InstallInfo],
) {
info := graph.GetNodeInfo(node)
if info != nil {
if info.Value.Reason < nodeInfo.Value.Reason {
return // refuse to downgrade reason from explicit to dep
}
}
graph.SetNodeInfo(node, nodeInfo)
}
func (g *Grapher) addNodes(
graph *topo.Graph[string, int],
graph *topo.Graph[string, *InstallInfo],
parentPkgName string,
deps []string,
depType packageType,
depType Reason,
) {
for _, depString := range deps {
depName, _, _ := splitDep(depString)
if g.dbExecutor.LocalSatisfierExists(depString) {
if g.fullGraph {
graph.SetNodeInfo(depName, &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[Local]})
g.ValidateAndSetNodeInfo(
graph,
depName,
&topo.NodeInfo[*InstallInfo]{Color: colorMap[depType], Background: bgColorMap[Local]})
if err := graph.DependOn(depName, parentPkgName); err != nil {
text.Warnln(depName, parentPkgName, err)
}
@@ -149,7 +221,17 @@ func (g *Grapher) addNodes(
text.Warnln("repo dep warn:", depName, parentPkgName, err)
}
graph.SetNodeInfo(alpmPkg.Name(), &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[Sync]})
g.ValidateAndSetNodeInfo(
graph,
alpmPkg.Name(),
&topo.NodeInfo[*InstallInfo]{
Color: colorMap[depType],
Background: bgColorMap[Sync],
Value: &InstallInfo{
Source: Sync,
Reason: depType,
},
})
if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && g.fullGraph {
newDepsSlice := make([]string, 0, len(newDeps))
@@ -166,7 +248,7 @@ func (g *Grapher) addNodes(
if aurPkgs, _ := g.aurCache.FindPackage(depName); len(aurPkgs) != 0 { // Check AUR
pkg := aurPkgs[0]
if len(aurPkgs) > 1 {
pkg := provideMenu(g.w, depName, aurPkgs, g.noConfirm)
pkg = provideMenu(g.w, depName, aurPkgs, g.noConfirm)
g.aurCache.SetProvideCache(depName, []*aur.Pkg{pkg})
}
@@ -178,14 +260,23 @@ func (g *Grapher) addNodes(
text.Warnln("aur dep warn:", pkg.PackageBase, parentPkgName, err)
}
graph.SetNodeInfo(pkg.PackageBase, &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[AUR]})
graph.SetNodeInfo(
pkg.PackageBase,
&topo.NodeInfo[*InstallInfo]{
Color: colorMap[depType],
Background: bgColorMap[AUR],
Value: &InstallInfo{
Source: AUR,
Reason: depType,
},
})
g.addDepNodes(pkg, graph)
continue
}
// no dep found. add as missing
graph.SetNodeInfo(depString, &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[Missing]})
graph.SetNodeInfo(depString, &topo.NodeInfo[*InstallInfo]{Color: colorMap[depType], Background: bgColorMap[Missing]})
}
}
+64 -19
View File
@@ -5,10 +5,6 @@ import (
"strings"
)
type Mapable interface {
Key() string
}
type (
AliasMap[T comparable] map[T]T
NodeSet[T comparable] map[T]bool
@@ -22,8 +18,9 @@ type NodeInfo[V any] struct {
}
type Graph[T comparable, V any] struct {
alias AliasMap[T]
nodes NodeSet[T]
alias AliasMap[T] // alias -> aliased
aliases DepMap[T] // aliased -> alias
nodes NodeSet[T]
// node info map
nodeInfo map[T]*NodeInfo[V]
@@ -41,6 +38,7 @@ func New[T comparable, V any]() *Graph[T, V] {
dependencies: make(DepMap[T]),
dependents: make(DepMap[T]),
alias: make(AliasMap[T]),
aliases: make(DepMap[T]),
nodeInfo: make(map[T]*NodeInfo[V]),
}
}
@@ -70,7 +68,9 @@ func (g *Graph[T, V]) Alias(node, alias T) error {
if _, ok := g.alias[alias]; ok {
return ErrConflictingAlias
}
g.alias[alias] = node
g.aliases.addNodeToNodeset(node, alias)
return nil
}
@@ -89,15 +89,23 @@ func (g *Graph[T, V]) getAlias(node T) T {
}
func (g *Graph[T, V]) SetNodeInfo(node T, nodeInfo *NodeInfo[V]) {
node = g.getAlias(node)
g.nodeInfo[node] = nodeInfo
g.nodeInfo[g.getAlias(node)] = nodeInfo
}
func (g *Graph[T, V]) GetNodeInfo(node T) *NodeInfo[V] {
node = g.getAlias(node)
return g.nodeInfo[g.getAlias(node)]
}
return g.nodeInfo[node]
// Retrieve aliases of a node.
func (g *Graph[T, V]) GetAliases(node T) []T {
size := len(g.aliases[node])
aliases := make([]T, 0, size)
for alias := range g.aliases[node] {
aliases = append(aliases, alias)
}
return aliases
}
func (g *Graph[T, V]) DependOn(child, parent T) error {
@@ -150,7 +158,9 @@ func (g *Graph[T, V]) String() string {
sb.WriteString(fmt.Sprintf("\t\"%v\" -> \"%v\";\n", parent, child))
}
}
sb.WriteString("}")
return sb.String()
}
@@ -180,13 +190,25 @@ func (g *Graph[T, V]) Leaves() []T {
return leaves
}
// TopoSortedLayers returns a slice of all of the graph nodes in topological sort order. That is,
// if `B` depends on `A`, then `A` is guaranteed to come before `B` in the sorted output.
// The graph is guaranteed to be cycle-free because cycles are detected while building the
// graph. Additionally, the output is grouped into "layers", which are guaranteed to not have
// any dependencies within each layer. This is useful, e.g. when building an execution plan for
// some DAG, in which case each element within each layer could be executed in parallel. If you
// do not need this layered property, use `Graph.TopoSorted()`, which flattens all elements.
// LeavesMap returns a map of leaves with the node as key and the node info value as value.
func (g *Graph[T, V]) LeavesMap() map[T]V {
leaves := make(map[T]V, 0)
for node := range g.nodes {
if _, ok := g.dependencies[node]; !ok {
nodeInfo := g.GetNodeInfo(node)
if nodeInfo == nil {
nodeInfo = &NodeInfo[V]{}
}
leaves[node] = nodeInfo.Value
}
}
return leaves
}
// TopoSortedLayers returns a slice of all of the graph nodes in topological sort order.
func (g *Graph[T, V]) TopoSortedLayers() [][]T {
layers := [][]T{}
@@ -209,6 +231,29 @@ func (g *Graph[T, V]) TopoSortedLayers() [][]T {
return layers
}
// TopoSortedLayerMap returns a slice of all of the graph nodes in topological sort order with their node info
func (g *Graph[T, V]) TopoSortedLayerMap() []map[T]V {
layers := []map[T]V{}
// Copy the graph
shrinkingGraph := g.clone()
for {
leaves := shrinkingGraph.LeavesMap()
if len(leaves) == 0 {
break
}
layers = append(layers, leaves)
for leafNode := range leaves {
shrinkingGraph.remove(leafNode)
}
}
return layers
}
func (dm DepMap[T]) removeFromDepmap(key, node T) {
if nodes := dm[key]; len(nodes) == 1 {
// The only element in the nodeset must be `node`, so we
@@ -240,7 +285,6 @@ func (g *Graph[T, V]) remove(node T) {
}
// TopoSorted returns all the nodes in the graph is topological sort order.
// See also `Graph.TopoSortedLayers()`.
func (g *Graph[T, V]) TopoSorted() []T {
nodeCount := 0
layers := g.TopoSortedLayers()
@@ -279,6 +323,7 @@ func (g *Graph[T, V]) clone() *Graph[T, V] {
dependencies: g.dependencies.copy(),
dependents: g.dependents.copy(),
nodes: g.nodes.copy(),
nodeInfo: g.nodeInfo, // not copied, as it is not modified
}
}