aur_install.go 12 KB

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