service.go 7.8 KB

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