service.go 9.1 KB

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