aur_install.go 10 KB

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