Просмотр исходного кода

Merge pull request #401 from Morganamilo/deps3

Dependency system rewrite
J Guerreiro 7 лет назад
Родитель
Сommit
c36bfc1237
12 измененных файлов с 1239 добавлено и 1123 удалено
  1. 4 0
      cmd.go
  2. 2 0
      config.go
  3. 0 354
      conflicts.go
  4. 170 0
      dep.go
  5. 275 0
      depCheck.go
  6. 120 0
      depOrder.go
  7. 482 0
      depPool.go
  8. 0 650
      dependencies.go
  9. 77 92
      install.go
  10. 10 0
      parser.go
  11. 77 18
      print.go
  12. 22 9
      utils.go

+ 4 - 0
cmd.go

@@ -296,6 +296,10 @@ func handleConfig(option, value string) bool {
 		config.SudoLoop = true
 	case "nosudoloop":
 		config.SudoLoop = false
+	case "provides":
+		config.Provides = true
+	case "noprovides":
+		config.Provides = false
 	default:
 		return false
 	}

+ 2 - 0
config.go

@@ -54,6 +54,7 @@ type Configuration struct {
 	Devel         bool   `json:"devel"`
 	CleanAfter    bool   `json:"cleanAfter"`
 	GitClone      bool   `json:"gitclone"`
+	Provides      bool   `json:"provides"`
 }
 
 var version = "5.688"
@@ -152,6 +153,7 @@ func defaultSettings(config *Configuration) {
 	config.AnswerEdit = ""
 	config.AnswerUpgrade = ""
 	config.GitClone = true
+	config.Provides = true
 }
 
 // Editor returns the preferred system editor.

+ 0 - 354
conflicts.go

@@ -1,354 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"strings"
-	"sync"
-
-	alpm "github.com/jguer/go-alpm"
-	gopkg "github.com/mikkeloscar/gopkgbuild"
-)
-
-// Checks a single conflict against every other to be installed package's
-// name and its provides.
-func checkInnerConflict(name string, conflict string, conflicts mapStringSet, dc *depCatagories) {
-	deps, err := gopkg.ParseDeps([]string{conflict})
-	if err != nil {
-		return
-	}
-	dep := deps[0]
-
-	for _, pkg := range dc.Aur {
-		if name == pkg.Name {
-			continue
-		}
-
-		version, err := gopkg.NewCompleteVersion(pkg.Version)
-		if err != nil {
-			return
-		}
-		if dep.Name == pkg.Name && version.Satisfies(dep) {
-			conflicts.Add(name, pkg.Name)
-			continue
-		}
-
-		for _, provide := range pkg.Provides {
-			// Provides are not versioned unless explicitly defined as
-			// such. If a conflict is versioned but a provide is
-			// not it can not conflict.
-			if (dep.MaxVer != nil || dep.MinVer != nil) && !strings.ContainsAny(provide, "><=") {
-				continue
-			}
-
-			var version *gopkg.CompleteVersion
-			var err error
-
-			pname, pversion := splitNameFromDep(provide)
-
-			if dep.Name != pname {
-				continue
-			}
-
-			if pversion != "" {
-				version, err = gopkg.NewCompleteVersion(provide)
-				if err != nil {
-					return
-				}
-			}
-
-			if version != nil && version.Satisfies(dep) {
-				conflicts.Add(name, pkg.Name)
-				break
-			}
-
-		}
-	}
-
-	for _, pkg := range dc.Repo {
-		if name == pkg.Name() {
-			continue
-		}
-
-		version, err := gopkg.NewCompleteVersion(pkg.Version())
-		if err != nil {
-			return
-		}
-
-		if dep.Name == pkg.Name() && version.Satisfies(dep) {
-			conflicts.Add(name, pkg.Name())
-			continue
-		}
-
-		pkg.Provides().ForEach(func(provide alpm.Depend) error {
-			// Provides are not versioned unless explicitly defined as
-			// such. If a conflict is versioned but a provide is
-			// not it can not conflict.
-			if (dep.MaxVer != nil || dep.MinVer != nil) && provide.Mod == alpm.DepModAny {
-				return nil
-			}
-
-			if dep.Name != pkg.Name() {
-				return nil
-			}
-
-			if provide.Mod == alpm.DepModAny {
-				conflicts.Add(name, pkg.Name())
-				return fmt.Errorf("")
-			}
-
-			version, err := gopkg.NewCompleteVersion(provide.Version)
-			if err != nil {
-				return nil
-			}
-
-			if version.Satisfies(dep) {
-				conflicts.Add(name, pkg.Name())
-				return fmt.Errorf("")
-			}
-
-			return nil
-		})
-	}
-}
-
-// Checks every to be installed package's conflicts against every other to be
-// installed package and its provides.
-func checkForInnerConflicts(dc *depCatagories) mapStringSet {
-	conflicts := make(mapStringSet)
-
-	for _, pkg := range dc.Aur {
-		for _, cpkg := range pkg.Conflicts {
-			checkInnerConflict(pkg.Name, cpkg, conflicts, dc)
-		}
-	}
-
-	for _, pkg := range dc.Repo {
-		pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
-			checkInnerConflict(pkg.Name(), conflict.String(), conflicts, dc)
-			return nil
-		})
-	}
-
-	return conflicts
-}
-
-// Checks a provide or packagename from a to be installed package
-// against every already installed package's conflicts
-func checkReverseConflict(name string, provide string, conflicts mapStringSet) error {
-	var version *gopkg.CompleteVersion
-	var err error
-
-	localDb, err := alpmHandle.LocalDb()
-	if err != nil {
-		return err
-	}
-
-	pname, pversion := splitNameFromDep(provide)
-	if pversion != "" {
-		version, err = gopkg.NewCompleteVersion(pversion)
-		if err != nil {
-			return nil
-		}
-	}
-
-	localDb.PkgCache().ForEach(func(pkg alpm.Package) error {
-		if name == pkg.Name() {
-			return nil
-		}
-
-		pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
-			deps, err := gopkg.ParseDeps([]string{conflict.String()})
-			if err != nil {
-				return nil
-			}
-
-			dep := deps[0]
-			// Provides are not versioned unless explicitly defined as
-			// such. If a conflict is versioned but a provide is
-			// not it can not conflict.
-			if (dep.MaxVer != nil || dep.MinVer != nil) && version == nil {
-				return nil
-			}
-
-			if dep.Name != pname {
-				return nil
-			}
-
-			if version == nil || version.Satisfies(dep) {
-				// Todo
-				conflicts.Add(name, pkg.Name()+" ("+provide+")")
-				return fmt.Errorf("")
-			}
-
-			return nil
-		})
-
-		return nil
-	})
-
-	return nil
-}
-
-// Checks the conflict of a to be installed package against the package name and
-// provides of every installed package.
-func checkConflict(name string, conflict string, conflicts mapStringSet) error {
-	localDb, err := alpmHandle.LocalDb()
-	if err != nil {
-		return err
-	}
-
-	deps, err := gopkg.ParseDeps([]string{conflict})
-	if err != nil {
-		return nil
-	}
-
-	dep := deps[0]
-
-	localDb.PkgCache().ForEach(func(pkg alpm.Package) error {
-		if name == pkg.Name() {
-			return nil
-		}
-
-		version, err := gopkg.NewCompleteVersion(pkg.Version())
-		if err != nil {
-			return nil
-		}
-
-		if dep.Name == pkg.Name() && version.Satisfies(dep) {
-			conflicts.Add(name, pkg.Name())
-			return nil
-		}
-
-		pkg.Provides().ForEach(func(provide alpm.Depend) error {
-			if dep.Name != provide.Name {
-				return nil
-			}
-
-			// Provides aren't version unless explicitly defined as
-			// such. If a conflict is versioned but a provide is
-			// not it can not conflict.
-			if (dep.MaxVer != nil || dep.MinVer != nil) && provide.Mod == alpm.DepModAny {
-				return nil
-			}
-
-			if provide.Mod == alpm.DepModAny {
-				conflicts.Add(name, pkg.Name()+" ("+provide.Name+")")
-				return fmt.Errorf("")
-			}
-
-			version, err := gopkg.NewCompleteVersion(provide.Version)
-			if err != nil {
-				return nil
-			}
-
-			if version.Satisfies(dep) {
-				conflicts.Add(name, pkg.Name()+" ("+provide.Name+")")
-				return fmt.Errorf("")
-			}
-
-			return nil
-		})
-
-		return nil
-	})
-
-	return nil
-}
-
-// Checks every to be installed package's conflicts against the names and
-// provides of every already installed package and checks every to be installed
-// package's name and provides against every already installed package.
-func checkForConflicts(dc *depCatagories) (mapStringSet, error) {
-	conflicts := make(mapStringSet)
-
-	for _, pkg := range dc.Aur {
-		for _, cpkg := range pkg.Conflicts {
-			checkConflict(pkg.Name, cpkg, conflicts)
-		}
-	}
-
-	for _, pkg := range dc.Repo {
-		pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
-			checkConflict(pkg.Name(), conflict.String(), conflicts)
-			return nil
-		})
-	}
-
-	for _, pkg := range dc.Aur {
-		checkReverseConflict(pkg.Name, pkg.Name, conflicts)
-		for _, ppkg := range pkg.Provides {
-			checkReverseConflict(pkg.Name, ppkg, conflicts)
-		}
-	}
-
-	for _, pkg := range dc.Repo {
-		checkReverseConflict(pkg.Name(), pkg.Name(), conflicts)
-		pkg.Provides().ForEach(func(provide alpm.Depend) error {
-			checkReverseConflict(pkg.Name(), provide.String(), conflicts)
-			return nil
-		})
-	}
-
-	return conflicts, nil
-}
-
-// Combiles checkForConflicts() and checkForInnerConflicts() in parallel and
-// does some printing.
-func checkForAllConflicts(dc *depCatagories) error {
-	var err error
-	var conflicts mapStringSet
-	var innerConflicts mapStringSet
-	var wg sync.WaitGroup
-	wg.Add(2)
-
-	fmt.Println(bold(cyan("::") + bold(" Checking for conflicts...")))
-	go func() {
-		conflicts, err = checkForConflicts(dc)
-		wg.Done()
-	}()
-
-	fmt.Println(bold(cyan("::") + bold(" Checking for inner conflicts...")))
-	go func() {
-		innerConflicts = checkForInnerConflicts(dc)
-		wg.Done()
-	}()
-
-	wg.Wait()
-
-	if err != nil {
-		return err
-	}
-
-	if len(innerConflicts) != 0 {
-		fmt.Println()
-		fmt.Println(bold(red(arrow)), bold("Inner conflicts found:"))
-
-		for name, pkgs := range innerConflicts {
-			str := red(bold(smallArrow)) + " " + name + ":"
-			for pkg := range pkgs {
-				str += " " + cyan(pkg)
-			}
-
-			fmt.Println(str)
-		}
-
-		return fmt.Errorf("Unresolvable package conflicts, aborting")
-	}
-
-	if len(conflicts) != 0 {
-		fmt.Println()
-		fmt.Println(bold(red(arrow)), bold("Package conflicts found:"))
-		for name, pkgs := range conflicts {
-			str := red(bold(smallArrow)) + " Installing " + cyan(name) + " will remove:"
-			for pkg := range pkgs {
-				str += " " + cyan(pkg)
-			}
-
-			fmt.Println(str)
-		}
-
-		fmt.Println()
-	}
-
-	return nil
-}

+ 170 - 0
dep.go

@@ -0,0 +1,170 @@
+package main
+
+import (
+	"fmt"
+	"strings"
+
+	alpm "github.com/jguer/go-alpm"
+	rpc "github.com/mikkeloscar/aur"
+)
+
+type providers struct {
+	lookfor string
+	Pkgs    []*rpc.Pkg
+}
+
+func makeProviders(name string) providers {
+	return providers{
+		name,
+		make([]*rpc.Pkg, 0),
+	}
+}
+
+func (q providers) Len() int {
+	return len(q.Pkgs)
+}
+
+func (q providers) Less(i, j int) bool {
+	if q.lookfor == q.Pkgs[i].Name {
+		return true
+	}
+
+	if q.lookfor == q.Pkgs[j].Name {
+		return false
+	}
+
+	return lessRunes([]rune(q.Pkgs[i].Name), []rune(q.Pkgs[j].Name))
+}
+
+func (q providers) Swap(i, j int) {
+	q.Pkgs[i], q.Pkgs[j] = q.Pkgs[j], q.Pkgs[i]
+}
+
+func splitDep(dep string) (string, string, string) {
+	mod := ""
+
+	split := strings.FieldsFunc(dep, func(c rune) bool {
+		match := c == '>' || c == '<' || c == '='
+
+		if match {
+			mod += string(c)
+		}
+
+		return match
+	})
+
+	if len(split) == 1 {
+		return split[0], "", ""
+	}
+
+	return split[0], mod, split[1]
+}
+
+func pkgSatisfies(name, version, dep string) bool {
+	depName, depMod, depVersion := splitDep(dep)
+
+	if depName != name {
+		return false
+	}
+
+	return verSatisfies(version, depMod, depVersion)
+}
+
+func provideSatisfies(provide, dep string) bool {
+	depName, depMod, depVersion := splitDep(dep)
+	provideName, provideMod, provideVersion := splitDep(provide)
+
+	if provideName != depName {
+		return false
+	}
+
+	// Unversioned provieds can not satisfy a versioned dep
+	if provideMod == "" && depMod != "" {
+		return false
+	}
+
+	return verSatisfies(provideVersion, depMod, depVersion)
+}
+
+func verSatisfies(ver1, mod, ver2 string) bool {
+	switch mod {
+	case "=":
+		return alpm.VerCmp(ver1, ver2) == 0
+	case "<":
+		return alpm.VerCmp(ver1, ver2) < 0
+	case "<=":
+		return alpm.VerCmp(ver1, ver2) <= 0
+	case ">":
+		return alpm.VerCmp(ver1, ver2) > 0
+	case ">=":
+		return alpm.VerCmp(ver1, ver2) >= 0
+	}
+
+	return true
+}
+
+func satisfiesAur(dep string, pkg *rpc.Pkg) bool {
+	if pkgSatisfies(pkg.Name, pkg.Version, dep) {
+		return true
+	}
+
+	for _, provide := range pkg.Provides {
+		if provideSatisfies(provide, dep) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func satisfiesRepo(dep string, pkg *alpm.Package) bool {
+	if pkgSatisfies(pkg.Name(), pkg.Version(), dep) {
+		return true
+	}
+
+	if pkg.Provides().ForEach(func(provide alpm.Depend) error {
+		if provideSatisfies(provide.String(), dep) {
+			return fmt.Errorf("")
+		}
+
+		return nil
+	}) != nil {
+		return true
+	}
+
+	return false
+}
+
+//split apart db/package to db and package
+func splitDbFromName(pkg string) (string, string) {
+	split := strings.SplitN(pkg, "/", 2)
+
+	if len(split) == 2 {
+		return split[0], split[1]
+	}
+	return "", split[0]
+}
+
+func getBases(pkgs map[string]*rpc.Pkg) map[string][]*rpc.Pkg {
+	bases := make(map[string][]*rpc.Pkg)
+
+	for _, pkg := range pkgs {
+		_, ok := bases[pkg.PackageBase]
+		if !ok {
+			bases[pkg.PackageBase] = make([]*rpc.Pkg, 0)
+		}
+		bases[pkg.PackageBase] = append(bases[pkg.PackageBase], pkg)
+	}
+
+	return bases
+}
+
+func isDevelName(name string) bool {
+	for _, suffix := range []string{"git", "svn", "hg", "bzr", "nightly"} {
+		if strings.HasSuffix(name, "-"+suffix) {
+			return true
+		}
+	}
+
+	return strings.Contains(name, "-always-")
+}

+ 275 - 0
depCheck.go

@@ -0,0 +1,275 @@
+package main
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+
+	alpm "github.com/jguer/go-alpm"
+	//	gopkg "github.com/mikkeloscar/gopkgbuild"
+)
+
+func (dp *depPool) checkInnerConflict(name string, conflict string, conflicts mapStringSet) {
+	for _, pkg := range dp.Aur {
+		if pkg.Name == name {
+			continue
+		}
+
+		if satisfiesAur(conflict, pkg) {
+			conflicts.Add(name, pkg.Name)
+		}
+	}
+
+	for _, pkg := range dp.Repo {
+		if pkg.Name() == name {
+			continue
+		}
+
+		if satisfiesRepo(conflict, pkg) {
+			conflicts.Add(name, pkg.Name())
+		}
+	}
+}
+
+func (dp *depPool) checkForwardConflict(name string, conflict string, conflicts mapStringSet) {
+	dp.LocalDb.PkgCache().ForEach(func(pkg alpm.Package) error {
+		if pkg.Name() == name || dp.hasPackage(pkg.Name()) {
+			return nil
+		}
+
+		if satisfiesRepo(conflict, &pkg) {
+			n := pkg.Name()
+			if n != conflict {
+				n += " (" + conflict + ")"
+			}
+			conflicts.Add(name, n)
+		}
+
+		return nil
+	})
+}
+
+func (dp *depPool) checkReverseConflict(name string, conflict string, conflicts mapStringSet) {
+	for _, pkg := range dp.Aur {
+		if pkg.Name == name {
+			continue
+		}
+
+		if satisfiesAur(conflict, pkg) {
+			if name != conflict {
+				name += " (" + conflict + ")"
+			}
+
+			conflicts.Add(pkg.Name, name)
+		}
+	}
+
+	for _, pkg := range dp.Repo {
+		if pkg.Name() == name {
+			continue
+		}
+
+		if satisfiesRepo(conflict, pkg) {
+			if name != conflict {
+				name += " (" + conflict + ")"
+			}
+
+			conflicts.Add(pkg.Name(), name)
+		}
+	}
+}
+
+func (dp *depPool) checkInnerConflicts(conflicts mapStringSet) {
+	for _, pkg := range dp.Aur {
+		for _, conflict := range pkg.Conflicts {
+			dp.checkInnerConflict(pkg.Name, conflict, conflicts)
+		}
+	}
+
+	for _, pkg := range dp.Repo {
+		pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
+			dp.checkInnerConflict(pkg.Name(), conflict.String(), conflicts)
+			return nil
+		})
+	}
+}
+
+func (dp *depPool) checkForwardConflicts(conflicts mapStringSet) {
+	for _, pkg := range dp.Aur {
+		for _, conflict := range pkg.Conflicts {
+			dp.checkForwardConflict(pkg.Name, conflict, conflicts)
+		}
+	}
+
+	for _, pkg := range dp.Repo {
+		pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
+			dp.checkForwardConflict(pkg.Name(), conflict.String(), conflicts)
+			return nil
+		})
+	}
+}
+
+func (dp *depPool) checkReverseConflicts(conflicts mapStringSet) {
+	dp.LocalDb.PkgCache().ForEach(func(pkg alpm.Package) error {
+		if dp.hasPackage(pkg.Name()) {
+			return nil
+		}
+
+		pkg.Conflicts().ForEach(func(conflict alpm.Depend) error {
+			dp.checkReverseConflict(pkg.Name(), conflict.String(), conflicts)
+			return nil
+		})
+
+		return nil
+	})
+}
+
+func (dp *depPool) CheckConflicts() error {
+	var wg sync.WaitGroup
+	innerConflicts := make(mapStringSet)
+	conflicts := make(mapStringSet)
+	wg.Add(2)
+
+	fmt.Println(bold(cyan("::") + bold(" Checking for conflicts...")))
+	go func() {
+		dp.checkForwardConflicts(conflicts)
+		dp.checkReverseConflicts(conflicts)
+		wg.Done()
+	}()
+
+	fmt.Println(bold(cyan("::") + bold(" Checking for inner conflicts...")))
+	go func() {
+		dp.checkInnerConflicts(innerConflicts)
+		wg.Done()
+	}()
+
+	wg.Wait()
+
+	if len(innerConflicts) != 0 {
+		fmt.Println()
+		fmt.Println(bold(red(arrow)), bold("Inner conflicts found:"))
+
+		for name, pkgs := range innerConflicts {
+			str := red(bold(smallArrow)) + " " + name + ":"
+			for pkg := range pkgs {
+				str += " " + cyan(pkg) + ","
+			}
+			str = strings.TrimSuffix(str, ",")
+
+			fmt.Println(str)
+		}
+
+		return fmt.Errorf("Unresolvable package conflicts, aborting")
+	}
+
+	if len(conflicts) != 0 {
+		fmt.Println()
+		fmt.Println(bold(red(arrow)), bold("Package conflicts found:"))
+		for name, pkgs := range conflicts {
+			str := red(bold(smallArrow)) + " Installing " + cyan(name) + " will remove:"
+			for pkg := range pkgs {
+				str += " " + cyan(pkg) + ","
+			}
+			str = strings.TrimSuffix(str, ",")
+
+			fmt.Println(str)
+		}
+
+		fmt.Println()
+	}
+
+	return nil
+}
+
+type missing struct {
+	Good    stringSet
+	Missing map[string][][]string
+}
+
+func (dp *depPool) _checkMissing(dep string, stack []string, missing *missing) {
+	if missing.Good.get(dep) {
+		return
+	}
+
+	if trees, ok := missing.Missing[dep]; ok {
+		for _, tree := range trees {
+			if stringSliceEqual(tree, stack) {
+				return
+			}
+		}
+		missing.Missing[dep] = append(missing.Missing[dep], stack)
+		return
+	}
+
+	aurPkg := dp.findSatisfierAur(dep)
+	if aurPkg != nil {
+		missing.Good.set(dep)
+		for _, deps := range [3][]string{aurPkg.Depends, aurPkg.MakeDepends, aurPkg.CheckDepends} {
+			for _, aurDep := range deps {
+				if _, err := dp.LocalDb.PkgCache().FindSatisfier(aurDep); err == nil {
+					missing.Good.set(aurDep)
+					continue
+				}
+
+				dp._checkMissing(aurDep, append(stack, aurPkg.Name), missing)
+			}
+		}
+
+		return
+	}
+
+	repoPkg := dp.findSatisfierRepo(dep)
+	if repoPkg != nil {
+		missing.Good.set(dep)
+		repoPkg.Depends().ForEach(func(repoDep alpm.Depend) error {
+			if _, err := dp.LocalDb.PkgCache().FindSatisfier(repoDep.String()); err == nil {
+				missing.Good.set(repoDep.String())
+				return nil
+			}
+
+			dp._checkMissing(repoDep.String(), append(stack, repoPkg.Name()), missing)
+			return nil
+		})
+
+		return
+	}
+
+	missing.Missing[dep] = [][]string{stack}
+}
+
+func (dp *depPool) CheckMissing() error {
+	missing := &missing{
+		make(stringSet),
+		make(map[string][][]string),
+	}
+
+	for _, target := range dp.Targets {
+		dp._checkMissing(target.DepString(), make([]string, 0), missing)
+	}
+
+	if len(missing.Missing) == 0 {
+		return nil
+	}
+
+	fmt.Println(bold(red(arrow+" Error: ")) + "Could not find all required packages:")
+	for dep, trees := range missing.Missing {
+		for _, tree := range trees {
+
+			fmt.Print("    ", cyan(dep))
+
+			if len(tree) == 0 {
+				fmt.Print(" (Target")
+			} else {
+				fmt.Print(" (Wanted by: ")
+				for n := 0; n < len(tree)-1; n++ {
+					fmt.Print(cyan(tree[n]), " -> ")
+				}
+				fmt.Print(cyan(tree[len(tree)-1]))
+			}
+
+			fmt.Println(")")
+		}
+	}
+
+	return fmt.Errorf("")
+}

+ 120 - 0
depOrder.go

@@ -0,0 +1,120 @@
+package main
+
+import (
+	alpm "github.com/jguer/go-alpm"
+	rpc "github.com/mikkeloscar/aur"
+)
+
+type depOrder struct {
+	Aur     []*rpc.Pkg
+	Repo    []*alpm.Package
+	Runtime stringSet
+	Bases   map[string][]*rpc.Pkg
+}
+
+func makeDepOrder() *depOrder {
+	return &depOrder{
+		make([]*rpc.Pkg, 0),
+		make([]*alpm.Package, 0),
+		make(stringSet),
+		make(map[string][]*rpc.Pkg),
+	}
+}
+
+func getDepOrder(dp *depPool) *depOrder {
+	do := makeDepOrder()
+
+	for _, target := range dp.Targets {
+		dep := target.DepString()
+		aurPkg := dp.Aur[dep]
+		if aurPkg != nil && pkgSatisfies(aurPkg.Name, aurPkg.Version, dep) {
+			do.orderPkgAur(aurPkg, dp, true)
+		}
+
+		aurPkg = dp.findSatisfierAur(dep)
+		if aurPkg != nil {
+			do.orderPkgAur(aurPkg, dp, true)
+		}
+
+		repoPkg := dp.findSatisfierRepo(dep)
+		if repoPkg != nil {
+			do.orderPkgRepo(repoPkg, dp, true)
+		}
+	}
+
+	return do
+}
+
+func (do *depOrder) orderPkgAur(pkg *rpc.Pkg, dp *depPool, runtime bool) {
+	if runtime {
+		do.Runtime.set(pkg.Name)
+	}
+	delete(dp.Aur, pkg.Name)
+
+	for i, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
+		for _, dep := range deps {
+			aurPkg := dp.findSatisfierAur(dep)
+			if aurPkg != nil {
+				do.orderPkgAur(aurPkg, dp, runtime && i == 0)
+			}
+
+			repoPkg := dp.findSatisfierRepo(dep)
+			if repoPkg != nil {
+				do.orderPkgRepo(repoPkg, dp, runtime && i == 0)
+			}
+		}
+	}
+
+	if _, ok := do.Bases[pkg.PackageBase]; !ok {
+		do.Aur = append(do.Aur, pkg)
+		do.Bases[pkg.PackageBase] = make([]*rpc.Pkg, 0)
+	}
+	do.Bases[pkg.PackageBase] = append(do.Bases[pkg.PackageBase], pkg)
+}
+
+func (do *depOrder) orderPkgRepo(pkg *alpm.Package, dp *depPool, runtime bool) {
+	if runtime {
+		do.Runtime.set(pkg.Name())
+	}
+	delete(dp.Repo, pkg.Name())
+
+	pkg.Depends().ForEach(func(dep alpm.Depend) (err error) {
+		repoPkg := dp.findSatisfierRepo(dep.String())
+		if repoPkg != nil {
+			do.orderPkgRepo(repoPkg, dp, runtime)
+		}
+
+		return nil
+	})
+
+	do.Repo = append(do.Repo, pkg)
+}
+
+func (do *depOrder) HasMake() bool {
+	lenAur := 0
+	for _, base := range do.Bases {
+		lenAur += len(base)
+	}
+
+	return len(do.Runtime) != lenAur+len(do.Repo)
+}
+
+func (do *depOrder) getMake() []string {
+	makeOnly := make([]string, 0, len(do.Aur)+len(do.Repo)-len(do.Runtime))
+
+	for _, base := range do.Bases {
+		for _, pkg := range base {
+			if !do.Runtime.get(pkg.Name) {
+				makeOnly = append(makeOnly, pkg.Name)
+			}
+		}
+	}
+
+	for _, pkg := range do.Repo {
+		if !do.Runtime.get(pkg.Name()) {
+			makeOnly = append(makeOnly, pkg.Name())
+		}
+	}
+
+	return makeOnly
+}

+ 482 - 0
depPool.go

@@ -0,0 +1,482 @@
+package main
+
+import (
+	"sort"
+	"strings"
+	"sync"
+
+	alpm "github.com/jguer/go-alpm"
+	rpc "github.com/mikkeloscar/aur"
+)
+
+type target struct {
+	Db      string
+	Name    string
+	Mod     string
+	Version string
+}
+
+func toTarget(pkg string) target {
+	db, dep := splitDbFromName(pkg)
+	name, mod, version := splitDep(dep)
+
+	return target{
+		db,
+		name,
+		mod,
+		version,
+	}
+}
+
+func (t target) DepString() string {
+	return t.Name + t.Mod + t.Version
+}
+
+func (t target) String() string {
+	if t.Db != "" {
+		return t.Db + "/" + t.DepString()
+	}
+
+	return t.DepString()
+}
+
+type depPool struct {
+	Targets  []target
+	Explicit stringSet
+	Repo     map[string]*alpm.Package
+	Aur      map[string]*rpc.Pkg
+	AurCache map[string]*rpc.Pkg
+	Groups   []string
+	LocalDb  *alpm.Db
+	SyncDb   alpm.DbList
+	Warnings *aurWarnings
+}
+
+func makeDepPool() (*depPool, error) {
+	localDb, err := alpmHandle.LocalDb()
+	if err != nil {
+		return nil, err
+	}
+	syncDb, err := alpmHandle.SyncDbs()
+	if err != nil {
+		return nil, err
+	}
+
+	dp := &depPool{
+		make([]target, 0),
+		make(stringSet),
+		make(map[string]*alpm.Package),
+		make(map[string]*rpc.Pkg),
+		make(map[string]*rpc.Pkg),
+		make([]string, 0),
+		localDb,
+		syncDb,
+		nil,
+	}
+
+	return dp, nil
+}
+
+// Includes db/ prefixes and group installs
+func (dp *depPool) ResolveTargets(pkgs []string) error {
+	// RPC requests are slow
+	// Combine as many AUR package requests as possible into a single RPC
+	// call
+	aurTargets := make(stringSet)
+
+	for _, pkg := range pkgs {
+		var err error
+		target := toTarget(pkg)
+
+		// skip targets already satisfied
+		// even if the user enters db/pkg and aur/pkg the latter will
+		// still get skiped even if it's from a different database to
+		// the one specified
+		// this is how pacman behaves
+		if dp.hasPackage(target.DepString()) {
+			continue
+		}
+
+		var foundPkg *alpm.Package
+		var singleDb *alpm.Db
+
+		// aur/ prefix means we only check the aur
+		if target.Db == "aur" {
+			dp.Targets = append(dp.Targets, target)
+			aurTargets.set(target.DepString())
+			continue
+		}
+
+		// if theres a different priefix only look in that repo
+		if target.Db != "" {
+			singleDb, err = alpmHandle.SyncDbByName(target.Db)
+			if err != nil {
+				return err
+			}
+			foundPkg, err = singleDb.PkgCache().FindSatisfier(target.DepString())
+			//otherwise find it in any repo
+		} else {
+			foundPkg, err = dp.SyncDb.FindSatisfier(target.DepString())
+		}
+
+		if err == nil {
+			dp.Targets = append(dp.Targets, target)
+			dp.Explicit.set(foundPkg.Name())
+			dp.ResolveRepoDependency(foundPkg)
+			continue
+		} else {
+			//check for groups
+			//currently we dont resolve the packages in a group
+			//only check if the group exists
+			//would be better to check the groups from singleDb if
+			//the user specified a db but theres no easy way to do
+			//it without making alpm_lists so dont bother for now
+			//db/group is probably a rare use case
+			group, err := dp.SyncDb.PkgCachebyGroup(target.Name)
+			if err == nil {
+				dp.Groups = append(dp.Groups, target.String())
+				group.ForEach(func(pkg alpm.Package) error {
+					dp.Explicit.set(pkg.Name())
+					return nil
+				})
+				continue
+			}
+		}
+
+		//if there was no db prefix check the aur
+		if target.Db == "" {
+			aurTargets.set(target.DepString())
+		}
+
+		dp.Targets = append(dp.Targets, target)
+	}
+
+	if len(aurTargets) > 0 {
+		return dp.resolveAURPackages(aurTargets, true)
+	}
+
+	return nil
+}
+
+// Pseudo provides finder.
+// Try to find provides by performing a search of the package name
+// This effectively performs -Ss on each package
+// then runs -Si on each result to cache the information.
+//
+// For example if you were to -S yay then yay -Ss would give:
+// yay-git yay-bin yay realyog pacui pacui-git ruby-yard
+// These packages will all be added to the cache incase they are needed later
+// Ofcouse only the first three packages provide yay, the rest are just false
+// positives.
+//
+// This method increases dependency resolve time
+func (dp *depPool) findProvides(pkgs stringSet) error {
+	var mux sync.Mutex
+	var wg sync.WaitGroup
+
+	doSearch := func(pkg string) {
+		defer wg.Done()
+		var err error
+		var results []rpc.Pkg
+
+		// Hack for a bigger search result, if the user wants
+		// java-envronment we can search for just java instead and get
+		// more hits.
+		words := strings.Split(pkg, "-")
+
+		for i := range words {
+			results, err = rpc.SearchByNameDesc(strings.Join(words[:i+1], "-"))
+			if err == nil {
+				break
+			}
+		}
+
+		if err != nil {
+			return
+		}
+
+		for _, result := range results {
+			mux.Lock()
+			if _, ok := dp.AurCache[result.Name]; !ok {
+				pkgs.set(result.Name)
+			}
+			mux.Unlock()
+		}
+	}
+
+	for pkg := range pkgs {
+		if _, err := dp.LocalDb.PkgByName(pkg); err == nil {
+			continue
+		}
+		wg.Add(1)
+		go doSearch(pkg)
+	}
+
+	wg.Wait()
+
+	return nil
+}
+
+func (dp *depPool) cacheAURPackages(_pkgs stringSet) error {
+	pkgs := _pkgs.copy()
+	query := make([]string, 0)
+
+	for pkg := range pkgs {
+		if _, ok := dp.AurCache[pkg]; ok {
+			pkgs.remove(pkg)
+		}
+	}
+
+	if len(pkgs) == 0 {
+		return nil
+	}
+
+	if config.Provides {
+		err := dp.findProvides(pkgs)
+		if err != nil {
+			return err
+		}
+	}
+
+	for pkg := range pkgs {
+		if _, ok := dp.AurCache[pkg]; !ok {
+			name, _, _ := splitDep(pkg)
+			query = append(query, name)
+		}
+	}
+
+	info, err := aurInfo(query, dp.Warnings)
+	if err != nil {
+		return err
+	}
+
+	for _, pkg := range info {
+		// Dump everything in cache just in case we need it later
+		dp.AurCache[pkg.Name] = pkg
+	}
+
+	return nil
+}
+
+func (dp *depPool) resolveAURPackages(pkgs stringSet, explicit bool) error {
+	newPackages := make(stringSet)
+	newAURPackages := make(stringSet)
+
+	err := dp.cacheAURPackages(pkgs)
+	if err != nil {
+		return err
+	}
+
+	if len(pkgs) == 0 {
+		return nil
+	}
+
+	for name := range pkgs {
+		_, ok := dp.Aur[name]
+		if ok {
+			continue
+		}
+
+		pkg := dp.findSatisfierAurCache(name)
+		if pkg == nil {
+			continue
+		}
+
+		if explicit {
+			dp.Explicit.set(pkg.Name)
+		}
+		dp.Aur[pkg.Name] = pkg
+
+		for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
+			for _, dep := range deps {
+				newPackages.set(dep)
+			}
+		}
+	}
+
+	for dep := range newPackages {
+		if dp.hasSatisfier(dep) {
+			continue
+		}
+
+		//has satisfier installed: skip
+		_, isInstalled := dp.LocalDb.PkgCache().FindSatisfier(dep)
+		if isInstalled == nil {
+			continue
+		}
+
+		//has satisfier in repo: fetch it
+		repoPkg, inRepos := dp.SyncDb.FindSatisfier(dep)
+		if inRepos == nil {
+			dp.ResolveRepoDependency(repoPkg)
+			continue
+		}
+
+		//assume it's in the aur
+		//ditch the versioning because the RPC cant handle it
+		newAURPackages.set(dep)
+
+	}
+
+	err = dp.resolveAURPackages(newAURPackages, false)
+
+	return err
+}
+
+func (dp *depPool) ResolveRepoDependency(pkg *alpm.Package) {
+	dp.Repo[pkg.Name()] = pkg
+
+	pkg.Depends().ForEach(func(dep alpm.Depend) (err error) {
+		//have satisfier in dep tree: skip
+		if dp.hasSatisfier(dep.String()) {
+			return
+		}
+
+		//has satisfier installed: skip
+		_, isInstalled := dp.LocalDb.PkgCache().FindSatisfier(dep.String())
+		if isInstalled == nil {
+			return
+		}
+
+		//has satisfier in repo: fetch it
+		repoPkg, inRepos := dp.SyncDb.FindSatisfier(dep.String())
+		if inRepos != nil {
+			return
+		}
+
+		dp.ResolveRepoDependency(repoPkg)
+
+		return nil
+	})
+}
+
+func getDepPool(pkgs []string, warnings *aurWarnings) (*depPool, error) {
+	dp, err := makeDepPool()
+	if err != nil {
+		return nil, err
+	}
+
+	dp.Warnings = warnings
+	err = dp.ResolveTargets(pkgs)
+
+	return dp, err
+}
+
+func (dp *depPool) findSatisfierAur(dep string) *rpc.Pkg {
+	for _, pkg := range dp.Aur {
+		if satisfiesAur(dep, pkg) {
+			return pkg
+		}
+	}
+
+	return nil
+}
+
+// This is mostly used to promote packages from the cache
+// to the Install list
+// Provide a pacman style provider menu if theres more than one candidate
+// TODO: maybe intermix repo providers in the menu
+func (dp *depPool) findSatisfierAurCache(dep string) *rpc.Pkg {
+	depName, _, _ := splitDep(dep)
+	seen := make(stringSet)
+	providers := makeProviders(depName)
+
+	if _, err := dp.LocalDb.PkgByName(depName); err == nil {
+		if pkg, ok := dp.AurCache[dep]; ok && pkgSatisfies(pkg.Name, pkg.Version, dep) {
+			return pkg
+		}
+	}
+
+	//this version prioratizes name over provides
+	//if theres a direct match for a package return
+	//that instead of using the menu
+	//
+	//providers := make(rpcPkgs, 0)
+	//for _, pkg := range dp.AurCache {
+	//	if pkgSatisfies(pkg.Name, pkg.Version, dep) {
+	//		return pkg
+	//	}
+	//}
+
+	//for _, pkg := range dp.AurCache {
+	//	for _, provide := range pkg.Provides {
+	//		if provideSatisfies(provide, dep) {
+	//			providers = append(providers, pkg)
+	//		}
+	//	}
+	//}
+
+	// This version acts slightly differenly from Pacman, It will give
+	// a menu even if a package with a matching name exists. I believe this
+	// method is better because most of the time you are choosing between
+	// foo and foo-git.
+	// Using Pacman's ways trying to install foo would never give you
+	// a menu.
+
+	for _, pkg := range dp.AurCache {
+		if seen.get(pkg.Name) {
+			continue
+		}
+
+		if pkgSatisfies(pkg.Name, pkg.Version, dep) {
+			providers.Pkgs = append(providers.Pkgs, pkg)
+			seen.set(pkg.Name)
+			continue
+		}
+
+		for _, provide := range pkg.Provides {
+			if provideSatisfies(provide, dep) {
+				providers.Pkgs = append(providers.Pkgs, pkg)
+				seen.set(pkg.Name)
+				continue
+			}
+		}
+	}
+
+	if providers.Len() == 1 {
+		return providers.Pkgs[0]
+	}
+
+	if providers.Len() > 1 {
+		sort.Sort(providers)
+		return providerMenu(dep, providers)
+	}
+
+	return nil
+}
+
+func (dp *depPool) findSatisfierRepo(dep string) *alpm.Package {
+	for _, pkg := range dp.Repo {
+		if satisfiesRepo(dep, pkg) {
+			return pkg
+		}
+	}
+
+	return nil
+}
+
+func (dp *depPool) hasSatisfier(dep string) bool {
+	return dp.findSatisfierRepo(dep) != nil || dp.findSatisfierAur(dep) != nil
+}
+
+func (dp *depPool) hasPackage(name string) bool {
+	for _, pkg := range dp.Repo {
+		if pkg.Name() == name {
+			return true
+		}
+	}
+
+	for _, pkg := range dp.Aur {
+		if pkg.Name == name {
+			return true
+		}
+	}
+
+	for _, pkg := range dp.Groups {
+		if pkg == name {
+			return true
+		}
+	}
+
+	return false
+}

+ 0 - 650
dependencies.go

@@ -1,650 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"strings"
-
-	alpm "github.com/jguer/go-alpm"
-	rpc "github.com/mikkeloscar/aur"
-	gopkg "github.com/mikkeloscar/gopkgbuild"
-)
-
-type depTree struct {
-	ToProcess stringSet
-	Repo      map[string]*alpm.Package
-	Aur       map[string]*rpc.Pkg
-	Missing   stringSet
-	Groups    stringSet
-	Provides  map[string]string
-	Warnings  *aurWarnings
-}
-
-type depCatagories struct {
-	Repo     []*alpm.Package
-	Aur      []*rpc.Pkg
-	MakeOnly stringSet
-	Bases    map[string][]*rpc.Pkg
-}
-
-func makeDepTree() *depTree {
-	dt := depTree{
-		make(stringSet),
-		make(map[string]*alpm.Package),
-		make(map[string]*rpc.Pkg),
-		make(stringSet),
-		make(stringSet),
-		make(map[string]string),
-		&aurWarnings{},
-	}
-
-	return &dt
-}
-
-func makeDependCatagories() *depCatagories {
-	dc := depCatagories{
-		make([]*alpm.Package, 0),
-		make([]*rpc.Pkg, 0),
-		make(stringSet),
-		make(map[string][]*rpc.Pkg),
-	}
-
-	return &dc
-}
-
-// Cut the version requirement from a dependency leaving just the name.
-func splitNameFromDep(dep string) (string, string) {
-	split := strings.FieldsFunc(dep, func(c rune) bool {
-		return c == '>' || c == '<' || c == '='
-	})
-
-	if len(split) == 1 {
-		return split[0], ""
-	}
-
-	return split[0], split[1]
-}
-
-//split apart db/package to db and package
-func splitDbFromName(pkg string) (string, string) {
-	split := strings.SplitN(pkg, "/", 2)
-
-	if len(split) == 2 {
-		return split[0], split[1]
-	}
-	return "", split[0]
-}
-
-func isDevelName(name string) bool {
-	for _, suffix := range []string{"git", "svn", "hg", "bzr", "nightly"} {
-		if strings.HasSuffix(name, suffix) {
-			return true
-		}
-	}
-
-	return strings.Contains(name, "-always-")
-}
-
-func getBases(pkgs map[string]*rpc.Pkg) map[string][]*rpc.Pkg {
-	bases := make(map[string][]*rpc.Pkg)
-
-nextpkg:
-	for _, pkg := range pkgs {
-		for _, base := range bases[pkg.PackageBase] {
-			if base == pkg {
-				continue nextpkg
-			}
-		}
-
-		_, ok := bases[pkg.PackageBase]
-		if !ok {
-			bases[pkg.PackageBase] = make([]*rpc.Pkg, 0)
-		}
-		bases[pkg.PackageBase] = append(bases[pkg.PackageBase], pkg)
-	}
-
-	return bases
-}
-
-func aurFindProvider(name string, dt *depTree) (string, *rpc.Pkg) {
-	dep, _ := splitNameFromDep(name)
-	aurpkg, exists := dt.Aur[dep]
-
-	if exists {
-		return dep, aurpkg
-	}
-
-	dep, exists = dt.Provides[dep]
-	if exists {
-		aurpkg, exists = dt.Aur[dep]
-		if exists {
-			return dep, aurpkg
-		}
-	}
-
-	return "", nil
-
-}
-
-func repoFindProvider(name string, dt *depTree) (string, *alpm.Package) {
-	dep, _ := splitNameFromDep(name)
-	alpmpkg, exists := dt.Repo[dep]
-
-	if exists {
-		return dep, alpmpkg
-	}
-
-	dep, exists = dt.Provides[dep]
-	if exists {
-		alpmpkg, exists = dt.Repo[dep]
-		if exists {
-			return dep, alpmpkg
-		}
-	}
-
-	return "", nil
-
-}
-
-// Step two of dependency resolving. We already have all the information on the
-// packages we need, now it's just about ordering them correctly.
-// pkgs is a list of targets, the packages we want to install. Dependencies are
-// not included.
-// For each package we want we iterate down the tree until we hit the bottom.
-// This is done recursively for each branch.
-// The start of the tree is defined as the package we want.
-// When we hit the bottom of the branch we know thats the first package
-// we need to install so we add it to the start of the to install
-// list (dc.Aur and dc.Repo).
-// We work our way up until there is another branch to go down and do it all
-// again.
-//
-// Here is a visual example:
-//
-//       a
-//      / \
-//      b  c
-//     / \
-//    d   e
-//
-// We see a and it needs b and c
-// We see b and it needs d and e
-// We see d - it needs nothing so we add d to our list and move up
-// We see e - it needs nothing so we add e to our list and move up
-// We see c - it needs nothing so we add c to our list and move up
-//
-// The final install order would come out as debca
-//
-// There is a little more to this, handling provides, multiple packages wanting the
-// same dependencies, etc. This is just the basic premise.
-func getDepCatagories(pkgs []string, dt *depTree) (*depCatagories, error) {
-	dc := makeDependCatagories()
-	seen := make(stringSet)
-
-	dc.Bases = getBases(dt.Aur)
-
-	for _, pkg := range pkgs {
-		dep, alpmpkg := repoFindProvider(pkg, dt)
-		if alpmpkg != nil {
-			repoDepCatagoriesRecursive(alpmpkg, dc, dt, false)
-			dc.Repo = append(dc.Repo, alpmpkg)
-			delete(dt.Repo, dep)
-		}
-
-		dep, aurpkg := aurFindProvider(pkg, dt)
-		if aurpkg != nil {
-			depCatagoriesRecursive(aurpkg, dc, dt, false, seen)
-			if !seen.get(aurpkg.PackageBase) {
-				dc.Aur = append(dc.Aur, aurpkg)
-				seen.set(aurpkg.PackageBase)
-			}
-
-			delete(dt.Aur, dep)
-		}
-	}
-
-	for _, base := range dc.Bases {
-		for _, pkg := range base {
-			for _, dep := range pkg.Depends {
-				dc.MakeOnly.remove(dep)
-			}
-		}
-	}
-
-	for _, pkg := range dc.Repo {
-		pkg.Depends().ForEach(func(_dep alpm.Depend) error {
-			dep := _dep.Name
-			dc.MakeOnly.remove(dep)
-
-			return nil
-		})
-	}
-
-	for _, pkg := range pkgs {
-		dc.MakeOnly.remove(pkg)
-	}
-
-	dupes := make(map[*alpm.Package]struct{})
-	filteredRepo := make([]*alpm.Package, 0)
-
-	for _, pkg := range dc.Repo {
-		_, ok := dupes[pkg]
-		if ok {
-			continue
-		}
-		dupes[pkg] = struct{}{}
-		filteredRepo = append(filteredRepo, pkg)
-	}
-
-	dc.Repo = filteredRepo
-
-	return dc, nil
-}
-
-func repoDepCatagoriesRecursive(pkg *alpm.Package, dc *depCatagories, dt *depTree, isMake bool) {
-	pkg.Depends().ForEach(func(_dep alpm.Depend) error {
-		dep, alpmpkg := repoFindProvider(_dep.Name, dt)
-		if alpmpkg != nil {
-			delete(dt.Repo, dep)
-			repoDepCatagoriesRecursive(alpmpkg, dc, dt, isMake)
-
-			if isMake {
-				dc.MakeOnly.set(alpmpkg.Name())
-			}
-
-			dc.Repo = append(dc.Repo, alpmpkg)
-		}
-
-		return nil
-	})
-}
-
-func depCatagoriesRecursive(_pkg *rpc.Pkg, dc *depCatagories, dt *depTree, isMake bool, seen stringSet) {
-	for _, pkg := range dc.Bases[_pkg.PackageBase] {
-		for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
-			for _, pkg := range deps {
-				dep, aurpkg := aurFindProvider(pkg, dt)
-				if aurpkg != nil {
-					delete(dt.Aur, dep)
-					depCatagoriesRecursive(aurpkg, dc, dt, isMake, seen)
-
-					if !seen.get(aurpkg.PackageBase) {
-						dc.Aur = append(dc.Aur, aurpkg)
-						seen.set(aurpkg.PackageBase)
-					}
-
-					if isMake {
-						dc.MakeOnly.set(aurpkg.Name)
-					}
-				}
-
-				dep, alpmpkg := repoFindProvider(pkg, dt)
-				if alpmpkg != nil {
-					delete(dt.Repo, dep)
-					repoDepCatagoriesRecursive(alpmpkg, dc, dt, isMake)
-
-					if isMake {
-						dc.MakeOnly.set(alpmpkg.Name())
-					}
-
-					dc.Repo = append(dc.Repo, alpmpkg)
-				}
-
-			}
-			isMake = true
-		}
-	}
-}
-
-// This is step one for dependency resolving. pkgs is a slice of the packages you
-// want to resolve the dependencies for. They can be a mix of aur and repo
-// dependencies. All unmet dependencies will be resolved.
-//
-// For Aur dependencies depends, makedepends and checkdepends are resolved but
-// for repo packages only depends are resolved as they are prebuilt.
-// The return will be split into three categories: Repo, Aur and Missing.
-// The return is in no way ordered. This step is is just aimed at gathering the
-// packages we need.
-//
-// This has been designed to make the least amount of rpc requests as possible.
-// Web requests are probably going to be the bottleneck here so minimizing them
-// provides a nice speed boost.
-//
-// Here is a visual expample of the request system.
-// Remember only unsatisfied packages are requested, if a package is already
-// installed we don't bother.
-//
-//      a
-//     / \
-//     b  c
-//    / \
-//   d   e
-//
-// We see a so we send a request for a
-// We see a wants b and c so we send a request for b and c
-// We see d and e so we send a request for d and e
-//
-// That's 5 packages in 3 requests. The amount of requests needed should always be
-// the same as the height of the tree.
-// The example does not really do this justice, In the real world where packages
-// have 10+ dependencies each this is a very nice optimization.
-func getDepTree(pkgs []string, warnings *aurWarnings) (*depTree, error) {
-	dt := makeDepTree()
-	dt.Warnings = warnings
-
-	localDb, err := alpmHandle.LocalDb()
-	if err != nil {
-		return dt, err
-	}
-	syncDb, err := alpmHandle.SyncDbs()
-	if err != nil {
-		return dt, err
-	}
-
-	for _, pkg := range pkgs {
-		db, name := splitDbFromName(pkg)
-		var foundPkg *alpm.Package
-		var singleDb *alpm.Db
-
-		if db == "aur" {
-			dt.ToProcess.set(name)
-			continue
-		}
-
-		// Check the repos for a matching dep
-		if db != "" {
-			singleDb, err = alpmHandle.SyncDbByName(db)
-			if err != nil {
-				return dt, err
-			}
-			foundPkg, err = singleDb.PkgCache().FindSatisfier(name)
-		} else {
-			foundPkg, err = syncDb.FindSatisfier(name)
-		}
-
-		if err == nil {
-			repoTreeRecursive(foundPkg, dt, localDb, syncDb)
-			continue
-		} else {
-			//would be better to check the groups from singleDb if
-			//the user specified a db but there's no easy way to do
-			//it without making alpm_lists so don't bother for now
-			//db/group is probably a rare use case
-			_, err := syncDb.PkgCachebyGroup(name)
-
-			if err == nil {
-				dt.Groups.set(pkg)
-				continue
-			}
-		}
-
-		if db == "" {
-			dt.ToProcess.set(name)
-		} else {
-			dt.Missing.set(pkg)
-		}
-	}
-
-	if len(dt.ToProcess) > 0 {
-		fmt.Println(bold(cyan("::") + bold(" Querying AUR...")))
-	}
-
-	err = depTreeRecursive(dt, localDb, syncDb, false)
-	if err != nil {
-		return dt, err
-	}
-
-	if !cmdArgs.existsArg("d", "nodeps") {
-		err = checkVersions(dt)
-	}
-
-	dt.Warnings.print()
-
-	return dt, err
-}
-
-// Takes a repo package,
-// gives all of the non installed deps,
-// repeats on each sub dep.
-func repoTreeRecursive(pkg *alpm.Package, dt *depTree, localDb *alpm.Db, syncDb alpm.DbList) (err error) {
-	_, exists := dt.Repo[pkg.Name()]
-	if exists {
-		return
-	}
-
-	_, exists = dt.Provides[pkg.Name()]
-	if exists {
-		return
-	}
-
-	dt.Repo[pkg.Name()] = pkg
-	(*pkg).Provides().ForEach(func(dep alpm.Depend) (err error) {
-		dt.Provides[dep.Name] = pkg.Name()
-		return nil
-	})
-
-	(*pkg).Depends().ForEach(func(dep alpm.Depend) (err error) {
-		_, exists := dt.Repo[dep.Name]
-		if exists {
-			return
-		}
-
-		_, isInstalled := localDb.PkgCache().FindSatisfier(dep.String())
-		if isInstalled == nil {
-			return
-		}
-
-		repoPkg, inRepos := syncDb.FindSatisfier(dep.String())
-		if inRepos == nil {
-			repoTreeRecursive(repoPkg, dt, localDb, syncDb)
-			return
-		}
-
-		dt.Missing.set(dep.String())
-
-		return
-	})
-
-	return
-}
-
-func depTreeRecursive(dt *depTree, localDb *alpm.Db, syncDb alpm.DbList, isMake bool) (err error) {
-	if len(dt.ToProcess) == 0 {
-		return
-	}
-
-	nextProcess := make(stringSet)
-	currentProcess := make(stringSet)
-	// Strip version conditions
-	for _dep := range dt.ToProcess {
-		dep, _ := splitNameFromDep(_dep)
-		currentProcess.set(dep)
-	}
-
-	// Assume toprocess only contains aur stuff we have not seen
-	info, err := aurInfo(currentProcess.toSlice(), dt.Warnings)
-
-	if err != nil {
-		return
-	}
-
-	// Cache the results
-	for _, pkg := range info {
-		dt.Aur[pkg.Name] = pkg
-
-		for _, provide := range pkg.Provides {
-			name, _ := splitNameFromDep(provide)
-			dt.Provides[name] = pkg.Name
-		}
-	}
-
-	// Loop through to process and check if we now have
-	// each packaged cached.
-	// If not cached, we assume it is missing.
-	for pkgName := range currentProcess {
-		pkg, exists := dt.Aur[pkgName]
-
-		// Did not get it in the request.
-		if !exists {
-			dt.Missing.set(pkgName)
-			continue
-		}
-
-		// for each dep and makedep
-		for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
-			for _, versionedDep := range deps {
-				dep, _ := splitNameFromDep(versionedDep)
-
-				_, exists = dt.Aur[dep]
-				// We have it cached so skip.
-				if exists {
-					continue
-				}
-
-				_, exists = dt.Provides[dep]
-				// We have it cached so skip.
-				if exists {
-					continue
-				}
-
-				_, exists = dt.Repo[dep]
-				// We have it cached so skip.
-				if exists {
-					continue
-				}
-
-				_, exists = dt.Missing[dep]
-				// We know it does not resolve so skip.
-				if exists {
-					continue
-				}
-
-				// Check if already installed.
-				_, isInstalled := localDb.PkgCache().FindSatisfier(versionedDep)
-				if isInstalled == nil && config.ReBuild != "tree" {
-					continue
-				}
-
-				// Check the repos for a matching dep.
-				repoPkg, inRepos := syncDb.FindSatisfier(versionedDep)
-				if inRepos == nil {
-					if isInstalled == nil && config.ReBuild == "tree" {
-						continue
-					}
-
-					repoTreeRecursive(repoPkg, dt, localDb, syncDb)
-					continue
-				}
-
-				// If all else fails add it to next search.
-				nextProcess.set(versionedDep)
-			}
-		}
-	}
-
-	dt.ToProcess = nextProcess
-	depTreeRecursive(dt, localDb, syncDb, true)
-
-	return
-}
-
-func checkVersions(dt *depTree) error {
-	has := make(mapStringSlice)
-	allDeps := make([]*gopkg.Dependency, 0)
-
-	localDb, err := alpmHandle.LocalDb()
-	if err != nil {
-		return err
-	}
-
-	for _, pkg := range dt.Aur {
-		for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
-			for _, dep := range deps {
-				_, _dep := splitNameFromDep(dep)
-				if _dep != "" {
-					deps, _ := gopkg.ParseDeps([]string{dep})
-					if deps[0] != nil {
-						allDeps = append(allDeps, deps[0])
-					}
-				}
-			}
-		}
-
-		has.Add(pkg.Name, pkg.Version)
-
-		if !isDevelName(pkg.Name) {
-			for _, name := range pkg.Provides {
-				_name, _ver := splitNameFromDep(name)
-				if _ver != "" {
-					has.Add(_name, _ver)
-				} else {
-					delete(has, _name)
-				}
-			}
-		}
-	}
-
-	for _, pkg := range dt.Repo {
-		pkg.Depends().ForEach(func(dep alpm.Depend) error {
-			if dep.Mod != alpm.DepModAny {
-				deps, _ := gopkg.ParseDeps([]string{dep.String()})
-				if deps[0] != nil {
-					allDeps = append(allDeps, deps[0])
-				}
-			}
-			return nil
-		})
-
-		has.Add(pkg.Name(), pkg.Version())
-
-		pkg.Provides().ForEach(func(dep alpm.Depend) error {
-			if dep.Mod != alpm.DepModAny {
-				has.Add(dep.Name, dep.Version)
-			} else {
-				delete(has, dep.Name)
-			}
-
-			return nil
-		})
-
-	}
-
-	localDb.PkgCache().ForEach(func(pkg alpm.Package) error {
-		pkg.Provides().ForEach(func(dep alpm.Depend) error {
-			if dep.Mod != alpm.DepModAny {
-				has.Add(dep.Name, dep.Version)
-			} else {
-				delete(has, dep.Name)
-			}
-
-			return nil
-		})
-
-		return nil
-	})
-
-	for _, dep := range allDeps {
-		satisfied := false
-		verStrs, ok := has[dep.Name]
-		if !ok {
-			continue
-		}
-
-		for _, verStr := range verStrs {
-			version, err := gopkg.NewCompleteVersion(verStr)
-			if err != nil {
-				return err
-			}
-
-			if version.Satisfies(dep) {
-				satisfied = true
-				break
-			}
-		}
-
-		if !satisfied {
-			dt.Missing.set(dep.String())
-		}
-	}
-
-	return nil
-}

+ 77 - 92
install.go

@@ -18,7 +18,7 @@ func install(parser *arguments) error {
 	requestTargets := parser.targets.toSlice()
 	var err error
 	var incompatible stringSet
-	var dc *depCatagories
+	var do *depOrder
 	var toClean []*rpc.Pkg
 	var toEdit []*rpc.Pkg
 
@@ -41,6 +41,14 @@ func install(parser *arguments) error {
 	remoteNamesCache := sliceToStringSet(remoteNames)
 	localNamesCache := sliceToStringSet(localNames)
 
+	//create the arguments to pass for the repo install
+	arguments := parser.copy()
+	arguments.delArg("y", "refresh")
+	arguments.delArg("asdeps", "asdep")
+	arguments.delArg("asexplicit", "asexp")
+	arguments.op = "S"
+	arguments.targets = make(stringSet)
+
 	//if we are doing -u also request all packages needing update
 	if parser.existsArg("u", "sysupgrade") {
 		aurUp, repoUp, err = upList(warnings)
@@ -48,61 +56,13 @@ func install(parser *arguments) error {
 			return err
 		}
 
-		for _, up := range aurUp {
-			requestTargets = append(requestTargets, "aur/"+up.Name)
-		}
-
-		for _, up := range repoUp {
-			requestTargets = append(requestTargets, up.Name)
-		}
-
-	}
-
-	//if len(aurTargets) > 0 || parser.existsArg("u", "sysupgrade") && len(remoteNames) > 0 {
-	//	fmt.Println(bold(cyan("::") + " Querying AUR..."))
-	//}
-	dt, err := getDepTree(requestTargets, warnings)
-	if err != nil {
-		return err
-	}
-
-	// Deptree will handle db/pkg prefixes. Now they can be striped from the
-	// targets.
-	for pkg := range parser.targets {
-		_, name := splitDbFromName(pkg)
-		parser.targets.remove(pkg)
-		parser.targets.set(name)
-	}
-
-	for i, pkg := range requestTargets {
-		_, name := splitDbFromName(pkg)
-		requestTargets[i] = name
-	}
-
-	if len(dt.Missing) > 0 {
-		str := bold(red(arrow+" Error: ")) + "Could not find all required packages:"
-
-		for name := range dt.Missing {
-			str += "\n    " + name
-		}
-
-		return fmt.Errorf("%s", str)
-	}
-
-	//create the arguments to pass for the repo install
-	arguments := parser.copy()
-	arguments.delArg("y", "refresh")
-	arguments.op = "S"
-	arguments.targets = make(stringSet)
+		warnings.print()
 
-	if parser.existsArg("u", "sysupgrade") {
 		ignore, aurUp, err := upgradePkgs(aurUp, repoUp)
 		if err != nil {
 			return err
 		}
 
-		requestTargets = parser.targets.toSlice()
-
 		for _, up := range repoUp {
 			if !ignore.get(up.Name) {
 				requestTargets = append(requestTargets, up.Name)
@@ -131,61 +91,65 @@ func install(parser *arguments) error {
 		}
 	}
 
-	hasAur := false
-	for pkg := range parser.targets {
-		_, ok := dt.Aur[pkg]
-		if ok {
-			hasAur = true
-		}
+	dp, err := getDepPool(requestTargets, warnings)
+	if err != nil {
+		return err
+	}
+
+	err = dp.CheckMissing()
+	if err != nil {
+		return err
 	}
 
+	err = dp.CheckConflicts()
+	if err != nil {
+		return err
+	}
+
+	hasAur := len(dp.Aur) > 0
+
 	if hasAur && 0 == os.Geteuid() {
 		return fmt.Errorf(bold(red(arrow)) + " Refusing to install AUR Packages as root, Aborting.")
 	}
 
-	dc, err = getDepCatagories(requestTargets, dt)
+	do = getDepOrder(dp)
 	if err != nil {
 		return err
 	}
 
-	for _, pkg := range dc.Repo {
+	for _, pkg := range do.Repo {
 		arguments.addTarget(pkg.DB().Name() + "/" + pkg.Name())
 	}
 
-	for pkg := range dt.Groups {
+	for _, pkg := range dp.Groups {
 		arguments.addTarget(pkg)
 	}
 
-	if len(dc.Aur) == 0 && len(arguments.targets) == 0 && !parser.existsArg("u", "sysupgrade") {
+	if len(do.Aur) == 0 && len(arguments.targets) == 0 && !parser.existsArg("u", "sysupgrade") {
 		fmt.Println("There is nothing to do")
 		return nil
 	}
 
 	if hasAur {
-		hasAur = len(dc.Aur) != 0
-
-		err = checkForAllConflicts(dc)
-		if err != nil {
-			return err
-		}
+		hasAur = len(do.Aur) != 0
 
-		printDepCatagories(dc)
+		do.Print()
 		fmt.Println()
 
-		if len(dc.MakeOnly) > 0 {
+		if do.HasMake() {
 			if !continueTask("Remove make dependencies after install?", "yY") {
 				removeMake = true
 			}
 		}
 
-		toClean, toEdit, err = cleanEditNumberMenu(dc.Aur, dc.Bases, remoteNamesCache)
+		toClean, toEdit, err = cleanEditNumberMenu(do.Aur, do.Bases, remoteNamesCache)
 		if err != nil {
 			return err
 		}
 
 		cleanBuilds(toClean)
 
-		err = downloadPkgBuilds(dc.Aur, parser.targets, dc.Bases)
+		err = downloadPkgBuilds(do.Aur, parser.targets, do.Bases)
 		if err != nil {
 			return err
 		}
@@ -205,17 +169,17 @@ func install(parser *arguments) error {
 		}
 
 		//initial srcinfo parse before pkgver() bump
-		err = parseSRCINFOFiles(dc.Aur, srcinfosStale, dc.Bases)
+		err = parseSRCINFOFiles(do.Aur, srcinfosStale, do.Bases)
 		if err != nil {
 			return err
 		}
 
-		incompatible, err = getIncompatible(dc.Aur, srcinfosStale, dc.Bases)
+		incompatible, err = getIncompatible(do.Aur, srcinfosStale, do.Bases)
 		if err != nil {
 			return err
 		}
 
-		err = checkPgpKeys(dc.Aur, dc.Bases, srcinfosStale)
+		err = checkPgpKeys(do.Aur, do.Bases, srcinfosStale)
 		if err != nil {
 			return err
 		}
@@ -229,10 +193,19 @@ func install(parser *arguments) error {
 
 		depArguments := makeArguments()
 		depArguments.addArg("D", "asdeps")
+		expArguments := makeArguments()
+		expArguments.addArg("D", "asexplicit")
+
+		for _, pkg := range do.Repo {
+			if !dp.Explicit.get(pkg.Name()) && !localNamesCache.get(pkg.Name()) && !remoteNamesCache.get(pkg.Name()) {
+				depArguments.addTarget(pkg.Name())
+				continue
+			}
 
-		for _, pkg := range dc.Repo {
-			if !parser.targets.get(pkg.Name()) && !localNamesCache.get(pkg.Name()) && !remoteNamesCache.get(pkg.Name()) {
+			if parser.existsArg("asdeps", "asdep") && dp.Explicit.get(pkg.Name()) {
 				depArguments.addTarget(pkg.Name())
+			} else if parser.existsArg("asexp", "asexplicit") && dp.Explicit.get(pkg.Name()) {
+				expArguments.addTarget(pkg.Name())
 			}
 		}
 
@@ -242,6 +215,13 @@ func install(parser *arguments) error {
 				return fmt.Errorf("%s%s", stderr, err)
 			}
 		}
+
+		if len(expArguments.targets) > 0 {
+			_, stderr, err := passToPacmanCapture(expArguments)
+			if err != nil {
+				return fmt.Errorf("%s%s", stderr, err)
+			}
+		}
 	}
 
 	if hasAur {
@@ -250,25 +230,21 @@ func install(parser *arguments) error {
 		uask := alpm.QuestionType(ask) | alpm.QuestionTypeConflictPkg
 		cmdArgs.globals["ask"] = fmt.Sprint(uask)
 
-		err = downloadPkgBuildsSources(dc.Aur, dc.Bases, incompatible)
+		err = downloadPkgBuildsSources(do.Aur, do.Bases, incompatible)
 		if err != nil {
 			return err
 		}
 
-		err = buildInstallPkgBuilds(dc.Aur, srcinfosStale, parser.targets, parser, dc.Bases, incompatible)
+		err = buildInstallPkgBuilds(dp, do, srcinfosStale, parser, incompatible)
 		if err != nil {
 			return err
 		}
 
-		if len(dc.MakeOnly) > 0 {
-			if !removeMake {
-				return nil
-			}
-
+		if removeMake {
 			removeArguments := makeArguments()
 			removeArguments.addArg("R", "u")
 
-			for pkg := range dc.MakeOnly {
+			for _, pkg := range do.getMake() {
 				removeArguments.addTarget(pkg)
 			}
 
@@ -283,7 +259,7 @@ func install(parser *arguments) error {
 		}
 
 		if config.CleanAfter {
-			clean(dc.Aur)
+			clean(do.Aur)
 		}
 
 		return nil
@@ -594,13 +570,13 @@ func downloadPkgBuildsSources(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, inco
 	return
 }
 
-func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, targets stringSet, parser *arguments, bases map[string][]*rpc.Pkg, incompatible stringSet) error {
+func buildInstallPkgBuilds(dp *depPool, do *depOrder, srcinfos map[string]*gopkg.PKGBUILD, parser *arguments, incompatible stringSet) error {
 	arch, err := alpmHandle.Arch()
 	if err != nil {
 		return err
 	}
 
-	for _, pkg := range pkgs {
+	for _, pkg := range do.Aur {
 		dir := filepath.Join(config.BuildDir, pkg.PackageBase)
 		built := true
 
@@ -623,8 +599,8 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD,
 			return err
 		}
 
-		if config.ReBuild == "no" || (config.ReBuild == "yes" && !targets.get(pkg.Name)) {
-			for _, split := range bases[pkg.PackageBase] {
+		if config.ReBuild == "no" || (config.ReBuild == "yes" && !dp.Explicit.get(pkg.Name)) {
+			for _, split := range do.Bases[pkg.PackageBase] {
 				file, err := completeFileName(dir, split.Name+"-"+version+"-"+arch+".pkg")
 				if err != nil {
 					return err
@@ -674,6 +650,8 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD,
 
 		depArguments := makeArguments()
 		depArguments.addArg("D", "asdeps")
+		expArguments := makeArguments()
+		expArguments.addArg("D", "asexplicit")
 
 		//remotenames: names of all non repo packages on the system
 		_, _, localNames, remoteNames, err := filterPackages()
@@ -686,7 +664,7 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD,
 		remoteNamesCache := sliceToStringSet(remoteNames)
 		localNamesCache := sliceToStringSet(localNames)
 
-		for _, split := range bases[pkg.PackageBase] {
+		for _, split := range do.Bases[pkg.PackageBase] {
 			file, err := completeFileName(dir, split.Name+"-"+version+"-"+arch+".pkg")
 			if err != nil {
 				return err
@@ -704,10 +682,17 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD,
 			}
 
 			arguments.addTarget(file)
-			//if !targets.get(split.Name) {
-			if !targets.get(split.Name) && !localNamesCache.get(split.Name) && !remoteNamesCache.get(split.Name) {
+			if !dp.Explicit.get(split.Name) && !localNamesCache.get(split.Name) && !remoteNamesCache.get(split.Name) {
 				depArguments.addTarget(split.Name)
 			}
+
+			if dp.Explicit.get(split.Name) {
+				if parser.existsArg("asdeps", "asdep") {
+					depArguments.addTarget(split.Name)
+				} else if parser.existsArg("asexplicit", "asexp") {
+					expArguments.addTarget(split.Name)
+				}
+			}
 		}
 
 		oldConfirm := config.NoConfirm
@@ -717,7 +702,7 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD,
 			return err
 		}
 
-		for _, pkg := range bases[pkg.PackageBase] {
+		for _, pkg := range do.Bases[pkg.PackageBase] {
 			updateVCSData(pkg.Name, srcinfo.Source)
 		}
 

+ 10 - 0
parser.go

@@ -41,6 +41,16 @@ func (set stringSet) toSlice() []string {
 	return slice
 }
 
+func (set stringSet) copy() stringSet {
+	newSet := make(stringSet)
+
+	for str := range set {
+		newSet.set(str)
+	}
+
+	return newSet
+}
+
 func sliceToStringSet(in []string) stringSet {
 	set := make(stringSet)
 

+ 77 - 18
print.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"bufio"
 	"bytes"
 	"encoding/xml"
 	"fmt"
@@ -188,7 +189,7 @@ func (u upSlice) print() {
 }
 
 // printDownloadsFromRepo prints repository packages to be downloaded
-func printDepCatagories(dc *depCatagories) {
+func (do *depOrder) Print() {
 	repo := ""
 	repoMake := ""
 	aur := ""
@@ -199,47 +200,47 @@ func printDepCatagories(dc *depCatagories) {
 	aurLen := 0
 	aurMakeLen := 0
 
-	for _, pkg := range dc.Repo {
-		if dc.MakeOnly.get(pkg.Name()) {
-			repoMake += "  " + pkg.Name() + "-" + pkg.Version()
-			repoMakeLen++
-		} else {
+	for _, pkg := range do.Repo {
+		if do.Runtime.get(pkg.Name()) {
 			repo += "  " + pkg.Name() + "-" + pkg.Version()
 			repoLen++
+		} else {
+			repoMake += "  " + pkg.Name() + "-" + pkg.Version()
+			repoMakeLen++
 		}
 	}
 
-	for _, pkg := range dc.Aur {
+	for _, pkg := range do.Aur {
 		pkgStr := "  " + pkg.PackageBase + "-" + pkg.Version
 		pkgStrMake := pkgStr
 
 		push := false
 		pushMake := false
 
-		if len(dc.Bases[pkg.PackageBase]) > 1 || pkg.PackageBase != pkg.Name {
+		if len(do.Bases[pkg.PackageBase]) > 1 || pkg.PackageBase != pkg.Name {
 			pkgStr += " ("
 			pkgStrMake += " ("
 
-			for _, split := range dc.Bases[pkg.PackageBase] {
-				if dc.MakeOnly.get(split.Name) {
-					pkgStrMake += split.Name + " "
-					aurMakeLen++
-					pushMake = true
-				} else {
+			for _, split := range do.Bases[pkg.PackageBase] {
+				if do.Runtime.get(split.Name) {
 					pkgStr += split.Name + " "
 					aurLen++
 					push = true
+				} else {
+					pkgStrMake += split.Name + " "
+					aurMakeLen++
+					pushMake = true
 				}
 			}
 
 			pkgStr = pkgStr[:len(pkgStr)-1] + ")"
 			pkgStrMake = pkgStrMake[:len(pkgStrMake)-1] + ")"
-		} else if dc.MakeOnly.get(pkg.Name) {
-			aurMakeLen++
-			pushMake = true
-		} else {
+		} else if do.Runtime.get(pkg.Name) {
 			aurLen++
 			push = true
+		} else {
+			aurMakeLen++
+			pushMake = true
 		}
 
 		if push {
@@ -572,3 +573,61 @@ func colourHash(name string) (output string) {
 	}
 	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", hash%6+31, name)
 }
+
+func providerMenu(dep string, providers providers) *rpc.Pkg {
+	size := providers.Len()
+
+	fmt.Print(bold(cyan(":: ")))
+	str := bold(fmt.Sprintf(bold("There are %d providers available for %s:"), size, dep))
+
+	size = 1
+	str += bold(cyan("\n:: ")) + bold("Repository AUR\n    ")
+
+	for _, pkg := range providers.Pkgs {
+		str += fmt.Sprintf("%d) %s ", size, pkg.Name)
+		size++
+	}
+
+	fmt.Println(str)
+
+	for {
+		fmt.Print("\nEnter a number (default=1): ")
+
+		if config.NoConfirm {
+			fmt.Println()
+			break
+		}
+
+		reader := bufio.NewReader(os.Stdin)
+		numberBuf, overflow, err := reader.ReadLine()
+
+		if err != nil {
+			fmt.Println(err)
+			break
+		}
+
+		if overflow {
+			fmt.Println("Input too long")
+			continue
+		}
+
+		if string(numberBuf) == "" {
+			return providers.Pkgs[0]
+		}
+
+		num, err := strconv.Atoi(string(numberBuf))
+		if err != nil {
+			fmt.Printf("%s invalid number: %s\n", red("error:"), string(numberBuf))
+			continue
+		}
+
+		if num < 1 || num > size {
+			fmt.Printf("%s invalid value: %d is not between %d and %d\n", red("error:"), num, 1, size)
+			continue
+		}
+
+		return providers.Pkgs[num-1]
+	}
+
+	return nil
+}

+ 22 - 9
utils.go

@@ -7,7 +7,6 @@ import (
 	"unicode"
 )
 
-type mapStringSlice map[string][]string
 type mapStringSet map[string]stringSet
 
 type intRange struct {
@@ -60,14 +59,6 @@ func (mss mapStringSet) Add(n string, v string) {
 	mss[n].set(v)
 }
 
-func (mss mapStringSlice) Add(n string, v string) {
-	_, ok := mss[n]
-	if !ok {
-		mss[n] = make([]string, 0, 1)
-	}
-	mss[n] = append(mss[n], v)
-}
-
 func completeFileName(dir, name string) (string, error) {
 	files, err := ioutil.ReadDir(dir)
 	if err != nil {
@@ -112,3 +103,25 @@ func lessRunes(iRunes, jRunes []rune) bool {
 
 	return len(iRunes) < len(jRunes)
 }
+
+func stringSliceEqual(a, b []string) bool {
+	if a == nil && b == nil {
+		return true
+	}
+
+	if a == nil || b == nil {
+		return false
+	}
+
+	if len(a) != len(b) {
+		return false
+	}
+
+	for i := 0; i < len(a); i++ {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+
+	return true
+}