aur_install.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "github.com/Jguer/yay/v12/pkg/db"
  7. "github.com/Jguer/yay/v12/pkg/dep"
  8. "github.com/Jguer/yay/v12/pkg/multierror"
  9. "github.com/Jguer/yay/v12/pkg/settings"
  10. "github.com/Jguer/yay/v12/pkg/settings/exe"
  11. "github.com/Jguer/yay/v12/pkg/settings/parser"
  12. "github.com/Jguer/yay/v12/pkg/text"
  13. "github.com/Jguer/yay/v12/pkg/vcs"
  14. mapset "github.com/deckarep/golang-set/v2"
  15. "github.com/leonelquinteros/gotext"
  16. )
  17. type (
  18. PostInstallHookFunc func(ctx context.Context) error
  19. Installer struct {
  20. dbExecutor db.Executor
  21. postInstallHooks []PostInstallHookFunc
  22. failedAndIgnored map[string]error
  23. exeCmd exe.ICmdBuilder
  24. vcsStore vcs.Store
  25. targetMode parser.TargetMode
  26. downloadOnly bool
  27. log *text.Logger
  28. manualConfirmRequired bool
  29. }
  30. )
  31. func NewInstaller(dbExecutor db.Executor,
  32. exeCmd exe.ICmdBuilder, vcsStore vcs.Store, targetMode parser.TargetMode,
  33. downloadOnly bool, logger *text.Logger,
  34. ) *Installer {
  35. return &Installer{
  36. dbExecutor: dbExecutor,
  37. postInstallHooks: []PostInstallHookFunc{},
  38. failedAndIgnored: map[string]error{},
  39. exeCmd: exeCmd,
  40. vcsStore: vcsStore,
  41. targetMode: targetMode,
  42. downloadOnly: downloadOnly,
  43. log: logger,
  44. manualConfirmRequired: true,
  45. }
  46. }
  47. func (installer *Installer) CompileFailedAndIgnored() error {
  48. if len(installer.failedAndIgnored) == 0 {
  49. return nil
  50. }
  51. return &FailedIgnoredPkgError{
  52. pkgErrors: installer.failedAndIgnored,
  53. }
  54. }
  55. func (installer *Installer) AddPostInstallHook(hook PostInstallHookFunc) {
  56. if hook == nil {
  57. return
  58. }
  59. installer.postInstallHooks = append(installer.postInstallHooks, hook)
  60. }
  61. func (installer *Installer) RunPostInstallHooks(ctx context.Context) error {
  62. var errMulti multierror.MultiError
  63. for _, hook := range installer.postInstallHooks {
  64. if err := hook(ctx); err != nil {
  65. errMulti.Add(err)
  66. }
  67. }
  68. return errMulti.Return()
  69. }
  70. func (installer *Installer) Install(ctx context.Context,
  71. cmdArgs *parser.Arguments,
  72. targets []map[string]*dep.InstallInfo,
  73. pkgBuildDirs map[string]string,
  74. excluded []string,
  75. manualConfirmRequired bool,
  76. ) error {
  77. installer.log.Debugln("manualConfirmRequired:", manualConfirmRequired)
  78. installer.manualConfirmRequired = manualConfirmRequired
  79. // Reorganize targets into layers of dependencies
  80. var errMulti multierror.MultiError
  81. for i := len(targets) - 1; i >= 0; i-- {
  82. lastLayer := i == 0
  83. errI := installer.handleLayer(ctx, cmdArgs, targets[i], pkgBuildDirs, lastLayer, excluded)
  84. if errI == nil && lastLayer {
  85. // success after rollups
  86. return nil
  87. }
  88. if errI != nil {
  89. errMulti.Add(errI)
  90. if lastLayer {
  91. break
  92. }
  93. // rollup
  94. installer.log.Warnln(gotext.Get("Failed to install layer, rolling up to next layer."), "error:", errI)
  95. targets[i-1] = mergeLayers(targets[i-1], targets[i])
  96. }
  97. }
  98. return errMulti.Return()
  99. }
  100. func mergeLayers(layer1, layer2 map[string]*dep.InstallInfo) map[string]*dep.InstallInfo {
  101. for name, info := range layer2 {
  102. layer1[name] = info
  103. }
  104. return layer1
  105. }
  106. func (installer *Installer) appendNoConfirm() bool {
  107. return !installer.manualConfirmRequired || settings.NoConfirm
  108. }
  109. func (installer *Installer) handleLayer(ctx context.Context,
  110. cmdArgs *parser.Arguments,
  111. layer map[string]*dep.InstallInfo,
  112. pkgBuildDirs map[string]string,
  113. lastLayer bool,
  114. excluded []string,
  115. ) error {
  116. // Install layer
  117. nameToBaseMap := make(map[string]string, 0)
  118. syncDeps, syncExp := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
  119. aurDeps, aurExp := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
  120. upgradeSync := false
  121. for name, info := range layer {
  122. switch info.Source {
  123. case dep.AUR, dep.SrcInfo:
  124. nameToBaseMap[name] = *info.AURBase
  125. switch info.Reason {
  126. case dep.Explicit:
  127. if cmdArgs.ExistsArg("asdeps", "asdep") {
  128. aurDeps.Add(name)
  129. } else {
  130. aurExp.Add(name)
  131. }
  132. case dep.Dep, dep.MakeDep, dep.CheckDep:
  133. aurDeps.Add(name)
  134. }
  135. case dep.Sync:
  136. if info.Upgrade {
  137. upgradeSync = true
  138. continue // do not add to targets, let pacman handle it
  139. }
  140. compositePkgName := fmt.Sprintf("%s/%s", *info.SyncDBName, name)
  141. switch info.Reason {
  142. case dep.Explicit:
  143. if cmdArgs.ExistsArg("asdeps", "asdep") {
  144. syncDeps.Add(compositePkgName)
  145. } else {
  146. syncExp.Add(compositePkgName)
  147. }
  148. case dep.Dep, dep.MakeDep, dep.CheckDep:
  149. syncDeps.Add(compositePkgName)
  150. }
  151. }
  152. }
  153. installer.log.Debugln("syncDeps", syncDeps, "SyncExp", syncExp,
  154. "aurDeps", aurDeps, "aurExp", aurExp, "upgrade", upgradeSync)
  155. errShow := installer.installSyncPackages(ctx, cmdArgs, syncDeps, syncExp,
  156. excluded, upgradeSync, installer.appendNoConfirm())
  157. if errShow != nil {
  158. return ErrInstallRepoPkgs
  159. }
  160. errAur := installer.installAURPackages(ctx, cmdArgs, aurDeps, aurExp,
  161. nameToBaseMap, pkgBuildDirs, true, lastLayer, installer.appendNoConfirm())
  162. return errAur
  163. }
  164. func (installer *Installer) installAURPackages(ctx context.Context,
  165. cmdArgs *parser.Arguments,
  166. aurDepNames, aurExpNames mapset.Set[string],
  167. nameToBase, pkgBuildDirsByBase map[string]string,
  168. installIncompatible bool,
  169. lastLayer bool,
  170. noConfirm bool,
  171. ) error {
  172. all := aurDepNames.Union(aurExpNames).ToSlice()
  173. if len(all) == 0 {
  174. return nil
  175. }
  176. deps, exps := make([]string, 0, aurDepNames.Cardinality()), make([]string, 0, aurExpNames.Cardinality())
  177. pkgArchives := make([]string, 0, len(exps)+len(deps))
  178. for _, name := range all {
  179. base := nameToBase[name]
  180. dir := pkgBuildDirsByBase[base]
  181. pkgdests, errMake := installer.buildPkg(ctx, dir, base, installIncompatible, cmdArgs.ExistsArg("needed"))
  182. if errMake != nil {
  183. if !lastLayer {
  184. return fmt.Errorf("%s - %w", gotext.Get("error making: %s", base), errMake)
  185. }
  186. installer.failedAndIgnored[name] = errMake
  187. text.Errorln(gotext.Get("error making: %s", base), "-", errMake)
  188. continue
  189. }
  190. if len(pkgdests) == 0 {
  191. text.Warnln(gotext.Get("nothing to install for %s", text.Cyan(base)))
  192. continue
  193. }
  194. newPKGArchives, hasDebug, err := installer.getNewTargets(pkgdests, name)
  195. if err != nil {
  196. return err
  197. }
  198. pkgArchives = append(pkgArchives, newPKGArchives...)
  199. if isDep := installer.isDep(cmdArgs, aurExpNames, name); isDep {
  200. deps = append(deps, name)
  201. } else {
  202. exps = append(exps, name)
  203. }
  204. if hasDebug {
  205. deps = append(deps, name+"-debug")
  206. }
  207. }
  208. if err := installPkgArchive(ctx, installer.exeCmd, installer.targetMode,
  209. installer.vcsStore, cmdArgs, pkgArchives, noConfirm); err != nil {
  210. return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err)
  211. }
  212. if err := setInstallReason(ctx, installer.exeCmd, installer.targetMode, cmdArgs, deps, exps); err != nil {
  213. return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err)
  214. }
  215. return nil
  216. }
  217. func (installer *Installer) buildPkg(ctx context.Context,
  218. dir, base string,
  219. installIncompatible, needed bool,
  220. ) (map[string]string, error) {
  221. args := []string{"--nobuild", "-fC"}
  222. if installIncompatible {
  223. args = append(args, "--ignorearch")
  224. }
  225. // pkgver bump
  226. if err := installer.exeCmd.Show(
  227. installer.exeCmd.BuildMakepkgCmd(ctx, dir, args...)); err != nil {
  228. return nil, err
  229. }
  230. pkgdests, pkgVersion, errList := parsePackageList(ctx, installer.exeCmd, dir)
  231. if errList != nil {
  232. return nil, errList
  233. }
  234. switch {
  235. case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly:
  236. args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
  237. pkgdests = map[string]string{}
  238. text.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion)))
  239. case pkgsAreBuilt(pkgdests):
  240. args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
  241. text.Warnln(gotext.Get("%s already made -- skipping build", text.Cyan(base+"-"+pkgVersion)))
  242. default:
  243. args = []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
  244. if installIncompatible {
  245. args = append(args, "--ignorearch")
  246. }
  247. }
  248. errMake := installer.exeCmd.Show(
  249. installer.exeCmd.BuildMakepkgCmd(ctx,
  250. dir, args...))
  251. if errMake != nil {
  252. return nil, errMake
  253. }
  254. if installer.downloadOnly {
  255. return map[string]string{}, nil
  256. }
  257. return pkgdests, nil
  258. }
  259. func (installer *Installer) pkgsAreAlreadyInstalled(pkgdests map[string]string, pkgVersion string) bool {
  260. for pkgName := range pkgdests {
  261. if !installer.dbExecutor.IsCorrectVersionInstalled(pkgName, pkgVersion) {
  262. return false
  263. }
  264. }
  265. return true
  266. }
  267. func pkgsAreBuilt(pkgdests map[string]string) bool {
  268. for _, pkgdest := range pkgdests {
  269. if _, err := os.Stat(pkgdest); err != nil {
  270. text.Debugln("pkgIsBuilt:", pkgdest, "does not exist")
  271. return false
  272. }
  273. }
  274. return true
  275. }
  276. func (*Installer) isDep(cmdArgs *parser.Arguments, aurExpNames mapset.Set[string], name string) bool {
  277. switch {
  278. case cmdArgs.ExistsArg("asdeps", "asdep"):
  279. return true
  280. case cmdArgs.ExistsArg("asexplicit", "asexp"):
  281. return false
  282. case aurExpNames.Contains(name):
  283. return false
  284. }
  285. return true
  286. }
  287. func (installer *Installer) getNewTargets(pkgdests map[string]string, name string,
  288. ) (archives []string, good bool, err error) {
  289. pkgdest, ok := pkgdests[name]
  290. if !ok {
  291. return nil, false, &PkgDestNotInListError{name: name}
  292. }
  293. pkgArchives := make([]string, 0, 2)
  294. if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) {
  295. return nil, false, &FindPkgDestError{name: name, pkgDest: pkgdest}
  296. }
  297. pkgArchives = append(pkgArchives, pkgdest)
  298. debugName := name + "-debug"
  299. pkgdestDebug, ok := pkgdests[debugName]
  300. if ok {
  301. if _, errStat := os.Stat(pkgdestDebug); errStat == nil {
  302. pkgArchives = append(pkgArchives, pkgdestDebug)
  303. } else {
  304. ok = false
  305. }
  306. }
  307. return pkgArchives, ok, nil
  308. }
  309. func (installer *Installer) installSyncPackages(ctx context.Context, cmdArgs *parser.Arguments,
  310. syncDeps, // repo targets that are deps
  311. syncExp mapset.Set[string], // repo targets that are exp
  312. excluded []string,
  313. upgrade bool, // run even without targets
  314. noConfirm bool,
  315. ) error {
  316. repoTargets := syncDeps.Union(syncExp).ToSlice()
  317. if len(repoTargets) == 0 && !upgrade {
  318. return nil
  319. }
  320. arguments := cmdArgs.Copy()
  321. arguments.DelArg("asdeps", "asdep")
  322. arguments.DelArg("asexplicit", "asexp")
  323. arguments.DelArg("i", "install")
  324. arguments.Op = "S"
  325. arguments.ClearTargets()
  326. arguments.AddTarget(repoTargets...)
  327. if len(excluded) > 0 {
  328. arguments.CreateOrAppendOption("ignore", excluded...)
  329. }
  330. errShow := installer.exeCmd.Show(installer.exeCmd.BuildPacmanCmd(ctx,
  331. arguments, installer.targetMode, noConfirm))
  332. if errD := asdeps(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncDeps.ToSlice()); errD != nil {
  333. return errD
  334. }
  335. if errE := asexp(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncExp.ToSlice()); errE != nil {
  336. return errE
  337. }
  338. return errShow
  339. }