aur.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. package dep
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. aurc "github.com/Jguer/aur"
  7. alpm "github.com/Jguer/go-alpm/v2"
  8. mapset "github.com/deckarep/golang-set/v2"
  9. "github.com/leonelquinteros/gotext"
  10. "github.com/Jguer/yay/v12/pkg/db"
  11. "github.com/Jguer/yay/v12/pkg/dep/topo"
  12. aur "github.com/Jguer/yay/v12/pkg/query"
  13. "github.com/Jguer/yay/v12/pkg/settings"
  14. "github.com/Jguer/yay/v12/pkg/text"
  15. "github.com/Jguer/yay/v12/pkg/vcs"
  16. )
  17. type AURHandler struct {
  18. log *text.Logger
  19. db db.Executor
  20. cfg *settings.Configuration
  21. vcsStore vcs.Store
  22. foundPkgs []string
  23. providerCache map[string][]aur.Pkg
  24. dbExecutor db.Executor
  25. aurClient aurc.QueryClient
  26. fullGraph bool // If true, the graph will include all dependencies including already installed ones or repo
  27. noConfirm bool // If true, the graph will not prompt for confirmation
  28. noDeps bool // If true, the graph will not include dependencies
  29. noCheckDeps bool // If true, the graph will not include check dependencies
  30. needed bool // If true, the graph will only include packages that are not installed
  31. }
  32. func (h *AURHandler) Test(target Target) bool {
  33. // FIXME: add test
  34. h.foundPkgs = append(h.foundPkgs, target.Name)
  35. return true
  36. }
  37. func (h *AURHandler) Graph(ctx context.Context, graph *topo.Graph[string, *InstallInfo]) error {
  38. var errA error
  39. _, errA = h.GraphFromAUR(ctx, graph, h.foundPkgs)
  40. if errA != nil {
  41. return errA
  42. }
  43. return nil
  44. }
  45. func (h *AURHandler) AddDepsForPkgs(ctx context.Context, pkgs []*aur.Pkg, graph *topo.Graph[string, *InstallInfo]) {
  46. for _, pkg := range pkgs {
  47. h.addDepNodes(ctx, pkg, graph)
  48. }
  49. }
  50. func (h *AURHandler) addDepNodes(ctx context.Context, pkg *aur.Pkg, graph *topo.Graph[string, *InstallInfo]) {
  51. if len(pkg.MakeDepends) > 0 {
  52. h.addNodes(ctx, graph, pkg.Name, pkg.MakeDepends, MakeDep)
  53. }
  54. if !h.noDeps && len(pkg.Depends) > 0 {
  55. h.addNodes(ctx, graph, pkg.Name, pkg.Depends, Dep)
  56. }
  57. if !h.noCheckDeps && !h.noDeps && len(pkg.CheckDepends) > 0 {
  58. h.addNodes(ctx, graph, pkg.Name, pkg.CheckDepends, CheckDep)
  59. }
  60. }
  61. func (h *AURHandler) GraphAURTarget(ctx context.Context,
  62. graph *topo.Graph[string, *InstallInfo],
  63. pkg *aurc.Pkg, instalInfo *InstallInfo,
  64. ) *topo.Graph[string, *InstallInfo] {
  65. if graph == nil {
  66. graph = NewGraph()
  67. }
  68. graph.AddNode(pkg.Name)
  69. h.AddAurPkgProvides(pkg, graph)
  70. validateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
  71. Color: colorMap[instalInfo.Reason],
  72. Background: bgColorMap[AUR],
  73. Value: instalInfo,
  74. })
  75. return graph
  76. }
  77. func (h *AURHandler) GraphFromAUR(ctx context.Context,
  78. graph *topo.Graph[string, *InstallInfo],
  79. targets []string,
  80. ) (*topo.Graph[string, *InstallInfo], error) {
  81. if graph == nil {
  82. graph = NewGraph()
  83. }
  84. if len(targets) == 0 {
  85. return graph, nil
  86. }
  87. aurPkgs, errCache := h.aurClient.Get(ctx, &aurc.Query{By: aurc.Name, Needles: targets})
  88. if errCache != nil {
  89. h.log.Errorln(errCache)
  90. }
  91. for i := range aurPkgs {
  92. pkg := &aurPkgs[i]
  93. if _, ok := h.providerCache[pkg.Name]; !ok {
  94. h.providerCache[pkg.Name] = []aurc.Pkg{*pkg}
  95. }
  96. }
  97. aurPkgsAdded := []*aurc.Pkg{}
  98. for _, target := range targets {
  99. if cachedProvidePkg, ok := h.providerCache[target]; ok {
  100. aurPkgs = cachedProvidePkg
  101. } else {
  102. var errA error
  103. aurPkgs, errA = h.aurClient.Get(ctx, &aurc.Query{By: aurc.Provides, Needles: []string{target}, Contains: true})
  104. if errA != nil {
  105. h.log.Errorln(gotext.Get("Failed to find AUR package for"), " ", target, ":", errA)
  106. }
  107. }
  108. if len(aurPkgs) == 0 {
  109. h.log.Errorln(gotext.Get("No AUR package found for"), " ", target)
  110. continue
  111. }
  112. aurPkg := &aurPkgs[0]
  113. if len(aurPkgs) > 1 {
  114. chosen := h.provideMenu(target, aurPkgs)
  115. aurPkg = chosen
  116. h.providerCache[target] = []aurc.Pkg{*aurPkg}
  117. }
  118. reason := Explicit
  119. if pkg := h.dbExecutor.LocalPackage(aurPkg.Name); pkg != nil {
  120. reason = Reason(pkg.Reason())
  121. if h.needed {
  122. if db.VerCmp(pkg.Version(), aurPkg.Version) >= 0 {
  123. h.log.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(pkg.Name()+"-"+pkg.Version())))
  124. continue
  125. }
  126. }
  127. }
  128. graph = h.GraphAURTarget(ctx, graph, aurPkg, &InstallInfo{
  129. AURBase: &aurPkg.PackageBase,
  130. Reason: reason,
  131. Source: AUR,
  132. Version: aurPkg.Version,
  133. })
  134. aurPkgsAdded = append(aurPkgsAdded, aurPkg)
  135. }
  136. h.AddDepsForPkgs(ctx, aurPkgsAdded, graph)
  137. return graph, nil
  138. }
  139. func (h *AURHandler) AddAurPkgProvides(pkg *aurc.Pkg, graph *topo.Graph[string, *InstallInfo]) {
  140. for i := range pkg.Provides {
  141. depName, mod, version := splitDep(pkg.Provides[i])
  142. h.log.Debugln(pkg.String() + " provides: " + depName)
  143. graph.Provides(depName, &alpm.Depend{
  144. Name: depName,
  145. Version: version,
  146. Mod: aurDepModToAlpmDep(mod),
  147. }, pkg.Name)
  148. }
  149. }
  150. // Removes found deps from the deps mapset and returns the found deps.
  151. func (h *AURHandler) findDepsFromAUR(ctx context.Context,
  152. deps mapset.Set[string],
  153. ) []aurc.Pkg {
  154. pkgsToAdd := make([]aurc.Pkg, 0, deps.Cardinality())
  155. if deps.Cardinality() == 0 {
  156. return []aurc.Pkg{}
  157. }
  158. missingNeedles := make([]string, 0, deps.Cardinality())
  159. for _, depString := range deps.ToSlice() {
  160. if _, ok := h.providerCache[depString]; !ok {
  161. depName, _, _ := splitDep(depString)
  162. missingNeedles = append(missingNeedles, depName)
  163. }
  164. }
  165. if len(missingNeedles) != 0 {
  166. h.log.Debugln("deps to find", missingNeedles)
  167. // provider search is more demanding than a simple search
  168. // try to find name match if possible and then try to find provides.
  169. aurPkgs, errCache := h.aurClient.Get(ctx, &aurc.Query{
  170. By: aurc.Name, Needles: missingNeedles, Contains: false,
  171. })
  172. if errCache != nil {
  173. h.log.Errorln(errCache)
  174. }
  175. for i := range aurPkgs {
  176. pkg := &aurPkgs[i]
  177. if deps.Contains(pkg.Name) {
  178. h.providerCache[pkg.Name] = append(h.providerCache[pkg.Name], *pkg)
  179. }
  180. for _, val := range pkg.Provides {
  181. if val == pkg.Name {
  182. continue
  183. }
  184. if deps.Contains(val) {
  185. h.providerCache[val] = append(h.providerCache[val], *pkg)
  186. }
  187. }
  188. }
  189. }
  190. for _, depString := range deps.ToSlice() {
  191. var aurPkgs []aurc.Pkg
  192. depName, _, _ := splitDep(depString)
  193. if cachedProvidePkg, ok := h.providerCache[depString]; ok {
  194. aurPkgs = cachedProvidePkg
  195. } else {
  196. var errA error
  197. aurPkgs, errA = h.aurClient.Get(ctx, &aurc.Query{By: aurc.Provides, Needles: []string{depName}, Contains: true})
  198. if errA != nil {
  199. h.log.Errorln(gotext.Get("Failed to find AUR package for"), depString, ":", errA)
  200. }
  201. }
  202. // remove packages that don't satisfy the dependency
  203. for i := 0; i < len(aurPkgs); i++ {
  204. if !satisfiesAur(depString, &aurPkgs[i]) {
  205. aurPkgs = append(aurPkgs[:i], aurPkgs[i+1:]...)
  206. i--
  207. }
  208. }
  209. if len(aurPkgs) == 0 {
  210. h.log.Errorln(gotext.Get("No AUR package found for"), " ", depString)
  211. continue
  212. }
  213. pkg := aurPkgs[0]
  214. if len(aurPkgs) > 1 {
  215. chosen := h.provideMenu(depString, aurPkgs)
  216. pkg = *chosen
  217. }
  218. h.providerCache[depString] = []aurc.Pkg{pkg}
  219. deps.Remove(depString)
  220. pkgsToAdd = append(pkgsToAdd, pkg)
  221. }
  222. return pkgsToAdd
  223. }
  224. func (h *AURHandler) addNodes(
  225. ctx context.Context,
  226. graph *topo.Graph[string, *InstallInfo],
  227. parentPkgName string,
  228. deps []string,
  229. depType Reason,
  230. ) {
  231. targetsToFind := mapset.NewThreadUnsafeSet(deps...)
  232. // Check if in graph already
  233. for _, depString := range targetsToFind.ToSlice() {
  234. depName, _, _ := splitDep(depString)
  235. if !graph.Exists(depName) && !graph.ProvidesExists(depName) {
  236. continue
  237. }
  238. if graph.Exists(depName) {
  239. if err := graph.DependOn(depName, parentPkgName); err != nil {
  240. h.log.Warnln(depString, parentPkgName, err)
  241. }
  242. targetsToFind.Remove(depString)
  243. }
  244. if p := graph.GetProviderNode(depName); p != nil {
  245. if provideSatisfies(p.String(), depString, p.Version) {
  246. if err := graph.DependOn(p.Provider, parentPkgName); err != nil {
  247. h.log.Warnln(p.Provider, parentPkgName, err)
  248. }
  249. targetsToFind.Remove(depString)
  250. }
  251. }
  252. }
  253. // Check installed
  254. for _, depString := range targetsToFind.ToSlice() {
  255. depName, _, _ := splitDep(depString)
  256. if !h.dbExecutor.LocalSatisfierExists(depString) {
  257. continue
  258. }
  259. if h.fullGraph {
  260. validateAndSetNodeInfo(
  261. graph,
  262. depName,
  263. &topo.NodeInfo[*InstallInfo]{Color: colorMap[depType], Background: bgColorMap[Local]})
  264. if err := graph.DependOn(depName, parentPkgName); err != nil {
  265. h.log.Warnln(depName, parentPkgName, err)
  266. }
  267. }
  268. targetsToFind.Remove(depString)
  269. }
  270. // Check Sync
  271. for _, depString := range targetsToFind.ToSlice() {
  272. alpmPkg := h.dbExecutor.SyncSatisfier(depString)
  273. if alpmPkg == nil {
  274. continue
  275. }
  276. if err := graph.DependOn(alpmPkg.Name(), parentPkgName); err != nil {
  277. h.log.Warnln("repo dep warn:", depString, parentPkgName, err)
  278. }
  279. dbName := alpmPkg.DB().Name()
  280. validateAndSetNodeInfo(
  281. graph,
  282. alpmPkg.Name(),
  283. &topo.NodeInfo[*InstallInfo]{
  284. Color: colorMap[depType],
  285. Background: bgColorMap[Sync],
  286. Value: &InstallInfo{
  287. Source: Sync,
  288. Reason: depType,
  289. Version: alpmPkg.Version(),
  290. SyncDBName: &dbName,
  291. },
  292. })
  293. if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && h.fullGraph {
  294. newDepsSlice := make([]string, 0, len(newDeps))
  295. for _, newDep := range newDeps {
  296. newDepsSlice = append(newDepsSlice, newDep.Name)
  297. }
  298. h.addNodes(ctx, graph, alpmPkg.Name(), newDepsSlice, Dep)
  299. }
  300. targetsToFind.Remove(depString)
  301. }
  302. // Check AUR
  303. pkgsToAdd := h.findDepsFromAUR(ctx, targetsToFind)
  304. for i := range pkgsToAdd {
  305. aurPkg := &pkgsToAdd[i]
  306. if err := graph.DependOn(aurPkg.Name, parentPkgName); err != nil {
  307. h.log.Warnln("aur dep warn:", aurPkg.Name, parentPkgName, err)
  308. }
  309. graph.SetNodeInfo(
  310. aurPkg.Name,
  311. &topo.NodeInfo[*InstallInfo]{
  312. Color: colorMap[depType],
  313. Background: bgColorMap[AUR],
  314. Value: &InstallInfo{
  315. Source: AUR,
  316. Reason: depType,
  317. AURBase: &aurPkg.PackageBase,
  318. Version: aurPkg.Version,
  319. },
  320. })
  321. h.addDepNodes(ctx, aurPkg, graph)
  322. }
  323. // Add missing to graph
  324. for _, depString := range targetsToFind.ToSlice() {
  325. depName, mod, ver := splitDep(depString)
  326. // no dep found. add as missing
  327. if err := graph.DependOn(depName, parentPkgName); err != nil {
  328. h.log.Warnln("missing dep warn:", depString, parentPkgName, err)
  329. }
  330. graph.SetNodeInfo(depName, &topo.NodeInfo[*InstallInfo]{
  331. Color: colorMap[depType],
  332. Background: bgColorMap[Missing],
  333. Value: &InstallInfo{
  334. Source: Missing,
  335. Reason: depType,
  336. Version: fmt.Sprintf("%s%s", mod, ver),
  337. },
  338. })
  339. }
  340. }
  341. func (h *AURHandler) provideMenu(dep string, options []aur.Pkg) *aur.Pkg {
  342. size := len(options)
  343. if size == 1 {
  344. return &options[0]
  345. }
  346. str := text.Bold(gotext.Get("There are %d providers available for %s:", size, dep))
  347. str += "\n"
  348. size = 1
  349. str += h.log.SprintOperationInfo(gotext.Get("Repository AUR"), "\n ")
  350. for i := range options {
  351. str += fmt.Sprintf("%d) %s ", size, options[i].Name)
  352. size++
  353. }
  354. h.log.OperationInfoln(str)
  355. for {
  356. h.log.Println(gotext.Get("\nEnter a number (default=1): "))
  357. if h.noConfirm {
  358. h.log.Println("1")
  359. return &options[0]
  360. }
  361. numberBuf, err := h.log.GetInput("", false)
  362. if err != nil {
  363. h.log.Errorln(err)
  364. break
  365. }
  366. if numberBuf == "" {
  367. return &options[0]
  368. }
  369. num, err := strconv.Atoi(numberBuf)
  370. if err != nil {
  371. h.log.Errorln(gotext.Get("invalid number: %s", numberBuf))
  372. continue
  373. }
  374. if num < 1 || num >= size {
  375. h.log.Errorln(gotext.Get("invalid value: %d is not between %d and %d",
  376. num, 1, size-1))
  377. continue
  378. }
  379. return &options[num-1]
  380. }
  381. return nil
  382. }
  383. func aurDepModToAlpmDep(mod string) alpm.DepMod {
  384. switch mod {
  385. case "=":
  386. return alpm.DepModEq
  387. case ">=":
  388. return alpm.DepModGE
  389. case "<=":
  390. return alpm.DepModLE
  391. case ">":
  392. return alpm.DepModGT
  393. case "<":
  394. return alpm.DepModLT
  395. }
  396. return alpm.DepModAny
  397. }