local_install.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. // Experimental code for install local with dependency refactoring
  2. // Not at feature parity with install.go
  3. package main
  4. import (
  5. "context"
  6. "fmt"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "github.com/Jguer/yay/v11/pkg/db"
  12. "github.com/Jguer/yay/v11/pkg/dep"
  13. "github.com/Jguer/yay/v11/pkg/download"
  14. "github.com/Jguer/yay/v11/pkg/metadata"
  15. "github.com/Jguer/yay/v11/pkg/multierror"
  16. "github.com/Jguer/yay/v11/pkg/settings"
  17. "github.com/Jguer/yay/v11/pkg/settings/exe"
  18. "github.com/Jguer/yay/v11/pkg/settings/parser"
  19. "github.com/Jguer/yay/v11/pkg/text"
  20. "github.com/leonelquinteros/gotext"
  21. "github.com/pkg/errors"
  22. gosrc "github.com/Morganamilo/go-srcinfo"
  23. mapset "github.com/deckarep/golang-set/v2"
  24. )
  25. var ErrInstallRepoPkgs = errors.New(gotext.Get("error installing repo packages"))
  26. func installLocalPKGBUILD(
  27. ctx context.Context,
  28. cmdArgs *parser.Arguments,
  29. dbExecutor db.Executor,
  30. ) error {
  31. aurCache, err := metadata.NewAURCache(filepath.Join(config.BuildDir, "aur.json"))
  32. if err != nil {
  33. return errors.Wrap(err, gotext.Get("failed to retrieve aur Cache"))
  34. }
  35. wd, err := os.Getwd()
  36. if err != nil {
  37. return errors.Wrap(err, gotext.Get("failed to retrieve working directory"))
  38. }
  39. if len(cmdArgs.Targets) > 1 {
  40. return errors.New(gotext.Get("only one target is allowed"))
  41. }
  42. if len(cmdArgs.Targets) == 1 {
  43. wd = cmdArgs.Targets[0]
  44. }
  45. pkgbuild, err := gosrc.ParseFile(filepath.Join(wd, ".SRCINFO"))
  46. if err != nil {
  47. return errors.Wrap(err, gotext.Get("failed to parse .SRCINFO"))
  48. }
  49. grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm, os.Stdout)
  50. graph, err := grapher.GraphFromSrcInfo(wd, pkgbuild)
  51. if err != nil {
  52. return err
  53. }
  54. topoSorted := graph.TopoSortedLayerMap()
  55. fmt.Println(topoSorted, len(topoSorted))
  56. preparer := &Preparer{
  57. dbExecutor: dbExecutor,
  58. cmdBuilder: config.Runtime.CmdBuilder,
  59. config: config,
  60. }
  61. installer := &Installer{dbExecutor: dbExecutor}
  62. err = preparer.Present(os.Stdout, topoSorted)
  63. if err != nil {
  64. return err
  65. }
  66. cleanFunc := preparer.ShouldCleanMakeDeps(ctx)
  67. if cleanFunc != nil {
  68. installer.AddPostInstallHook(cleanFunc)
  69. }
  70. pkgBuildDirs, err := preparer.PrepareWorkspace(ctx, topoSorted)
  71. if err != nil {
  72. return err
  73. }
  74. err = installer.Install(ctx, cmdArgs, topoSorted, pkgBuildDirs)
  75. if err != nil {
  76. if errHook := installer.RunPostInstallHooks(ctx); errHook != nil {
  77. text.Errorln(errHook)
  78. }
  79. return err
  80. }
  81. return installer.RunPostInstallHooks(ctx)
  82. }
  83. type Preparer struct {
  84. dbExecutor db.Executor
  85. cmdBuilder exe.ICmdBuilder
  86. config *settings.Configuration
  87. pkgBuildDirs []string
  88. makeDeps []string
  89. }
  90. func (preper *Preparer) ShouldCleanMakeDeps(ctx context.Context) PostInstallHookFunc {
  91. if len(preper.makeDeps) == 0 {
  92. return nil
  93. }
  94. switch preper.config.RemoveMake {
  95. case "yes":
  96. break
  97. case "no":
  98. return nil
  99. default:
  100. if !text.ContinueTask(os.Stdin, gotext.Get("Remove make dependencies after install?"), false, settings.NoConfirm) {
  101. return nil
  102. }
  103. }
  104. return func(ctx context.Context) error {
  105. return removeMake(ctx, preper.config.Runtime.CmdBuilder, preper.makeDeps)
  106. }
  107. }
  108. func (preper *Preparer) Present(w io.Writer, targets []map[string]*dep.InstallInfo) error {
  109. pkgsBySourceAndReason := map[string]map[string][]string{}
  110. for _, layer := range targets {
  111. for pkgName, info := range layer {
  112. source := dep.SourceNames[info.Source]
  113. reason := dep.ReasonNames[info.Reason]
  114. pkgStr := text.Cyan(fmt.Sprintf("%s-%s", pkgName, info.Version))
  115. if _, ok := pkgsBySourceAndReason[source]; !ok {
  116. pkgsBySourceAndReason[source] = map[string][]string{}
  117. }
  118. pkgsBySourceAndReason[source][reason] = append(pkgsBySourceAndReason[source][reason], pkgStr)
  119. if info.Reason == dep.MakeDep {
  120. preper.makeDeps = append(preper.makeDeps, pkgName)
  121. }
  122. }
  123. }
  124. for source, pkgsByReason := range pkgsBySourceAndReason {
  125. for reason, pkgs := range pkgsByReason {
  126. fmt.Fprintf(w, text.Bold("%s %s (%d):")+" %s\n",
  127. source,
  128. reason,
  129. len(pkgs),
  130. strings.Join(pkgs, ", "))
  131. }
  132. }
  133. return nil
  134. }
  135. func (preper *Preparer) PrepareWorkspace(ctx context.Context, targets []map[string]*dep.InstallInfo) (map[string]string, error) {
  136. aurBases := mapset.NewThreadUnsafeSet[string]()
  137. pkgBuildDirs := make(map[string]string, 0)
  138. for _, layer := range targets {
  139. for pkgName, info := range layer {
  140. if info.Source == dep.AUR {
  141. pkgBase := *info.AURBase
  142. aurBases.Add(pkgBase)
  143. pkgBuildDirs[pkgName] = filepath.Join(config.BuildDir, pkgBase)
  144. } else if info.Source == dep.SrcInfo {
  145. pkgBuildDirs[pkgName] = *info.SrcinfoPath
  146. }
  147. }
  148. }
  149. if _, errA := download.AURPKGBUILDRepos(ctx,
  150. preper.cmdBuilder, aurBases.ToSlice(), config.AURURL, config.BuildDir, false); errA != nil {
  151. return nil, errA
  152. }
  153. if errP := downloadPKGBUILDSourceFanout(ctx, config.Runtime.CmdBuilder,
  154. preper.pkgBuildDirs, false, config.MaxConcurrentDownloads); errP != nil {
  155. text.Errorln(errP)
  156. }
  157. return pkgBuildDirs, nil
  158. }
  159. type (
  160. PostInstallHookFunc func(ctx context.Context) error
  161. Installer struct {
  162. dbExecutor db.Executor
  163. postInstallHooks []PostInstallHookFunc
  164. }
  165. )
  166. func (installer *Installer) AddPostInstallHook(hook PostInstallHookFunc) {
  167. installer.postInstallHooks = append(installer.postInstallHooks, hook)
  168. }
  169. func (Installer *Installer) RunPostInstallHooks(ctx context.Context) error {
  170. var errMulti multierror.MultiError
  171. for _, hook := range Installer.postInstallHooks {
  172. if err := hook(ctx); err != nil {
  173. errMulti.Add(err)
  174. }
  175. }
  176. return errMulti.Return()
  177. }
  178. func (installer *Installer) Install(ctx context.Context,
  179. cmdArgs *parser.Arguments,
  180. targets []map[string]*dep.InstallInfo,
  181. pkgBuildDirs map[string]string,
  182. ) error {
  183. // Reorganize targets into layers of dependencies
  184. for i := len(targets) - 1; i >= 0; i-- {
  185. err := installer.handleLayer(ctx, cmdArgs, targets[i], pkgBuildDirs)
  186. if err != nil {
  187. // rollback
  188. return err
  189. }
  190. }
  191. return nil
  192. }
  193. func (installer *Installer) handleLayer(ctx context.Context,
  194. cmdArgs *parser.Arguments, layer map[string]*dep.InstallInfo, pkgBuildDirs map[string]string,
  195. ) error {
  196. // Install layer
  197. nameToBaseMap := make(map[string]string, 0)
  198. syncDeps, syncExp := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
  199. aurDeps, aurExp := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
  200. for name, info := range layer {
  201. switch info.Source {
  202. case dep.SrcInfo:
  203. fallthrough
  204. case dep.AUR:
  205. nameToBaseMap[name] = *info.AURBase
  206. switch info.Reason {
  207. case dep.Explicit:
  208. if cmdArgs.ExistsArg("asdeps", "asdep") {
  209. aurDeps.Add(name)
  210. } else {
  211. aurExp.Add(name)
  212. }
  213. case dep.CheckDep:
  214. fallthrough
  215. case dep.MakeDep:
  216. fallthrough
  217. case dep.Dep:
  218. aurDeps.Add(name)
  219. }
  220. case dep.Sync:
  221. switch info.Reason {
  222. case dep.Explicit:
  223. if cmdArgs.ExistsArg("asdeps", "asdep") {
  224. syncDeps.Add(name)
  225. } else {
  226. syncExp.Add(name)
  227. }
  228. case dep.CheckDep:
  229. fallthrough
  230. case dep.MakeDep:
  231. fallthrough
  232. case dep.Dep:
  233. syncDeps.Add(name)
  234. }
  235. }
  236. }
  237. fmt.Println(syncDeps, syncExp)
  238. errShow := installer.installSyncPackages(ctx, cmdArgs, syncDeps, syncExp)
  239. if errShow != nil {
  240. return ErrInstallRepoPkgs
  241. }
  242. errAur := installer.installAURPackages(ctx, cmdArgs, aurDeps, aurExp, nameToBaseMap, pkgBuildDirs, false)
  243. return errAur
  244. }
  245. func (installer *Installer) installAURPackages(ctx context.Context,
  246. cmdArgs *parser.Arguments,
  247. aurDepNames, aurExpNames mapset.Set[string],
  248. nameToBase, pkgBuildDirsByBase map[string]string,
  249. installIncompatible bool,
  250. ) error {
  251. deps, exp := make([]string, 0, aurDepNames.Cardinality()), make([]string, 0, aurExpNames.Cardinality())
  252. for _, name := range aurDepNames.Union(aurExpNames).ToSlice() {
  253. base := nameToBase[name]
  254. dir := pkgBuildDirsByBase[base]
  255. args := []string{"--nobuild", "-fC"}
  256. if installIncompatible {
  257. args = append(args, "--ignorearch")
  258. }
  259. // pkgver bump
  260. if err := config.Runtime.CmdBuilder.Show(
  261. config.Runtime.CmdBuilder.BuildMakepkgCmd(ctx, dir, args...)); err != nil {
  262. return errors.New(gotext.Get("error making: %s", base))
  263. }
  264. pkgdests, _, errList := parsePackageList(ctx, dir)
  265. if errList != nil {
  266. return errList
  267. }
  268. args = []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
  269. if installIncompatible {
  270. args = append(args, "--ignorearch")
  271. }
  272. if errMake := config.Runtime.CmdBuilder.Show(
  273. config.Runtime.CmdBuilder.BuildMakepkgCmd(ctx,
  274. dir, args...)); errMake != nil {
  275. return errors.New(gotext.Get("error making: %s", base))
  276. }
  277. names, err := installer.getNewTargets(pkgdests, name)
  278. if err != nil {
  279. return err
  280. }
  281. isDep := installer.isDep(cmdArgs, aurExpNames, name)
  282. if isDep {
  283. deps = append(deps, names...)
  284. } else {
  285. exp = append(exp, names...)
  286. }
  287. }
  288. if err := doInstall(ctx, cmdArgs, deps, exp); err != nil {
  289. return errors.New(fmt.Sprintf(gotext.Get("error installing:")+" %v %v", deps, exp))
  290. }
  291. return nil
  292. }
  293. func (*Installer) isDep(cmdArgs *parser.Arguments, aurExpNames mapset.Set[string], name string) bool {
  294. switch {
  295. case cmdArgs.ExistsArg("asdeps", "asdep"):
  296. return true
  297. case cmdArgs.ExistsArg("asexplicit", "asexp"):
  298. return false
  299. case aurExpNames.Contains(name):
  300. return false
  301. }
  302. return true
  303. }
  304. func (installer *Installer) getNewTargets(pkgdests map[string]string, name string,
  305. ) ([]string, error) {
  306. pkgdest, ok := pkgdests[name]
  307. names := make([]string, 0, 2)
  308. if !ok {
  309. return nil, errors.New(gotext.Get("could not find PKGDEST for: %s", name))
  310. }
  311. if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) {
  312. return nil, errors.New(
  313. gotext.Get(
  314. "the PKGDEST for %s is listed by makepkg but does not exist: %s",
  315. name, pkgdest))
  316. }
  317. names = append(names, name)
  318. debugName := pkgdest + "-debug"
  319. pkgdestDebug, ok := pkgdests[debugName]
  320. if ok {
  321. if _, errStat := os.Stat(pkgdestDebug); errStat == nil {
  322. names = append(names, debugName)
  323. }
  324. }
  325. return names, nil
  326. }
  327. func (*Installer) installSyncPackages(ctx context.Context, cmdArgs *parser.Arguments,
  328. syncDeps, // repo targets that are deps
  329. syncExp mapset.Set[string], // repo targets that are exp
  330. ) error {
  331. repoTargets := syncDeps.Union(syncExp).ToSlice()
  332. if len(repoTargets) == 0 {
  333. return nil
  334. }
  335. arguments := cmdArgs.Copy()
  336. arguments.DelArg("asdeps", "asdep")
  337. arguments.DelArg("asexplicit", "asexp")
  338. arguments.DelArg("i", "install")
  339. arguments.Op = "S"
  340. arguments.ClearTargets()
  341. arguments.AddTarget(repoTargets...)
  342. errShow := config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
  343. arguments, config.Runtime.Mode, settings.NoConfirm))
  344. if errD := asdeps(ctx, cmdArgs, syncDeps.ToSlice()); errD != nil {
  345. return errD
  346. }
  347. if errE := asexp(ctx, cmdArgs, syncExp.ToSlice()); errE != nil {
  348. return errE
  349. }
  350. return errShow
  351. }