123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- package main
- import (
- "sort"
- "strings"
- "sync"
- alpm "github.com/jguer/go-alpm"
- rpc "github.com/mikkeloscar/aur"
- )
- type depSolver struct {
- Aur []Base
- Repo []*alpm.Package
- Runtime stringSet
- Targets []target
- Explicit stringSet
- AurCache map[string]*rpc.Pkg
- Groups []string
- LocalDb *alpm.Db
- SyncDb alpm.DbList
- Seen stringSet
- Warnings *aurWarnings
- }
- func makeDepSolver() (*depSolver, error) {
- localDb, err := alpmHandle.LocalDb()
- if err != nil {
- return nil, err
- }
- syncDb, err := alpmHandle.SyncDbs()
- if err != nil {
- return nil, err
- }
- return &depSolver{
- make([]Base, 0),
- make([]*alpm.Package, 0),
- make(stringSet),
- make([]target, 0),
- make(stringSet),
- make(map[string]*rpc.Pkg),
- make([]string, 0),
- localDb,
- syncDb,
- make(stringSet),
- nil,
- }, nil
- }
- func getDepSolver(pkgs []string, warnings *aurWarnings) (*depSolver, error) {
- ds, err := makeDepSolver()
- if err != nil {
- return nil, err
- }
- ds.Warnings = warnings
- err = ds.resolveTargets(pkgs)
- if err != nil {
- return nil, err
- }
- ds.resolveRuntime()
- return ds, err
- }
- // Includes db/ prefixes and group installs
- func (ds *depSolver) resolveTargets(pkgs []string) error {
- // RPC requests are slow
- // Combine as many AUR package requests as possible into a single RPC
- // call
- aurTargets := make(stringSet)
- pkgs = removeInvalidTargets(pkgs)
- 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 skipped even if it's from a different database to
- // the one specified
- // this is how pacman behaves
- if ds.hasPackage(target.DepString()) {
- continue
- }
- var foundPkg *alpm.Package
- var singleDb *alpm.Db
- // aur/ prefix means we only check the aur
- if target.Db == "aur" || mode == ModeAUR {
- ds.Targets = append(ds.Targets, target)
- aurTargets.set(target.DepString())
- continue
- }
- // If there'ss 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 = ds.SyncDb.FindSatisfier(target.DepString())
- }
- if err == nil {
- ds.Targets = append(ds.Targets, target)
- ds.Explicit.set(foundPkg.Name())
- ds.ResolveRepoDependency(foundPkg)
- continue
- } else {
- //check for groups
- //currently we don't 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 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
- group, err := ds.SyncDb.PkgCachebyGroup(target.Name)
- if err == nil {
- ds.Groups = append(ds.Groups, target.String())
- group.ForEach(func(pkg alpm.Package) error {
- ds.Explicit.set(pkg.Name())
- return nil
- })
- continue
- }
- }
- //if there was no db prefix check the aur
- if target.Db == "" {
- aurTargets.set(target.DepString())
- }
- ds.Targets = append(ds.Targets, target)
- }
- if len(aurTargets) > 0 && (mode == ModeAny || mode == ModeAUR) {
- return ds.resolveAURPackages(aurTargets, true)
- }
- return nil
- }
- func (ds *depSolver) hasPackage(name string) bool {
- for _, pkg := range ds.Repo {
- if pkg.Name() == name {
- return true
- }
- }
- for _, base := range ds.Aur {
- for _, pkg := range base {
- if pkg.Name == name {
- return true
- }
- }
- }
- for _, pkg := range ds.Groups {
- if pkg == name {
- return true
- }
- }
- return false
- }
- func (ds *depSolver) findSatisfierAur(dep string) *rpc.Pkg {
- for _, base := range ds.Aur {
- for _, pkg := range base {
- if satisfiesAur(dep, pkg) {
- return pkg
- }
- }
- }
- return nil
- }
- func (ds *depSolver) findSatisfierRepo(dep string) *alpm.Package {
- for _, pkg := range ds.Repo {
- if satisfiesRepo(dep, pkg) {
- return pkg
- }
- }
- return nil
- }
- func (ds *depSolver) hasSatisfier(dep string) bool {
- return ds.findSatisfierRepo(dep) != nil || ds.findSatisfierAur(dep) != nil
- }
- func (ds *depSolver) ResolveRepoDependency(pkg *alpm.Package) {
- if ds.Seen.get(pkg.Name()) {
- return
- }
- ds.Repo = append(ds.Repo, pkg)
- ds.Seen.set(pkg.Name())
- pkg.Depends().ForEach(func(dep alpm.Depend) (err error) {
- //have satisfier in dep tree: skip
- if ds.hasSatisfier(dep.String()) {
- return
- }
- //has satisfier installed: skip
- _, isInstalled := ds.LocalDb.PkgCache().FindSatisfier(dep.String())
- if isInstalled == nil {
- return
- }
- //has satisfier in repo: fetch it
- repoPkg, inRepos := ds.SyncDb.FindSatisfier(dep.String())
- if inRepos != nil {
- return
- }
- ds.ResolveRepoDependency(repoPkg)
- return nil
- })
- }
- // This is mostly used to promote packages from the cache
- // to the Install list
- // Provide a pacman style provider menu if there's more than one candidate
- // This acts slightly differently 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.
- // TODO: maybe intermix repo providers in the menu
- func (ds *depSolver) findSatisfierAurCache(dep string) *rpc.Pkg {
- depName, _, _ := splitDep(dep)
- seen := make(stringSet)
- providers := makeProviders(depName)
- if _, err := ds.LocalDb.PkgByName(depName); err == nil {
- if pkg, ok := ds.AurCache[dep]; ok && pkgSatisfies(pkg.Name, pkg.Version, dep) {
- return pkg
- }
- }
- if cmdArgs.op == "Y" || cmdArgs.op == "yay" {
- for _, pkg := range ds.AurCache {
- if pkgSatisfies(pkg.Name, pkg.Version, dep) {
- for _, target := range ds.Targets {
- if target.Name == pkg.Name {
- return pkg
- }
- }
- }
- }
- }
- for _, pkg := range ds.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 (ds *depSolver) cacheAURPackages(_pkgs stringSet) error {
- pkgs := _pkgs.copy()
- query := make([]string, 0)
- for pkg := range pkgs {
- if _, ok := ds.AurCache[pkg]; ok {
- pkgs.remove(pkg)
- }
- }
- if len(pkgs) == 0 {
- return nil
- }
- if config.Provides {
- err := ds.findProvides(pkgs)
- if err != nil {
- return err
- }
- }
- for pkg := range pkgs {
- if _, ok := ds.AurCache[pkg]; !ok {
- name, _, _ := splitDep(pkg)
- query = append(query, name)
- }
- }
- info, err := aurInfo(query, ds.Warnings)
- if err != nil {
- return err
- }
- for _, pkg := range info {
- // Dump everything in cache just in case we need it later
- ds.AurCache[pkg.Name] = pkg
- }
- 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 in case 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 (ds *depSolver) 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 := ds.AurCache[result.Name]; !ok {
- pkgs.set(result.Name)
- }
- mux.Unlock()
- }
- }
- for pkg := range pkgs {
- if _, err := ds.LocalDb.PkgByName(pkg); err == nil {
- continue
- }
- wg.Add(1)
- go doSearch(pkg)
- }
- wg.Wait()
- return nil
- }
- func (ds *depSolver) resolveAURPackages(pkgs stringSet, explicit bool) error {
- newPackages := make(stringSet)
- newAURPackages := make(stringSet)
- toAdd := make([]*rpc.Pkg, 0)
- if len(pkgs) == 0 {
- return nil
- }
- err := ds.cacheAURPackages(pkgs)
- if err != nil {
- return err
- }
- for name := range pkgs {
- if ds.Seen.get(name) {
- continue
- }
- pkg := ds.findSatisfierAurCache(name)
- if pkg == nil {
- continue
- }
- if explicit {
- ds.Explicit.set(pkg.Name)
- }
- ds.Seen.set(pkg.Name)
- toAdd = append(toAdd, pkg)
- for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} {
- for _, dep := range deps {
- newPackages.set(dep)
- }
- }
- }
- for dep := range newPackages {
- if ds.hasSatisfier(dep) {
- continue
- }
- _, isInstalled := ds.LocalDb.PkgCache().FindSatisfier(dep) //has satisfier installed: skip
- hm := hideMenus
- hideMenus = isInstalled == nil
- repoPkg, inRepos := ds.SyncDb.FindSatisfier(dep) //has satisfier in repo: fetch it
- hideMenus = hm
- if isInstalled == nil && (config.ReBuild != "tree" || inRepos == nil) {
- continue
- }
- if inRepos == nil {
- ds.ResolveRepoDependency(repoPkg)
- continue
- }
- //assume it's in the aur
- //ditch the versioning because the RPC can't handle it
- newAURPackages.set(dep)
- }
- err = ds.resolveAURPackages(newAURPackages, false)
- for _, pkg := range toAdd {
- if !ds.hasPackage(pkg.Name) {
- ds.Aur = baseAppend(ds.Aur, pkg)
- }
- }
- return err
- }
- func (ds *depSolver) Print() {
- repo := ""
- repoMake := ""
- aur := ""
- aurMake := ""
- repoLen := 0
- repoMakeLen := 0
- aurLen := 0
- aurMakeLen := 0
- for _, pkg := range ds.Repo {
- if ds.Runtime.get(pkg.Name()) {
- repo += " " + pkg.Name() + "-" + pkg.Version()
- repoLen++
- } else {
- repoMake += " " + pkg.Name() + "-" + pkg.Version()
- repoMakeLen++
- }
- }
- for _, base := range ds.Aur {
- pkg := base.Pkgbase()
- pkgStr := " " + pkg + "-" + base[0].Version
- pkgStrMake := pkgStr
- push := false
- pushMake := false
- if len(base) > 1 || pkg != base[0].Name {
- pkgStr += " ("
- pkgStrMake += " ("
- for _, split := range base {
- if ds.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 ds.Runtime.get(base[0].Name) {
- aurLen++
- push = true
- } else {
- aurMakeLen++
- pushMake = true
- }
- if push {
- aur += pkgStr
- }
- if pushMake {
- aurMake += pkgStrMake
- }
- }
- printDownloads("Repo", repoLen, repo)
- printDownloads("Repo Make", repoMakeLen, repoMake)
- printDownloads("Aur", aurLen, aur)
- printDownloads("Aur Make", aurMakeLen, aurMake)
- }
- func (ds *depSolver) resolveRuntime() {
- for _, pkg := range ds.Repo {
- if ds.Explicit.get(pkg.Name()) {
- ds.Runtime.set(pkg.Name())
- ds.resolveRuntimeRepo(pkg)
- }
- }
- for _, base := range ds.Aur {
- for _, pkg := range base {
- if ds.Explicit.get(pkg.Name) {
- ds.Runtime.set(pkg.Name)
- ds.resolveRuntimeAur(pkg)
- }
- }
- }
- }
- func (ds *depSolver) resolveRuntimeRepo(pkg *alpm.Package) {
- pkg.Depends().ForEach(func(dep alpm.Depend) (err error) {
- for _, pkg := range ds.Repo {
- if ds.Runtime.get(pkg.Name()) {
- continue
- }
- if satisfiesRepo(dep.String(), pkg) {
- ds.Runtime.set(pkg.Name())
- ds.resolveRuntimeRepo(pkg)
- }
- }
- return nil
- })
- }
- func (ds *depSolver) resolveRuntimeAur(pkg *rpc.Pkg) {
- for _, dep := range pkg.Depends {
- for _, pkg := range ds.Repo {
- if ds.Runtime.get(pkg.Name()) {
- continue
- }
- if satisfiesRepo(dep, pkg) {
- ds.Runtime.set(pkg.Name())
- ds.resolveRuntimeRepo(pkg)
- }
- }
- for _, base := range ds.Aur {
- for _, pkg := range base {
- if ds.Runtime.get(pkg.Name) {
- continue
- }
- if satisfiesAur(dep, pkg) {
- ds.Runtime.set(pkg.Name)
- ds.resolveRuntimeAur(pkg)
- }
- }
- }
- }
- }
- func (ds *depSolver) HasMake() bool {
- lenAur := 0
- for _, base := range ds.Aur {
- lenAur += len(base)
- }
- return len(ds.Runtime) != lenAur+len(ds.Repo)
- }
- func (ds *depSolver) getMake() []string {
- makeOnly := make([]string, 0, len(ds.Aur)+len(ds.Repo)-len(ds.Runtime))
- for _, base := range ds.Aur {
- for _, pkg := range base {
- if !ds.Runtime.get(pkg.Name) {
- makeOnly = append(makeOnly, pkg.Name)
- }
- }
- }
- for _, pkg := range ds.Repo {
- if !ds.Runtime.get(pkg.Name()) {
- makeOnly = append(makeOnly, pkg.Name())
- }
- }
- return makeOnly
- }
|