service.go 8.8 KB

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