123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- package main
- import (
- "sort"
- "strings"
- "sync"
- alpm "github.com/Jguer/go-alpm"
- rpc "github.com/mikkeloscar/aur"
- "github.com/Jguer/yay/v10/pkg/settings"
- "github.com/Jguer/yay/v10/pkg/stringset"
- )
- type target struct {
- DB string
- Name string
- Mod string
- Version string
- }
- func toTarget(pkg string) target {
- db, dep := splitDBFromName(pkg)
- name, mod, depVersion := splitDep(dep)
- return target{
- DB: db,
- Name: name,
- Mod: mod,
- Version: depVersion,
- }
- }
- 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.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(alpmHandle *alpm.Handle) (*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.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, alpmHandle *alpm.Handle) error {
- // RPC requests are slow
- // Combine as many AUR package requests as possible into a single RPC
- // call
- aurTargets := make(stringset.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 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" || config.Runtime.Mode == settings.ModeAUR {
- dp.Targets = append(dp.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 = 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 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 := dp.SyncDB.FindGroupPkgs(target.Name)
- if !group.Empty() {
- 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 && (config.Runtime.Mode == settings.ModeAny || config.Runtime.Mode == settings.ModeAUR) {
- 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 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 (dp *depPool) findProvides(pkgs stringset.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.Search(strings.Join(words[:i+1], "-"))
- if err == nil {
- break
- }
- }
- if err != nil {
- return
- }
- for iR := range results {
- mux.Lock()
- if _, ok := dp.AurCache[results[iR].Name]; !ok {
- pkgs.Set(results[iR].Name)
- }
- mux.Unlock()
- }
- }
- for pkg := range pkgs {
- if dp.LocalDB.Pkg(pkg) != nil {
- continue
- }
- wg.Add(1)
- go doSearch(pkg)
- }
- wg.Wait()
- return nil
- }
- func (dp *depPool) cacheAURPackages(_pkgs stringset.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, _, ver := splitDep(pkg)
- if ver != "" {
- query = append(query, name, name+"-"+ver)
- } else {
- 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.StringSet, explicit bool) error {
- newPackages := make(stringset.StringSet)
- newAURPackages := make(stringset.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
- }
- _, isInstalled := dp.LocalDB.PkgCache().FindSatisfier(dep) // has satisfier installed: skip
- hm := hideMenus
- hideMenus = isInstalled == nil
- repoPkg, inRepos := dp.SyncDB.FindSatisfier(dep) // has satisfier in repo: fetch it
- hideMenus = hm
- if isInstalled == nil && (config.ReBuild != "tree" || inRepos == nil) {
- continue
- }
- if inRepos == nil {
- dp.ResolveRepoDependency(repoPkg)
- continue
- }
- // assume it's in the aur
- // ditch the versioning because the RPC can't 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, alpmHandle *alpm.Handle) (*depPool, error) {
- dp, err := makeDepPool(alpmHandle)
- if err != nil {
- return nil, err
- }
- dp.Warnings = warnings
- err = dp.ResolveTargets(pkgs, alpmHandle)
- 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 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 (dp *depPool) findSatisfierAurCache(dep string) *rpc.Pkg {
- depName, _, _ := splitDep(dep)
- seen := make(stringset.StringSet)
- providerSlice := makeProviders(depName)
- if dp.LocalDB.Pkg(depName) != nil {
- if pkg, ok := dp.AurCache[dep]; ok && pkgSatisfies(pkg.Name, pkg.Version, dep) {
- return pkg
- }
- }
- if cmdArgs.Op == "Y" || cmdArgs.Op == "yay" {
- for _, pkg := range dp.AurCache {
- if pkgSatisfies(pkg.Name, pkg.Version, dep) {
- for _, target := range dp.Targets {
- if target.Name == pkg.Name {
- return pkg
- }
- }
- }
- }
- }
- for _, pkg := range dp.AurCache {
- if seen.Get(pkg.Name) {
- continue
- }
- if pkgSatisfies(pkg.Name, pkg.Version, dep) {
- providerSlice.Pkgs = append(providerSlice.Pkgs, pkg)
- seen.Set(pkg.Name)
- continue
- }
- for _, provide := range pkg.Provides {
- if provideSatisfies(provide, dep) {
- providerSlice.Pkgs = append(providerSlice.Pkgs, pkg)
- seen.Set(pkg.Name)
- continue
- }
- }
- }
- if !config.Provides && providerSlice.Len() >= 1 {
- return providerSlice.Pkgs[0]
- }
- if providerSlice.Len() == 1 {
- return providerSlice.Pkgs[0]
- }
- if providerSlice.Len() > 1 {
- sort.Sort(providerSlice)
- return providerMenu(dep, providerSlice)
- }
- 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
- }
|