service.go 8.8 KB

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