mirror of
https://github.com/Jguer/yay
synced 2024-10-02 22:33:47 +00:00
wip
This commit is contained in:
parent
859b7c703f
commit
b054828aa8
2
cmd.go
2
cmd.go
|
@ -330,7 +330,7 @@ func handleUpgrade(ctx context.Context,
|
|||
config *settings.Configuration, dbExecutor db.Executor, cmdArgs *parser.Arguments,
|
||||
) error {
|
||||
if cmdArgs.ExistsArg("i", "install") {
|
||||
return installLocalPKGBUILD(ctx, cmdArgs, dbExecutor, false)
|
||||
return installLocalPKGBUILD(ctx, cmdArgs, dbExecutor, config.Runtime.AURClient, false)
|
||||
}
|
||||
|
||||
return config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
|
||||
|
|
3
go.mod
3
go.mod
|
@ -19,8 +19,9 @@ require (
|
|||
github.com/adrg/strutil v0.3.0
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
|
2
go.sum
2
go.sum
|
@ -23,6 +23,8 @@ github.com/leonelquinteros/gotext v1.5.0 h1:ODY7LzLpZWWSJdAHnzhreOr6cwLXTAmc914F
|
|||
github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
|
@ -634,8 +634,7 @@ func buildInstallPkgbuilds(
|
|||
satisfied := true
|
||||
all:
|
||||
for _, pkg := range base {
|
||||
for _, deps := range dep.ComputeCombinedDepList(pkg, noDeps, noCheck) {
|
||||
for _, dep := range deps {
|
||||
for _, dep := range dep.ComputeCombinedDepList(pkg, noDeps, noCheck) {
|
||||
if !dp.AlpmExecutor.LocalSatisfierExists(dep) {
|
||||
satisfied = false
|
||||
text.Warnln(gotext.Get("%s not satisfied, flushing install queue", dep))
|
||||
|
@ -644,7 +643,6 @@ func buildInstallPkgbuilds(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !satisfied || !config.BatchInstall {
|
||||
err = doInstall(ctx, arguments, cmdArgs, deps, exp)
|
||||
|
|
150
local_install.go
150
local_install.go
|
@ -2,16 +2,166 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Jguer/aur"
|
||||
"github.com/Jguer/yay/v11/pkg/db"
|
||||
"github.com/Jguer/yay/v11/pkg/dep"
|
||||
"github.com/Jguer/yay/v11/pkg/query"
|
||||
"github.com/Jguer/yay/v11/pkg/settings/parser"
|
||||
"github.com/Jguer/yay/v11/pkg/topo"
|
||||
gosrc "github.com/Morganamilo/go-srcinfo"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func archStringToString(alpmArches []string, archString []gosrc.ArchString) []string {
|
||||
pkgs := make([]string, 0, len(archString))
|
||||
|
||||
for _, arch := range archString {
|
||||
if alpmArchIsSupported(alpmArches, arch.Arch) {
|
||||
pkgs = append(pkgs, arch.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func makeAURPKGFromSrcinfo(dbExecutor db.Executor, srcInfo *gosrc.Srcinfo) ([]aur.Pkg, error) {
|
||||
pkgs := make([]aur.Pkg, 0, 1)
|
||||
|
||||
alpmArch, err := dbExecutor.AlpmArchitectures()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
alpmArch = append(alpmArch, "") // srcinfo assumes no value as ""
|
||||
|
||||
for _, pkg := range srcInfo.Packages {
|
||||
pkgs = append(pkgs, aur.Pkg{
|
||||
ID: 0,
|
||||
Name: pkg.Pkgname,
|
||||
PackageBaseID: 0,
|
||||
PackageBase: srcInfo.Pkgbase,
|
||||
Version: srcInfo.Version(),
|
||||
Description: pkg.Pkgdesc,
|
||||
URL: pkg.URL,
|
||||
Depends: append(archStringToString(alpmArch, pkg.Depends), archStringToString(alpmArch, srcInfo.Package.Depends)...),
|
||||
MakeDepends: archStringToString(alpmArch, srcInfo.PackageBase.MakeDepends),
|
||||
CheckDepends: archStringToString(alpmArch, srcInfo.PackageBase.CheckDepends),
|
||||
Conflicts: append(archStringToString(alpmArch, pkg.Conflicts), archStringToString(alpmArch, srcInfo.Package.Conflicts)...),
|
||||
Provides: append(archStringToString(alpmArch, pkg.Provides), archStringToString(alpmArch, srcInfo.Package.Provides)...),
|
||||
Replaces: append(archStringToString(alpmArch, pkg.Replaces), archStringToString(alpmArch, srcInfo.Package.Replaces)...),
|
||||
OptDepends: []string{},
|
||||
Groups: pkg.Groups,
|
||||
License: pkg.License,
|
||||
Keywords: []string{},
|
||||
})
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func splitDep(dep string) (pkg, mod, ver string) {
|
||||
split := strings.FieldsFunc(dep, func(c rune) bool {
|
||||
match := c == '>' || c == '<' || c == '='
|
||||
|
||||
if match {
|
||||
mod += string(c)
|
||||
}
|
||||
|
||||
return match
|
||||
})
|
||||
|
||||
if len(split) == 0 {
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
if len(split) == 1 {
|
||||
return split[0], "", ""
|
||||
}
|
||||
|
||||
return split[0], mod, split[1]
|
||||
}
|
||||
|
||||
func installLocalPKGBUILD(
|
||||
ctx context.Context,
|
||||
cmdArgs *parser.Arguments,
|
||||
dbExecutor db.Executor,
|
||||
aurClient aur.ClientInterface,
|
||||
ignoreProviders bool,
|
||||
) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, gotext.Get("failed to retrieve working directory"))
|
||||
}
|
||||
|
||||
if len(cmdArgs.Targets) > 1 {
|
||||
return errors.New(gotext.Get("only one target is allowed"))
|
||||
}
|
||||
|
||||
if len(cmdArgs.Targets) == 1 {
|
||||
wd = cmdArgs.Targets[0]
|
||||
}
|
||||
|
||||
pkgbuild, err := gosrc.ParseFile(filepath.Join(wd, ".SRCINFO"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, gotext.Get("failed to parse .SRCINFO"))
|
||||
}
|
||||
|
||||
aurPkgs, err := makeAURPKGFromSrcinfo(dbExecutor, pkgbuild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph := topo.New[string]()
|
||||
|
||||
for _, pkg := range aurPkgs {
|
||||
depSlice := dep.ComputeCombinedDepList(&pkg, false, false)
|
||||
addNodes(dbExecutor, aurClient, pkg.Name, pkg.PackageBase, depSlice, graph)
|
||||
}
|
||||
|
||||
fmt.Println(graph)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addNodes(dbExecutor db.Executor, aurClient aur.ClientInterface, pkgName string, pkgBase string, deps []string, graph *topo.Graph[string]) {
|
||||
graph.AddNode(pkgBase)
|
||||
graph.Alias(pkgBase, pkgName)
|
||||
|
||||
for _, depString := range deps {
|
||||
depName, _, _ := splitDep(depString)
|
||||
|
||||
if dbExecutor.LocalSatisfierExists(depString) {
|
||||
continue
|
||||
}
|
||||
|
||||
graph.DependOn(depName, pkgBase)
|
||||
|
||||
if alpmPkg := dbExecutor.SyncSatisfier(depString); alpmPkg != nil {
|
||||
newDeps := alpmPkg.Depends().Slice()
|
||||
newDepsSlice := make([]string, 0, len(newDeps))
|
||||
|
||||
for _, newDep := range newDeps {
|
||||
newDepsSlice = append(newDepsSlice, newDep.Name)
|
||||
}
|
||||
|
||||
addNodes(dbExecutor, aurClient, alpmPkg.Name(), alpmPkg.Base(), newDepsSlice, graph)
|
||||
}
|
||||
|
||||
warnings := query.AURWarnings{}
|
||||
if aurPkgs, _ := query.AURInfo(context.TODO(), aurClient, []string{depName}, &warnings, 1); len(aurPkgs) != 0 {
|
||||
pkg := aurPkgs[0]
|
||||
newDeps := dep.ComputeCombinedDepList(pkg, false, false)
|
||||
newDepsSlice := make([]string, 0, len(newDeps))
|
||||
|
||||
addNodes(dbExecutor, aurClient, pkg.PackageBase, pkg.Name, newDepsSlice, graph)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -230,8 +230,7 @@ func (dp *Pool) _checkMissing(dep string, stack []string, missing *missing, noDe
|
|||
missing.Good.Set(dep)
|
||||
|
||||
combinedDepList := ComputeCombinedDepList(aurPkg, noDeps, noCheckDeps)
|
||||
for _, deps := range combinedDepList {
|
||||
for _, aurDep := range deps {
|
||||
for _, aurDep := range combinedDepList {
|
||||
if dp.AlpmExecutor.LocalSatisfierExists(aurDep) {
|
||||
missing.Good.Set(aurDep)
|
||||
continue
|
||||
|
@ -239,7 +238,6 @@ func (dp *Pool) _checkMissing(dep string, stack []string, missing *missing, noDe
|
|||
|
||||
dp._checkMissing(aurDep, append(stack, aurPkg.Name), missing, noDeps, noCheckDeps)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -48,8 +48,7 @@ func (do *Order) orderPkgAur(pkg *aur.Pkg, dp *Pool, runtime, noDeps, noCheckDep
|
|||
|
||||
delete(dp.Aur, pkg.Name)
|
||||
|
||||
for i, deps := range ComputeCombinedDepList(pkg, noDeps, noCheckDeps) {
|
||||
for _, dep := range deps {
|
||||
for i, dep := range ComputeCombinedDepList(pkg, noDeps, noCheckDeps) {
|
||||
if aurPkg := dp.findSatisfierAur(dep); aurPkg != nil {
|
||||
do.orderPkgAur(aurPkg, dp, runtime && i == 0, noDeps, noCheckDeps)
|
||||
}
|
||||
|
@ -58,7 +57,6 @@ func (do *Order) orderPkgAur(pkg *aur.Pkg, dp *Pool, runtime, noDeps, noCheckDep
|
|||
do.orderPkgRepo(repoPkg, dp, runtime && i == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, base := range do.Aur {
|
||||
if base.Pkgbase() == pkg.PackageBase {
|
||||
|
|
|
@ -275,17 +275,17 @@ func (dp *Pool) cacheAURPackages(ctx context.Context, _pkgs stringset.StringSet,
|
|||
|
||||
// Compute dependency lists used in Package dep searching and ordering.
|
||||
// Order sensitive TOFIX.
|
||||
func ComputeCombinedDepList(pkg *aur.Pkg, noDeps, noCheckDeps bool) [][]string {
|
||||
combinedDepList := make([][]string, 0, 3)
|
||||
func ComputeCombinedDepList(pkg *aur.Pkg, noDeps, noCheckDeps bool) []string {
|
||||
combinedDepList := make([]string, 0, len(pkg.Depends)+len(pkg.MakeDepends)+len(pkg.CheckDepends))
|
||||
|
||||
if !noDeps {
|
||||
combinedDepList = append(combinedDepList, pkg.Depends)
|
||||
combinedDepList = append(combinedDepList, pkg.Depends...)
|
||||
}
|
||||
|
||||
combinedDepList = append(combinedDepList, pkg.MakeDepends)
|
||||
combinedDepList = append(combinedDepList, pkg.MakeDepends...)
|
||||
|
||||
if !noCheckDeps {
|
||||
combinedDepList = append(combinedDepList, pkg.CheckDepends)
|
||||
combinedDepList = append(combinedDepList, pkg.CheckDepends...)
|
||||
}
|
||||
|
||||
return combinedDepList
|
||||
|
@ -326,12 +326,10 @@ func (dp *Pool) resolveAURPackages(ctx context.Context,
|
|||
dp.Aur[pkg.Name] = pkg
|
||||
|
||||
combinedDepList := ComputeCombinedDepList(pkg, noDeps, noCheckDeps)
|
||||
for _, deps := range combinedDepList {
|
||||
for _, dep := range deps {
|
||||
for _, dep := range combinedDepList {
|
||||
newPackages.Set(dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for dep := range newPackages {
|
||||
if dp.hasSatisfier(dep) {
|
||||
|
|
|
@ -19,7 +19,7 @@ type Pkg = aur.Pkg
|
|||
// of packages exceeds the number set in config.RequestSplitN.
|
||||
// If the number does exceed config.RequestSplitN multiple aur requests will be
|
||||
// performed concurrently.
|
||||
func AURInfo(ctx context.Context, aurClient *aur.Client, names []string, warnings *AURWarnings, splitN int) ([]*Pkg, error) {
|
||||
func AURInfo(ctx context.Context, aurClient aur.ClientInterface, names []string, warnings *AURWarnings, splitN int) ([]*Pkg, error) {
|
||||
info := make([]*Pkg, 0, len(names))
|
||||
seen := make(map[string]int)
|
||||
|
||||
|
|
292
pkg/topo/dep.go
Normal file
292
pkg/topo/dep.go
Normal file
|
@ -0,0 +1,292 @@
|
|||
package topo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Mapable interface {
|
||||
Key() string
|
||||
}
|
||||
|
||||
type (
|
||||
AliasMap[T comparable] map[T]T
|
||||
NodeSet[T comparable] map[T]bool
|
||||
DepMap[T comparable] map[T]NodeSet[T]
|
||||
)
|
||||
|
||||
type Graph[T comparable] struct {
|
||||
alias AliasMap[T]
|
||||
nodes NodeSet[T]
|
||||
|
||||
// `dependencies` tracks child -> parents.
|
||||
dependencies DepMap[T]
|
||||
// `dependents` tracks parent -> children.
|
||||
dependents DepMap[T]
|
||||
// Keep track of the nodes of the graph themselves.
|
||||
}
|
||||
|
||||
func New[T comparable]() *Graph[T] {
|
||||
return &Graph[T]{
|
||||
nodes: make(NodeSet[T]),
|
||||
dependencies: make(DepMap[T]),
|
||||
dependents: make(DepMap[T]),
|
||||
alias: make(AliasMap[T]),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graph[T]) Alias(node, alias T) error {
|
||||
if alias == node {
|
||||
return ErrSelfReferential
|
||||
}
|
||||
|
||||
// add node
|
||||
g.nodes[node] = true
|
||||
|
||||
// add alias
|
||||
if _, ok := g.alias[alias]; ok {
|
||||
return ErrConflictingAlias
|
||||
}
|
||||
g.alias[alias] = node
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph[T]) AddNode(node T) {
|
||||
// check aliases
|
||||
if aliasNode, ok := g.alias[node]; ok {
|
||||
node = aliasNode
|
||||
}
|
||||
|
||||
g.nodes[node] = true
|
||||
}
|
||||
|
||||
func (g *Graph[T]) DependOn(child, parent T) error {
|
||||
if child == parent {
|
||||
return ErrSelfReferential
|
||||
}
|
||||
|
||||
if g.DependsOn(parent, child) {
|
||||
return ErrCircular
|
||||
}
|
||||
|
||||
g.AddNode(parent)
|
||||
g.AddNode(child)
|
||||
|
||||
// Add nodes.
|
||||
g.nodes[parent] = true
|
||||
g.nodes[child] = true
|
||||
|
||||
// Add edges.
|
||||
g.dependents.addNodeToNodeset(parent, child)
|
||||
g.dependencies.addNodeToNodeset(child, parent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph[T]) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("digraph {\n")
|
||||
// sb.WriteString("rankdir=LR;\n")
|
||||
sb.WriteString("node [shape = record, ordering=out];\n")
|
||||
for node := range g.nodes {
|
||||
sb.WriteString(fmt.Sprintf("\t\"%v\";\n", node))
|
||||
}
|
||||
for parent, children := range g.dependencies {
|
||||
for child := range children {
|
||||
sb.WriteString(fmt.Sprintf("\t\"%v\" -> \"%v\";\n", parent, child))
|
||||
}
|
||||
}
|
||||
sb.WriteString("}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (g *Graph[T]) DependsOn(child, parent T) bool {
|
||||
deps := g.Dependencies(child)
|
||||
_, ok := deps[parent]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (g *Graph[T]) HasDependent(parent, child T) bool {
|
||||
deps := g.Dependents(parent)
|
||||
_, ok := deps[child]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (g *Graph[T]) Leaves() []T {
|
||||
leaves := make([]T, 0)
|
||||
|
||||
for node := range g.nodes {
|
||||
if _, ok := g.dependencies[node]; !ok {
|
||||
leaves = append(leaves, node)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
func (g *Graph[T]) TopoSortedLayers() [][]T {
|
||||
layers := [][]T{}
|
||||
|
||||
// Copy the graph
|
||||
shrinkingGraph := g.clone()
|
||||
|
||||
for {
|
||||
leaves := shrinkingGraph.Leaves()
|
||||
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
|
||||
// can delete the entry entirely.
|
||||
delete(dm, key)
|
||||
} else {
|
||||
// Otherwise, remove the single node from the nodeset.
|
||||
delete(nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graph[T]) remove(node T) {
|
||||
// Remove edges from things that depend on `node`.
|
||||
for dependent := range g.dependents[node] {
|
||||
g.dependencies.removeFromDepmap(dependent, node)
|
||||
}
|
||||
|
||||
delete(g.dependents, node)
|
||||
|
||||
// Remove all edges from node to the things it depends on.
|
||||
for dependency := range g.dependencies[node] {
|
||||
g.dependents.removeFromDepmap(dependency, node)
|
||||
}
|
||||
|
||||
delete(g.dependencies, node)
|
||||
|
||||
// Finally, remove the node itself.
|
||||
delete(g.nodes, node)
|
||||
}
|
||||
|
||||
// TopoSorted returns all the nodes in the graph is topological sort order.
|
||||
// See also `Graph.TopoSortedLayers()`.
|
||||
func (g *Graph[T]) TopoSorted() []T {
|
||||
nodeCount := 0
|
||||
layers := g.TopoSortedLayers()
|
||||
|
||||
for _, layer := range layers {
|
||||
nodeCount += len(layer)
|
||||
}
|
||||
|
||||
allNodes := make([]T, 0, nodeCount)
|
||||
|
||||
for _, layer := range layers {
|
||||
allNodes = append(allNodes, layer...)
|
||||
}
|
||||
|
||||
return allNodes
|
||||
}
|
||||
|
||||
func (g *Graph[T]) Dependencies(child T) NodeSet[T] {
|
||||
return g.buildTransitive(child, g.immediateDependencies)
|
||||
}
|
||||
|
||||
func (g *Graph[T]) immediateDependencies(node T) NodeSet[T] {
|
||||
return g.dependencies[node]
|
||||
}
|
||||
|
||||
func (g *Graph[T]) Dependents(parent T) NodeSet[T] {
|
||||
return g.buildTransitive(parent, g.immediateDependents)
|
||||
}
|
||||
|
||||
func (g *Graph[T]) immediateDependents(node T) NodeSet[T] {
|
||||
return g.dependents[node]
|
||||
}
|
||||
|
||||
func (g *Graph[T]) clone() *Graph[T] {
|
||||
return &Graph[T]{
|
||||
dependencies: g.dependencies.copy(),
|
||||
dependents: g.dependents.copy(),
|
||||
nodes: g.nodes.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
// buildTransitive starts at `root` and continues calling `nextFn` to keep discovering more nodes until
|
||||
// the graph cannot produce any more. It returns the set of all discovered nodes.
|
||||
func (g *Graph[T]) buildTransitive(root T, nextFn func(T) NodeSet[T]) NodeSet[T] {
|
||||
if _, ok := g.nodes[root]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make(NodeSet[T])
|
||||
searchNext := []T{root}
|
||||
|
||||
for len(searchNext) > 0 {
|
||||
// List of new nodes from this layer of the dependency graph. This is
|
||||
// assigned to `searchNext` at the end of the outer "discovery" loop.
|
||||
discovered := []T{}
|
||||
|
||||
for _, node := range searchNext {
|
||||
// For each node to discover, find the next nodes.
|
||||
for nextNode := range nextFn(node) {
|
||||
// If we have not seen the node before, add it to the output as well
|
||||
// as the list of nodes to traverse in the next iteration.
|
||||
if _, ok := out[nextNode]; !ok {
|
||||
out[nextNode] = true
|
||||
|
||||
discovered = append(discovered, nextNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchNext = discovered
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (s NodeSet[T]) copy() NodeSet[T] {
|
||||
out := make(NodeSet[T], len(s))
|
||||
for k, v := range s {
|
||||
out[k] = v
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (m DepMap[T]) copy() DepMap[T] {
|
||||
out := make(DepMap[T], len(m))
|
||||
for k, v := range m {
|
||||
out[k] = v.copy()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (dm DepMap[T]) addNodeToNodeset(key, node T) {
|
||||
nodes, ok := dm[key]
|
||||
if !ok {
|
||||
nodes = make(NodeSet[T])
|
||||
dm[key] = nodes
|
||||
}
|
||||
|
||||
nodes[node] = true
|
||||
}
|
7
pkg/topo/errors.go
Normal file
7
pkg/topo/errors.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package topo
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrSelfReferential = errors.New("self-referential dependencies not allowed")
|
||||
var ErrConflictingAlias = errors.New("alias already defined")
|
||||
var ErrCircular = errors.New("circular dependencies not allowed")
|
Loading…
Reference in a new issue