service.go 7.6 KB

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