Browse Source

topo sorted map

jguer 2 years ago
parent
commit
4a73dfb0ca
3 changed files with 231 additions and 44 deletions
  1. 54 3
      local_install.go
  2. 113 22
      pkg/dep/depGraph.go
  3. 64 19
      pkg/topo/dep.go

+ 54 - 3
local_install.go

@@ -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
 }

+ 113 - 22
pkg/dep/depGraph.go

@@ -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
 )
 
+func (r Reason) String() string {
+	return reasonNames[r]
+}
+
+func (s Source) String() string {
+	return sourceNames[s]
+}
+
 const (
-	Explicit packageType = iota
-	Dep
-	MakeDep
-	CheckDep
+	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 sourceType = iota
+	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
pkg/topo/dep.go

@@ -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)]
+}
+
+// Retrieve aliases of a node.
+func (g *Graph[T, V]) GetAliases(node T) []T {
+	size := len(g.aliases[node])
+	aliases := make([]T, 0, size)
 
-	return g.nodeInfo[node]
+	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
 	}
 }