aur_install.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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, syncGroups := mapset.NewThreadUnsafeSet[string](),
  119. mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
  120. aurDeps, aurExp := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
  121. upgradeSync := false
  122. for name, info := range layer {
  123. switch info.Source {
  124. case dep.AUR, dep.SrcInfo:
  125. nameToBaseMap[name] = *info.AURBase
  126. switch info.Reason {
  127. case dep.Explicit:
  128. if cmdArgs.ExistsArg("asdeps", "asdep") {
  129. aurDeps.Add(name)
  130. } else {
  131. aurExp.Add(name)
  132. }
  133. case dep.Dep, dep.MakeDep, dep.CheckDep:
  134. aurDeps.Add(name)
  135. }
  136. case dep.Sync:
  137. if info.Upgrade {
  138. upgradeSync = true
  139. continue // do not add to targets, let pacman handle it
  140. }
  141. compositePkgName := fmt.Sprintf("%s/%s", *info.SyncDBName, name)
  142. if info.IsGroup {
  143. syncGroups.Add(compositePkgName)
  144. continue
  145. }
  146. switch info.Reason {
  147. case dep.Explicit:
  148. if cmdArgs.ExistsArg("asdeps", "asdep") {
  149. syncDeps.Add(compositePkgName)
  150. } else {
  151. syncExp.Add(compositePkgName)
  152. }
  153. case dep.Dep, dep.MakeDep, dep.CheckDep:
  154. syncDeps.Add(compositePkgName)
  155. }
  156. }
  157. }
  158. installer.log.Debugln("syncDeps", syncDeps, "SyncExp", syncExp,
  159. "aurDeps", aurDeps, "aurExp", aurExp, "upgrade", upgradeSync)
  160. errShow := installer.installSyncPackages(ctx, cmdArgs, syncDeps, syncExp, syncGroups,
  161. excluded, upgradeSync, installer.appendNoConfirm())
  162. if errShow != nil {
  163. return ErrInstallRepoPkgs
  164. }
  165. errAur := installer.installAURPackages(ctx, cmdArgs, aurDeps, aurExp,
  166. nameToBaseMap, pkgBuildDirs, true, lastLayer, installer.appendNoConfirm())
  167. return errAur
  168. }
  169. func (installer *Installer) installAURPackages(ctx context.Context,
  170. cmdArgs *parser.Arguments,
  171. aurDepNames, aurExpNames mapset.Set[string],
  172. nameToBase, pkgBuildDirsByBase map[string]string,
  173. installIncompatible bool,
  174. lastLayer bool,
  175. noConfirm bool,
  176. ) error {
  177. all := aurDepNames.Union(aurExpNames).ToSlice()
  178. if len(all) == 0 {
  179. return nil
  180. }
  181. deps, exps := make([]string, 0, aurDepNames.Cardinality()), make([]string, 0, aurExpNames.Cardinality())
  182. pkgArchives := make([]string, 0, len(exps)+len(deps))
  183. for _, name := range all {
  184. base := nameToBase[name]
  185. dir := pkgBuildDirsByBase[base]
  186. pkgdests, errMake := installer.buildPkg(ctx, dir, base, installIncompatible, cmdArgs.ExistsArg("needed"))
  187. if errMake != nil {
  188. if !lastLayer {
  189. return fmt.Errorf("%s - %w", gotext.Get("error making: %s", base), errMake)
  190. }
  191. installer.failedAndIgnored[name] = errMake
  192. text.Errorln(gotext.Get("error making: %s", base), "-", errMake)
  193. continue
  194. }
  195. if len(pkgdests) == 0 {
  196. text.Warnln(gotext.Get("nothing to install for %s", text.Cyan(base)))
  197. continue
  198. }
  199. newPKGArchives, hasDebug, err := installer.getNewTargets(pkgdests, name)
  200. if err != nil {
  201. return err
  202. }
  203. pkgArchives = append(pkgArchives, newPKGArchives...)
  204. if isDep := installer.isDep(cmdArgs, aurExpNames, name); isDep {
  205. deps = append(deps, name)
  206. } else {
  207. exps = append(exps, name)
  208. }
  209. if hasDebug {
  210. deps = append(deps, name+"-debug")
  211. }
  212. }
  213. if err := installPkgArchive(ctx, installer.exeCmd, installer.targetMode,
  214. installer.vcsStore, cmdArgs, pkgArchives, noConfirm); err != nil {
  215. return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err)
  216. }
  217. if err := setInstallReason(ctx, installer.exeCmd, installer.targetMode, cmdArgs, deps, exps); err != nil {
  218. return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err)
  219. }
  220. return nil
  221. }
  222. func (installer *Installer) buildPkg(ctx context.Context,
  223. dir, base string,
  224. installIncompatible, needed bool,
  225. ) (map[string]string, error) {
  226. args := []string{"--nobuild", "-fC"}
  227. if installIncompatible {
  228. args = append(args, "--ignorearch")
  229. }
  230. // pkgver bump
  231. if err := installer.exeCmd.Show(
  232. installer.exeCmd.BuildMakepkgCmd(ctx, dir, args...)); err != nil {
  233. return nil, err
  234. }
  235. pkgdests, pkgVersion, errList := parsePackageList(ctx, installer.exeCmd, dir)
  236. if errList != nil {
  237. return nil, errList
  238. }
  239. switch {
  240. case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly:
  241. args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
  242. pkgdests = map[string]string{}
  243. text.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion)))
  244. case pkgsAreBuilt(pkgdests):
  245. args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
  246. text.Warnln(gotext.Get("%s already made -- skipping build", text.Cyan(base+"-"+pkgVersion)))
  247. default:
  248. args = []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
  249. if installIncompatible {
  250. args = append(args, "--ignorearch")
  251. }
  252. }
  253. errMake := installer.exeCmd.Show(
  254. installer.exeCmd.BuildMakepkgCmd(ctx,
  255. dir, args...))
  256. if errMake != nil {
  257. return nil, errMake
  258. }
  259. if installer.downloadOnly {
  260. return map[string]string{}, nil
  261. }
  262. return pkgdests, nil
  263. }
  264. func (installer *Installer) pkgsAreAlreadyInstalled(pkgdests map[string]string, pkgVersion string) bool {
  265. for pkgName := range pkgdests {
  266. if !installer.dbExecutor.IsCorrectVersionInstalled(pkgName, pkgVersion) {
  267. return false
  268. }
  269. }
  270. return true
  271. }
  272. func pkgsAreBuilt(pkgdests map[string]string) bool {
  273. for _, pkgdest := range pkgdests {
  274. if _, err := os.Stat(pkgdest); err != nil {
  275. text.Debugln("pkgIsBuilt:", pkgdest, "does not exist")
  276. return false
  277. }
  278. }
  279. return true
  280. }
  281. func (*Installer) isDep(cmdArgs *parser.Arguments, aurExpNames mapset.Set[string], name string) bool {
  282. switch {
  283. case cmdArgs.ExistsArg("asdeps", "asdep"):
  284. return true
  285. case cmdArgs.ExistsArg("asexplicit", "asexp"):
  286. return false
  287. case aurExpNames.Contains(name):
  288. return false
  289. }
  290. return true
  291. }
  292. func (installer *Installer) getNewTargets(pkgdests map[string]string, name string,
  293. ) (archives []string, good bool, err error) {
  294. pkgdest, ok := pkgdests[name]
  295. if !ok {
  296. return nil, false, &PkgDestNotInListError{name: name}
  297. }
  298. pkgArchives := make([]string, 0, 2)
  299. if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) {
  300. return nil, false, &FindPkgDestError{name: name, pkgDest: pkgdest}
  301. }
  302. pkgArchives = append(pkgArchives, pkgdest)
  303. debugName := name + "-debug"
  304. pkgdestDebug, ok := pkgdests[debugName]
  305. if ok {
  306. if _, errStat := os.Stat(pkgdestDebug); errStat == nil {
  307. pkgArchives = append(pkgArchives, pkgdestDebug)
  308. } else {
  309. ok = false
  310. }
  311. }
  312. return pkgArchives, ok, nil
  313. }
  314. func (installer *Installer) installSyncPackages(ctx context.Context, cmdArgs *parser.Arguments,
  315. syncDeps, // repo targets that are deps
  316. syncExp mapset.Set[string], // repo targets that are exp
  317. syncGroups mapset.Set[string], // repo targets that are groups
  318. excluded []string,
  319. upgrade bool, // run even without targets
  320. noConfirm bool,
  321. ) error {
  322. repoTargets := syncDeps.Union(syncExp).Union(syncGroups).ToSlice()
  323. if len(repoTargets) == 0 && !upgrade {
  324. return nil
  325. }
  326. arguments := cmdArgs.Copy()
  327. arguments.DelArg("asdeps", "asdep")
  328. arguments.DelArg("asexplicit", "asexp")
  329. arguments.DelArg("i", "install")
  330. arguments.Op = "S"
  331. arguments.ClearTargets()
  332. arguments.AddTarget(repoTargets...)
  333. if len(excluded) > 0 {
  334. arguments.CreateOrAppendOption("ignore", excluded...)
  335. }
  336. errShow := installer.exeCmd.Show(installer.exeCmd.BuildPacmanCmd(ctx,
  337. arguments, installer.targetMode, noConfirm))
  338. if errD := asdeps(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncDeps.ToSlice()); errD != nil {
  339. return errD
  340. }
  341. if errE := asexp(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncExp.ToSlice()); errE != nil {
  342. return errE
  343. }
  344. return errShow
  345. }