depGraph.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. package dep
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "strconv"
  7. "github.com/Jguer/yay/v11/pkg/db"
  8. "github.com/Jguer/yay/v11/pkg/metadata"
  9. aur "github.com/Jguer/yay/v11/pkg/query"
  10. "github.com/Jguer/yay/v11/pkg/text"
  11. "github.com/Jguer/yay/v11/pkg/topo"
  12. gosrc "github.com/Morganamilo/go-srcinfo"
  13. "github.com/leonelquinteros/gotext"
  14. )
  15. type InstallInfo struct {
  16. Source Source
  17. Reason Reason
  18. SrcinfoPath *string
  19. AURBase *string
  20. }
  21. func (i *InstallInfo) String() string {
  22. return fmt.Sprintf("InstallInfo{Source: %v, Reason: %v}", i.Source, i.Reason)
  23. }
  24. type (
  25. Reason int
  26. Source int
  27. )
  28. func (r Reason) String() string {
  29. return reasonNames[r]
  30. }
  31. func (s Source) String() string {
  32. return sourceNames[s]
  33. }
  34. const (
  35. Explicit Reason = iota // 0
  36. Dep // 1
  37. MakeDep // 2
  38. CheckDep // 3
  39. )
  40. var reasonNames = map[Reason]string{
  41. Explicit: gotext.Get("explicit"),
  42. Dep: gotext.Get("dep"),
  43. MakeDep: gotext.Get("makedep"),
  44. CheckDep: gotext.Get("checkdep"),
  45. }
  46. const (
  47. AUR Source = iota
  48. Sync
  49. Local
  50. SrcInfo
  51. Missing
  52. )
  53. var sourceNames = map[Source]string{
  54. AUR: gotext.Get("aur"),
  55. Sync: gotext.Get("sync"),
  56. Local: gotext.Get("local"),
  57. SrcInfo: gotext.Get("srcinfo"),
  58. Missing: gotext.Get("missing"),
  59. }
  60. var bgColorMap = map[Source]string{
  61. AUR: "lightblue",
  62. Sync: "lemonchiffon",
  63. Local: "darkolivegreen1",
  64. Missing: "tomato",
  65. }
  66. var colorMap = map[Reason]string{
  67. Explicit: "black",
  68. Dep: "deeppink",
  69. MakeDep: "navyblue",
  70. CheckDep: "forestgreen",
  71. }
  72. type Grapher struct {
  73. dbExecutor db.Executor
  74. aurCache *metadata.AURCache
  75. fullGraph bool // If true, the graph will include all dependencies including already installed ones or repo
  76. noConfirm bool
  77. w io.Writer // output writer
  78. }
  79. func NewGrapher(dbExecutor db.Executor, aurCache *metadata.AURCache, fullGraph, noConfirm bool, output io.Writer) *Grapher {
  80. return &Grapher{
  81. dbExecutor: dbExecutor,
  82. aurCache: aurCache,
  83. fullGraph: fullGraph,
  84. noConfirm: noConfirm,
  85. w: output,
  86. }
  87. }
  88. func (g *Grapher) GraphFromSrcInfo(pkgBuildDir string, pkgbuild *gosrc.Srcinfo) (*topo.Graph[string, *InstallInfo], error) {
  89. graph := topo.New[string, *InstallInfo]()
  90. aurPkgs, err := makeAURPKGFromSrcinfo(g.dbExecutor, pkgbuild)
  91. if err != nil {
  92. return nil, err
  93. }
  94. for _, pkg := range aurPkgs {
  95. pkg := pkg
  96. g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
  97. Color: colorMap[Explicit],
  98. Background: bgColorMap[AUR],
  99. Value: &InstallInfo{
  100. Source: SrcInfo,
  101. Reason: Explicit,
  102. SrcinfoPath: &pkgBuildDir,
  103. AURBase: &pkg.PackageBase,
  104. },
  105. })
  106. g.addDepNodes(&pkg, graph)
  107. }
  108. return graph, nil
  109. }
  110. func (g *Grapher) addDepNodes(pkg *aur.Pkg, graph *topo.Graph[string, *InstallInfo]) {
  111. if len(pkg.MakeDepends) > 0 {
  112. g.addNodes(graph, pkg.Name, pkg.MakeDepends, MakeDep)
  113. }
  114. if !false && len(pkg.Depends) > 0 {
  115. g.addNodes(graph, pkg.Name, pkg.Depends, Dep)
  116. }
  117. if !false && len(pkg.CheckDepends) > 0 {
  118. g.addNodes(graph, pkg.Name, pkg.CheckDepends, CheckDep)
  119. }
  120. }
  121. func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string, *InstallInfo], error) {
  122. graph := topo.New[string, *InstallInfo]()
  123. for _, target := range targets {
  124. aurPkgs, _ := g.aurCache.FindPackage(target)
  125. pkg := provideMenu(g.w, target, aurPkgs, g.noConfirm)
  126. g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{
  127. Color: colorMap[Explicit],
  128. Background: bgColorMap[AUR],
  129. Value: &InstallInfo{
  130. Source: AUR,
  131. Reason: Explicit,
  132. AURBase: &pkg.PackageBase,
  133. },
  134. })
  135. g.addDepNodes(pkg, graph)
  136. }
  137. return graph, nil
  138. }
  139. func (g *Grapher) ValidateAndSetNodeInfo(graph *topo.Graph[string, *InstallInfo],
  140. node string, nodeInfo *topo.NodeInfo[*InstallInfo],
  141. ) {
  142. info := graph.GetNodeInfo(node)
  143. if info != nil && info.Value != nil {
  144. if info.Value.Reason < nodeInfo.Value.Reason {
  145. return // refuse to downgrade reason from explicit to dep
  146. }
  147. }
  148. graph.SetNodeInfo(node, nodeInfo)
  149. }
  150. func (g *Grapher) addNodes(
  151. graph *topo.Graph[string, *InstallInfo],
  152. parentPkgName string,
  153. deps []string,
  154. depType Reason,
  155. ) {
  156. for _, depString := range deps {
  157. depName, _, _ := splitDep(depString)
  158. if g.dbExecutor.LocalSatisfierExists(depString) {
  159. if g.fullGraph {
  160. g.ValidateAndSetNodeInfo(
  161. graph,
  162. depName,
  163. &topo.NodeInfo[*InstallInfo]{Color: colorMap[depType], Background: bgColorMap[Local]})
  164. if err := graph.DependOn(depName, parentPkgName); err != nil {
  165. text.Warnln(depName, parentPkgName, err)
  166. }
  167. }
  168. continue
  169. }
  170. if graph.Exists(depName) {
  171. if err := graph.DependOn(depName, parentPkgName); err != nil {
  172. text.Warnln(depName, parentPkgName, err)
  173. }
  174. continue
  175. }
  176. // Check ALPM
  177. if alpmPkg := g.dbExecutor.SyncSatisfier(depString); alpmPkg != nil {
  178. if err := graph.DependOn(alpmPkg.Name(), parentPkgName); err != nil {
  179. text.Warnln("repo dep warn:", depName, parentPkgName, err)
  180. }
  181. g.ValidateAndSetNodeInfo(
  182. graph,
  183. alpmPkg.Name(),
  184. &topo.NodeInfo[*InstallInfo]{
  185. Color: colorMap[depType],
  186. Background: bgColorMap[Sync],
  187. Value: &InstallInfo{
  188. Source: Sync,
  189. Reason: depType,
  190. },
  191. })
  192. if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && g.fullGraph {
  193. newDepsSlice := make([]string, 0, len(newDeps))
  194. for _, newDep := range newDeps {
  195. newDepsSlice = append(newDepsSlice, newDep.Name)
  196. }
  197. g.addNodes(graph, alpmPkg.Name(), newDepsSlice, Dep)
  198. }
  199. continue
  200. }
  201. if aurPkgs, _ := g.aurCache.FindPackage(depName); len(aurPkgs) != 0 { // Check AUR
  202. pkg := aurPkgs[0]
  203. if len(aurPkgs) > 1 {
  204. pkg = provideMenu(g.w, depName, aurPkgs, g.noConfirm)
  205. g.aurCache.SetProvideCache(depName, []*aur.Pkg{pkg})
  206. }
  207. if err := graph.DependOn(pkg.Name, parentPkgName); err != nil {
  208. text.Warnln("aur dep warn:", pkg.Name, parentPkgName, err)
  209. }
  210. graph.SetNodeInfo(
  211. pkg.Name,
  212. &topo.NodeInfo[*InstallInfo]{
  213. Color: colorMap[depType],
  214. Background: bgColorMap[AUR],
  215. Value: &InstallInfo{
  216. Source: AUR,
  217. Reason: depType,
  218. AURBase: &pkg.PackageBase,
  219. },
  220. })
  221. g.addDepNodes(pkg, graph)
  222. continue
  223. }
  224. // no dep found. add as missing
  225. graph.SetNodeInfo(depString, &topo.NodeInfo[*InstallInfo]{Color: colorMap[depType], Background: bgColorMap[Missing]})
  226. }
  227. }
  228. func provideMenu(w io.Writer, dep string, options []*aur.Pkg, noConfirm bool) *aur.Pkg {
  229. size := len(options)
  230. if size == 1 {
  231. return options[0]
  232. }
  233. str := text.Bold(gotext.Get("There are %d providers available for %s:", size, dep))
  234. str += "\n"
  235. size = 1
  236. str += text.SprintOperationInfo(gotext.Get("Repository AUR"), "\n ")
  237. for _, pkg := range options {
  238. str += fmt.Sprintf("%d) %s ", size, pkg.Name)
  239. size++
  240. }
  241. text.OperationInfoln(str)
  242. for {
  243. fmt.Fprintln(w, gotext.Get("\nEnter a number (default=1): "))
  244. if noConfirm {
  245. fmt.Fprintln(w, "1")
  246. return options[0]
  247. }
  248. numberBuf, err := text.GetInput("", false)
  249. if err != nil {
  250. fmt.Fprintln(os.Stderr, err)
  251. break
  252. }
  253. if numberBuf == "" {
  254. return options[0]
  255. }
  256. num, err := strconv.Atoi(numberBuf)
  257. if err != nil {
  258. text.Errorln(gotext.Get("invalid number: %s", numberBuf))
  259. continue
  260. }
  261. if num < 1 || num >= size {
  262. text.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size-1))
  263. continue
  264. }
  265. return options[num-1]
  266. }
  267. return nil
  268. }
  269. func makeAURPKGFromSrcinfo(dbExecutor db.Executor, srcInfo *gosrc.Srcinfo) ([]aur.Pkg, error) {
  270. pkgs := make([]aur.Pkg, 0, 1)
  271. alpmArch, err := dbExecutor.AlpmArchitectures()
  272. if err != nil {
  273. return nil, err
  274. }
  275. alpmArch = append(alpmArch, "") // srcinfo assumes no value as ""
  276. for _, pkg := range srcInfo.Packages {
  277. pkgs = append(pkgs, aur.Pkg{
  278. ID: 0,
  279. Name: pkg.Pkgname,
  280. PackageBaseID: 0,
  281. PackageBase: srcInfo.Pkgbase,
  282. Version: srcInfo.Version(),
  283. Description: pkg.Pkgdesc,
  284. URL: pkg.URL,
  285. Depends: append(archStringToString(alpmArch, pkg.Depends),
  286. archStringToString(alpmArch, srcInfo.Package.Depends)...),
  287. MakeDepends: archStringToString(alpmArch, srcInfo.PackageBase.MakeDepends),
  288. CheckDepends: archStringToString(alpmArch, srcInfo.PackageBase.CheckDepends),
  289. Conflicts: append(archStringToString(alpmArch, pkg.Conflicts),
  290. archStringToString(alpmArch, srcInfo.Package.Conflicts)...),
  291. Provides: append(archStringToString(alpmArch, pkg.Provides),
  292. archStringToString(alpmArch, srcInfo.Package.Provides)...),
  293. Replaces: append(archStringToString(alpmArch, pkg.Replaces),
  294. archStringToString(alpmArch, srcInfo.Package.Replaces)...),
  295. OptDepends: []string{},
  296. Groups: pkg.Groups,
  297. License: pkg.License,
  298. Keywords: []string{},
  299. })
  300. }
  301. return pkgs, nil
  302. }
  303. func archStringToString(alpmArches []string, archString []gosrc.ArchString) []string {
  304. pkgs := make([]string, 0, len(archString))
  305. for _, arch := range archString {
  306. if db.ArchIsSupported(alpmArches, arch.Arch) {
  307. pkgs = append(pkgs, arch.Value)
  308. }
  309. }
  310. return pkgs
  311. }