service.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  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/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/topo"
  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. 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. }
  103. for i := range aurUp.Up {
  104. up := &aurUp.Up[i]
  105. // add devel packages if they are not already in the list
  106. if names.Contains(up.Name) {
  107. continue
  108. }
  109. // check if deps are satisfied for aur packages
  110. reason := dep.Explicit
  111. if up.Reason == alpm.PkgReasonDepend {
  112. reason = dep.Dep
  113. }
  114. if filter != nil && !filter(up) {
  115. continue
  116. }
  117. aurPkg := aurdata[up.Name]
  118. graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{
  119. Reason: reason,
  120. Source: dep.AUR,
  121. AURBase: &aurPkg.PackageBase,
  122. Upgrade: true,
  123. Version: up.RemoteVersion,
  124. LocalVersion: up.LocalVersion,
  125. })
  126. }
  127. if u.cfg.Mode.AtLeastRepo() {
  128. u.log.OperationInfoln(gotext.Get("Searching databases for updates..."))
  129. syncUpgrades, err := u.dbExecutor.SyncUpgrades(enableDowngrade)
  130. for _, up := range syncUpgrades {
  131. dbName := up.Package.DB().Name()
  132. if filter != nil && !filter(&db.Upgrade{
  133. Name: up.Package.Name(),
  134. RemoteVersion: up.Package.Version(),
  135. Repository: dbName,
  136. Base: up.Package.Base(),
  137. LocalVersion: up.LocalVersion,
  138. Reason: up.Reason,
  139. }) {
  140. continue
  141. }
  142. reason := dep.Explicit
  143. if up.Reason == alpm.PkgReasonDepend {
  144. reason = dep.Dep
  145. }
  146. graph = u.grapher.GraphSyncPkg(ctx, graph, up.Package, &dep.InstallInfo{
  147. Source: dep.Sync,
  148. Reason: reason,
  149. Version: up.Package.Version(),
  150. SyncDBName: &dbName,
  151. LocalVersion: up.LocalVersion,
  152. Upgrade: true,
  153. })
  154. }
  155. errs.Add(err)
  156. }
  157. return errs.Return()
  158. }
  159. func (u *UpgradeService) graphToUpSlice(graph *topo.Graph[string, *dep.InstallInfo]) (aurUp, repoUp UpSlice) {
  160. aurUp = UpSlice{Up: make([]Upgrade, 0, graph.Len())}
  161. repoUp = UpSlice{Up: make([]Upgrade, 0, graph.Len()), Repos: u.dbExecutor.Repos()}
  162. _ = graph.ForEach(func(name string, info *dep.InstallInfo) error {
  163. alpmReason := alpm.PkgReasonDepend
  164. if info.Reason == dep.Explicit {
  165. alpmReason = alpm.PkgReasonExplicit
  166. }
  167. parents := graph.ImmediateDependencies(name)
  168. extra := ""
  169. if len(parents) > 0 && !info.Upgrade && info.Reason == dep.MakeDep {
  170. reducedParents := parents.Slice()[:int(math.Min(cutOffExtra, float64(len(parents))))]
  171. if len(parents) > cutOffExtra {
  172. reducedParents = append(reducedParents, "...")
  173. }
  174. extra = fmt.Sprintf(" (%s of %s)", dep.ReasonNames[info.Reason], strings.Join(reducedParents, ", "))
  175. }
  176. if info.Source == dep.AUR {
  177. aurRepo := "aur"
  178. if info.Devel {
  179. aurRepo = "devel"
  180. }
  181. aurUp.Up = append(aurUp.Up, Upgrade{
  182. Name: name,
  183. RemoteVersion: info.Version,
  184. Repository: aurRepo,
  185. Base: *info.AURBase,
  186. LocalVersion: info.LocalVersion,
  187. Reason: alpmReason,
  188. Extra: extra,
  189. })
  190. } else if info.Source == dep.Sync {
  191. repoUp.Up = append(repoUp.Up, Upgrade{
  192. Name: name,
  193. RemoteVersion: info.Version,
  194. Repository: *info.SyncDBName,
  195. Base: "",
  196. LocalVersion: info.LocalVersion,
  197. Reason: alpmReason,
  198. Extra: extra,
  199. })
  200. }
  201. return nil
  202. })
  203. return aurUp, repoUp
  204. }
  205. func (u *UpgradeService) GraphUpgrades(ctx context.Context,
  206. graph *topo.Graph[string, *dep.InstallInfo],
  207. enableDowngrade bool, filter Filter,
  208. ) (*topo.Graph[string, *dep.InstallInfo], error) {
  209. if graph == nil {
  210. graph = topo.New[string, *dep.InstallInfo]()
  211. }
  212. err := u.upGraph(ctx, graph, enableDowngrade, filter)
  213. if err != nil {
  214. return graph, err
  215. }
  216. if graph.Len() == 0 {
  217. return graph, nil
  218. }
  219. return graph, nil
  220. }
  221. // userExcludeUpgrades asks the user which packages to exclude from the upgrade and
  222. // removes them from the graph
  223. func (u *UpgradeService) UserExcludeUpgrades(graph *topo.Graph[string, *dep.InstallInfo]) ([]string, error) {
  224. if graph.Len() == 0 {
  225. return []string{}, nil
  226. }
  227. aurUp, repoUp := u.graphToUpSlice(graph)
  228. sort.Sort(repoUp)
  229. sort.Sort(aurUp)
  230. allUp := UpSlice{Repos: append(repoUp.Repos, aurUp.Repos...)}
  231. for _, up := range repoUp.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. for _, up := range aurUp.Up {
  239. if up.LocalVersion == "" && up.Reason != alpm.PkgReasonExplicit {
  240. allUp.PulledDeps = append(allUp.PulledDeps, up)
  241. } else {
  242. allUp.Up = append(allUp.Up, up)
  243. }
  244. }
  245. if len(allUp.PulledDeps) > 0 {
  246. u.log.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")),
  247. len(allUp.PulledDeps), text.Bold(gotext.Get("%s will also be installed for this operation",
  248. gotext.GetN("dependency", "dependencies", len(allUp.PulledDeps)))))
  249. allUp.PrintDeps(u.log)
  250. }
  251. u.log.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")),
  252. len(allUp.Up), text.Bold(gotext.Get("%s to upgrade/install.", gotext.GetN("package", "packages", len(allUp.Up)))))
  253. allUp.Print(u.log)
  254. u.log.Infoln(gotext.Get("%s to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)", gotext.GetN("package", "packages", len(allUp.Up))))
  255. u.log.Warnln(gotext.Get("Excluding packages may cause partial upgrades and break systems"))
  256. numbers, err := u.log.GetInput(u.cfg.AnswerUpgrade, settings.NoConfirm)
  257. if err != nil {
  258. return nil, err
  259. }
  260. // upgrade menu asks you which packages to NOT upgrade so in this case
  261. // exclude and include are kind of swapped
  262. exclude, include, otherExclude, otherInclude := intrange.ParseNumberMenu(numbers)
  263. isInclude := len(include) == 0 && len(otherInclude) == 0
  264. excluded := make([]string, 0)
  265. for i := range allUp.Up {
  266. up := &allUp.Up[i]
  267. if isInclude && otherExclude.Get(up.Repository) {
  268. u.log.Debugln("pruning", up.Name)
  269. excluded = append(excluded, graph.Prune(up.Name)...)
  270. continue
  271. }
  272. if isInclude && exclude.Get(len(allUp.Up)-i) {
  273. u.log.Debugln("pruning", up.Name)
  274. excluded = append(excluded, graph.Prune(up.Name)...)
  275. continue
  276. }
  277. if !isInclude && !(include.Get(len(allUp.Up)-i) || otherInclude.Get(up.Repository)) {
  278. u.log.Debugln("pruning", up.Name)
  279. excluded = append(excluded, graph.Prune(up.Name)...)
  280. continue
  281. }
  282. }
  283. return excluded, nil
  284. }