Browse Source

add local graph util

jguer 2 years ago
parent
commit
f7286b25ae
10 changed files with 369 additions and 94 deletions
  1. 1 8
      go.mod
  2. 0 8
      go.sum
  3. 1 58
      main.go
  4. 97 0
      pkg/cmd/graph/main.go
  5. 177 0
      pkg/dep/depGraph.go
  6. 9 4
      pkg/metadata/metadata_aur.go
  7. 64 0
      pkg/settings/pacman.go
  8. 2 2
      main_test.go
  9. 13 11
      pkg/topo/dep.go
  10. 5 3
      pkg/topo/errors.go

+ 1 - 8
go.mod

@@ -27,15 +27,8 @@ require (
 require github.com/tidwall/gjson v1.14.3
 
 require (
-	github.com/josharian/intern v1.0.0 // indirect
-	github.com/pkg/profile v1.6.0 // indirect
-)
-
-require (
-	github.com/goccy/go-json v0.9.11 // indirect
-	github.com/itchyny/gojq v0.12.8 // indirect
+	github.com/itchyny/gojq v0.12.8
 	github.com/itchyny/timefmt-go v0.1.3 // indirect
-	github.com/mailru/easyjson v0.7.7
 	github.com/ohler55/ojg v1.14.4
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect

+ 0 - 8
go.sum

@@ -17,8 +17,6 @@ github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEh
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
-github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
@@ -26,12 +24,8 @@ github.com/itchyny/gojq v0.12.8 h1:Zxcwq8w4IeR8JJYEtoG2MWJZUv0RGY6QqJcO1cqV8+A=
 github.com/itchyny/gojq v0.12.8/go.mod h1:gE2kZ9fVRU0+JAksaTzjIlgnCa2akU+a1V0WXgJQN5c=
 github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
 github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
-github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/leonelquinteros/gotext v1.5.0 h1:ODY7LzLpZWWSJdAHnzhreOr6cwLXTAmc914FOauSkBM=
 github.com/leonelquinteros/gotext v1.5.0/go.mod h1:OCiUVHuhP9LGFBQ1oAmdtNCHJCiHiQA8lf4nAifHkr0=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
@@ -40,8 +34,6 @@ github.com/ohler55/ojg v1.14.4 h1:L2ds8AlB5t/QbqSfhRwvagJzQ7pgmdrefMIypQs0Xik=
 github.com/ohler55/ojg v1.14.4/go.mod h1:7Ghirupn8NC8hSSDpI0gcjorPxj+vSVIONDWfliHR1k=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
-github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=

+ 1 - 58
main.go

@@ -2,14 +2,11 @@ package main // import "github.com/Jguer/yay"
 
 import (
 	"context"
-	"fmt"
 	"os"
 	"os/exec"
 	"runtime/debug"
 
-	pacmanconf "github.com/Morganamilo/go-pacmanconf"
 	"github.com/leonelquinteros/gotext"
-	"golang.org/x/term"
 
 	"github.com/Jguer/yay/v11/pkg/db"
 	"github.com/Jguer/yay/v11/pkg/db/ialpm"
@@ -35,60 +32,6 @@ func initGotext() {
 	}
 }
 
-func initAlpm(cmdArgs *parser.Arguments, pacmanConfigPath string) (*pacmanconf.Config, bool, error) {
-	root := "/"
-	if value, _, exists := cmdArgs.GetArg("root", "r"); exists {
-		root = value
-	}
-
-	pacmanConf, stderr, err := pacmanconf.PacmanConf("--config", pacmanConfigPath, "--root", root)
-	if err != nil {
-		cmdErr := err
-		if stderr != "" {
-			cmdErr = fmt.Errorf("%s\n%s", err, stderr)
-		}
-
-		return nil, false, cmdErr
-	}
-
-	if dbPath, _, exists := cmdArgs.GetArg("dbpath", "b"); exists {
-		pacmanConf.DBPath = dbPath
-	}
-
-	if arch := cmdArgs.GetArgs("arch"); arch != nil {
-		pacmanConf.Architecture = append(pacmanConf.Architecture, arch...)
-	}
-
-	if ignoreArray := cmdArgs.GetArgs("ignore"); ignoreArray != nil {
-		pacmanConf.IgnorePkg = append(pacmanConf.IgnorePkg, ignoreArray...)
-	}
-
-	if ignoreGroupsArray := cmdArgs.GetArgs("ignoregroup"); ignoreGroupsArray != nil {
-		pacmanConf.IgnoreGroup = append(pacmanConf.IgnoreGroup, ignoreGroupsArray...)
-	}
-
-	if cacheArray := cmdArgs.GetArgs("cachedir"); cacheArray != nil {
-		pacmanConf.CacheDir = cacheArray
-	}
-
-	if gpgDir, _, exists := cmdArgs.GetArg("gpgdir"); exists {
-		pacmanConf.GPGDir = gpgDir
-	}
-
-	useColor := pacmanConf.Color && term.IsTerminal(int(os.Stdout.Fd()))
-
-	switch value, _, _ := cmdArgs.GetArg("color"); value {
-	case "always":
-		useColor = true
-	case "auto":
-		useColor = term.IsTerminal(int(os.Stdout.Fd()))
-	case "never":
-		useColor = false
-	}
-
-	return pacmanConf, useColor, nil
-}
-
 func main() {
 	var (
 		err error
@@ -155,7 +98,7 @@ func main() {
 
 	var useColor bool
 
-	config.Runtime.PacmanConf, useColor, err = initAlpm(cmdArgs, config.PacmanConf)
+	config.Runtime.PacmanConf, useColor, err = settings.RetrievePacmanConfig(cmdArgs, config.PacmanConf)
 	if err != nil {
 		if str := err.Error(); str != "" {
 			text.Errorln(str)

+ 97 - 0
pkg/cmd/graph/main.go

@@ -0,0 +1,97 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/Jguer/yay/v11/pkg/db/ialpm"
+	"github.com/Jguer/yay/v11/pkg/dep"
+	"github.com/Jguer/yay/v11/pkg/metadata"
+	"github.com/Jguer/yay/v11/pkg/settings"
+	"github.com/Jguer/yay/v11/pkg/settings/parser"
+	"github.com/Jguer/yay/v11/pkg/text"
+	"github.com/leonelquinteros/gotext"
+	"github.com/pkg/errors"
+)
+
+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 handleCmd() error {
+	config, err := settings.NewConfig("")
+	if err != nil {
+		return err
+	}
+
+	cmdArgs := parser.MakeArguments()
+	if err := config.ParseCommandLine(cmdArgs); err != nil {
+		return err
+	}
+
+	pacmanConf, _, err := settings.RetrievePacmanConfig(cmdArgs, config.PacmanConf)
+	if err != nil {
+		return err
+	}
+
+	dbExecutor, err := ialpm.NewExecutor(pacmanConf)
+	if err != nil {
+		return err
+	}
+
+	aurCache, err := metadata.NewAURCache(filepath.Join(config.BuildDir, "aur.json"))
+	if err != nil {
+		return errors.Wrap(err, gotext.Get("failed to retrieve aur Cache"))
+	}
+
+	grapher := dep.NewGrapher(dbExecutor, aurCache, true, settings.NoConfirm, os.Stdout)
+
+	return graphPackage(grapher, cmdArgs.Targets)
+}
+
+func main() {
+	if err := handleCmd(); err != nil {
+		text.Errorln(err)
+		os.Exit(1)
+	}
+}
+
+func graphPackage(
+	grapher *dep.Grapher,
+	targets []string,
+) error {
+	if len(targets) != 1 {
+		return errors.New(gotext.Get("only one target is allowed"))
+	}
+
+	graph, err := grapher.GraphFromAURCache([]string{targets[0]})
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintln(os.Stdout, graph.String())
+	fmt.Fprintln(os.Stdout, graph.TopoSortedLayers())
+	fmt.Fprintln(os.Stdout, graph.TopoSorted())
+
+	return nil
+}

+ 177 - 0
pkg/dep/depGraph.go

@@ -0,0 +1,177 @@
+package dep
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+
+	"github.com/Jguer/yay/v11/pkg/db"
+	"github.com/Jguer/yay/v11/pkg/metadata"
+	aur "github.com/Jguer/yay/v11/pkg/query"
+	"github.com/Jguer/yay/v11/pkg/text"
+	"github.com/Jguer/yay/v11/pkg/topo"
+	"github.com/leonelquinteros/gotext"
+)
+
+type Grapher struct {
+	dbExecutor db.Executor
+	aurCache   *metadata.AURCache
+	fullGraph  bool // If true, the graph will include all dependencies including already installed ones or repo
+	noConfirm  bool
+	w          io.Writer // output writer
+}
+
+func NewGrapher(dbExecutor db.Executor, aurCache *metadata.AURCache, fullGraph, noConfirm bool, output io.Writer) *Grapher {
+	return &Grapher{
+		dbExecutor: dbExecutor,
+		aurCache:   aurCache,
+		fullGraph:  fullGraph,
+		noConfirm:  noConfirm,
+		w:          output,
+	}
+}
+
+func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string], error) {
+	graph := topo.New[string]()
+
+	for _, target := range targets {
+		aurPkgs, _ := g.aurCache.FindPackage(target)
+		pkg := provideMenu(g.w, target, aurPkgs, g.noConfirm)
+
+		depSlice := ComputeCombinedDepList(pkg, false, false)
+		g.addNodes(graph, pkg.Name, depSlice)
+	}
+
+	return graph, nil
+}
+
+func (g *Grapher) addNodes(
+	graph *topo.Graph[string],
+	parentPkgName string,
+	deps []string,
+) {
+	for _, depString := range deps {
+		depName, _, _ := splitDep(depString)
+
+		if g.dbExecutor.LocalSatisfierExists(depString) {
+			if g.fullGraph {
+				graph.SetNodeInfo(depName, &topo.NodeInfo{Color: "green"})
+				if err := graph.DependOn(depName, parentPkgName); err != nil {
+					text.Warnln(depName, parentPkgName, err)
+				}
+			}
+			continue
+		}
+
+		if graph.Exists(depName) {
+			if err := graph.DependOn(depName, parentPkgName); err != nil {
+				text.Warnln(depName, parentPkgName, err)
+			}
+
+			continue
+		}
+
+		// Check ALPM
+		if alpmPkg := g.dbExecutor.SyncSatisfier(depString); alpmPkg != nil {
+			if err := graph.DependOn(alpmPkg.Name(), parentPkgName); err != nil {
+				text.Warnln("repo dep warn:", depName, parentPkgName, err)
+			}
+
+			graph.SetNodeInfo(alpmPkg.Name(), &topo.NodeInfo{Color: "blue"})
+
+			if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && g.fullGraph {
+				newDepsSlice := make([]string, 0, len(newDeps))
+				for _, newDep := range newDeps {
+					newDepsSlice = append(newDepsSlice, newDep.Name)
+				}
+
+				g.addNodes(graph, alpmPkg.Name(), newDepsSlice)
+			}
+
+			continue
+		}
+
+		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)
+				g.aurCache.SetProvideCache(depName, []*aur.Pkg{pkg})
+			}
+
+			if err := graph.Alias(pkg.PackageBase, pkg.Name); err != nil {
+				text.Warnln("aur alias warn:", pkg.PackageBase, pkg.Name, err)
+			}
+
+			if err := graph.DependOn(pkg.PackageBase, parentPkgName); err != nil {
+				text.Warnln("aur dep warn:", pkg.PackageBase, parentPkgName, err)
+			}
+
+			graph.SetNodeInfo(pkg.PackageBase, &topo.NodeInfo{Color: "lightgreen"})
+
+			if newDeps := ComputeCombinedDepList(pkg, false, false); len(newDeps) != 0 {
+				g.addNodes(graph, pkg.Name, newDeps)
+			}
+
+			continue
+		}
+	}
+}
+
+func provideMenu(w io.Writer, dep string, options []*aur.Pkg, noConfirm bool) *aur.Pkg {
+	size := len(options)
+	if size == 1 {
+		return options[0]
+	}
+
+	str := text.Bold(gotext.Get("There are %d providers available for %s:", size, dep))
+	str += "\n"
+
+	size = 1
+	str += text.SprintOperationInfo(gotext.Get("Repository AUR"), "\n    ")
+
+	for _, pkg := range options {
+		str += fmt.Sprintf("%d) %s ", size, pkg.Name)
+		size++
+	}
+
+	text.OperationInfoln(str)
+
+	for {
+		fmt.Fprintln(w, gotext.Get("\nEnter a number (default=1): "))
+
+		if noConfirm {
+			fmt.Fprintln(w, "1")
+
+			return options[0]
+		}
+
+		numberBuf, err := text.GetInput("", false)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, err)
+
+			break
+		}
+
+		if numberBuf == "" {
+			return options[0]
+		}
+
+		num, err := strconv.Atoi(numberBuf)
+		if err != nil {
+			text.Errorln(gotext.Get("invalid number: %s", numberBuf))
+
+			continue
+		}
+
+		if num < 1 || num >= size {
+			text.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size-1))
+
+			continue
+		}
+
+		return options[num-1]
+	}
+
+	return nil
+}

+ 9 - 4
pkg/metadata/metadata_aur.go

@@ -40,18 +40,23 @@ func (a *AURCache) DebugInfo() {
 	fmt.Println("Cache Hits", a.cacheHits)
 }
 
-func (a *AURCache) FindDep(depName string) ([]*aur.Pkg, error) {
-	if pkgs, ok := a.provideCache[depName]; ok {
+func (a *AURCache) SetProvideCache(needle string, pkgs []*aur.Pkg) {
+	a.provideCache[needle] = pkgs
+}
+
+// Get returns a list of packages that provide the given search term
+func (a *AURCache) FindPackage(needle string) ([]*aur.Pkg, error) {
+	if pkgs, ok := a.provideCache[needle]; ok {
 		a.cacheHits++
 		return pkgs, nil
 	}
 
-	final, error := a.gojqGet(depName)
+	final, error := a.gojqGet(needle)
 	if error != nil {
 		return nil, error
 	}
 
-	a.provideCache[depName] = final
+	a.provideCache[needle] = final
 
 	return final, nil
 }

+ 64 - 0
pkg/settings/pacman.go

@@ -0,0 +1,64 @@
+package settings
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/Jguer/yay/v11/pkg/settings/parser"
+	pacmanconf "github.com/Morganamilo/go-pacmanconf"
+	"golang.org/x/term"
+)
+
+func RetrievePacmanConfig(cmdArgs *parser.Arguments, pacmanConfigPath string) (*pacmanconf.Config, bool, error) {
+	root := "/"
+	if value, _, exists := cmdArgs.GetArg("root", "r"); exists {
+		root = value
+	}
+
+	pacmanConf, stderr, err := pacmanconf.PacmanConf("--config", pacmanConfigPath, "--root", root)
+	if err != nil {
+		cmdErr := err
+		if stderr != "" {
+			cmdErr = fmt.Errorf("%s\n%s", err, stderr)
+		}
+
+		return nil, false, cmdErr
+	}
+
+	if dbPath, _, exists := cmdArgs.GetArg("dbpath", "b"); exists {
+		pacmanConf.DBPath = dbPath
+	}
+
+	if arch := cmdArgs.GetArgs("arch"); arch != nil {
+		pacmanConf.Architecture = append(pacmanConf.Architecture, arch...)
+	}
+
+	if ignoreArray := cmdArgs.GetArgs("ignore"); ignoreArray != nil {
+		pacmanConf.IgnorePkg = append(pacmanConf.IgnorePkg, ignoreArray...)
+	}
+
+	if ignoreGroupsArray := cmdArgs.GetArgs("ignoregroup"); ignoreGroupsArray != nil {
+		pacmanConf.IgnoreGroup = append(pacmanConf.IgnoreGroup, ignoreGroupsArray...)
+	}
+
+	if cacheArray := cmdArgs.GetArgs("cachedir"); cacheArray != nil {
+		pacmanConf.CacheDir = cacheArray
+	}
+
+	if gpgDir, _, exists := cmdArgs.GetArg("gpgdir"); exists {
+		pacmanConf.GPGDir = gpgDir
+	}
+
+	useColor := pacmanConf.Color && term.IsTerminal(int(os.Stdout.Fd()))
+
+	switch value, _, _ := cmdArgs.GetArg("color"); value {
+	case "always":
+		useColor = true
+	case "auto":
+		useColor = term.IsTerminal(int(os.Stdout.Fd()))
+	case "never":
+		useColor = false
+	}
+
+	return pacmanConf, useColor, nil
+}

+ 2 - 2
main_test.go

@@ -1,4 +1,4 @@
-package main
+package settings
 
 import (
 	"testing"
@@ -43,7 +43,7 @@ func TestPacmanConf(t *testing.T) {
 		},
 	}
 
-	pacmanConf, color, err := initAlpm(parser.MakeArguments(), "testdata/pacman.conf")
+	pacmanConf, color, err := RetrievePacmanConfig(parser.MakeArguments(), "../../testdata/pacman.conf")
 	assert.Nil(t, err)
 	assert.NotNil(t, pacmanConf)
 	assert.Equal(t, color, false)

+ 13 - 11
pkg/topo/dep.go

@@ -49,9 +49,7 @@ func (g *Graph[T]) Len() int {
 
 func (g *Graph[T]) Exists(node T) bool {
 	// check aliases
-	if aliasNode, ok := g.alias[node]; ok {
-		node = aliasNode
-	}
+	node = g.getAlias(node)
 
 	_, ok := g.nodes[node]
 	return ok
@@ -59,7 +57,7 @@ func (g *Graph[T]) Exists(node T) bool {
 
 func (g *Graph[T]) Alias(node, alias T) error {
 	if alias == node {
-		return ErrSelfReferential
+		return nil
 	}
 
 	// add node
@@ -75,24 +73,28 @@ func (g *Graph[T]) Alias(node, alias T) error {
 }
 
 func (g *Graph[T]) AddNode(node T) {
-	// check aliases
-	if aliasNode, ok := g.alias[node]; ok {
-		node = aliasNode
-	}
+	node = g.getAlias(node)
 
 	g.nodes[node] = true
 }
 
-func (g *Graph[T]) SetNodeInfo(node T, nodeInfo *NodeInfo) {
-	// check aliases
+func (g *Graph[T]) getAlias(node T) T {
 	if aliasNode, ok := g.alias[node]; ok {
-		node = aliasNode
+		return aliasNode
 	}
+	return node
+}
+
+func (g *Graph[T]) SetNodeInfo(node T, nodeInfo *NodeInfo) {
+	node = g.getAlias(node)
 
 	g.nodeInfo[node] = *nodeInfo
 }
 
 func (g *Graph[T]) DependOn(child, parent T) error {
+	child = g.getAlias(child)
+	parent = g.getAlias(parent)
+
 	if child == parent {
 		return ErrSelfReferential
 	}

+ 5 - 3
pkg/topo/errors.go

@@ -2,6 +2,8 @@ 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")
+var (
+	ErrSelfReferential  = errors.New("self-referential dependencies not allowed")
+	ErrConflictingAlias = errors.New("alias already defined")
+	ErrCircular         = errors.New("circular dependencies not allowed")
+)