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,
|
config *settings.Configuration, dbExecutor db.Executor, cmdArgs *parser.Arguments,
|
||||||
) error {
|
) error {
|
||||||
if cmdArgs.ExistsArg("i", "install") {
|
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,
|
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/adrg/strutil v0.3.0
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // 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
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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/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 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|
12
install.go
12
install.go
|
@ -634,14 +634,12 @@ func buildInstallPkgbuilds(
|
||||||
satisfied := true
|
satisfied := true
|
||||||
all:
|
all:
|
||||||
for _, pkg := range base {
|
for _, pkg := range base {
|
||||||
for _, deps := range dep.ComputeCombinedDepList(pkg, noDeps, noCheck) {
|
for _, dep := range dep.ComputeCombinedDepList(pkg, noDeps, noCheck) {
|
||||||
for _, dep := range deps {
|
if !dp.AlpmExecutor.LocalSatisfierExists(dep) {
|
||||||
if !dp.AlpmExecutor.LocalSatisfierExists(dep) {
|
satisfied = false
|
||||||
satisfied = false
|
text.Warnln(gotext.Get("%s not satisfied, flushing install queue", dep))
|
||||||
text.Warnln(gotext.Get("%s not satisfied, flushing install queue", dep))
|
|
||||||
|
|
||||||
break all
|
break all
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
150
local_install.go
150
local_install.go
|
@ -2,16 +2,166 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Jguer/aur"
|
||||||
"github.com/Jguer/yay/v11/pkg/db"
|
"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/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(
|
func installLocalPKGBUILD(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cmdArgs *parser.Arguments,
|
cmdArgs *parser.Arguments,
|
||||||
dbExecutor db.Executor,
|
dbExecutor db.Executor,
|
||||||
|
aurClient aur.ClientInterface,
|
||||||
ignoreProviders bool,
|
ignoreProviders bool,
|
||||||
) error {
|
) 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
|
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,15 +230,13 @@ func (dp *Pool) _checkMissing(dep string, stack []string, missing *missing, noDe
|
||||||
missing.Good.Set(dep)
|
missing.Good.Set(dep)
|
||||||
|
|
||||||
combinedDepList := ComputeCombinedDepList(aurPkg, noDeps, noCheckDeps)
|
combinedDepList := ComputeCombinedDepList(aurPkg, noDeps, noCheckDeps)
|
||||||
for _, deps := range combinedDepList {
|
for _, aurDep := range combinedDepList {
|
||||||
for _, aurDep := range deps {
|
if dp.AlpmExecutor.LocalSatisfierExists(aurDep) {
|
||||||
if dp.AlpmExecutor.LocalSatisfierExists(aurDep) {
|
missing.Good.Set(aurDep)
|
||||||
missing.Good.Set(aurDep)
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dp._checkMissing(aurDep, append(stack, aurPkg.Name), missing, noDeps, noCheckDeps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dp._checkMissing(aurDep, append(stack, aurPkg.Name), missing, noDeps, noCheckDeps)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -48,15 +48,13 @@ func (do *Order) orderPkgAur(pkg *aur.Pkg, dp *Pool, runtime, noDeps, noCheckDep
|
||||||
|
|
||||||
delete(dp.Aur, pkg.Name)
|
delete(dp.Aur, pkg.Name)
|
||||||
|
|
||||||
for i, deps := range ComputeCombinedDepList(pkg, noDeps, noCheckDeps) {
|
for i, dep := range ComputeCombinedDepList(pkg, noDeps, noCheckDeps) {
|
||||||
for _, dep := range deps {
|
if aurPkg := dp.findSatisfierAur(dep); aurPkg != nil {
|
||||||
if aurPkg := dp.findSatisfierAur(dep); aurPkg != nil {
|
do.orderPkgAur(aurPkg, dp, runtime && i == 0, noDeps, noCheckDeps)
|
||||||
do.orderPkgAur(aurPkg, dp, runtime && i == 0, noDeps, noCheckDeps)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if repoPkg := dp.findSatisfierRepo(dep); repoPkg != nil {
|
if repoPkg := dp.findSatisfierRepo(dep); repoPkg != nil {
|
||||||
do.orderPkgRepo(repoPkg, dp, runtime && i == 0)
|
do.orderPkgRepo(repoPkg, dp, runtime && i == 0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,17 +275,17 @@ func (dp *Pool) cacheAURPackages(ctx context.Context, _pkgs stringset.StringSet,
|
||||||
|
|
||||||
// Compute dependency lists used in Package dep searching and ordering.
|
// Compute dependency lists used in Package dep searching and ordering.
|
||||||
// Order sensitive TOFIX.
|
// Order sensitive TOFIX.
|
||||||
func ComputeCombinedDepList(pkg *aur.Pkg, noDeps, noCheckDeps bool) [][]string {
|
func ComputeCombinedDepList(pkg *aur.Pkg, noDeps, noCheckDeps bool) []string {
|
||||||
combinedDepList := make([][]string, 0, 3)
|
combinedDepList := make([]string, 0, len(pkg.Depends)+len(pkg.MakeDepends)+len(pkg.CheckDepends))
|
||||||
|
|
||||||
if !noDeps {
|
if !noDeps {
|
||||||
combinedDepList = append(combinedDepList, pkg.Depends)
|
combinedDepList = append(combinedDepList, pkg.Depends...)
|
||||||
}
|
}
|
||||||
|
|
||||||
combinedDepList = append(combinedDepList, pkg.MakeDepends)
|
combinedDepList = append(combinedDepList, pkg.MakeDepends...)
|
||||||
|
|
||||||
if !noCheckDeps {
|
if !noCheckDeps {
|
||||||
combinedDepList = append(combinedDepList, pkg.CheckDepends)
|
combinedDepList = append(combinedDepList, pkg.CheckDepends...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return combinedDepList
|
return combinedDepList
|
||||||
|
@ -326,10 +326,8 @@ func (dp *Pool) resolveAURPackages(ctx context.Context,
|
||||||
dp.Aur[pkg.Name] = pkg
|
dp.Aur[pkg.Name] = pkg
|
||||||
|
|
||||||
combinedDepList := ComputeCombinedDepList(pkg, noDeps, noCheckDeps)
|
combinedDepList := ComputeCombinedDepList(pkg, noDeps, noCheckDeps)
|
||||||
for _, deps := range combinedDepList {
|
for _, dep := range combinedDepList {
|
||||||
for _, dep := range deps {
|
newPackages.Set(dep)
|
||||||
newPackages.Set(dep)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Pkg = aur.Pkg
|
||||||
// of packages exceeds the number set in config.RequestSplitN.
|
// of packages exceeds the number set in config.RequestSplitN.
|
||||||
// If the number does exceed config.RequestSplitN multiple aur requests will be
|
// If the number does exceed config.RequestSplitN multiple aur requests will be
|
||||||
// performed concurrently.
|
// 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))
|
info := make([]*Pkg, 0, len(names))
|
||||||
seen := make(map[string]int)
|
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