yay/pkg/dep/depPool.go

556 lines
13 KiB
Go
Raw Normal View History

2020-07-10 02:36:45 +02:00
package dep
import (
2021-05-13 07:27:24 +02:00
"context"
2020-07-10 02:36:45 +02:00
"fmt"
"os"
"sort"
2020-07-10 02:36:45 +02:00
"strconv"
"strings"
"sync"
2021-05-13 07:27:24 +02:00
"github.com/Jguer/aur"
2021-06-09 03:54:03 +02:00
alpm "github.com/Jguer/go-alpm/v2"
2020-07-10 02:36:45 +02:00
"github.com/leonelquinteros/gotext"
2021-09-08 22:28:08 +02:00
"github.com/Jguer/yay/v11/pkg/db"
"github.com/Jguer/yay/v11/pkg/query"
"github.com/Jguer/yay/v11/pkg/settings"
"github.com/Jguer/yay/v11/pkg/settings/parser"
"github.com/Jguer/yay/v11/pkg/stringset"
"github.com/Jguer/yay/v11/pkg/text"
)
2020-07-10 02:36:45 +02:00
type Pool struct {
2020-07-29 01:53:25 +02:00
Targets []Target
Explicit stringset.StringSet
Repo map[string]db.IPackage
2021-02-16 16:10:04 +01:00
Aur map[string]*query.Pkg
AurCache map[string]*query.Pkg
2020-07-29 01:53:25 +02:00
Groups []string
2020-08-16 23:41:38 +02:00
AlpmExecutor db.Executor
2020-07-29 01:53:25 +02:00
Warnings *query.AURWarnings
2022-08-23 00:20:09 +02:00
aurClient aur.ClientInterface
}
2022-08-23 00:20:09 +02:00
func newPool(dbExecutor db.Executor, aurClient aur.ClientInterface) *Pool {
2020-07-10 02:36:45 +02:00
dp := &Pool{
2021-05-13 07:27:24 +02:00
Targets: []Target{},
Explicit: map[string]struct{}{},
Repo: map[string]alpm.IPackage{},
Aur: map[string]*aur.Pkg{},
AurCache: map[string]*aur.Pkg{},
Groups: []string{},
AlpmExecutor: dbExecutor,
Warnings: nil,
aurClient: aurClient,
}
2020-07-29 01:53:25 +02:00
return dp
}
2021-08-11 20:13:28 +02:00
// Includes db/ prefixes and group installs.
2021-08-12 18:56:23 +02:00
func (dp *Pool) ResolveTargets(ctx context.Context, pkgs []string,
mode parser.TargetMode,
ignoreProviders, noConfirm, provides bool, rebuild string, splitN int, noDeps, noCheckDeps bool, assumeInstalled []string,
) error {
// RPC requests are slow
2021-05-13 07:27:24 +02:00
// Combine as many AUR package requests as possible into a single RPC call
aurTargets := make(stringset.StringSet)
2020-07-10 02:36:45 +02:00
pkgs = query.RemoveInvalidTargets(pkgs, mode)
for _, pkg := range pkgs {
2020-07-10 02:36:45 +02:00
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
2021-07-27 19:35:56 +02:00
if dp.hasPackage(target.DepString()) || isInAssumeInstalled(target.DepString(), assumeInstalled) {
continue
}
var foundPkg db.IPackage
// aur/ prefix means we only check the aur
if target.DB == "aur" || mode == parser.ModeAUR {
dp.Targets = append(dp.Targets, target)
aurTargets.Set(target.DepString())
2021-08-11 20:13:28 +02:00
continue
}
// If there's a different prefix only look in that repo
2019-02-04 17:56:02 +01:00
if target.DB != "" {
foundPkg = dp.AlpmExecutor.SatisfierFromDB(target.DepString(), target.DB)
} else {
2020-07-29 01:53:25 +02:00
// otherwise find it in any repo
foundPkg = dp.AlpmExecutor.SyncSatisfier(target.DepString())
}
2020-07-29 01:53:25 +02:00
if foundPkg != nil {
dp.Targets = append(dp.Targets, target)
dp.Explicit.Set(foundPkg.Name())
dp.ResolveRepoDependency(foundPkg, noDeps)
2021-08-11 20:13:28 +02:00
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
2020-07-29 01:53:25 +02:00
groupPackages := dp.AlpmExecutor.PackagesFromGroup(target.Name)
if len(groupPackages) > 0 {
dp.Groups = append(dp.Groups, target.String())
2020-07-29 01:53:25 +02:00
for _, pkg := range groupPackages {
dp.Explicit.Set(pkg.Name())
2020-07-29 01:53:25 +02:00
}
continue
}
}
// if there was no db prefix check the aur
2019-02-04 17:56:02 +01:00
if target.DB == "" {
aurTargets.Set(target.DepString())
}
dp.Targets = append(dp.Targets, target)
}
2021-08-09 13:26:32 +02:00
if len(aurTargets) > 0 && mode.AtLeastAUR() {
2021-08-12 18:56:23 +02:00
return dp.resolveAURPackages(ctx, aurTargets, true, ignoreProviders,
noConfirm, provides, rebuild, splitN, noDeps, noCheckDeps)
}
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.
//
2021-08-11 20:13:28 +02:00
// This method increases dependency resolve time.
2021-08-12 18:56:23 +02:00
func (dp *Pool) findProvides(ctx context.Context, pkgs stringset.StringSet) error {
2021-08-11 20:13:28 +02:00
var (
mux sync.Mutex
wg sync.WaitGroup
)
doSearch := func(pkg string) {
defer wg.Done()
2021-08-11 20:13:28 +02:00
var (
err error
results []query.Pkg
)
// Hack for a bigger search result, if the user wants
// java-envronment we can search for just java instead and get
// more hits.
pkg, _, _ = splitDep(pkg) // openimagedenoise-git > ispc-git #1234
words := strings.Split(pkg, "-")
for i := range words {
2021-08-12 18:56:23 +02:00
results, err = dp.aurClient.Search(ctx, strings.Join(words[:i+1], "-"), aur.None)
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.AlpmExecutor.LocalPackage(pkg) != nil {
continue
}
2021-08-11 20:13:28 +02:00
wg.Add(1)
2021-08-11 20:13:28 +02:00
go doSearch(pkg)
}
wg.Wait()
return nil
}
2021-08-12 18:56:23 +02:00
func (dp *Pool) cacheAURPackages(ctx context.Context, _pkgs stringset.StringSet, provides bool, splitN int) error {
pkgs := _pkgs.Copy()
2020-07-10 02:36:45 +02:00
toQuery := make([]string, 0)
for pkg := range pkgs {
if _, ok := dp.AurCache[pkg]; ok {
pkgs.Remove(pkg)
}
}
if len(pkgs) == 0 {
return nil
}
2020-07-10 02:36:45 +02:00
if provides {
2021-08-12 18:56:23 +02:00
err := dp.findProvides(ctx, pkgs)
if err != nil {
return err
}
}
for pkg := range pkgs {
if _, ok := dp.AurCache[pkg]; !ok {
name, _, ver := splitDep(pkg)
if ver != "" {
2020-07-10 02:36:45 +02:00
toQuery = append(toQuery, name, name+"-"+ver)
} else {
2020-07-10 02:36:45 +02:00
toQuery = append(toQuery, name)
}
}
}
2021-08-12 18:56:23 +02:00
info, err := query.AURInfo(ctx, dp.aurClient, toQuery, dp.Warnings, splitN)
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
}
// Compute dependency lists used in Package dep searching and ordering.
2021-08-11 20:13:28 +02:00
// Order sensitive TOFIX.
2022-08-23 00:20:09 +02:00
func ComputeCombinedDepList(pkg *aur.Pkg, noDeps, noCheckDeps bool) []string {
combinedDepList := make([]string, 0, len(pkg.Depends)+len(pkg.MakeDepends)+len(pkg.CheckDepends))
if !noDeps {
2022-08-23 00:20:09 +02:00
combinedDepList = append(combinedDepList, pkg.Depends...)
}
2022-08-23 00:20:09 +02:00
combinedDepList = append(combinedDepList, pkg.MakeDepends...)
if !noCheckDeps {
2022-08-23 00:20:09 +02:00
combinedDepList = append(combinedDepList, pkg.CheckDepends...)
}
return combinedDepList
}
2021-08-12 18:56:23 +02:00
func (dp *Pool) resolveAURPackages(ctx context.Context,
pkgs stringset.StringSet,
2020-07-10 02:36:45 +02:00
explicit, ignoreProviders, noConfirm, provides bool,
rebuild string, splitN int, noDeps, noCheckDeps bool,
) error {
newPackages := make(stringset.StringSet)
newAURPackages := make(stringset.StringSet)
2021-08-12 18:56:23 +02:00
err := dp.cacheAURPackages(ctx, pkgs, provides, splitN)
if err != nil {
return err
}
if len(pkgs) == 0 {
return nil
}
for name := range pkgs {
_, ok := dp.Aur[name]
if ok {
continue
}
2020-07-10 02:36:45 +02:00
pkg := dp.findSatisfierAurCache(name, ignoreProviders, noConfirm, provides)
if pkg == nil {
continue
}
if explicit {
dp.Explicit.Set(pkg.Name)
}
2021-08-11 20:13:28 +02:00
dp.Aur[pkg.Name] = pkg
combinedDepList := ComputeCombinedDepList(pkg, noDeps, noCheckDeps)
2022-08-23 00:20:09 +02:00
for _, dep := range combinedDepList {
newPackages.Set(dep)
}
}
for dep := range newPackages {
if dp.hasSatisfier(dep) {
continue
}
2020-07-29 01:53:25 +02:00
isInstalled := dp.AlpmExecutor.LocalSatisfierExists(dep)
2020-07-10 02:36:45 +02:00
hm := settings.HideMenus
2020-07-29 01:53:25 +02:00
settings.HideMenus = isInstalled
repoPkg := dp.AlpmExecutor.SyncSatisfier(dep) // has satisfier in repo: fetch it
2020-07-10 02:36:45 +02:00
settings.HideMenus = hm
2021-08-11 20:13:28 +02:00
2020-07-29 01:53:25 +02:00
if isInstalled && (rebuild != "tree" || repoPkg != nil) {
continue
}
2020-07-29 01:53:25 +02:00
if repoPkg != nil {
dp.ResolveRepoDependency(repoPkg, false)
continue
}
// assume it's in the aur
// ditch the versioning because the RPC can't handle it
newAURPackages.Set(dep)
}
2021-08-12 18:56:23 +02:00
err = dp.resolveAURPackages(ctx, newAURPackages, false, ignoreProviders,
noConfirm, provides, rebuild, splitN, noDeps, noCheckDeps)
2021-08-11 20:13:28 +02:00
return err
}
func (dp *Pool) ResolveRepoDependency(pkg db.IPackage, noDeps bool) {
dp.Repo[pkg.Name()] = pkg
2021-08-11 20:13:28 +02:00
if noDeps {
return
}
2020-07-29 01:53:25 +02:00
for _, dep := range dp.AlpmExecutor.PackageDepends(pkg) {
if dp.hasSatisfier(dep.String()) {
2020-07-29 01:53:25 +02:00
continue
}
// has satisfier installed: skip
2020-07-29 01:53:25 +02:00
if dp.AlpmExecutor.LocalSatisfierExists(dep.String()) {
continue
}
// has satisfier in repo: fetch it
if repoPkg := dp.AlpmExecutor.SyncSatisfier(dep.String()); repoPkg != nil {
dp.ResolveRepoDependency(repoPkg, noDeps)
}
2020-07-29 01:53:25 +02:00
}
}
2021-08-12 18:56:23 +02:00
func GetPool(ctx context.Context, pkgs []string,
2020-07-10 02:36:45 +02:00
warnings *query.AURWarnings,
2020-08-16 23:41:38 +02:00
dbExecutor db.Executor,
2022-08-23 00:20:09 +02:00
aurClient aur.ClientInterface,
mode parser.TargetMode,
2020-07-10 02:36:45 +02:00
ignoreProviders, noConfirm, provides bool,
rebuild string, splitN int, noDeps bool, noCheckDeps bool, assumeInstalled []string,
) (*Pool, error) {
2021-12-18 21:56:16 +01:00
dp := newPool(dbExecutor, aurClient)
dp.Warnings = warnings
2021-08-12 18:56:23 +02:00
err := dp.ResolveTargets(ctx, pkgs, mode, ignoreProviders, noConfirm, provides,
rebuild, splitN, noDeps, noCheckDeps, assumeInstalled)
return dp, err
}
2021-02-16 16:10:04 +01:00
func (dp *Pool) findSatisfierAur(dep string) *query.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.
2021-08-11 20:13:28 +02:00
// TODO: maybe intermix repo providers in the menu.
2021-02-16 16:10:04 +01:00
func (dp *Pool) findSatisfierAurCache(dep string, ignoreProviders, noConfirm, provides bool) *query.Pkg {
depName, _, _ := splitDep(dep)
seen := make(stringset.StringSet)
providerSlice := makeProviders(depName)
if dp.AlpmExecutor.LocalPackage(depName) != nil {
if pkg, ok := dp.AurCache[dep]; ok && pkgSatisfies(pkg.Name, pkg.Version, dep) {
return pkg
}
}
2020-07-08 03:22:01 +02:00
if ignoreProviders {
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)
2021-08-11 20:13:28 +02:00
continue
}
for _, provide := range pkg.Provides {
if provideSatisfies(provide, dep, pkg.Version) {
providerSlice.Pkgs = append(providerSlice.Pkgs, pkg)
seen.Set(pkg.Name)
2021-08-11 20:13:28 +02:00
continue
}
}
}
2020-07-10 02:36:45 +02:00
if !provides && providerSlice.Len() >= 1 {
return providerSlice.Pkgs[0]
}
if providerSlice.Len() == 1 {
return providerSlice.Pkgs[0]
}
if providerSlice.Len() > 1 {
sort.Sort(providerSlice)
2020-07-10 02:36:45 +02:00
return providerMenu(dep, providerSlice, noConfirm)
}
return nil
}
func (dp *Pool) findSatisfierRepo(dep string) db.IPackage {
for _, pkg := range dp.Repo {
2020-07-29 01:53:25 +02:00
if satisfiesRepo(dep, pkg, dp.AlpmExecutor) {
return pkg
}
}
return nil
}
2020-07-10 02:36:45 +02:00
func (dp *Pool) hasSatisfier(dep string) bool {
return dp.findSatisfierRepo(dep) != nil || dp.findSatisfierAur(dep) != nil
}
2020-07-10 02:36:45 +02:00
func (dp *Pool) 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
}
2020-07-10 02:36:45 +02:00
2021-07-27 19:35:56 +02:00
func isInAssumeInstalled(name string, assumeInstalled []string) bool {
for _, pkgAndVersion := range assumeInstalled {
assumeName, _, _ := splitDep(pkgAndVersion)
2021-08-11 20:13:28 +02:00
depName, _, _ := splitDep(name)
if assumeName == depName {
2021-07-27 19:35:56 +02:00
return true
}
}
return false
}
2021-02-16 16:10:04 +01:00
func providerMenu(dep string, providers providers, noConfirm bool) *query.Pkg {
2020-07-10 02:36:45 +02:00
size := providers.Len()
str := text.Bold(gotext.Get("There are %d providers available for %s:", size, dep))
str += "\n"
2020-07-10 02:36:45 +02:00
size = 1
str += text.SprintOperationInfo(gotext.Get("Repository AUR"), "\n ")
for _, pkg := range providers.Pkgs {
str += fmt.Sprintf("%d) %s ", size, pkg.Name)
size++
}
text.OperationInfoln(str)
for {
fmt.Println(gotext.Get("\nEnter a number (default=1): "))
2020-07-10 02:36:45 +02:00
if noConfirm {
fmt.Println("1")
return providers.Pkgs[0]
}
numberBuf, err := text.GetInput("", false)
2020-07-10 02:36:45 +02:00
if err != nil {
fmt.Fprintln(os.Stderr, err)
2021-08-11 20:13:28 +02:00
2020-07-10 02:36:45 +02:00
break
}
if numberBuf == "" {
2020-07-10 02:36:45 +02:00
return providers.Pkgs[0]
}
num, err := strconv.Atoi(numberBuf)
2020-07-10 02:36:45 +02:00
if err != nil {
text.Errorln(gotext.Get("invalid number: %s", numberBuf))
2020-07-10 02:36:45 +02:00
continue
}
if num < 1 || num >= size {
text.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size-1))
continue
}
return providers.Pkgs[num-1]
}
return nil
}