service.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package upgrade
  2. import (
  3. "context"
  4. "sort"
  5. "github.com/Jguer/aur"
  6. "github.com/Jguer/go-alpm/v2"
  7. mapset "github.com/deckarep/golang-set/v2"
  8. "github.com/leonelquinteros/gotext"
  9. "github.com/Jguer/yay/v11/pkg/db"
  10. "github.com/Jguer/yay/v11/pkg/dep"
  11. "github.com/Jguer/yay/v11/pkg/intrange"
  12. "github.com/Jguer/yay/v11/pkg/multierror"
  13. "github.com/Jguer/yay/v11/pkg/settings"
  14. "github.com/Jguer/yay/v11/pkg/text"
  15. "github.com/Jguer/yay/v11/pkg/topo"
  16. "github.com/Jguer/yay/v11/pkg/vcs"
  17. )
  18. type UpgradeService struct {
  19. grapher *dep.Grapher
  20. aurCache aur.QueryClient
  21. dbExecutor db.Executor
  22. vcsStore vcs.Store
  23. runtime *settings.Runtime
  24. cfg *settings.Configuration
  25. log *text.Logger
  26. noConfirm bool
  27. }
  28. func NewUpgradeService(grapher *dep.Grapher, aurCache aur.QueryClient,
  29. dbExecutor db.Executor, vcsStore vcs.Store, runtime *settings.Runtime,
  30. cfg *settings.Configuration, noConfirm bool, logger *text.Logger,
  31. ) *UpgradeService {
  32. return &UpgradeService{
  33. grapher: grapher,
  34. aurCache: aurCache,
  35. dbExecutor: dbExecutor,
  36. vcsStore: vcsStore,
  37. runtime: runtime,
  38. cfg: cfg,
  39. noConfirm: noConfirm,
  40. log: logger,
  41. }
  42. }
  43. // upGraph adds packages to upgrade to the graph.
  44. func (u *UpgradeService) upGraph(ctx context.Context, graph *topo.Graph[string, *dep.InstallInfo],
  45. enableDowngrade bool,
  46. filter Filter,
  47. ) (err error) {
  48. var (
  49. develUp UpSlice
  50. errs multierror.MultiError
  51. aurdata = make(map[string]*aur.Pkg)
  52. aurUp UpSlice
  53. )
  54. remote := u.dbExecutor.InstalledRemotePackages()
  55. remoteNames := u.dbExecutor.InstalledRemotePackageNames()
  56. if u.runtime.Mode.AtLeastAUR() {
  57. u.log.OperationInfoln(gotext.Get("Searching AUR for updates..."))
  58. _aurdata, err := u.aurCache.Get(ctx, &aur.Query{Needles: remoteNames, By: aur.Name})
  59. errs.Add(err)
  60. if err == nil {
  61. for i := range _aurdata {
  62. pkg := &_aurdata[i]
  63. aurdata[pkg.Name] = pkg
  64. }
  65. aurUp = UpAUR(remote, aurdata, u.cfg.TimeUpdate, enableDowngrade)
  66. }
  67. if u.cfg.Devel {
  68. u.log.OperationInfoln(gotext.Get("Checking development packages..."))
  69. develUp = UpDevel(ctx, remote, aurdata, u.vcsStore)
  70. u.vcsStore.CleanOrphans(remote)
  71. }
  72. }
  73. names := mapset.NewThreadUnsafeSet[string]()
  74. for i := range develUp.Up {
  75. up := &develUp.Up[i]
  76. // check if deps are satisfied for aur packages
  77. reason := dep.Explicit
  78. if up.Reason == alpm.PkgReasonDepend {
  79. reason = dep.Dep
  80. }
  81. if filter != nil && !filter(up) {
  82. continue
  83. }
  84. aurPkg := aurdata[up.Name]
  85. graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{
  86. Reason: reason,
  87. Source: dep.AUR,
  88. AURBase: &aurPkg.PackageBase,
  89. Upgrade: true,
  90. Devel: true,
  91. LocalVersion: up.LocalVersion,
  92. Version: up.RemoteVersion,
  93. })
  94. names.Add(up.Name)
  95. }
  96. for i := range aurUp.Up {
  97. up := &aurUp.Up[i]
  98. // add devel packages if they are not already in the list
  99. if names.Contains(up.Name) {
  100. continue
  101. }
  102. // check if deps are satisfied for aur packages
  103. reason := dep.Explicit
  104. if up.Reason == alpm.PkgReasonDepend {
  105. reason = dep.Dep
  106. }
  107. if filter != nil && !filter(up) {
  108. continue
  109. }
  110. aurPkg := aurdata[up.Name]
  111. graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{
  112. Reason: reason,
  113. Source: dep.AUR,
  114. AURBase: &aurPkg.PackageBase,
  115. Upgrade: true,
  116. Version: up.RemoteVersion,
  117. LocalVersion: up.LocalVersion,
  118. })
  119. }
  120. if u.cfg.Runtime.Mode.AtLeastRepo() {
  121. u.log.OperationInfoln(gotext.Get("Searching databases for updates..."))
  122. syncUpgrades, err := u.dbExecutor.SyncUpgrades(enableDowngrade)
  123. for _, up := range syncUpgrades {
  124. dbName := up.Package.DB().Name()
  125. if filter != nil && !filter(&db.Upgrade{
  126. Name: up.Package.Name(),
  127. RemoteVersion: up.Package.Version(),
  128. Repository: dbName,
  129. Base: up.Package.Base(),
  130. LocalVersion: up.LocalVersion,
  131. Reason: up.Reason,
  132. }) {
  133. continue
  134. }
  135. reason := dep.Explicit
  136. if up.Reason == alpm.PkgReasonDepend {
  137. reason = dep.Dep
  138. }
  139. graph = u.grapher.GraphSyncPkg(ctx, graph, up.Package, &dep.InstallInfo{
  140. Source: dep.Sync,
  141. Reason: reason,
  142. Version: up.Package.Version(),
  143. SyncDBName: &dbName,
  144. LocalVersion: up.LocalVersion,
  145. Upgrade: true,
  146. })
  147. }
  148. errs.Add(err)
  149. }
  150. return errs.Return()
  151. }
  152. func (u *UpgradeService) graphToUpSlice(graph *topo.Graph[string, *dep.InstallInfo]) (aurUp, repoUp UpSlice) {
  153. aurUp = UpSlice{Up: make([]Upgrade, 0, graph.Len())}
  154. repoUp = UpSlice{Up: make([]Upgrade, 0, graph.Len()), Repos: u.dbExecutor.Repos()}
  155. _ = graph.ForEach(func(name string, info *dep.InstallInfo) error {
  156. alpmReason := alpm.PkgReasonExplicit
  157. if info.Reason == dep.Dep {
  158. alpmReason = alpm.PkgReasonDepend
  159. }
  160. if info.Source == dep.AUR {
  161. aurRepo := "aur"
  162. if info.Devel {
  163. aurRepo = "devel"
  164. }
  165. aurUp.Up = append(aurUp.Up, Upgrade{
  166. Name: name,
  167. RemoteVersion: info.Version,
  168. Repository: aurRepo,
  169. Base: *info.AURBase,
  170. LocalVersion: info.LocalVersion,
  171. Reason: alpmReason,
  172. })
  173. } else if info.Source == dep.Sync {
  174. repoUp.Up = append(repoUp.Up, Upgrade{
  175. Name: name,
  176. RemoteVersion: info.Version,
  177. Repository: *info.SyncDBName,
  178. Base: "",
  179. LocalVersion: info.LocalVersion,
  180. Reason: alpmReason,
  181. })
  182. }
  183. return nil
  184. })
  185. return aurUp, repoUp
  186. }
  187. func (u *UpgradeService) GraphUpgrades(ctx context.Context,
  188. graph *topo.Graph[string, *dep.InstallInfo],
  189. enableDowngrade bool,
  190. ) ([]string, *topo.Graph[string, *dep.InstallInfo], error) {
  191. if graph == nil {
  192. graph = topo.New[string, *dep.InstallInfo]()
  193. }
  194. err := u.upGraph(ctx, graph, enableDowngrade,
  195. func(*Upgrade) bool { return true })
  196. if err != nil {
  197. return []string{}, graph, err
  198. }
  199. if graph.Len() == 0 {
  200. return []string{}, graph, nil
  201. }
  202. excluded, errUp := u.userExcludeUpgrades(graph)
  203. return excluded, graph, errUp
  204. }
  205. // userExcludeUpgrades asks the user which packages to exclude from the upgrade and
  206. // removes them from the graph
  207. func (u *UpgradeService) userExcludeUpgrades(graph *topo.Graph[string, *dep.InstallInfo]) ([]string, error) {
  208. allUpLen := graph.Len()
  209. aurUp, repoUp := u.graphToUpSlice(graph)
  210. sort.Sort(repoUp)
  211. sort.Sort(aurUp)
  212. allUp := UpSlice{Up: append(repoUp.Up, aurUp.Up...), Repos: append(repoUp.Repos, aurUp.Repos...)}
  213. u.log.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")), allUpLen, text.Bold(gotext.Get("Packages to upgrade.")))
  214. allUp.Print(u.log)
  215. u.log.Infoln(gotext.Get("Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)"))
  216. u.log.Warnln(gotext.Get("May cause partial upgrades and break systems"))
  217. numbers, err := u.log.GetInput(u.cfg.AnswerUpgrade, settings.NoConfirm)
  218. if err != nil {
  219. return nil, err
  220. }
  221. // upgrade menu asks you which packages to NOT upgrade so in this case
  222. // exclude and include are kind of swapped
  223. exclude, include, otherExclude, otherInclude := intrange.ParseNumberMenu(numbers)
  224. isInclude := len(include) == 0 && len(otherInclude) == 0
  225. excluded := make([]string, 0)
  226. for i := range allUp.Up {
  227. up := &allUp.Up[i]
  228. if isInclude && otherExclude.Get(up.Repository) {
  229. u.log.Debugln("pruning", up.Name)
  230. excluded = append(excluded, graph.Prune(up.Name)...)
  231. }
  232. if isInclude && exclude.Get(allUpLen-i) {
  233. u.log.Debugln("pruning", up.Name)
  234. excluded = append(excluded, graph.Prune(up.Name)...)
  235. continue
  236. }
  237. if !isInclude && !(include.Get(allUpLen-i) || otherInclude.Get(up.Repository)) {
  238. u.log.Debugln("pruning", up.Name)
  239. excluded = append(excluded, graph.Prune(up.Name)...)
  240. continue
  241. }
  242. }
  243. return excluded, nil
  244. }