diff --git a/cmd.go b/cmd.go index 26865c05..1441f3b5 100644 --- a/cmd.go +++ b/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 } diff --git a/config.go b/config.go index 00a9c8f4..105b397a 100644 --- a/config.go +++ b/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. diff --git a/conflicts.go b/conflicts.go deleted file mode 100644 index 9f03b945..00000000 --- a/conflicts.go +++ /dev/null @@ -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 -} diff --git a/dep.go b/dep.go new file mode 100644 index 00000000..3328331f --- /dev/null +++ b/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-") +} diff --git a/depCheck.go b/depCheck.go new file mode 100644 index 00000000..08e9a632 --- /dev/null +++ b/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("") +} diff --git a/depOrder.go b/depOrder.go new file mode 100644 index 00000000..bb3a5273 --- /dev/null +++ b/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 +} diff --git a/depPool.go b/depPool.go new file mode 100644 index 00000000..0ccc27de --- /dev/null +++ b/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 +} diff --git a/dependencies.go b/dependencies.go deleted file mode 100644 index b795673b..00000000 --- a/dependencies.go +++ /dev/null @@ -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 -} diff --git a/install.go b/install.go index af09391f..ccd64143 100644 --- a/install.go +++ b/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) - } + warnings.print() - 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) - - 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 + hasAur = len(do.Aur) != 0 - err = checkForAllConflicts(dc) - if err != nil { - return err - } - - 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 dc.Repo { - if !parser.targets.get(pkg.Name()) && !localNamesCache.get(pkg.Name()) && !remoteNamesCache.get(pkg.Name()) { + for _, pkg := range do.Repo { + if !dp.Explicit.get(pkg.Name()) && !localNamesCache.get(pkg.Name()) && !remoteNamesCache.get(pkg.Name()) { depArguments.addTarget(pkg.Name()) + continue + } + + 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) } diff --git a/parser.go b/parser.go index 7a6787a1..c384a79d 100644 --- a/parser.go +++ b/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) diff --git a/print.go b/print.go index 8bbd1037..06fa18a6 100644 --- a/print.go +++ b/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 +} diff --git a/utils.go b/utils.go index 2439981f..f1760677 100644 --- a/utils.go +++ b/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 +}