浏览代码

refactor(yay): move cfg inside of runtime (#2259)

* rework relationship between runtime and cfg

* separate runtime from cfg

* simplify instantiation logic

* move installer to appropriate package

* move operator to sync package

* add tests for srcinfo service

* consolidate srcinfo service in sync

* add logger to srcinfo

* add logger to preparer

* remove unused text functions

* remove remaining text.* from srcinfo

* remove global logger parts

* remove global org method exports

* remove global logger

* move text->input

* add rule to prevent fmt.Print

* update golangci go version

* remove outdated FAQs

* remove outdated FAQs
Jo 1 年之前
父节点
当前提交
8916cd174b
共有 74 个文件被更改,包括 1435 次插入1327 次删除
  1. 6 1
      .golangci.yml
  2. 1 27
      README.md
  3. 26 54
      clean.go
  4. 3 4
      clean_test.go
  5. 93 92
      cmd.go
  6. 12 11
      cmd_test.go
  7. 0 53
      errors.go
  8. 11 11
      get.go
  9. 10 11
      local_install.go
  10. 82 76
      local_install_test.go
  11. 19 49
      main.go
  12. 11 9
      pkg/cmd/graph/main.go
  13. 0 4
      pkg/dep/topo/dep.go
  14. 2 2
      pkg/download/aur.go
  15. 1 1
      pkg/download/aur_test.go
  16. 9 9
      pkg/download/unified.go
  17. 12 7
      pkg/download/unified_test.go
  18. 9 8
      pkg/menus/clean_menu.go
  19. 10 10
      pkg/menus/diff_menu.go
  20. 9 8
      pkg/menus/edit_menu.go
  21. 9 8
      pkg/menus/menu.go
  22. 9 9
      pkg/news/news.go
  23. 9 8
      pkg/news/news_test.go
  24. 0 3
      pkg/query/aur_warnings.go
  25. 3 3
      pkg/query/filter.go
  26. 1 1
      pkg/query/query_builder.go
  27. 2 2
      pkg/settings/pacman.go
  28. 2 2
      pkg/settings/pacman_test.go
  29. 27 11
      pkg/settings/runtime.go
  30. 15 15
      pkg/settings/runtime_test.go
  31. 0 5
      pkg/settings/args.go
  32. 50 79
      pkg/settings/config.go
  33. 3 3
      pkg/settings/config_test.go
  34. 22 6
      pkg/settings/exe/cmd_builder.go
  35. 4 0
      pkg/settings/exe/exec.go
  36. 4 0
      pkg/settings/exe/mock.go
  37. 2 2
      pkg/settings/migrations.go
  38. 6 11
      pkg/settings/migrations_test.go
  39. 62 0
      pkg/sync/build/errors.go
  40. 13 13
      aur_install.go
  41. 13 12
      aur_install_test.go
  42. 56 122
      install.go
  43. 10 13
      pkg/pgp/keys.go
  44. 7 1
      pkg/pgp/keys_test.go
  45. 0 0
      pkg/sync/srcinfo/pgp/testdata/11E521D646982372EB577A1F8F0871F202119294
  46. 0 0
      pkg/sync/srcinfo/pgp/testdata/487EACC08557AD082088DABA1EB2638FF56C0C53
  47. 0 0
      pkg/sync/srcinfo/pgp/testdata/647F28654894E3BD457199BE38DBBDC86092693E
  48. 0 0
      pkg/sync/srcinfo/pgp/testdata/A314827C4E4250A204CE6E13284FC34C8E4B1A25
  49. 0 0
      pkg/sync/srcinfo/pgp/testdata/ABAF11C65A2970B130ABE3C479BE3E4300411886
  50. 0 0
      pkg/sync/srcinfo/pgp/testdata/B6C8F98282B944E3B0D5C2530FC3042E345AD05D
  51. 0 0
      pkg/sync/srcinfo/pgp/testdata/C52048C0C0748FEE227D47A2702353E0F7E48EDB
  52. 13 12
      pkg/srcinfo/service.go
  53. 132 0
      pkg/sync/srcinfo/service_test.go
  54. 138 0
      pkg/sync/sync.go
  55. 1 1
      aur_source.go
  56. 1 1
      aur_source_test.go
  57. 62 0
      pkg/sync/workdir/clean.go
  58. 39 0
      pkg/sync/workdir/merge.go
  59. 29 23
      preparer.go
  60. 9 2
      preparer_test.go
  61. 51 5
      pkg/text/input.go
  62. 0 139
      pkg/text/print.go
  63. 6 0
      pkg/text/service.go
  64. 0 51
      pkg/text/text.go
  65. 7 3
      pkg/text/text_test.go
  66. 8 6
      pkg/vcs/vcs_test.go
  67. 129 55
      print.go
  68. 13 13
      print_test.go
  69. 12 11
      query.go
  70. 17 16
      query_test.go
  71. 26 125
      sync.go
  72. 72 73
      sync_test.go
  73. 11 12
      vcs.go
  74. 4 3
      vote.go

+ 6 - 1
.golangci.yml

@@ -47,6 +47,7 @@ linters:
   # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
   # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
   disable-all: true
   disable-all: true
   enable:
   enable:
+    - forbidigo
     - bodyclose
     - bodyclose
     - dogsled
     - dogsled
     - dupl
     - dupl
@@ -80,8 +81,12 @@ linters:
     - whitespace
     - whitespace
 
 
 run:
 run:
-  go: "1.18"
+  go: "1.20"
   timeout: "10m"
   timeout: "10m"
+  forbidigo:
+    forbid:
+      - p: ^fmt\.Print.*$
+        msg: Do not commit print statements.
 
 
 issues:
 issues:
   exclude-rules:
   exclude-rules:

+ 1 - 27
README.md

@@ -111,17 +111,6 @@ pacman -S --needed git base-devel yay
   Make sure you have the `Color` option in your `/etc/pacman.conf`
   Make sure you have the `Color` option in your `/etc/pacman.conf`
   (see issue [#123](https://github.com/Jguer/yay/issues/123)).
   (see issue [#123](https://github.com/Jguer/yay/issues/123)).
 
 
-- **Yay is not prompting to skip packages during system upgrade.**
-
-  The default behavior was changed after
-  [v8.918](https://github.com/Jguer/yay/releases/tag/v8.918)
-  (see [3bdb534](https://github.com/Jguer/yay/commit/3bdb5343218d99d40f8a449b887348611f6bdbfc)
-  and issue [#554](https://github.com/Jguer/yay/issues/554)).
-  To restore the package-skip behavior use `--combinedupgrade` (make
-  it permanent by appending `--save`). Note: skipping packages will leave your
-  system in a
-  [partially-upgraded state](https://wiki.archlinux.org/index.php/System_maintenance#Partial_upgrades_are_unsupported).
-
 - **Sometimes diffs are printed to the terminal, and other times they are paged via less. How do I fix this?**
 - **Sometimes diffs are printed to the terminal, and other times they are paged via less. How do I fix this?**
 
 
   Yay uses `git diff` to display diffs, which by default tells less not to
   Yay uses `git diff` to display diffs, which by default tells less not to
@@ -137,7 +126,7 @@ pacman -S --needed git base-devel yay
   `yay -{OPERATION} --aur`
   `yay -{OPERATION} --aur`
   `yay -{OPERATION} --repo`
   `yay -{OPERATION} --repo`
 
 
-- **An `Out Of Date AUR Packages` message is displayed. Why doesn't Yay update them?**
+- **A `Flagged Out Of Date AUR Packages` message is displayed. Why doesn't Yay update them?**
 
 
   This message does not mean that updated AUR packages are available. It means
   This message does not mean that updated AUR packages are available. It means
   the packages have been flagged out of date on the AUR, but
   the packages have been flagged out of date on the AUR, but
@@ -159,21 +148,6 @@ pacman -S --needed git base-devel yay
 
 
   Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
   Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
 
 
-- **What settings do you use?**
-
-  ```sh
-  yay -Y --devel --combinedupgrade --batchinstall --save
-  ```
-
-  Pacman conf options:
-
-  ```conf
-  UseSyslog
-  Color
-  CheckSpace
-  VerbosePkgLists
-  ```
-
 ## Support
 ## Support
 
 
 All support related to Yay should be requested via GitHub issues. Since Yay is not
 All support related to Yay should be requested via GitHub issues. Since Yay is not

+ 26 - 54
clean.go

@@ -2,7 +2,6 @@ package main
 
 
 import (
 import (
 	"context"
 	"context"
-	"fmt"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 
 
@@ -11,10 +10,10 @@ import (
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
-	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 // CleanDependencies removes all dangling dependencies in system.
 // CleanDependencies removes all dangling dependencies in system.
@@ -49,13 +48,13 @@ func cleanRemove(ctx context.Context, cfg *settings.Configuration,
 			arguments, cfg.Mode, settings.NoConfirm))
 			arguments, cfg.Mode, settings.NoConfirm))
 }
 }
 
 
-func syncClean(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
+func syncClean(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
 	keepInstalled := false
 	keepInstalled := false
 	keepCurrent := false
 	keepCurrent := false
 
 
 	_, removeAll, _ := cmdArgs.GetArg("c", "clean")
 	_, removeAll, _ := cmdArgs.GetArg("c", "clean")
 
 
-	for _, v := range cfg.Runtime.PacmanConf.CleanMethod {
+	for _, v := range run.PacmanConf.CleanMethod {
 		if v == "KeepInstalled" {
 		if v == "KeepInstalled" {
 			keepInstalled = true
 			keepInstalled = true
 		} else if v == "KeepCurrent" {
 		} else if v == "KeepCurrent" {
@@ -63,14 +62,14 @@ func syncClean(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser
 		}
 		}
 	}
 	}
 
 
-	if cfg.Mode.AtLeastRepo() {
-		if err := cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm)); err != nil {
+	if run.Cfg.Mode.AtLeastRepo() {
+		if err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm)); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
-	if !cfg.Mode.AtLeastAUR() {
+	if !run.Cfg.Mode.AtLeastAUR() {
 		return nil
 		return nil
 	}
 	}
 
 
@@ -81,10 +80,10 @@ func syncClean(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser
 		question = gotext.Get("Do you want to remove all other AUR packages from cache?")
 		question = gotext.Get("Do you want to remove all other AUR packages from cache?")
 	}
 	}
 
 
-	fmt.Println(gotext.Get("\nBuild directory:"), cfg.BuildDir)
+	run.Logger.Println(gotext.Get("\nBuild directory:"), run.Cfg.BuildDir)
 
 
-	if text.ContinueTask(os.Stdin, question, true, settings.NoConfirm) {
-		if err := cleanAUR(ctx, cfg, keepInstalled, keepCurrent, removeAll, dbExecutor); err != nil {
+	if run.Logger.ContinueTask(question, true, settings.NoConfirm) {
+		if err := cleanAUR(ctx, run, keepInstalled, keepCurrent, removeAll, dbExecutor); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -93,24 +92,24 @@ func syncClean(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser
 		return nil
 		return nil
 	}
 	}
 
 
-	if text.ContinueTask(os.Stdin, gotext.Get("Do you want to remove ALL untracked AUR files?"), true, settings.NoConfirm) {
-		return cleanUntracked(ctx, cfg)
+	if run.Logger.ContinueTask(gotext.Get("Do you want to remove ALL untracked AUR files?"), true, settings.NoConfirm) {
+		return cleanUntracked(ctx, run)
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func cleanAUR(ctx context.Context, cfg *settings.Configuration,
+func cleanAUR(ctx context.Context, run *runtime.Runtime,
 	keepInstalled, keepCurrent, removeAll bool, dbExecutor db.Executor,
 	keepInstalled, keepCurrent, removeAll bool, dbExecutor db.Executor,
 ) error {
 ) error {
-	cfg.Runtime.Logger.Println(gotext.Get("removing AUR packages from cache..."))
+	run.Logger.Println(gotext.Get("removing AUR packages from cache..."))
 
 
 	installedBases := mapset.NewThreadUnsafeSet[string]()
 	installedBases := mapset.NewThreadUnsafeSet[string]()
 	inAURBases := mapset.NewThreadUnsafeSet[string]()
 	inAURBases := mapset.NewThreadUnsafeSet[string]()
 
 
 	remotePackages := dbExecutor.InstalledRemotePackages()
 	remotePackages := dbExecutor.InstalledRemotePackages()
 
 
-	files, err := os.ReadDir(cfg.BuildDir)
+	files, err := os.ReadDir(run.Cfg.BuildDir)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -130,7 +129,7 @@ func cleanAUR(ctx context.Context, cfg *settings.Configuration,
 	// Querying the AUR is slow and needs internet so don't do it if we
 	// Querying the AUR is slow and needs internet so don't do it if we
 	// don't need to.
 	// don't need to.
 	if keepCurrent {
 	if keepCurrent {
-		info, errInfo := cfg.Runtime.AURClient.Get(ctx, &aur.Query{
+		info, errInfo := run.AURClient.Get(ctx, &aur.Query{
 			Needles: cachedPackages,
 			Needles: cachedPackages,
 		})
 		})
 		if errInfo != nil {
 		if errInfo != nil {
@@ -165,20 +164,20 @@ func cleanAUR(ctx context.Context, cfg *settings.Configuration,
 			}
 			}
 		}
 		}
 
 
-		dir := filepath.Join(cfg.BuildDir, file.Name())
-		cfg.Runtime.Logger.Debugln("removing", dir)
+		dir := filepath.Join(run.Cfg.BuildDir, file.Name())
+		run.Logger.Debugln("removing", dir)
 		if err = os.RemoveAll(dir); err != nil {
 		if err = os.RemoveAll(dir); err != nil {
-			cfg.Runtime.Logger.Warnln(gotext.Get("Unable to remove %s: %s", dir, err))
+			run.Logger.Warnln(gotext.Get("Unable to remove %s: %s", dir, err))
 		}
 		}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func cleanUntracked(ctx context.Context, cfg *settings.Configuration) error {
-	cfg.Runtime.Logger.Println(gotext.Get("removing untracked AUR files from cache..."))
+func cleanUntracked(ctx context.Context, run *runtime.Runtime) error {
+	run.Logger.Println(gotext.Get("removing untracked AUR files from cache..."))
 
 
-	files, err := os.ReadDir(cfg.BuildDir)
+	files, err := os.ReadDir(run.Cfg.BuildDir)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -188,12 +187,11 @@ func cleanUntracked(ctx context.Context, cfg *settings.Configuration) error {
 			continue
 			continue
 		}
 		}
 
 
-		dir := filepath.Join(cfg.BuildDir, file.Name())
-		cfg.Runtime.Logger.Debugln("cleaning", dir)
+		dir := filepath.Join(run.Cfg.BuildDir, file.Name())
+		run.Logger.Debugln("cleaning", dir)
 		if isGitRepository(dir) {
 		if isGitRepository(dir) {
-			if err := cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fx")); err != nil {
-				cfg.Runtime.Logger.Warnln(gotext.Get("Unable to clean:"), dir)
-
+			if err := run.CmdBuilder.Show(run.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fx")); err != nil {
+				run.Logger.Warnln(gotext.Get("Unable to clean:"), dir)
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -206,29 +204,3 @@ func isGitRepository(dir string) bool {
 	_, err := os.Stat(filepath.Join(dir, ".git"))
 	_, err := os.Stat(filepath.Join(dir, ".git"))
 	return !os.IsNotExist(err)
 	return !os.IsNotExist(err)
 }
 }
-
-func cleanAfter(ctx context.Context, config *settings.Configuration,
-	cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string,
-) {
-	fmt.Println(gotext.Get("removing untracked AUR files from cache..."))
-
-	i := 0
-	for _, dir := range pkgbuildDirs {
-		text.OperationInfoln(gotext.Get("Cleaning (%d/%d): %s", i+1, len(pkgbuildDirs), text.Cyan(dir)))
-
-		_, stderr, err := cmdBuilder.Capture(
-			cmdBuilder.BuildGitCmd(
-				ctx, dir, "reset", "--hard", "HEAD"))
-		if err != nil {
-			text.Errorln(gotext.Get("error resetting %s: %s", dir, stderr))
-		}
-
-		if err := config.Runtime.CmdBuilder.Show(
-			config.Runtime.CmdBuilder.BuildGitCmd(
-				ctx, dir, "clean", "-fx", "--exclude", "*.pkg.*")); err != nil {
-			fmt.Fprintln(os.Stderr, err)
-		}
-
-		i++
-	}
-}

+ 3 - 4
clean_test.go

@@ -15,6 +15,7 @@ import (
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/require"
 
 
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
@@ -90,15 +91,13 @@ func TestCleanHanging(t *testing.T) {
 				Runner:           mockRunner,
 				Runner:           mockRunner,
 				SudoLoopEnabled:  false,
 				SudoLoopEnabled:  false,
 			}
 			}
-			cfg := &settings.Configuration{
-				Runtime: &settings.Runtime{CmdBuilder: cmdBuilder},
-			}
 
 
+			run := &runtime.Runtime{CmdBuilder: cmdBuilder, Cfg: &settings.Configuration{}}
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddArg(tc.args...)
 			cmdArgs.AddArg(tc.args...)
 
 
 			err := handleCmd(context.Background(),
 			err := handleCmd(context.Background(),
-				cfg, cmdArgs, dbExc,
+				run, cmdArgs, dbExc,
 			)
 			)
 
 
 			require.NoError(t, err)
 			require.NoError(t, err)

+ 93 - 92
cmd.go

@@ -17,6 +17,7 @@ import (
 	"github.com/Jguer/yay/v12/pkg/intrange"
 	"github.com/Jguer/yay/v12/pkg/intrange"
 	"github.com/Jguer/yay/v12/pkg/news"
 	"github.com/Jguer/yay/v12/pkg/news"
 	"github.com/Jguer/yay/v12/pkg/query"
 	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
@@ -25,8 +26,8 @@ import (
 	"github.com/Jguer/yay/v12/pkg/vcs"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 )
 )
 
 
-func usage() {
-	fmt.Println(`Usage:
+func usage(logger *text.Logger) {
+	logger.Println(`Usage:
     yay
     yay
     yay <operation> [...]
     yay <operation> [...]
     yay <package(s)>
     yay <package(s)>
@@ -146,50 +147,49 @@ getpkgbuild specific options:
     -p --print            Print pkgbuild of packages`)
     -p --print            Print pkgbuild of packages`)
 }
 }
 
 
-func handleCmd(ctx context.Context, cfg *settings.Configuration,
+func handleCmd(ctx context.Context, run *runtime.Runtime,
 	cmdArgs *parser.Arguments, dbExecutor db.Executor,
 	cmdArgs *parser.Arguments, dbExecutor db.Executor,
 ) error {
 ) error {
 	if cmdArgs.ExistsArg("h", "help") {
 	if cmdArgs.ExistsArg("h", "help") {
-		return handleHelp(ctx, cfg, cmdArgs)
+		return handleHelp(ctx, run, cmdArgs)
 	}
 	}
 
 
-	if cfg.SudoLoop && cmdArgs.NeedRoot(cfg.Mode) {
-		cfg.Runtime.CmdBuilder.SudoLoop()
+	if run.Cfg.SudoLoop && cmdArgs.NeedRoot(run.Cfg.Mode) {
+		run.CmdBuilder.SudoLoop()
 	}
 	}
 
 
 	switch cmdArgs.Op {
 	switch cmdArgs.Op {
 	case "V", "version":
 	case "V", "version":
-		handleVersion()
-
+		handleVersion(run.Logger)
 		return nil
 		return nil
 	case "D", "database":
 	case "D", "database":
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	case "F", "files":
 	case "F", "files":
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	case "Q", "query":
 	case "Q", "query":
-		return handleQuery(ctx, cfg, cmdArgs, dbExecutor)
+		return handleQuery(ctx, run, cmdArgs, dbExecutor)
 	case "R", "remove":
 	case "R", "remove":
-		return handleRemove(ctx, cfg, cmdArgs, cfg.Runtime.VCSStore)
+		return handleRemove(ctx, run, cmdArgs, run.VCSStore)
 	case "S", "sync":
 	case "S", "sync":
-		return handleSync(ctx, cfg, cmdArgs, dbExecutor)
+		return handleSync(ctx, run, cmdArgs, dbExecutor)
 	case "T", "deptest":
 	case "T", "deptest":
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	case "U", "upgrade":
 	case "U", "upgrade":
-		return handleUpgrade(ctx, cfg, cmdArgs)
+		return handleUpgrade(ctx, run, cmdArgs)
 	case "B", "build":
 	case "B", "build":
-		return handleBuild(ctx, cfg, dbExecutor, cmdArgs)
+		return handleBuild(ctx, run, dbExecutor, cmdArgs)
 	case "G", "getpkgbuild":
 	case "G", "getpkgbuild":
-		return handleGetpkgbuild(ctx, cfg, cmdArgs, dbExecutor)
+		return handleGetpkgbuild(ctx, run, cmdArgs, dbExecutor)
 	case "P", "show":
 	case "P", "show":
-		return handlePrint(ctx, cfg, cmdArgs, dbExecutor)
+		return handlePrint(ctx, run, cmdArgs, dbExecutor)
 	case "Y", "yay":
 	case "Y", "yay":
-		return handleYay(ctx, cfg, cmdArgs, cfg.Runtime.CmdBuilder,
-			dbExecutor, cfg.Runtime.QueryBuilder)
+		return handleYay(ctx, run, cmdArgs, run.CmdBuilder,
+			dbExecutor, run.QueryBuilder)
 	case "W", "web":
 	case "W", "web":
-		return handleWeb(ctx, cfg, cmdArgs)
+		return handleWeb(ctx, run, cmdArgs)
 	}
 	}
 
 
 	return errors.New(gotext.Get("unhandled operation"))
 	return errors.New(gotext.Get("unhandled operation"))
@@ -219,19 +219,19 @@ func getFilter(cmdArgs *parser.Arguments) (upgrade.Filter, error) {
 	}, nil
 	}, nil
 }
 }
 
 
-func handleQuery(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
+func handleQuery(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
 	if cmdArgs.ExistsArg("u", "upgrades") {
 	if cmdArgs.ExistsArg("u", "upgrades") {
 		filter, err := getFilter(cmdArgs)
 		filter, err := getFilter(cmdArgs)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 
 
-		return printUpdateList(ctx, cfg, cmdArgs, dbExecutor,
+		return printUpdateList(ctx, run, cmdArgs, dbExecutor,
 			cmdArgs.ExistsDouble("u", "sysupgrade"), filter)
 			cmdArgs.ExistsDouble("u", "sysupgrade"), filter)
 	}
 	}
 
 
-	if err := cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-		cmdArgs, cfg.Mode, settings.NoConfirm)); err != nil {
+	if err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+		cmdArgs, run.Cfg.Mode, settings.NoConfirm)); err != nil {
 		if str := err.Error(); strings.Contains(str, "exit status") {
 		if str := err.Error(); strings.Contains(str, "exit status") {
 			// yay -Qdt should not output anything in case of error
 			// yay -Qdt should not output anything in case of error
 			return fmt.Errorf("")
 			return fmt.Errorf("")
@@ -243,138 +243,139 @@ func handleQuery(ctx context.Context, cfg *settings.Configuration, cmdArgs *pars
 	return nil
 	return nil
 }
 }
 
 
-func handleHelp(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments) error {
-	usage()
+func handleHelp(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error {
+	usage(run.Logger)
 	switch cmdArgs.Op {
 	switch cmdArgs.Op {
 	case "Y", "yay", "G", "getpkgbuild", "P", "show", "W", "web", "B", "build":
 	case "Y", "yay", "G", "getpkgbuild", "P", "show", "W", "web", "B", "build":
 		return nil
 		return nil
 	}
 	}
 
 
-	cfg.Runtime.Logger.Println("\npacman operation specific options:")
-	return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-		cmdArgs, cfg.Mode, settings.NoConfirm))
+	run.Logger.Println("\npacman operation specific options:")
+	return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+		cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 }
 }
 
 
-func handleVersion() {
-	fmt.Printf("yay v%s - libalpm v%s\n", yayVersion, alpm.Version())
+func handleVersion(logger *text.Logger) {
+	logger.Printf("yay v%s - libalpm v%s\n", yayVersion, alpm.Version())
 }
 }
 
 
-func handlePrint(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
+func handlePrint(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
 	switch {
 	switch {
 	case cmdArgs.ExistsArg("d", "defaultconfig"):
 	case cmdArgs.ExistsArg("d", "defaultconfig"):
 		tmpConfig := settings.DefaultConfig(yayVersion)
 		tmpConfig := settings.DefaultConfig(yayVersion)
-		fmt.Printf("%v", tmpConfig)
+		run.Logger.Printf("%v", tmpConfig)
 
 
 		return nil
 		return nil
 	case cmdArgs.ExistsArg("g", "currentconfig"):
 	case cmdArgs.ExistsArg("g", "currentconfig"):
-		fmt.Printf("%v", cfg)
+		run.Logger.Printf("%v", run.Cfg)
 
 
 		return nil
 		return nil
 	case cmdArgs.ExistsArg("w", "news"):
 	case cmdArgs.ExistsArg("w", "news"):
 		double := cmdArgs.ExistsDouble("w", "news")
 		double := cmdArgs.ExistsDouble("w", "news")
 		quiet := cmdArgs.ExistsArg("q", "quiet")
 		quiet := cmdArgs.ExistsArg("q", "quiet")
 
 
-		return news.PrintNewsFeed(ctx, cfg.Runtime.HTTPClient, dbExecutor.LastBuildTime(), cfg.BottomUp, double, quiet)
+		return news.PrintNewsFeed(ctx, run.HTTPClient, run.Logger,
+			dbExecutor.LastBuildTime(), run.Cfg.BottomUp, double, quiet)
 	case cmdArgs.ExistsArg("c", "complete"):
 	case cmdArgs.ExistsArg("c", "complete"):
-		return completion.Show(ctx, cfg.Runtime.HTTPClient, dbExecutor,
-			cfg.AURURL, cfg.CompletionPath, cfg.CompletionInterval, cmdArgs.ExistsDouble("c", "complete"))
+		return completion.Show(ctx, run.HTTPClient, dbExecutor,
+			run.Cfg.AURURL, run.Cfg.CompletionPath, run.Cfg.CompletionInterval, cmdArgs.ExistsDouble("c", "complete"))
 	case cmdArgs.ExistsArg("s", "stats"):
 	case cmdArgs.ExistsArg("s", "stats"):
-		return localStatistics(ctx, cfg, dbExecutor)
+		return localStatistics(ctx, run, dbExecutor)
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func handleYay(ctx context.Context, cfg *settings.Configuration,
+func handleYay(ctx context.Context, run *runtime.Runtime,
 	cmdArgs *parser.Arguments, cmdBuilder exe.ICmdBuilder,
 	cmdArgs *parser.Arguments, cmdBuilder exe.ICmdBuilder,
 	dbExecutor db.Executor, queryBuilder query.Builder,
 	dbExecutor db.Executor, queryBuilder query.Builder,
 ) error {
 ) error {
 	switch {
 	switch {
 	case cmdArgs.ExistsArg("gendb"):
 	case cmdArgs.ExistsArg("gendb"):
-		return createDevelDB(ctx, cfg, dbExecutor)
+		return createDevelDB(ctx, run, dbExecutor)
 	case cmdArgs.ExistsDouble("c"):
 	case cmdArgs.ExistsDouble("c"):
-		return cleanDependencies(ctx, cfg, cmdBuilder, cmdArgs, dbExecutor, true)
+		return cleanDependencies(ctx, run.Cfg, cmdBuilder, cmdArgs, dbExecutor, true)
 	case cmdArgs.ExistsArg("c", "clean"):
 	case cmdArgs.ExistsArg("c", "clean"):
-		return cleanDependencies(ctx, cfg, cmdBuilder, cmdArgs, dbExecutor, false)
+		return cleanDependencies(ctx, run.Cfg, cmdBuilder, cmdArgs, dbExecutor, false)
 	case len(cmdArgs.Targets) > 0:
 	case len(cmdArgs.Targets) > 0:
-		return displayNumberMenu(ctx, cfg, cmdArgs.Targets, dbExecutor, queryBuilder, cmdArgs)
+		return displayNumberMenu(ctx, run, cmdArgs.Targets, dbExecutor, queryBuilder, cmdArgs)
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func handleWeb(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments) error {
+func handleWeb(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error {
 	switch {
 	switch {
 	case cmdArgs.ExistsArg("v", "vote"):
 	case cmdArgs.ExistsArg("v", "vote"):
-		return handlePackageVote(ctx, cmdArgs.Targets, cfg.Runtime.AURClient,
-			cfg.Runtime.VoteClient, true)
+		return handlePackageVote(ctx, cmdArgs.Targets, run.AURClient, run.Logger,
+			run.VoteClient, true)
 	case cmdArgs.ExistsArg("u", "unvote"):
 	case cmdArgs.ExistsArg("u", "unvote"):
-		return handlePackageVote(ctx, cmdArgs.Targets, cfg.Runtime.AURClient,
-			cfg.Runtime.VoteClient, false)
+		return handlePackageVote(ctx, cmdArgs.Targets, run.AURClient, run.Logger,
+			run.VoteClient, false)
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func handleGetpkgbuild(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments, dbExecutor download.DBSearcher) error {
+func handleGetpkgbuild(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor download.DBSearcher) error {
 	if cmdArgs.ExistsArg("p", "print") {
 	if cmdArgs.ExistsArg("p", "print") {
-		return printPkgbuilds(dbExecutor, cfg.Runtime.AURClient,
-			cfg.Runtime.HTTPClient, cmdArgs.Targets, cfg.Mode, cfg.AURURL)
+		return printPkgbuilds(dbExecutor, run.AURClient,
+			run.HTTPClient, run.Logger, cmdArgs.Targets, run.Cfg.Mode, run.Cfg.AURURL)
 	}
 	}
 
 
-	return getPkgbuilds(ctx, dbExecutor, cfg.Runtime.AURClient, cfg,
+	return getPkgbuilds(ctx, dbExecutor, run.AURClient, run,
 		cmdArgs.Targets, cmdArgs.ExistsArg("f", "force"))
 		cmdArgs.Targets, cmdArgs.ExistsArg("f", "force"))
 }
 }
 
 
 func handleUpgrade(ctx context.Context,
 func handleUpgrade(ctx context.Context,
-	config *settings.Configuration, cmdArgs *parser.Arguments,
+	run *runtime.Runtime, cmdArgs *parser.Arguments,
 ) error {
 ) error {
-	return config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-		cmdArgs, config.Mode, settings.NoConfirm))
+	return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+		cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 }
 }
 
 
 // -B* options
 // -B* options
 func handleBuild(ctx context.Context,
 func handleBuild(ctx context.Context,
-	config *settings.Configuration, dbExecutor db.Executor, cmdArgs *parser.Arguments,
+	run *runtime.Runtime, dbExecutor db.Executor, cmdArgs *parser.Arguments,
 ) error {
 ) error {
 	if cmdArgs.ExistsArg("i", "install") {
 	if cmdArgs.ExistsArg("i", "install") {
-		return installLocalPKGBUILD(ctx, config, cmdArgs, dbExecutor)
+		return installLocalPKGBUILD(ctx, run, cmdArgs, dbExecutor)
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func handleSync(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
+func handleSync(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error {
 	targets := cmdArgs.Targets
 	targets := cmdArgs.Targets
 
 
 	switch {
 	switch {
 	case cmdArgs.ExistsArg("s", "search"):
 	case cmdArgs.ExistsArg("s", "search"):
-		return syncSearch(ctx, targets, dbExecutor, cfg.Runtime.QueryBuilder, !cmdArgs.ExistsArg("q", "quiet"))
+		return syncSearch(ctx, targets, dbExecutor, run.QueryBuilder, !cmdArgs.ExistsArg("q", "quiet"))
 	case cmdArgs.ExistsArg("p", "print", "print-format"):
 	case cmdArgs.ExistsArg("p", "print", "print-format"):
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	case cmdArgs.ExistsArg("c", "clean"):
 	case cmdArgs.ExistsArg("c", "clean"):
-		return syncClean(ctx, cfg, cmdArgs, dbExecutor)
+		return syncClean(ctx, run, cmdArgs, dbExecutor)
 	case cmdArgs.ExistsArg("l", "list"):
 	case cmdArgs.ExistsArg("l", "list"):
-		return syncList(ctx, cfg, cfg.Runtime.HTTPClient, cmdArgs, dbExecutor)
+		return syncList(ctx, run, run.HTTPClient, cmdArgs, dbExecutor)
 	case cmdArgs.ExistsArg("g", "groups"):
 	case cmdArgs.ExistsArg("g", "groups"):
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	case cmdArgs.ExistsArg("i", "info"):
 	case cmdArgs.ExistsArg("i", "info"):
-		return syncInfo(ctx, cfg, cmdArgs, targets, dbExecutor)
+		return syncInfo(ctx, run, cmdArgs, targets, dbExecutor)
 	case cmdArgs.ExistsArg("u", "sysupgrade") || len(cmdArgs.Targets) > 0:
 	case cmdArgs.ExistsArg("u", "sysupgrade") || len(cmdArgs.Targets) > 0:
-		return syncInstall(ctx, cfg, cmdArgs, dbExecutor)
+		return syncInstall(ctx, run, cmdArgs, dbExecutor)
 	case cmdArgs.ExistsArg("y", "refresh"):
 	case cmdArgs.ExistsArg("y", "refresh"):
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func handleRemove(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments, localCache vcs.Store) error {
-	err := cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-		cmdArgs, cfg.Mode, settings.NoConfirm))
+func handleRemove(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, localCache vcs.Store) error {
+	err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+		cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	if err == nil {
 	if err == nil {
 		localCache.RemovePackages(cmdArgs.Targets)
 		localCache.RemovePackages(cmdArgs.Targets)
 	}
 	}
@@ -383,7 +384,7 @@ func handleRemove(ctx context.Context, cfg *settings.Configuration, cmdArgs *par
 }
 }
 
 
 // NumberMenu presents a CLI for selecting packages to install.
 // NumberMenu presents a CLI for selecting packages to install.
-func displayNumberMenu(ctx context.Context, cfg *settings.Configuration, pkgS []string, dbExecutor db.Executor,
+func displayNumberMenu(ctx context.Context, run *runtime.Runtime, pkgS []string, dbExecutor db.Executor,
 	queryBuilder query.Builder, cmdArgs *parser.Arguments,
 	queryBuilder query.Builder, cmdArgs *parser.Arguments,
 ) error {
 ) error {
 	queryBuilder.Execute(ctx, dbExecutor, pkgS)
 	queryBuilder.Execute(ctx, dbExecutor, pkgS)
@@ -397,9 +398,9 @@ func displayNumberMenu(ctx context.Context, cfg *settings.Configuration, pkgS []
 		return nil
 		return nil
 	}
 	}
 
 
-	cfg.Runtime.Logger.Infoln(gotext.Get("Packages to install (eg: 1 2 3, 1-3 or ^4)"))
+	run.Logger.Infoln(gotext.Get("Packages to install (eg: 1 2 3, 1-3 or ^4)"))
 
 
-	numberBuf, err := cfg.Runtime.Logger.GetInput("", false)
+	numberBuf, err := run.Logger.GetInput("", false)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -415,27 +416,27 @@ func displayNumberMenu(ctx context.Context, cfg *settings.Configuration, pkgS []
 	cmdArgs.Targets = targets
 	cmdArgs.Targets = targets
 
 
 	if len(cmdArgs.Targets) == 0 {
 	if len(cmdArgs.Targets) == 0 {
-		fmt.Println(gotext.Get(" there is nothing to do"))
+		run.Logger.Println(gotext.Get(" there is nothing to do"))
 		return nil
 		return nil
 	}
 	}
 
 
-	return syncInstall(ctx, cfg, cmdArgs, dbExecutor)
+	return syncInstall(ctx, run, cmdArgs, dbExecutor)
 }
 }
 
 
-func syncList(ctx context.Context, cfg *settings.Configuration,
+func syncList(ctx context.Context, run *runtime.Runtime,
 	httpClient *http.Client, cmdArgs *parser.Arguments, dbExecutor db.Executor,
 	httpClient *http.Client, cmdArgs *parser.Arguments, dbExecutor db.Executor,
 ) error {
 ) error {
 	aur := false
 	aur := false
 
 
 	for i := len(cmdArgs.Targets) - 1; i >= 0; i-- {
 	for i := len(cmdArgs.Targets) - 1; i >= 0; i-- {
-		if cmdArgs.Targets[i] == "aur" && cfg.Mode.AtLeastAUR() {
+		if cmdArgs.Targets[i] == "aur" && run.Cfg.Mode.AtLeastAUR() {
 			cmdArgs.Targets = append(cmdArgs.Targets[:i], cmdArgs.Targets[i+1:]...)
 			cmdArgs.Targets = append(cmdArgs.Targets[:i], cmdArgs.Targets[i+1:]...)
 			aur = true
 			aur = true
 		}
 		}
 	}
 	}
 
 
-	if cfg.Mode.AtLeastAUR() && (len(cmdArgs.Targets) == 0 || aur) {
-		req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.AURURL+"/packages.gz", http.NoBody)
+	if run.Cfg.Mode.AtLeastAUR() && (len(cmdArgs.Targets) == 0 || aur) {
+		req, err := http.NewRequestWithContext(ctx, http.MethodGet, run.Cfg.AURURL+"/packages.gz", http.NoBody)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -453,22 +454,22 @@ func syncList(ctx context.Context, cfg *settings.Configuration,
 		for scanner.Scan() {
 		for scanner.Scan() {
 			name := scanner.Text()
 			name := scanner.Text()
 			if cmdArgs.ExistsArg("q", "quiet") {
 			if cmdArgs.ExistsArg("q", "quiet") {
-				fmt.Println(name)
+				run.Logger.Println(name)
 			} else {
 			} else {
-				fmt.Printf("%s %s %s", text.Magenta("aur"), text.Bold(name), text.Bold(text.Green(gotext.Get("unknown-version"))))
+				run.Logger.Printf("%s %s %s", text.Magenta("aur"), text.Bold(name), text.Bold(text.Green(gotext.Get("unknown-version"))))
 
 
 				if dbExecutor.LocalPackage(name) != nil {
 				if dbExecutor.LocalPackage(name) != nil {
-					fmt.Print(text.Bold(text.Blue(gotext.Get(" [Installed]"))))
+					run.Logger.Print(text.Bold(text.Blue(gotext.Get(" [Installed]"))))
 				}
 				}
 
 
-				fmt.Println()
+				run.Logger.Println()
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	if cfg.Mode.AtLeastRepo() && (len(cmdArgs.Targets) != 0 || !aur) {
-		return cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			cmdArgs, cfg.Mode, settings.NoConfirm))
+	if run.Cfg.Mode.AtLeastRepo() && (len(cmdArgs.Targets) != 0 || !aur) {
+		return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			cmdArgs, run.Cfg.Mode, settings.NoConfirm))
 	}
 	}
 
 
 	return nil
 	return nil

+ 12 - 11
cmd_test.go

@@ -19,6 +19,7 @@ import (
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	"github.com/Jguer/yay/v12/pkg/query"
 	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
@@ -103,19 +104,19 @@ func TestYogurtMenuAURDB(t *testing.T) {
 		},
 		},
 	}
 	}
 	logger := text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test")
 	logger := text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test")
-	cfg := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     logger,
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			QueryBuilder: query.NewSourceQueryBuilder(aurCache, logger, "votes", parser.ModeAny, "name",
-				true, false, true),
-			AURClient: aurCache,
+
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
 		},
 		},
+		Logger:     logger,
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		QueryBuilder: query.NewSourceQueryBuilder(aurCache, logger, "votes", parser.ModeAny, "name",
+			true, false, true),
+		AURClient: aurCache,
 	}
 	}
-
-	err = handleCmd(context.Background(), cfg, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	wantCapture := []string{}
 	wantCapture := []string{}

+ 0 - 53
errors.go

@@ -7,56 +7,3 @@ import (
 )
 )
 
 
 var ErrPackagesNotFound = errors.New(gotext.Get("could not find all required packages"))
 var ErrPackagesNotFound = errors.New(gotext.Get("could not find all required packages"))
-
-type NoPkgDestsFoundError struct {
-	dir string
-}
-
-func (e *NoPkgDestsFoundError) Error() string {
-	return gotext.Get("could not find any package archives listed in %s", e.dir)
-}
-
-type SetPkgReasonError struct {
-	exp bool // explicit
-}
-
-func (e *SetPkgReasonError) Error() string {
-	reason := gotext.Get("explicit")
-	if !e.exp {
-		reason = gotext.Get("dependency")
-	}
-
-	return gotext.Get("error updating package install reason to %s", reason)
-}
-
-type FindPkgDestError struct {
-	name, pkgDest string
-}
-
-func (e *FindPkgDestError) Error() string {
-	return gotext.Get(
-		"the PKGDEST for %s is listed by makepkg but does not exist: %s",
-		e.name, e.pkgDest)
-}
-
-type PkgDestNotInListError struct {
-	name string
-}
-
-func (e *PkgDestNotInListError) Error() string {
-	return gotext.Get("could not find PKGDEST for: %s", e.name)
-}
-
-type FailedIgnoredPkgError struct {
-	pkgErrors map[string]error
-}
-
-func (e *FailedIgnoredPkgError) Error() string {
-	msg := gotext.Get("Failed to install the following packages. Manual intervention is required:")
-
-	for pkg, err := range e.pkgErrors {
-		msg += "\n" + pkg + " - " + err.Error()
-	}
-
-	return msg
-}

+ 11 - 11
get.go

@@ -11,24 +11,24 @@ import (
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
 	"github.com/Jguer/yay/v12/pkg/download"
 	"github.com/Jguer/yay/v12/pkg/download"
-	"github.com/Jguer/yay/v12/pkg/settings"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 // yay -Gp.
 // yay -Gp.
-func printPkgbuilds(dbExecutor download.DBSearcher, aurClient aur.QueryClient, httpClient *http.Client, targets []string,
+func printPkgbuilds(dbExecutor download.DBSearcher, aurClient aur.QueryClient,
+	httpClient *http.Client, logger *text.Logger, targets []string,
 	mode parser.TargetMode, aurURL string,
 	mode parser.TargetMode, aurURL string,
 ) error {
 ) error {
-	pkgbuilds, err := download.PKGBUILDs(dbExecutor, aurClient, httpClient, targets, aurURL, mode)
+	pkgbuilds, err := download.PKGBUILDs(dbExecutor, aurClient, httpClient, logger, targets, aurURL, mode)
 	if err != nil {
 	if err != nil {
-		text.Errorln(err)
+		logger.Errorln(err)
 	}
 	}
 
 
 	if len(pkgbuilds) != 0 {
 	if len(pkgbuilds) != 0 {
 		for target, pkgbuild := range pkgbuilds {
 		for target, pkgbuild := range pkgbuilds {
-			fmt.Printf("\n\n# %s\n\n", target)
-			fmt.Print(string(pkgbuild))
+			logger.Printf("\n\n# %s\n\n%s", target, string(pkgbuild))
 		}
 		}
 	}
 	}
 
 
@@ -41,7 +41,7 @@ func printPkgbuilds(dbExecutor download.DBSearcher, aurClient aur.QueryClient, h
 			}
 			}
 		}
 		}
 
 
-		text.Warnln(gotext.Get("Unable to find the following packages:"), " ", strings.Join(missing, ", "))
+		logger.Warnln(gotext.Get("Unable to find the following packages:"), " ", strings.Join(missing, ", "))
 
 
 		return fmt.Errorf("")
 		return fmt.Errorf("")
 	}
 	}
@@ -51,7 +51,7 @@ func printPkgbuilds(dbExecutor download.DBSearcher, aurClient aur.QueryClient, h
 
 
 // yay -G.
 // yay -G.
 func getPkgbuilds(ctx context.Context, dbExecutor download.DBSearcher, aurClient aur.QueryClient,
 func getPkgbuilds(ctx context.Context, dbExecutor download.DBSearcher, aurClient aur.QueryClient,
-	config *settings.Configuration, targets []string, force bool,
+	run *runtime.Runtime, targets []string, force bool,
 ) error {
 ) error {
 	wd, err := os.Getwd()
 	wd, err := os.Getwd()
 	if err != nil {
 	if err != nil {
@@ -59,9 +59,9 @@ func getPkgbuilds(ctx context.Context, dbExecutor download.DBSearcher, aurClient
 	}
 	}
 
 
 	cloned, errD := download.PKGBUILDRepos(ctx, dbExecutor, aurClient,
 	cloned, errD := download.PKGBUILDRepos(ctx, dbExecutor, aurClient,
-		config.Runtime.CmdBuilder, targets, config.Mode, config.AURURL, wd, force)
+		run.CmdBuilder, run.Logger, targets, run.Cfg.Mode, run.Cfg.AURURL, wd, force)
 	if errD != nil {
 	if errD != nil {
-		text.Errorln(errD)
+		run.Logger.Errorln(errD)
 	}
 	}
 
 
 	if len(targets) != len(cloned) {
 	if len(targets) != len(cloned) {
@@ -73,7 +73,7 @@ func getPkgbuilds(ctx context.Context, dbExecutor download.DBSearcher, aurClient
 			}
 			}
 		}
 		}
 
 
-		text.Warnln(gotext.Get("Unable to find the following packages:"), " ", strings.Join(missing, ", "))
+		run.Logger.Warnln(gotext.Get("Unable to find the following packages:"), " ", strings.Join(missing, ", "))
 
 
 		err = fmt.Errorf("")
 		err = fmt.Errorf("")
 	}
 	}

+ 10 - 11
local_install.go

@@ -12,19 +12,18 @@ import (
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/multierror"
 	"github.com/Jguer/yay/v12/pkg/multierror"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
+	"github.com/Jguer/yay/v12/pkg/sync"
 
 
 	gosrc "github.com/Morganamilo/go-srcinfo"
 	gosrc "github.com/Morganamilo/go-srcinfo"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
 
 
-var (
-	ErrInstallRepoPkgs = errors.New(gotext.Get("error installing repo packages"))
-	ErrNoBuildFiles    = errors.New(gotext.Get("cannot find PKGBUILD and .SRCINFO in directory"))
-)
+var ErrNoBuildFiles = errors.New(gotext.Get("cannot find PKGBUILD and .SRCINFO in directory"))
 
 
 func srcinfoExists(ctx context.Context,
 func srcinfoExists(ctx context.Context,
 	cmdBuilder exe.ICmdBuilder, targetDir string,
 	cmdBuilder exe.ICmdBuilder, targetDir string,
@@ -56,12 +55,12 @@ func srcinfoExists(ctx context.Context,
 
 
 func installLocalPKGBUILD(
 func installLocalPKGBUILD(
 	ctx context.Context,
 	ctx context.Context,
-	config *settings.Configuration,
+	run *runtime.Runtime,
 	cmdArgs *parser.Arguments,
 	cmdArgs *parser.Arguments,
 	dbExecutor db.Executor,
 	dbExecutor db.Executor,
 ) error {
 ) error {
-	aurCache := config.Runtime.AURClient
-	noCheck := strings.Contains(config.MFlags, "--nocheck")
+	aurCache := run.AURClient
+	noCheck := strings.Contains(run.Cfg.MFlags, "--nocheck")
 
 
 	if len(cmdArgs.Targets) < 1 {
 	if len(cmdArgs.Targets) < 1 {
 		return errors.New(gotext.Get("no target directories specified"))
 		return errors.New(gotext.Get("no target directories specified"))
@@ -69,7 +68,7 @@ func installLocalPKGBUILD(
 
 
 	srcInfos := map[string]*gosrc.Srcinfo{}
 	srcInfos := map[string]*gosrc.Srcinfo{}
 	for _, targetDir := range cmdArgs.Targets {
 	for _, targetDir := range cmdArgs.Targets {
-		if err := srcinfoExists(ctx, config.Runtime.CmdBuilder, targetDir); err != nil {
+		if err := srcinfoExists(ctx, run.CmdBuilder, targetDir); err != nil {
 			return err
 			return err
 		}
 		}
 
 
@@ -83,13 +82,13 @@ func installLocalPKGBUILD(
 
 
 	grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm,
 	grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm,
 		cmdArgs.ExistsDouble("d", "nodeps"), noCheck, cmdArgs.ExistsArg("needed"),
 		cmdArgs.ExistsDouble("d", "nodeps"), noCheck, cmdArgs.ExistsArg("needed"),
-		config.Runtime.Logger.Child("grapher"))
+		run.Logger.Child("grapher"))
 	graph, err := grapher.GraphFromSrcInfos(ctx, nil, srcInfos)
 	graph, err := grapher.GraphFromSrcInfos(ctx, nil, srcInfos)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	opService := NewOperationService(ctx, config, dbExecutor)
+	opService := sync.NewOperationService(ctx, dbExecutor, run)
 	multiErr := &multierror.MultiError{}
 	multiErr := &multierror.MultiError{}
 	targets := graph.TopoSortedLayerMap(func(name string, ii *dep.InstallInfo) error {
 	targets := graph.TopoSortedLayerMap(func(name string, ii *dep.InstallInfo) error {
 		if ii.Source == dep.Missing {
 		if ii.Source == dep.Missing {
@@ -101,5 +100,5 @@ func installLocalPKGBUILD(
 	if err := multiErr.Return(); err != nil {
 	if err := multiErr.Return(); err != nil {
 		return err
 		return err
 	}
 	}
-	return opService.Run(ctx, cmdArgs, targets, []string{})
+	return opService.Run(ctx, run, cmdArgs, targets, []string{})
 }
 }

+ 82 - 76
local_install_test.go

@@ -6,6 +6,7 @@ package main
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
+	"io"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path/filepath"
 	"path/filepath"
@@ -19,12 +20,18 @@ import (
 
 
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
+	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 )
 )
 
 
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
 func TestIntegrationLocalInstall(t *testing.T) {
 func TestIntegrationLocalInstall(t *testing.T) {
 	makepkgBin := t.TempDir() + "/makepkg"
 	makepkgBin := t.TempDir() + "/makepkg"
 	pacmanBin := t.TempDir() + "/pacman"
 	pacmanBin := t.TempDir() + "/pacman"
@@ -142,21 +149,21 @@ func TestIntegrationLocalInstall(t *testing.T) {
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 	}
 	}
 
 
-	config := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), config, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
@@ -263,20 +270,19 @@ func TestIntegrationLocalInstallMissingDep(t *testing.T) {
 		LocalPackageFn: func(string) mock.IPackage { return nil },
 		LocalPackageFn: func(string) mock.IPackage { return nil },
 	}
 	}
 
 
-	config := &settings.Configuration{
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg:        &settings.Configuration{},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), config, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.ErrorContains(t, err, wantErr.Error())
 	require.ErrorContains(t, err, wantErr.Error())
 
 
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
@@ -421,21 +427,21 @@ func TestIntegrationLocalInstallNeeded(t *testing.T) {
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 	}
 	}
 
 
-	config := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), config, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	require.Len(t, mockRunner.ShowCalls, len(wantShow), "show calls: %v", mockRunner.ShowCalls)
 	require.Len(t, mockRunner.ShowCalls, len(wantShow), "show calls: %v", mockRunner.ShowCalls)
@@ -585,22 +591,22 @@ func TestIntegrationLocalInstallGenerateSRCINFO(t *testing.T) {
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 	}
 	}
 
 
-	config := &settings.Configuration{
-		RemoveMake: "no",
-		Debug:      false,
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+			Debug:      false,
+		},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), config, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
@@ -651,7 +657,6 @@ func TestIntegrationLocalInstallMissingFiles(t *testing.T) {
 	wantCapture := []string{}
 	wantCapture := []string{}
 
 
 	captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
 	captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
-		fmt.Println(cmd.Args)
 		if cmd.Args[1] == "--printsrcinfo" {
 		if cmd.Args[1] == "--printsrcinfo" {
 			return string(srcinfo), "", nil
 			return string(srcinfo), "", nil
 		}
 		}
@@ -722,16 +727,17 @@ func TestIntegrationLocalInstallMissingFiles(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	config := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	config := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+			Debug:      false,
+		},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
@@ -848,16 +854,16 @@ func TestIntegrationLocalInstallWithDepsProvides(t *testing.T) {
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 	}
 	}
 
 
-	config := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	config := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
@@ -988,21 +994,21 @@ func TestIntegrationLocalInstallTwoSrcInfosWithDeps(t *testing.T) {
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 		InstalledRemotePackageNamesFn: func() []string { return []string{} },
 	}
 	}
 
 
-	config := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     NewTestLogger(),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     newTestLogger(),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), config, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))
 	require.Len(t, mockRunner.ShowCalls, len(wantShow))

+ 19 - 49
main.go

@@ -9,9 +9,8 @@ import (
 
 
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
-	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db/ialpm"
 	"github.com/Jguer/yay/v12/pkg/db/ialpm"
-	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
@@ -39,6 +38,7 @@ func initGotext() {
 }
 }
 
 
 func main() {
 func main() {
+	fallbackLog := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, false, "fallback")
 	var (
 	var (
 		err error
 		err error
 		ctx = context.Background()
 		ctx = context.Background()
@@ -47,7 +47,7 @@ func main() {
 
 
 	defer func() {
 	defer func() {
 		if rec := recover(); rec != nil {
 		if rec := recover(); rec != nil {
-			text.Errorln(rec)
+			fallbackLog.Errorln(rec)
 			debug.PrintStack()
 			debug.PrintStack()
 		}
 		}
 
 
@@ -57,15 +57,15 @@ func main() {
 	initGotext()
 	initGotext()
 
 
 	if os.Geteuid() == 0 {
 	if os.Geteuid() == 0 {
-		text.Warnln(gotext.Get("Avoid running yay as root/sudo."))
+		fallbackLog.Warnln(gotext.Get("Avoid running yay as root/sudo."))
 	}
 	}
 
 
 	configPath := settings.GetConfigPath()
 	configPath := settings.GetConfigPath()
 	// Parse config
 	// Parse config
-	cfg, err := settings.NewConfig(configPath, yayVersion)
+	cfg, err := settings.NewConfig(fallbackLog, configPath, yayVersion)
 	if err != nil {
 	if err != nil {
 		if str := err.Error(); str != "" {
 		if str := err.Error(); str != "" {
-			text.Errorln(str)
+			fallbackLog.Errorln(str)
 		}
 		}
 
 
 		ret = 1
 		ret = 1
@@ -73,13 +73,9 @@ func main() {
 		return
 		return
 	}
 	}
 
 
-	if cfg.Debug {
-		text.GlobalLogger.Debug = true
-	}
-
-	if errS := cfg.RunMigrations(
+	if errS := cfg.RunMigrations(fallbackLog,
 		settings.DefaultMigrations(), configPath, yayVersion); errS != nil {
 		settings.DefaultMigrations(), configPath, yayVersion); errS != nil {
-		text.Errorln(errS)
+		fallbackLog.Errorln(errS)
 	}
 	}
 
 
 	cmdArgs := parser.MakeArguments()
 	cmdArgs := parser.MakeArguments()
@@ -87,7 +83,7 @@ func main() {
 	// Parse command line
 	// Parse command line
 	if err = cfg.ParseCommandLine(cmdArgs); err != nil {
 	if err = cfg.ParseCommandLine(cmdArgs); err != nil {
 		if str := err.Error(); str != "" {
 		if str := err.Error(); str != "" {
-			text.Errorln(str)
+			fallbackLog.Errorln(str)
 		}
 		}
 
 
 		ret = 1
 		ret = 1
@@ -97,15 +93,15 @@ func main() {
 
 
 	if cfg.SaveConfig {
 	if cfg.SaveConfig {
 		if errS := cfg.Save(configPath, yayVersion); errS != nil {
 		if errS := cfg.Save(configPath, yayVersion); errS != nil {
-			text.Errorln(errS)
+			fallbackLog.Errorln(errS)
 		}
 		}
 	}
 	}
 
 
-	// Build runtime
-	runtime, err := settings.BuildRuntime(cfg, cmdArgs, yayVersion)
+	// Build run
+	run, err := runtime.NewRuntime(cfg, cmdArgs, yayVersion)
 	if err != nil {
 	if err != nil {
 		if str := err.Error(); str != "" {
 		if str := err.Error(); str != "" {
-			text.Errorln(str)
+			fallbackLog.Errorln(str)
 		}
 		}
 
 
 		ret = 1
 		ret = 1
@@ -113,35 +109,10 @@ func main() {
 		return
 		return
 	}
 	}
 
 
-	cfg.Runtime = runtime
-
-	cfg.Runtime.QueryBuilder = query.NewSourceQueryBuilder(
-		cfg.Runtime.AURClient,
-		cfg.Runtime.Logger.Child("mixed.querybuilder"), cfg.SortBy,
-		cfg.Mode, cfg.SearchBy,
-		cfg.BottomUp, cfg.SingleLineResults, cfg.SeparateSources)
-
-	var useColor bool
-
-	cfg.Runtime.PacmanConf, useColor, err = settings.RetrievePacmanConfig(cmdArgs, cfg.PacmanConf)
+	dbExecutor, err := ialpm.NewExecutor(run.PacmanConf, run.Logger.Child("db"))
 	if err != nil {
 	if err != nil {
 		if str := err.Error(); str != "" {
 		if str := err.Error(); str != "" {
-			text.Errorln(str)
-		}
-
-		ret = 1
-
-		return
-	}
-
-	cfg.Runtime.CmdBuilder.SetPacmanDBPath(cfg.Runtime.PacmanConf.DBPath)
-
-	text.UseColor = useColor
-
-	dbExecutor, err := ialpm.NewExecutor(cfg.Runtime.PacmanConf, runtime.Logger.Child("db"))
-	if err != nil {
-		if str := err.Error(); str != "" {
-			text.Errorln(str)
+			fallbackLog.Errorln(str)
 		}
 		}
 
 
 		ret = 1
 		ret = 1
@@ -151,19 +122,18 @@ func main() {
 
 
 	defer func() {
 	defer func() {
 		if rec := recover(); rec != nil {
 		if rec := recover(); rec != nil {
-			text.Errorln(rec)
-			debug.PrintStack()
+			fallbackLog.Errorln(rec, string(debug.Stack()))
 		}
 		}
 
 
 		dbExecutor.Cleanup()
 		dbExecutor.Cleanup()
 	}()
 	}()
 
 
-	if err = handleCmd(ctx, cfg, cmdArgs, db.Executor(dbExecutor)); err != nil {
+	if err = handleCmd(ctx, run, cmdArgs, dbExecutor); err != nil {
 		if str := err.Error(); str != "" {
 		if str := err.Error(); str != "" {
-			text.Errorln(str)
+			fallbackLog.Errorln(str)
 			if cmdArgs.ExistsArg("c") && cmdArgs.ExistsArg("y") && cmdArgs.Op == "S" {
 			if cmdArgs.ExistsArg("c") && cmdArgs.ExistsArg("y") && cmdArgs.Op == "S" {
 				// Remove after 2023-10-01
 				// Remove after 2023-10-01
-				text.Errorln("Did you mean 'yay -Yc'?")
+				fallbackLog.Errorln("Did you mean 'yay -Yc'?")
 			}
 			}
 		}
 		}
 
 

+ 11 - 9
pkg/cmd/graph/main.go

@@ -8,6 +8,7 @@ import (
 
 
 	"github.com/Jguer/yay/v12/pkg/db/ialpm"
 	"github.com/Jguer/yay/v12/pkg/db/ialpm"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
@@ -17,44 +18,45 @@ import (
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
 
 
-func handleCmd() error {
-	config, err := settings.NewConfig(settings.GetConfigPath(), "")
+func handleCmd(logger *text.Logger) error {
+	cfg, err := settings.NewConfig(logger, settings.GetConfigPath(), "")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	cmdArgs := parser.MakeArguments()
 	cmdArgs := parser.MakeArguments()
-	if errP := config.ParseCommandLine(cmdArgs); errP != nil {
+	if errP := cfg.ParseCommandLine(cmdArgs); errP != nil {
 		return errP
 		return errP
 	}
 	}
 
 
-	pacmanConf, _, err := settings.RetrievePacmanConfig(cmdArgs, config.PacmanConf)
+	run, err := runtime.NewRuntime(cfg, cmdArgs, "1.0.0")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	dbExecutor, err := ialpm.NewExecutor(pacmanConf, text.GlobalLogger)
+	dbExecutor, err := ialpm.NewExecutor(run.PacmanConf, logger)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	aurCache, err := metadata.New(
 	aurCache, err := metadata.New(
 		metadata.WithCacheFilePath(
 		metadata.WithCacheFilePath(
-			filepath.Join(config.BuildDir, "aur.json")))
+			filepath.Join(cfg.BuildDir, "aur.json")))
 	if err != nil {
 	if err != nil {
 		return errors.Wrap(err, gotext.Get("failed to retrieve aur Cache"))
 		return errors.Wrap(err, gotext.Get("failed to retrieve aur Cache"))
 	}
 	}
 
 
 	grapher := dep.NewGrapher(dbExecutor, aurCache, true, settings.NoConfirm,
 	grapher := dep.NewGrapher(dbExecutor, aurCache, true, settings.NoConfirm,
 		cmdArgs.ExistsDouble("d", "nodeps"), false, false,
 		cmdArgs.ExistsDouble("d", "nodeps"), false, false,
-		config.Runtime.Logger.Child("grapher"))
+		run.Logger.Child("grapher"))
 
 
 	return graphPackage(context.Background(), grapher, cmdArgs.Targets)
 	return graphPackage(context.Background(), grapher, cmdArgs.Targets)
 }
 }
 
 
 func main() {
 func main() {
-	if err := handleCmd(); err != nil {
-		text.Errorln(err)
+	fallbackLog := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, false, "fallback")
+	if err := handleCmd(fallbackLog); err != nil {
+		fallbackLog.Errorln(err)
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 }
 }

+ 0 - 4
pkg/dep/topo/dep.go

@@ -5,8 +5,6 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/Jguer/go-alpm/v2"
 	"github.com/Jguer/go-alpm/v2"
-
-	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 type (
 type (
@@ -244,7 +242,6 @@ func (g *Graph[T, V]) Prune(node T) []T {
 	// Remove edges from things that depend on `node`.
 	// Remove edges from things that depend on `node`.
 	for dependent := range g.dependents[node] {
 	for dependent := range g.dependents[node] {
 		last := g.dependencies.removeFromDepmap(dependent, node)
 		last := g.dependencies.removeFromDepmap(dependent, node)
-		text.Debugln("pruning dependent", dependent, last)
 		if last {
 		if last {
 			pruned = append(pruned, g.Prune(dependent)...)
 			pruned = append(pruned, g.Prune(dependent)...)
 		}
 		}
@@ -255,7 +252,6 @@ func (g *Graph[T, V]) Prune(node T) []T {
 	// Remove all edges from node to the things it depends on.
 	// Remove all edges from node to the things it depends on.
 	for dependency := range g.dependencies[node] {
 	for dependency := range g.dependencies[node] {
 		last := g.dependents.removeFromDepmap(dependency, node)
 		last := g.dependents.removeFromDepmap(dependency, node)
-		text.Debugln("pruning dependency", dependency, last)
 		if last {
 		if last {
 			pruned = append(pruned, g.Prune(dependency)...)
 			pruned = append(pruned, g.Prune(dependency)...)
 		}
 		}

+ 2 - 2
pkg/download/aur.go

@@ -48,7 +48,7 @@ func AURPKGBUILDRepo(ctx context.Context, cmdBuilder exe.GitCmdBuilder, aurURL,
 
 
 func AURPKGBUILDRepos(
 func AURPKGBUILDRepos(
 	ctx context.Context,
 	ctx context.Context,
-	cmdBuilder exe.GitCmdBuilder,
+	cmdBuilder exe.GitCmdBuilder, logger *text.Logger,
 	targets []string, aurURL, dest string, force bool,
 	targets []string, aurURL, dest string, force bool,
 ) (map[string]bool, error) {
 ) (map[string]bool, error) {
 	cloned := make(map[string]bool, len(targets))
 	cloned := make(map[string]bool, len(targets))
@@ -80,7 +80,7 @@ func AURPKGBUILDRepos(
 				mux.Unlock()
 				mux.Unlock()
 			}
 			}
 
 
-			text.OperationInfoln(
+			logger.OperationInfoln(
 				gotext.Get("(%d/%d) Downloaded PKGBUILD: %s",
 				gotext.Get("(%d/%d) Downloaded PKGBUILD: %s",
 					progress, len(targets), text.Cyan(target)))
 					progress, len(targets), text.Cyan(target)))
 
 

+ 1 - 1
pkg/download/aur_test.go

@@ -158,7 +158,7 @@ func TestAURPKGBUILDRepos(t *testing.T) {
 			GitFlags: []string{},
 			GitFlags: []string{},
 		},
 		},
 	}
 	}
-	cloned, err := AURPKGBUILDRepos(context.Background(), cmdBuilder, targets, "https://aur.archlinux.org", dir, false)
+	cloned, err := AURPKGBUILDRepos(context.Background(), cmdBuilder, newTestLogger(), targets, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	assert.EqualValues(t, map[string]bool{"yay": true, "yay-bin": false, "yay-git": true}, cloned)
 	assert.EqualValues(t, map[string]bool{"yay": true, "yay-bin": false, "yay-git": true}, cloned)

+ 9 - 9
pkg/download/unified.go

@@ -81,8 +81,8 @@ func getURLName(pkg db.IPackage) string {
 	return name
 	return name
 }
 }
 
 
-func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *http.Client, targets []string,
-	aurURL string, mode parser.TargetMode,
+func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *http.Client,
+	logger *text.Logger, targets []string, aurURL string, mode parser.TargetMode,
 ) (map[string][]byte, error) {
 ) (map[string][]byte, error) {
 	pkgbuilds := make(map[string][]byte, len(targets))
 	pkgbuilds := make(map[string][]byte, len(targets))
 
 
@@ -96,7 +96,7 @@ func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *htt
 
 
 	for _, target := range targets {
 	for _, target := range targets {
 		// Probably replaceable by something in query.
 		// Probably replaceable by something in query.
-		dbName, name, isAUR, toSkip := getPackageUsableName(dbExecutor, aurClient, target, mode)
+		dbName, name, isAUR, toSkip := getPackageUsableName(dbExecutor, aurClient, logger, target, mode)
 		if toSkip {
 		if toSkip {
 			continue
 			continue
 		}
 		}
@@ -136,7 +136,7 @@ func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *htt
 }
 }
 
 
 func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.QueryClient,
 func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.QueryClient,
-	cmdBuilder exe.GitCmdBuilder,
+	cmdBuilder exe.GitCmdBuilder, logger *text.Logger,
 	targets []string, mode parser.TargetMode, aurURL, dest string, force bool,
 	targets []string, mode parser.TargetMode, aurURL, dest string, force bool,
 ) (map[string]bool, error) {
 ) (map[string]bool, error) {
 	cloned := make(map[string]bool, len(targets))
 	cloned := make(map[string]bool, len(targets))
@@ -151,7 +151,7 @@ func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.Que
 
 
 	for _, target := range targets {
 	for _, target := range targets {
 		// Probably replaceable by something in query.
 		// Probably replaceable by something in query.
-		dbName, name, isAUR, toSkip := getPackageUsableName(dbExecutor, aurClient, target, mode)
+		dbName, name, isAUR, toSkip := getPackageUsableName(dbExecutor, aurClient, logger, target, mode)
 		if toSkip {
 		if toSkip {
 			continue
 			continue
 		}
 		}
@@ -184,11 +184,11 @@ func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.Que
 			}
 			}
 
 
 			if aur {
 			if aur {
-				text.OperationInfoln(
+				logger.OperationInfoln(
 					gotext.Get("(%d/%d) Downloaded PKGBUILD: %s",
 					gotext.Get("(%d/%d) Downloaded PKGBUILD: %s",
 						progress, len(targets), text.Cyan(pkgName)))
 						progress, len(targets), text.Cyan(pkgName)))
 			} else {
 			} else {
-				text.OperationInfoln(
+				logger.OperationInfoln(
 					gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s",
 					gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s",
 						progress, len(targets), text.Cyan(pkgName)))
 						progress, len(targets), text.Cyan(pkgName)))
 			}
 			}
@@ -206,7 +206,7 @@ func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.Que
 
 
 // TODO: replace with dep.ResolveTargets.
 // TODO: replace with dep.ResolveTargets.
 func getPackageUsableName(dbExecutor DBSearcher, aurClient aur.QueryClient,
 func getPackageUsableName(dbExecutor DBSearcher, aurClient aur.QueryClient,
-	target string, mode parser.TargetMode,
+	logger *text.Logger, target string, mode parser.TargetMode,
 ) (dbname, pkgname string, isAUR, toSkip bool) {
 ) (dbname, pkgname string, isAUR, toSkip bool) {
 	dbName, name := text.SplitDBFromName(target)
 	dbName, name := text.SplitDBFromName(target)
 	if dbName != "aur" && mode.AtLeastRepo() {
 	if dbName != "aur" && mode.AtLeastRepo() {
@@ -239,7 +239,7 @@ func getPackageUsableName(dbExecutor DBSearcher, aurClient aur.QueryClient,
 		Needles:  []string{name},
 		Needles:  []string{name},
 	})
 	})
 	if err != nil {
 	if err != nil {
-		text.Warnln(err)
+		logger.Warnln(err)
 		return dbName, name, true, true
 		return dbName, name, true, true
 	}
 	}
 
 

+ 12 - 7
pkg/download/unified_test.go

@@ -5,6 +5,7 @@ package download
 
 
 import (
 import (
 	"context"
 	"context"
+	"io"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -22,6 +23,10 @@ import (
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
 // GIVEN 2 aur packages and 1 in repo
 // GIVEN 2 aur packages and 1 in repo
 // GIVEN package in repo is already present
 // GIVEN package in repo is already present
 // WHEN defining package db as a target
 // WHEN defining package db as a target
@@ -56,7 +61,7 @@ func TestPKGBUILDReposDefinedDBPull(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
-		cmdBuilder,
+		cmdBuilder, newTestLogger(),
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -90,7 +95,7 @@ func TestPKGBUILDReposDefinedDBClone(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
-		cmdBuilder,
+		cmdBuilder, newTestLogger(),
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -124,7 +129,7 @@ func TestPKGBUILDReposClone(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
-		cmdBuilder,
+		cmdBuilder, newTestLogger(),
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -158,7 +163,7 @@ func TestPKGBUILDReposNotFound(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
-		cmdBuilder,
+		cmdBuilder, newTestLogger(),
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -192,7 +197,7 @@ func TestPKGBUILDReposRepoMode(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
-		cmdBuilder,
+		cmdBuilder, newTestLogger(),
 		targets, parser.ModeRepo, "https://aur.archlinux.org", dir, false)
 		targets, parser.ModeRepo, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -230,7 +235,7 @@ func TestPKGBUILDFull(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 
 
-	fetched, err := PKGBUILDs(searcher, mockClient, &http.Client{},
+	fetched, err := PKGBUILDs(searcher, mockClient, &http.Client{}, newTestLogger(),
 		targets, "https://aur.archlinux.org", parser.ModeAny)
 		targets, "https://aur.archlinux.org", parser.ModeAny)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -268,7 +273,7 @@ func TestPKGBUILDReposMissingAUR(t *testing.T) {
 		absPackagesDB: map[string]string{"yay": "core"},
 		absPackagesDB: map[string]string{"yay": "core"},
 	}
 	}
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
 	cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
-		cmdBuilder,
+		cmdBuilder, newTestLogger(),
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 		targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
 
 
 	assert.NoError(t, err)
 	assert.NoError(t, err)

+ 9 - 8
pkg/menus/clean_menu.go

@@ -9,6 +9,7 @@ import (
 	mapset "github.com/deckarep/golang-set/v2"
 	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
@@ -23,7 +24,7 @@ func anyExistInCache(pkgbuildDirs map[string]string) bool {
 	return false
 	return false
 }
 }
 
 
-func CleanFn(ctx context.Context, config *settings.Configuration, w io.Writer,
+func CleanFn(ctx context.Context, run *runtime.Runtime, w io.Writer,
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 ) error {
 ) error {
 	if len(pkgbuildDirsByBase) == 0 {
 	if len(pkgbuildDirsByBase) == 0 {
@@ -49,25 +50,25 @@ func CleanFn(ctx context.Context, config *settings.Configuration, w io.Writer,
 		bases = append(bases, pkg)
 		bases = append(bases, pkg)
 	}
 	}
 
 
-	toClean, errClean := selectionMenu(w, pkgbuildDirsByBase, bases, installed,
+	toClean, errClean := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed,
 		gotext.Get("Packages to cleanBuild?"),
 		gotext.Get("Packages to cleanBuild?"),
-		settings.NoConfirm, config.AnswerClean, skipFunc)
+		settings.NoConfirm, run.Cfg.AnswerClean, skipFunc)
 	if errClean != nil {
 	if errClean != nil {
 		return errClean
 		return errClean
 	}
 	}
 
 
 	for i, base := range toClean {
 	for i, base := range toClean {
 		dir := pkgbuildDirsByBase[base]
 		dir := pkgbuildDirsByBase[base]
-		text.OperationInfoln(gotext.Get("Deleting (%d/%d): %s", i+1, len(toClean), text.Cyan(dir)))
+		run.Logger.OperationInfoln(gotext.Get("Deleting (%d/%d): %s", i+1, len(toClean), text.Cyan(dir)))
 
 
-		if err := config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildGitCmd(ctx, dir, "reset", "--hard", "origin/HEAD")); err != nil {
-			text.Warnln(gotext.Get("Unable to clean:"), dir)
+		if err := run.CmdBuilder.Show(run.CmdBuilder.BuildGitCmd(ctx, dir, "reset", "--hard", "origin/HEAD")); err != nil {
+			run.Logger.Warnln(gotext.Get("Unable to clean:"), dir)
 
 
 			return err
 			return err
 		}
 		}
 
 
-		if err := config.Runtime.CmdBuilder.Show(config.Runtime.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fdx")); err != nil {
-			text.Warnln(gotext.Get("Unable to clean:"), dir)
+		if err := run.CmdBuilder.Show(run.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fdx")); err != nil {
+			run.Logger.Warnln(gotext.Get("Unable to clean:"), dir)
 
 
 			return err
 			return err
 		}
 		}

+ 10 - 10
pkg/menus/diff_menu.go

@@ -5,13 +5,13 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"os"
 	"strings"
 	"strings"
 
 
 	mapset "github.com/deckarep/golang-set/v2"
 	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
 	"github.com/Jguer/yay/v12/pkg/multierror"
 	"github.com/Jguer/yay/v12/pkg/multierror"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
@@ -22,7 +22,7 @@ const (
 	gitDiffRefName = "AUR_SEEN"
 	gitDiffRefName = "AUR_SEEN"
 )
 )
 
 
-func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder,
+func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder, logger *text.Logger,
 	pkgbuildDirs map[string]string, bases []string,
 	pkgbuildDirs map[string]string, bases []string,
 ) error {
 ) error {
 	var errMulti multierror.MultiError
 	var errMulti multierror.MultiError
@@ -46,7 +46,7 @@ func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder,
 			}
 			}
 
 
 			if !hasDiff {
 			if !hasDiff {
-				text.Warnln(gotext.Get("%s: No changes -- skipping", text.Cyan(pkg)))
+				logger.Warnln(gotext.Get("%s: No changes -- skipping", text.Cyan(pkg)))
 
 
 				continue
 				continue
 			}
 			}
@@ -145,7 +145,7 @@ func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgb
 	return errMulti.Return()
 	return errMulti.Return()
 }
 }
 
 
-func DiffFn(ctx context.Context, config *settings.Configuration, w io.Writer,
+func DiffFn(ctx context.Context, run *runtime.Runtime, w io.Writer,
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 ) error {
 ) error {
 	if len(pkgbuildDirsByBase) == 0 {
 	if len(pkgbuildDirsByBase) == 0 {
@@ -157,23 +157,23 @@ func DiffFn(ctx context.Context, config *settings.Configuration, w io.Writer,
 		bases = append(bases, base)
 		bases = append(bases, base)
 	}
 	}
 
 
-	toDiff, errMenu := selectionMenu(w, pkgbuildDirsByBase, bases, installed, gotext.Get("Diffs to show?"),
-		settings.NoConfirm, config.AnswerDiff, nil)
+	toDiff, errMenu := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed, gotext.Get("Diffs to show?"),
+		settings.NoConfirm, run.Cfg.AnswerDiff, nil)
 	if errMenu != nil || len(toDiff) == 0 {
 	if errMenu != nil || len(toDiff) == 0 {
 		return errMenu
 		return errMenu
 	}
 	}
 
 
-	if errD := showPkgbuildDiffs(ctx, config.Runtime.CmdBuilder, pkgbuildDirsByBase, toDiff); errD != nil {
+	if errD := showPkgbuildDiffs(ctx, run.CmdBuilder, run.Logger, pkgbuildDirsByBase, toDiff); errD != nil {
 		return errD
 		return errD
 	}
 	}
 
 
-	fmt.Println()
+	run.Logger.Println()
 
 
-	if !text.ContinueTask(os.Stdin, gotext.Get("Proceed with install?"), true, false) {
+	if !run.Logger.ContinueTask(gotext.Get("Proceed with install?"), true, false) {
 		return settings.ErrUserAbort{}
 		return settings.ErrUserAbort{}
 	}
 	}
 
 
-	if errUpd := updatePkgbuildSeenRef(ctx, config.Runtime.CmdBuilder, pkgbuildDirsByBase, toDiff); errUpd != nil {
+	if errUpd := updatePkgbuildSeenRef(ctx, run.CmdBuilder, pkgbuildDirsByBase, toDiff); errUpd != nil {
 		return errUpd
 		return errUpd
 	}
 	}
 
 

+ 9 - 8
pkg/menus/edit_menu.go

@@ -14,6 +14,7 @@ import (
 	mapset "github.com/deckarep/golang-set/v2"
 	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
@@ -59,7 +60,7 @@ func editor(log *text.Logger, editorConfig, editorFlags string, noConfirm bool)
 		for {
 		for {
 			log.Infoln(gotext.Get("Edit PKGBUILD with?"))
 			log.Infoln(gotext.Get("Edit PKGBUILD with?"))
 
 
-			editorInput, err := text.GetInput(os.Stdin, "", noConfirm)
+			editorInput, err := log.GetInput("", noConfirm)
 			if err != nil {
 			if err != nil {
 				log.Errorln(err)
 				log.Errorln(err)
 				continue
 				continue
@@ -113,7 +114,7 @@ func editPkgbuilds(log *text.Logger, pkgbuildDirs map[string]string, bases []str
 	return nil
 	return nil
 }
 }
 
 
-func EditFn(ctx context.Context, cfg *settings.Configuration, w io.Writer,
+func EditFn(ctx context.Context, run *runtime.Runtime, w io.Writer,
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 ) error {
 ) error {
 	if len(pkgbuildDirsByBase) == 0 {
 	if len(pkgbuildDirsByBase) == 0 {
@@ -125,21 +126,21 @@ func EditFn(ctx context.Context, cfg *settings.Configuration, w io.Writer,
 		bases = append(bases, pkg)
 		bases = append(bases, pkg)
 	}
 	}
 
 
-	toEdit, errMenu := selectionMenu(w, pkgbuildDirsByBase, bases, installed,
-		gotext.Get("PKGBUILDs to edit?"), settings.NoConfirm, cfg.AnswerEdit, nil)
+	toEdit, errMenu := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed,
+		gotext.Get("PKGBUILDs to edit?"), settings.NoConfirm, run.Cfg.AnswerEdit, nil)
 	if errMenu != nil || len(toEdit) == 0 {
 	if errMenu != nil || len(toEdit) == 0 {
 		return errMenu
 		return errMenu
 	}
 	}
 
 
 	// TOFIX: remove or use srcinfo data
 	// TOFIX: remove or use srcinfo data
-	if errEdit := editPkgbuilds(cfg.Runtime.Logger, pkgbuildDirsByBase,
-		toEdit, cfg.Editor, cfg.EditorFlags, nil, settings.NoConfirm); errEdit != nil {
+	if errEdit := editPkgbuilds(run.Logger, pkgbuildDirsByBase,
+		toEdit, run.Cfg.Editor, run.Cfg.EditorFlags, nil, settings.NoConfirm); errEdit != nil {
 		return errEdit
 		return errEdit
 	}
 	}
 
 
-	cfg.Runtime.Logger.Println()
+	run.Logger.Println()
 
 
-	if !text.ContinueTask(os.Stdin, gotext.Get("Proceed with install?"), true, false) {
+	if !run.Logger.ContinueTask(gotext.Get("Proceed with install?"), true, false) {
 		return settings.ErrUserAbort{}
 		return settings.ErrUserAbort{}
 	}
 	}
 
 

+ 9 - 8
pkg/menus/menu.go

@@ -2,7 +2,6 @@ package menus
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"io"
 	"os"
 	"os"
 
 
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
@@ -14,7 +13,9 @@ import (
 	mapset "github.com/deckarep/golang-set/v2"
 	mapset "github.com/deckarep/golang-set/v2"
 )
 )
 
 
-func pkgbuildNumberMenu(w io.Writer, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string]) {
+func pkgbuildNumberMenu(logger *text.Logger, pkgbuildDirs map[string]string,
+	bases []string, installed mapset.Set[string],
+) {
 	toPrint := ""
 	toPrint := ""
 
 
 	for n, pkgBase := range bases {
 	for n, pkgBase := range bases {
@@ -34,20 +35,20 @@ func pkgbuildNumberMenu(w io.Writer, pkgbuildDirs map[string]string, bases []str
 		toPrint += "\n"
 		toPrint += "\n"
 	}
 	}
 
 
-	fmt.Fprint(w, toPrint)
+	logger.Print(toPrint)
 }
 }
 
 
-func selectionMenu(w io.Writer, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string],
+func selectionMenu(logger *text.Logger, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string],
 	message string, noConfirm bool, defaultAnswer string, skipFunc func(string) bool,
 	message string, noConfirm bool, defaultAnswer string, skipFunc func(string) bool,
 ) ([]string, error) {
 ) ([]string, error) {
 	selected := make([]string, 0)
 	selected := make([]string, 0)
 
 
-	pkgbuildNumberMenu(w, pkgbuildDirs, bases, installed)
+	pkgbuildNumberMenu(logger, pkgbuildDirs, bases, installed)
 
 
-	text.Infoln(message)
-	text.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", text.Cyan(gotext.Get("[N]one"))))
+	logger.Infoln(message)
+	logger.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", text.Cyan(gotext.Get("[N]one"))))
 
 
-	selectInput, err := text.GetInput(os.Stdin, defaultAnswer, noConfirm)
+	selectInput, err := logger.GetInput(defaultAnswer, noConfirm)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 9 - 9
pkg/news/news.go

@@ -4,11 +4,9 @@ import (
 	"bytes"
 	"bytes"
 	"context"
 	"context"
 	"encoding/xml"
 	"encoding/xml"
-	"fmt"
 	"html"
 	"html"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
-	"os"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -23,13 +21,13 @@ type item struct {
 	Creator     string `xml:"dc:creator"`
 	Creator     string `xml:"dc:creator"`
 }
 }
 
 
-func (item *item) print(buildTime time.Time, all, quiet bool) {
+func (item *item) printNews(logger *text.Logger, buildTime time.Time, all, quiet bool) {
 	var fd string
 	var fd string
 
 
 	date, err := time.Parse(time.RFC1123Z, item.PubDate)
 	date, err := time.Parse(time.RFC1123Z, item.PubDate)
 
 
 	if err != nil {
 	if err != nil {
-		fmt.Fprintln(os.Stderr, err)
+		logger.Errorln(err)
 	} else {
 	} else {
 		fd = text.FormatTime(int(date.Unix()))
 		fd = text.FormatTime(int(date.Unix()))
 		if !all && !buildTime.IsZero() {
 		if !all && !buildTime.IsZero() {
@@ -39,11 +37,11 @@ func (item *item) print(buildTime time.Time, all, quiet bool) {
 		}
 		}
 	}
 	}
 
 
-	fmt.Println(text.Bold(text.Magenta(fd)), text.Bold(strings.TrimSpace(item.Title)))
+	logger.Println(text.Bold(text.Magenta(fd)), text.Bold(strings.TrimSpace(item.Title)))
 
 
 	if !quiet {
 	if !quiet {
 		desc := strings.TrimSpace(parseNews(item.Description))
 		desc := strings.TrimSpace(parseNews(item.Description))
-		fmt.Println(desc)
+		logger.Println(desc)
 	}
 	}
 }
 }
 
 
@@ -60,7 +58,9 @@ type rss struct {
 	Channel channel `xml:"channel"`
 	Channel channel `xml:"channel"`
 }
 }
 
 
-func PrintNewsFeed(ctx context.Context, client *http.Client, cutOffDate time.Time, bottomUp, all, quiet bool) error {
+func PrintNewsFeed(ctx context.Context, client *http.Client, logger *text.Logger,
+	cutOffDate time.Time, bottomUp, all, quiet bool,
+) error {
 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://archlinux.org/feeds/news", http.NoBody)
 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://archlinux.org/feeds/news", http.NoBody)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -87,11 +87,11 @@ func PrintNewsFeed(ctx context.Context, client *http.Client, cutOffDate time.Tim
 
 
 	if bottomUp {
 	if bottomUp {
 		for i := len(rssGot.Channel.Items) - 1; i >= 0; i-- {
 		for i := len(rssGot.Channel.Items) - 1; i >= 0; i-- {
-			rssGot.Channel.Items[i].print(cutOffDate, all, quiet)
+			rssGot.Channel.Items[i].printNews(logger, cutOffDate, all, quiet)
 		}
 		}
 	} else {
 	} else {
 		for i := 0; i < len(rssGot.Channel.Items); i++ {
 		for i := 0; i < len(rssGot.Channel.Items); i++ {
-			rssGot.Channel.Items[i].print(cutOffDate, all, quiet)
+			rssGot.Channel.Items[i].printNews(logger, cutOffDate, all, quiet)
 		}
 		}
 	}
 	}
 
 

+ 9 - 8
pkg/news/news_test.go

@@ -8,12 +8,15 @@ import (
 	"io"
 	"io"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
+	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
 	"github.com/bradleyjkemp/cupaloy"
 	"github.com/bradleyjkemp/cupaloy"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	"gopkg.in/h2non/gock.v1"
 	"gopkg.in/h2non/gock.v1"
+
+	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 const lastNews = `
 const lastNews = `
@@ -135,17 +138,16 @@ func TestPrintNewsFeed(t *testing.T) {
 
 
 			defer gock.Off()
 			defer gock.Off()
 
 
-			rescueStdout := os.Stdout
 			r, w, _ := os.Pipe()
 			r, w, _ := os.Pipe()
-			os.Stdout = w
+			logger := text.NewLogger(w, w, strings.NewReader(""), false, "logger")
 
 
-			err := PrintNewsFeed(context.Background(), &http.Client{}, tt.args.cutOffDate, tt.args.bottomUp, tt.args.all, tt.args.quiet)
+			err := PrintNewsFeed(context.Background(), &http.Client{}, logger,
+				tt.args.cutOffDate, tt.args.bottomUp, tt.args.all, tt.args.quiet)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 
 
 			w.Close()
 			w.Close()
 			out, _ := io.ReadAll(r)
 			out, _ := io.ReadAll(r)
 			cupaloy.SnapshotT(t, out)
 			cupaloy.SnapshotT(t, out)
-			os.Stdout = rescueStdout
 		})
 		})
 	}
 	}
 }
 }
@@ -164,15 +166,14 @@ func TestPrintNewsFeedSameDay(t *testing.T) {
 
 
 	defer gock.Off()
 	defer gock.Off()
 
 
-	rescueStdout := os.Stdout
 	r, w, _ := os.Pipe()
 	r, w, _ := os.Pipe()
-	os.Stdout = w
+	logger := text.NewLogger(w, w, strings.NewReader(""), false, "logger")
 
 
-	err := PrintNewsFeed(context.Background(), &http.Client{}, lastNewsTime, true, false, false)
+	err := PrintNewsFeed(context.Background(), &http.Client{}, logger,
+		lastNewsTime, true, false, false)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	w.Close()
 	w.Close()
 	out, _ := io.ReadAll(r)
 	out, _ := io.ReadAll(r)
 	cupaloy.SnapshotT(t, out)
 	cupaloy.SnapshotT(t, out)
-	os.Stdout = rescueStdout
 }
 }

+ 0 - 3
pkg/query/aur_warnings.go

@@ -22,9 +22,6 @@ type AURWarnings struct {
 }
 }
 
 
 func NewWarnings(logger *text.Logger) *AURWarnings {
 func NewWarnings(logger *text.Logger) *AURWarnings {
-	if logger == nil {
-		logger = text.GlobalLogger
-	}
 	return &AURWarnings{log: logger}
 	return &AURWarnings{log: logger}
 }
 }
 
 

+ 3 - 3
pkg/query/filter.go

@@ -7,19 +7,19 @@ import (
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
-func RemoveInvalidTargets(targets []string, mode parser.TargetMode) []string {
+func RemoveInvalidTargets(logger *text.Logger, targets []string, mode parser.TargetMode) []string {
 	filteredTargets := make([]string, 0)
 	filteredTargets := make([]string, 0)
 
 
 	for _, target := range targets {
 	for _, target := range targets {
 		dbName, _ := text.SplitDBFromName(target)
 		dbName, _ := text.SplitDBFromName(target)
 
 
 		if dbName == "aur" && !mode.AtLeastAUR() {
 		if dbName == "aur" && !mode.AtLeastAUR() {
-			text.Warnln(gotext.Get("%s: can't use target with option --repo -- skipping", text.Cyan(target)))
+			logger.Warnln(gotext.Get("%s: can't use target with option --repo -- skipping", text.Cyan(target)))
 			continue
 			continue
 		}
 		}
 
 
 		if dbName != "aur" && dbName != "" && !mode.AtLeastRepo() {
 		if dbName != "aur" && dbName != "" && !mode.AtLeastRepo() {
-			text.Warnln(gotext.Get("%s: can't use target with option --aur -- skipping", text.Cyan(target)))
+			logger.Warnln(gotext.Get("%s: can't use target with option --aur -- skipping", text.Cyan(target)))
 			continue
 			continue
 		}
 		}
 
 

+ 1 - 1
pkg/query/query_builder.go

@@ -130,7 +130,7 @@ func (a *abstractResults) Less(i, j int) bool {
 func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) {
 func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) {
 	var aurErr error
 	var aurErr error
 
 
-	pkgS = RemoveInvalidTargets(pkgS, s.targetMode)
+	pkgS = RemoveInvalidTargets(s.logger, pkgS, s.targetMode)
 
 
 	metric := &metrics.Hamming{
 	metric := &metrics.Hamming{
 		CaseSensitive: false,
 		CaseSensitive: false,

+ 2 - 2
pkg/settings/pacman.go

@@ -1,4 +1,4 @@
-package settings
+package runtime
 
 
 import (
 import (
 	"fmt"
 	"fmt"
@@ -10,7 +10,7 @@ import (
 	"golang.org/x/term"
 	"golang.org/x/term"
 )
 )
 
 
-func RetrievePacmanConfig(cmdArgs *parser.Arguments, pacmanConfigPath string) (*pacmanconf.Config, bool, error) {
+func retrievePacmanConfig(cmdArgs *parser.Arguments, pacmanConfigPath string) (*pacmanconf.Config, bool, error) {
 	root := "/"
 	root := "/"
 	if value, _, exists := cmdArgs.GetArg("root", "r"); exists {
 	if value, _, exists := cmdArgs.GetArg("root", "r"); exists {
 		root = value
 		root = value

+ 2 - 2
pkg/settings/pacman_test.go

@@ -1,7 +1,7 @@
 //go:build !integration
 //go:build !integration
 // +build !integration
 // +build !integration
 
 
-package settings
+package runtime
 
 
 import (
 import (
 	"testing"
 	"testing"
@@ -46,7 +46,7 @@ func TestPacmanConf(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	pacmanConf, color, err := RetrievePacmanConfig(parser.MakeArguments(), "../../testdata/pacman.conf")
+	pacmanConf, color, err := retrievePacmanConfig(parser.MakeArguments(), "../../testdata/pacman.conf")
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 	assert.NotNil(t, pacmanConf)
 	assert.NotNil(t, pacmanConf)
 	assert.Equal(t, color, false)
 	assert.Equal(t, color, false)

+ 27 - 11
pkg/settings/runtime.go

@@ -1,4 +1,4 @@
-package settings
+package runtime
 
 
 import (
 import (
 	"context"
 	"context"
@@ -9,8 +9,8 @@ import (
 
 
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
-	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/query"
 	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
@@ -24,6 +24,7 @@ import (
 )
 )
 
 
 type Runtime struct {
 type Runtime struct {
+	Cfg          *settings.Configuration
 	QueryBuilder query.Builder
 	QueryBuilder query.Builder
 	PacmanConf   *pacmanconf.Config
 	PacmanConf   *pacmanconf.Config
 	VCSStore     vcs.Store
 	VCSStore     vcs.Store
@@ -31,13 +32,12 @@ type Runtime struct {
 	HTTPClient   *http.Client
 	HTTPClient   *http.Client
 	VoteClient   *vote.Client
 	VoteClient   *vote.Client
 	AURClient    aur.QueryClient
 	AURClient    aur.QueryClient
-	DBExecutor   db.Executor
 	Logger       *text.Logger
 	Logger       *text.Logger
 }
 }
 
 
-func BuildRuntime(cfg *Configuration, cmdArgs *parser.Arguments, version string) (*Runtime, error) {
+func NewRuntime(cfg *settings.Configuration, cmdArgs *parser.Arguments, version string) (*Runtime, error) {
 	logger := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, cfg.Debug, "runtime")
 	logger := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, cfg.Debug, "runtime")
-	cmdBuilder := cfg.CmdBuilder(nil)
+	runner := exe.NewOSRunner(logger.Child("runner"))
 
 
 	httpClient := &http.Client{
 	httpClient := &http.Client{
 		CheckRedirect: func(req *http.Request, via []*http.Request) error {
 		CheckRedirect: func(req *http.Request, via []*http.Request) error {
@@ -86,6 +86,16 @@ func BuildRuntime(cfg *Configuration, cmdArgs *parser.Arguments, version string)
 		aurCache = aurClient
 		aurCache = aurClient
 	}
 	}
 
 
+	pacmanConf, useColor, err := retrievePacmanConfig(cmdArgs, cfg.PacmanConf)
+	if err != nil {
+		return nil, err
+	}
+
+	// FIXME: get rid of global
+	text.UseColor = useColor
+
+	cmdBuilder := exe.NewCmdBuilder(cfg, runner, logger.Child("cmdbuilder"), pacmanConf.DBPath)
+
 	vcsStore := vcs.NewInfoStore(
 	vcsStore := vcs.NewInfoStore(
 		cfg.VCSFilePath, cmdBuilder,
 		cfg.VCSFilePath, cmdBuilder,
 		logger.Child("vcs"))
 		logger.Child("vcs"))
@@ -94,17 +104,23 @@ func BuildRuntime(cfg *Configuration, cmdArgs *parser.Arguments, version string)
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	runtime := &Runtime{
-		QueryBuilder: nil,
-		PacmanConf:   nil,
+	queryBuilder := query.NewSourceQueryBuilder(
+		aurClient,
+		logger.Child("mixed.querybuilder"), cfg.SortBy,
+		cfg.Mode, cfg.SearchBy,
+		cfg.BottomUp, cfg.SingleLineResults, cfg.SeparateSources)
+
+	run := &Runtime{
+		Cfg:          cfg,
+		QueryBuilder: queryBuilder,
+		PacmanConf:   pacmanConf,
 		VCSStore:     vcsStore,
 		VCSStore:     vcsStore,
 		CmdBuilder:   cmdBuilder,
 		CmdBuilder:   cmdBuilder,
 		HTTPClient:   &http.Client{},
 		HTTPClient:   &http.Client{},
 		VoteClient:   voteClient,
 		VoteClient:   voteClient,
 		AURClient:    aurCache,
 		AURClient:    aurCache,
-		DBExecutor:   nil,
-		Logger:       text.NewLogger(os.Stdout, os.Stderr, os.Stdin, cfg.Debug, "runtime"),
+		Logger:       logger,
 	}
 	}
 
 
-	return runtime, nil
+	return run, nil
 }
 }

+ 15 - 15
pkg/settings/runtime_test.go

@@ -1,16 +1,17 @@
 //go:build !integration
 //go:build !integration
 // +build !integration
 // +build !integration
 
 
-package settings_test
+package runtime_test
 
 
 import (
 import (
 	"testing"
 	"testing"
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
-	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 func TestBuildRuntime(t *testing.T) {
 func TestBuildRuntime(t *testing.T) {
@@ -23,24 +24,23 @@ func TestBuildRuntime(t *testing.T) {
 		AURRPCURL:   "https://aur.archlinux.org/rpc",
 		AURRPCURL:   "https://aur.archlinux.org/rpc",
 		BuildDir:    "/tmp",
 		BuildDir:    "/tmp",
 		VCSFilePath: "",
 		VCSFilePath: "",
-		Runtime:     &settings.Runtime{Logger: text.NewLogger(nil, nil, nil, false, "")},
+		PacmanConf:  "../../testdata/pacman.conf",
 	}
 	}
 	cmdArgs := parser.MakeArguments()
 	cmdArgs := parser.MakeArguments()
 	version := "1.0.0"
 	version := "1.0.0"
 
 
 	// Call the function being tested
 	// Call the function being tested
-	runtime, err := settings.BuildRuntime(cfg, cmdArgs, version)
+	run, err := runtime.NewRuntime(cfg, cmdArgs, version)
+	require.NoError(t, err)
 
 
 	// Assert the function's output
 	// Assert the function's output
-	assert.NotNil(t, runtime)
-	assert.Nil(t, err)
-	assert.Nil(t, runtime.QueryBuilder)
-	assert.Nil(t, runtime.PacmanConf)
-	assert.NotNil(t, runtime.VCSStore)
-	assert.NotNil(t, runtime.CmdBuilder)
-	assert.NotNil(t, runtime.HTTPClient)
-	assert.NotNil(t, runtime.VoteClient)
-	assert.NotNil(t, runtime.AURClient)
-	assert.Nil(t, runtime.DBExecutor)
-	assert.NotNil(t, runtime.Logger)
+	assert.NotNil(t, run)
+	assert.NotNil(t, run.QueryBuilder)
+	assert.NotNil(t, run.PacmanConf)
+	assert.NotNil(t, run.VCSStore)
+	assert.NotNil(t, run.CmdBuilder)
+	assert.NotNil(t, run.HTTPClient)
+	assert.NotNil(t, run.VoteClient)
+	assert.NotNil(t, run.AURClient)
+	assert.NotNil(t, run.Logger)
 }
 }

+ 0 - 5
pkg/settings/args.go

@@ -5,7 +5,6 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
-	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 func (c *Configuration) ParseCommandLine(a *parser.Arguments) error {
 func (c *Configuration) ParseCommandLine(a *parser.Arguments) error {
@@ -15,9 +14,6 @@ func (c *Configuration) ParseCommandLine(a *parser.Arguments) error {
 
 
 	c.extractYayOptions(a)
 	c.extractYayOptions(a)
 
 
-	// Reload CmdBuilder
-	c.Runtime.CmdBuilder = c.CmdBuilder(nil)
-
 	return nil
 	return nil
 }
 }
 
 
@@ -59,7 +55,6 @@ func (c *Configuration) handleOption(option, value string) bool {
 		c.CleanAfter = false
 		c.CleanAfter = false
 	case "debug":
 	case "debug":
 		c.Debug = true
 		c.Debug = true
-		text.GlobalLogger.Debug = true
 		return false
 		return false
 	case "devel":
 	case "devel":
 		c.Devel = true
 		c.Devel = true

+ 50 - 79
pkg/settings/config.go

@@ -9,7 +9,6 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 
 
-	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 
 
@@ -24,53 +23,52 @@ var NoConfirm = false
 
 
 // Configuration stores yay's config.
 // Configuration stores yay's config.
 type Configuration struct {
 type Configuration struct {
-	Runtime                *Runtime `json:"-"`
-	AURURL                 string   `json:"aururl"`
-	AURRPCURL              string   `json:"aurrpcurl"`
-	BuildDir               string   `json:"buildDir"`
-	Editor                 string   `json:"editor"`
-	EditorFlags            string   `json:"editorflags"`
-	MakepkgBin             string   `json:"makepkgbin"`
-	MakepkgConf            string   `json:"makepkgconf"`
-	PacmanBin              string   `json:"pacmanbin"`
-	PacmanConf             string   `json:"pacmanconf"`
-	ReDownload             string   `json:"redownload"`
-	AnswerClean            string   `json:"answerclean"`
-	AnswerDiff             string   `json:"answerdiff"`
-	AnswerEdit             string   `json:"answeredit"`
-	AnswerUpgrade          string   `json:"answerupgrade"`
-	GitBin                 string   `json:"gitbin"`
-	GpgBin                 string   `json:"gpgbin"`
-	GpgFlags               string   `json:"gpgflags"`
-	MFlags                 string   `json:"mflags"`
-	SortBy                 string   `json:"sortby"`
-	SearchBy               string   `json:"searchby"`
-	GitFlags               string   `json:"gitflags"`
-	RemoveMake             string   `json:"removemake"`
-	SudoBin                string   `json:"sudobin"`
-	SudoFlags              string   `json:"sudoflags"`
-	Version                string   `json:"version"`
-	RequestSplitN          int      `json:"requestsplitn"`
-	CompletionInterval     int      `json:"completionrefreshtime"`
-	MaxConcurrentDownloads int      `json:"maxconcurrentdownloads"`
-	BottomUp               bool     `json:"bottomup"`
-	SudoLoop               bool     `json:"sudoloop"`
-	TimeUpdate             bool     `json:"timeupdate"`
-	Devel                  bool     `json:"devel"`
-	CleanAfter             bool     `json:"cleanAfter"`
-	Provides               bool     `json:"provides"`
-	PGPFetch               bool     `json:"pgpfetch"`
-	CleanMenu              bool     `json:"cleanmenu"`
-	DiffMenu               bool     `json:"diffmenu"`
-	EditMenu               bool     `json:"editmenu"`
-	CombinedUpgrade        bool     `json:"combinedupgrade"`
-	UseAsk                 bool     `json:"useask"`
-	BatchInstall           bool     `json:"batchinstall"`
-	SingleLineResults      bool     `json:"singlelineresults"`
-	SeparateSources        bool     `json:"separatesources"`
-	Debug                  bool     `json:"debug"`
-	UseRPC                 bool     `json:"rpc"`
-	DoubleConfirm          bool     `json:"doubleconfirm"` // confirm install before and after build
+	AURURL                 string `json:"aururl"`
+	AURRPCURL              string `json:"aurrpcurl"`
+	BuildDir               string `json:"buildDir"`
+	Editor                 string `json:"editor"`
+	EditorFlags            string `json:"editorflags"`
+	MakepkgBin             string `json:"makepkgbin"`
+	MakepkgConf            string `json:"makepkgconf"`
+	PacmanBin              string `json:"pacmanbin"`
+	PacmanConf             string `json:"pacmanconf"`
+	ReDownload             string `json:"redownload"`
+	AnswerClean            string `json:"answerclean"`
+	AnswerDiff             string `json:"answerdiff"`
+	AnswerEdit             string `json:"answeredit"`
+	AnswerUpgrade          string `json:"answerupgrade"`
+	GitBin                 string `json:"gitbin"`
+	GpgBin                 string `json:"gpgbin"`
+	GpgFlags               string `json:"gpgflags"`
+	MFlags                 string `json:"mflags"`
+	SortBy                 string `json:"sortby"`
+	SearchBy               string `json:"searchby"`
+	GitFlags               string `json:"gitflags"`
+	RemoveMake             string `json:"removemake"`
+	SudoBin                string `json:"sudobin"`
+	SudoFlags              string `json:"sudoflags"`
+	Version                string `json:"version"`
+	RequestSplitN          int    `json:"requestsplitn"`
+	CompletionInterval     int    `json:"completionrefreshtime"`
+	MaxConcurrentDownloads int    `json:"maxconcurrentdownloads"`
+	BottomUp               bool   `json:"bottomup"`
+	SudoLoop               bool   `json:"sudoloop"`
+	TimeUpdate             bool   `json:"timeupdate"`
+	Devel                  bool   `json:"devel"`
+	CleanAfter             bool   `json:"cleanAfter"`
+	Provides               bool   `json:"provides"`
+	PGPFetch               bool   `json:"pgpfetch"`
+	CleanMenu              bool   `json:"cleanmenu"`
+	DiffMenu               bool   `json:"diffmenu"`
+	EditMenu               bool   `json:"editmenu"`
+	CombinedUpgrade        bool   `json:"combinedupgrade"`
+	UseAsk                 bool   `json:"useask"`
+	BatchInstall           bool   `json:"batchinstall"`
+	SingleLineResults      bool   `json:"singlelineresults"`
+	SeparateSources        bool   `json:"separatesources"`
+	Debug                  bool   `json:"debug"`
+	UseRPC                 bool   `json:"rpc"`
+	DoubleConfirm          bool   `json:"doubleconfirm"` // confirm install before and after build
 
 
 	CompletionPath string `json:"-"`
 	CompletionPath string `json:"-"`
 	VCSFilePath    string `json:"-"`
 	VCSFilePath    string `json:"-"`
@@ -237,19 +235,16 @@ func DefaultConfig(version string) *Configuration {
 		Debug:                  false,
 		Debug:                  false,
 		UseRPC:                 true,
 		UseRPC:                 true,
 		DoubleConfirm:          true,
 		DoubleConfirm:          true,
-		Runtime: &Runtime{
-			Logger: text.GlobalLogger,
-		},
-		Mode: parser.ModeAny,
+		Mode:                   parser.ModeAny,
 	}
 	}
 }
 }
 
 
-func NewConfig(configPath, version string) (*Configuration, error) {
+func NewConfig(logger *text.Logger, configPath, version string) (*Configuration, error) {
 	newConfig := DefaultConfig(version)
 	newConfig := DefaultConfig(version)
 
 
 	cacheHome, errCache := getCacheHome()
 	cacheHome, errCache := getCacheHome()
-	if errCache != nil {
-		text.Errorln(errCache)
+	if errCache != nil && logger != nil {
+		logger.Errorln(errCache)
 	}
 	}
 
 
 	newConfig.BuildDir = cacheHome
 	newConfig.BuildDir = cacheHome
@@ -295,27 +290,3 @@ func (c *Configuration) load(configPath string) {
 		}
 		}
 	}
 	}
 }
 }
-
-func (c *Configuration) CmdBuilder(runner exe.Runner) exe.ICmdBuilder {
-	if runner == nil {
-		runner = &exe.OSRunner{Log: c.Runtime.Logger.Child("runner")}
-	}
-
-	return &exe.CmdBuilder{
-		GitBin:           c.GitBin,
-		GitFlags:         strings.Fields(c.GitFlags),
-		GPGBin:           c.GpgBin,
-		GPGFlags:         strings.Fields(c.GpgFlags),
-		MakepkgFlags:     strings.Fields(c.MFlags),
-		MakepkgConfPath:  c.MakepkgConf,
-		MakepkgBin:       c.MakepkgBin,
-		SudoBin:          c.SudoBin,
-		SudoFlags:        strings.Fields(c.SudoFlags),
-		SudoLoopEnabled:  c.SudoLoop,
-		PacmanBin:        c.PacmanBin,
-		PacmanConfigPath: c.PacmanConf,
-		PacmanDBPath:     "",
-		Runner:           runner,
-		Log:              c.Runtime.Logger.Child("cmd_builder"),
-	}
-}

+ 3 - 3
pkg/settings/config_test.go

@@ -36,7 +36,7 @@ func TestNewConfig(t *testing.T) {
 	_, err = f.WriteString(string(configJSON))
 	_, err = f.WriteString(string(configJSON))
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
-	newConfig, err := NewConfig(GetConfigPath(), "v1.0.0")
+	newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	assert.Equal(t, filepath.Join(cacheDir, "test-build-dir"), newConfig.BuildDir)
 	assert.Equal(t, filepath.Join(cacheDir, "test-build-dir"), newConfig.BuildDir)
@@ -69,7 +69,7 @@ func TestNewConfigAURDEST(t *testing.T) {
 	_, err = f.WriteString(string(configJSON))
 	_, err = f.WriteString(string(configJSON))
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
-	newConfig, err := NewConfig(GetConfigPath(), "v1.0.0")
+	newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	assert.Equal(t, filepath.Join(cacheDir, "test-build-dir"), newConfig.BuildDir)
 	assert.Equal(t, filepath.Join(cacheDir, "test-build-dir"), newConfig.BuildDir)
@@ -102,7 +102,7 @@ func TestNewConfigAURDESTTildeExpansion(t *testing.T) {
 	_, err = f.WriteString(string(configJSON))
 	_, err = f.WriteString(string(configJSON))
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
-	newConfig, err := NewConfig(GetConfigPath(), "v1.0.0")
+	newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0")
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	assert.Equal(t, filepath.Join(homeDir, "test-build-dir"), newConfig.BuildDir)
 	assert.Equal(t, filepath.Join(homeDir, "test-build-dir"), newConfig.BuildDir)

+ 22 - 6
pkg/settings/exe/cmd_builder.go

@@ -15,6 +15,7 @@ import (
 	mapset "github.com/deckarep/golang-set/v2"
 	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
 
 
+	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
@@ -38,7 +39,6 @@ type ICmdBuilder interface {
 	BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd
 	BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd
 	BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd
 	BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd
 	AddMakepkgFlag(string)
 	AddMakepkgFlag(string)
-	SetPacmanDBPath(string)
 	SudoLoop()
 	SudoLoop()
 }
 }
 
 
@@ -60,6 +60,26 @@ type CmdBuilder struct {
 	Log              *text.Logger
 	Log              *text.Logger
 }
 }
 
 
+func NewCmdBuilder(cfg *settings.Configuration, runner Runner, logger *text.Logger, dbPath string) *CmdBuilder {
+	return &CmdBuilder{
+		GitBin:           cfg.GitBin,
+		GitFlags:         strings.Fields(cfg.GitFlags),
+		GPGBin:           cfg.GpgBin,
+		GPGFlags:         strings.Fields(cfg.GpgFlags),
+		MakepkgFlags:     strings.Fields(cfg.MFlags),
+		MakepkgConfPath:  cfg.MakepkgConf,
+		MakepkgBin:       cfg.MakepkgBin,
+		SudoBin:          cfg.SudoBin,
+		SudoFlags:        strings.Fields(cfg.SudoFlags),
+		SudoLoopEnabled:  cfg.SudoLoop,
+		PacmanBin:        cfg.PacmanBin,
+		PacmanConfigPath: cfg.PacmanConf,
+		PacmanDBPath:     dbPath,
+		Runner:           runner,
+		Log:              logger,
+	}
+}
+
 func (c *CmdBuilder) BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd {
 func (c *CmdBuilder) BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd {
 	args := make([]string, len(c.GPGFlags), len(c.GPGFlags)+len(extraArgs))
 	args := make([]string, len(c.GPGFlags), len(c.GPGFlags)+len(extraArgs))
 	copy(args, c.GPGFlags)
 	copy(args, c.GPGFlags)
@@ -135,10 +155,6 @@ func (c *CmdBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs
 	return cmd
 	return cmd
 }
 }
 
 
-func (c *CmdBuilder) SetPacmanDBPath(dbPath string) {
-	c.PacmanDBPath = dbPath
-}
-
 // deElevateCommand, `systemd-run` code based on pikaur.
 // deElevateCommand, `systemd-run` code based on pikaur.
 func (c *CmdBuilder) deElevateCommand(ctx context.Context, cmd *exec.Cmd) *exec.Cmd {
 func (c *CmdBuilder) deElevateCommand(ctx context.Context, cmd *exec.Cmd) *exec.Cmd {
 	if os.Geteuid() != 0 {
 	if os.Geteuid() != 0 {
@@ -242,7 +258,7 @@ func (c *CmdBuilder) waitLock(dbPath string) {
 		time.Sleep(3 * time.Second)
 		time.Sleep(3 * time.Second)
 
 
 		if _, err := os.Stat(lockDBPath); err != nil {
 		if _, err := os.Stat(lockDBPath); err != nil {
-			fmt.Println()
+			c.Log.Println()
 
 
 			return
 			return
 		}
 		}

+ 4 - 0
pkg/settings/exe/exec.go

@@ -19,6 +19,10 @@ type OSRunner struct {
 	Log *text.Logger
 	Log *text.Logger
 }
 }
 
 
+func NewOSRunner(log *text.Logger) *OSRunner {
+	return &OSRunner{log}
+}
+
 func (r *OSRunner) Show(cmd *exec.Cmd) error {
 func (r *OSRunner) Show(cmd *exec.Cmd) error {
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	cmd.SysProcAttr = &syscall.SysProcAttr{
 	cmd.SysProcAttr = &syscall.SysProcAttr{

+ 4 - 0
pkg/settings/exe/mock.go

@@ -36,6 +36,10 @@ type MockRunner struct {
 	CaptureFn      func(cmd *exec.Cmd) (stdout string, stderr string, err error)
 	CaptureFn      func(cmd *exec.Cmd) (stdout string, stderr string, err error)
 }
 }
 
 
+func (m *MockBuilder) BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd {
+	return exec.CommandContext(ctx, "gpg", extraArgs...)
+}
+
 func (m *MockBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd {
 func (m *MockBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd {
 	var res *exec.Cmd
 	var res *exec.Cmd
 	if m.BuildMakepkgCmdFn != nil {
 	if m.BuildMakepkgCmdFn != nil {

+ 2 - 2
pkg/settings/migrations.go

@@ -45,7 +45,7 @@ func DefaultMigrations() []configMigration {
 	}
 	}
 }
 }
 
 
-func (c *Configuration) RunMigrations(migrations []configMigration,
+func (c *Configuration) RunMigrations(logger *text.Logger, migrations []configMigration,
 	configPath, newVersion string,
 	configPath, newVersion string,
 ) error {
 ) error {
 	saveConfig := false
 	saveConfig := false
@@ -53,7 +53,7 @@ func (c *Configuration) RunMigrations(migrations []configMigration,
 	for _, migration := range migrations {
 	for _, migration := range migrations {
 		if db.VerCmp(migration.TargetVersion(), c.Version) > 0 {
 		if db.VerCmp(migration.TargetVersion(), c.Version) > 0 {
 			if migration.Do(c) {
 			if migration.Do(c) {
-				text.Infoln("Config migration executed (",
+				logger.Infoln("Config migration executed (",
 					migration.TargetVersion(), "):", migration)
 					migration.TargetVersion(), "):", migration)
 
 
 				saveConfig = true
 				saveConfig = true

+ 6 - 11
pkg/settings/migrations_test.go

@@ -16,6 +16,10 @@ import (
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
 func TestMigrationNothingToDo(t *testing.T) {
 func TestMigrationNothingToDo(t *testing.T) {
 	t.Parallel()
 	t.Parallel()
 	// Create temporary file for config
 	// Create temporary file for config
@@ -28,13 +32,10 @@ func TestMigrationNothingToDo(t *testing.T) {
 	config := Configuration{
 	config := Configuration{
 		Version: "99.0.0",
 		Version: "99.0.0",
 		// Create runtime with runtimeVersion
 		// Create runtime with runtimeVersion
-		Runtime: &Runtime{
-			Logger: text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), false, "test"),
-		},
 	}
 	}
 
 
 	// Run Migration
 	// Run Migration
-	err = config.RunMigrations(DefaultMigrations(), testFilePath, "20.0.0")
+	err = config.RunMigrations(newTestLogger(), DefaultMigrations(), testFilePath, "20.0.0")
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	// Check file contents if wantSave otherwise check file empty
 	// Check file contents if wantSave otherwise check file empty
@@ -53,9 +54,6 @@ func TestProvidesMigrationDo(t *testing.T) {
 	migration := &configProviderMigration{}
 	migration := &configProviderMigration{}
 	config := Configuration{
 	config := Configuration{
 		Provides: true,
 		Provides: true,
-		Runtime: &Runtime{
-			Logger: text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), false, "test"),
-		},
 	}
 	}
 
 
 	assert.True(t, migration.Do(&config))
 	assert.True(t, migration.Do(&config))
@@ -135,13 +133,10 @@ func TestProvidesMigration(t *testing.T) {
 				Version:  tc.testConfig.Version,
 				Version:  tc.testConfig.Version,
 				Provides: tc.testConfig.Provides,
 				Provides: tc.testConfig.Provides,
 				// Create runtime with runtimeVersion
 				// Create runtime with runtimeVersion
-				Runtime: &Runtime{
-					Logger: text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), false, "test"),
-				},
 			}
 			}
 
 
 			// Run Migration
 			// Run Migration
-			err = tcConfig.RunMigrations(
+			err = tcConfig.RunMigrations(newTestLogger(),
 				[]configMigration{&configProviderMigration{}},
 				[]configMigration{&configProviderMigration{}},
 				testFilePath, tc.newVersion)
 				testFilePath, tc.newVersion)
 
 

+ 62 - 0
pkg/sync/build/errors.go

@@ -0,0 +1,62 @@
+package build
+
+import (
+	"errors"
+
+	"github.com/leonelquinteros/gotext"
+)
+
+var ErrInstallRepoPkgs = errors.New(gotext.Get("error installing repo packages"))
+
+type FailedIgnoredPkgError struct {
+	pkgErrors map[string]error
+}
+
+func (e *FailedIgnoredPkgError) Error() string {
+	msg := gotext.Get("Failed to install the following packages. Manual intervention is required:")
+
+	for pkg, err := range e.pkgErrors {
+		msg += "\n" + pkg + " - " + err.Error()
+	}
+
+	return msg
+}
+
+type PkgDestNotInListError struct {
+	name string
+}
+
+func (e *PkgDestNotInListError) Error() string {
+	return gotext.Get("could not find PKGDEST for: %s", e.name)
+}
+
+type FindPkgDestError struct {
+	name, pkgDest string
+}
+
+func (e *FindPkgDestError) Error() string {
+	return gotext.Get(
+		"the PKGDEST for %s is listed by makepkg but does not exist: %s",
+		e.name, e.pkgDest)
+}
+
+type SetPkgReasonError struct {
+	exp bool // explicit
+}
+
+func (e *SetPkgReasonError) Error() string {
+	reason := gotext.Get("explicit")
+	if !e.exp {
+		reason = gotext.Get("dependency")
+	}
+
+	return gotext.Get("error updating package install reason to %s", reason)
+}
+
+type NoPkgDestsFoundError struct {
+	dir string
+}
+
+func (e *NoPkgDestsFoundError) Error() string {
+	return gotext.Get("could not find any package archives listed in %s", e.dir)
+}

+ 13 - 13
aur_install.go

@@ -1,4 +1,4 @@
-package main
+package build
 
 
 import (
 import (
 	"context"
 	"context"
@@ -54,12 +54,12 @@ func NewInstaller(dbExecutor db.Executor,
 	}
 	}
 }
 }
 
 
-func (installer *Installer) CompileFailedAndIgnored() error {
+func (installer *Installer) CompileFailedAndIgnored() (map[string]error, error) {
 	if len(installer.failedAndIgnored) == 0 {
 	if len(installer.failedAndIgnored) == 0 {
-		return nil
+		return installer.failedAndIgnored, nil
 	}
 	}
 
 
-	return &FailedIgnoredPkgError{
+	return installer.failedAndIgnored, &FailedIgnoredPkgError{
 		pkgErrors: installer.failedAndIgnored,
 		pkgErrors: installer.failedAndIgnored,
 	}
 	}
 }
 }
@@ -234,12 +234,12 @@ func (installer *Installer) installAURPackages(ctx context.Context,
 			}
 			}
 
 
 			installer.failedAndIgnored[name] = errMake
 			installer.failedAndIgnored[name] = errMake
-			text.Errorln(gotext.Get("error making: %s", base), "-", errMake)
+			installer.log.Errorln(gotext.Get("error making: %s", base), "-", errMake)
 			continue
 			continue
 		}
 		}
 
 
 		if len(pkgdests) == 0 {
 		if len(pkgdests) == 0 {
-			text.Warnln(gotext.Get("nothing to install for %s", text.Cyan(base)))
+			installer.log.Warnln(gotext.Get("nothing to install for %s", text.Cyan(base)))
 			continue
 			continue
 		}
 		}
 
 
@@ -298,10 +298,10 @@ func (installer *Installer) buildPkg(ctx context.Context,
 	case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly:
 	case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly:
 		args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
 		args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
 		pkgdests = map[string]string{}
 		pkgdests = map[string]string{}
-		text.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion)))
+		installer.log.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion)))
 	case installer.skipAlreadyBuiltPkg(isTarget, pkgdests):
 	case installer.skipAlreadyBuiltPkg(isTarget, pkgdests):
 		args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
 		args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
-		text.Warnln(gotext.Get("%s already made -- skipping build", text.Cyan(base+"-"+pkgVersion)))
+		installer.log.Warnln(gotext.Get("%s already made -- skipping build", text.Cyan(base+"-"+pkgVersion)))
 	default:
 	default:
 		args = []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
 		args = []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
 		if installIncompatible {
 		if installIncompatible {
@@ -333,10 +333,10 @@ func (installer *Installer) pkgsAreAlreadyInstalled(pkgdests map[string]string,
 	return true
 	return true
 }
 }
 
 
-func pkgsAreBuilt(pkgdests map[string]string) bool {
+func pkgsAreBuilt(logger *text.Logger, pkgdests map[string]string) bool {
 	for _, pkgdest := range pkgdests {
 	for _, pkgdest := range pkgdests {
 		if _, err := os.Stat(pkgdest); err != nil {
 		if _, err := os.Stat(pkgdest); err != nil {
-			text.Debugln("pkgIsBuilt:", pkgdest, "does not exist")
+			logger.Debugln("pkgIsBuilt:", pkgdest, "does not exist")
 			return false
 			return false
 		}
 		}
 	}
 	}
@@ -347,14 +347,14 @@ func pkgsAreBuilt(pkgdests map[string]string) bool {
 func (installer *Installer) skipAlreadyBuiltPkg(isTarget bool, pkgdests map[string]string) bool {
 func (installer *Installer) skipAlreadyBuiltPkg(isTarget bool, pkgdests map[string]string) bool {
 	switch installer.rebuildMode {
 	switch installer.rebuildMode {
 	case parser.RebuildModeNo:
 	case parser.RebuildModeNo:
-		return pkgsAreBuilt(pkgdests)
+		return pkgsAreBuilt(installer.log, pkgdests)
 	case parser.RebuildModeYes:
 	case parser.RebuildModeYes:
-		return !isTarget && pkgsAreBuilt(pkgdests)
+		return !isTarget && pkgsAreBuilt(installer.log, pkgdests)
 	// case parser.RebuildModeTree: // TODO
 	// case parser.RebuildModeTree: // TODO
 	// case parser.RebuildModeAll: // TODO
 	// case parser.RebuildModeAll: // TODO
 	default:
 	default:
 		// same as RebuildModeNo
 		// same as RebuildModeNo
-		return pkgsAreBuilt(pkgdests)
+		return pkgsAreBuilt(installer.log, pkgdests)
 	}
 	}
 }
 }
 
 

+ 13 - 12
aur_install_test.go

@@ -1,4 +1,4 @@
-package main
+package build
 
 
 import (
 import (
 	"context"
 	"context"
@@ -21,7 +21,7 @@ import (
 	"github.com/Jguer/yay/v12/pkg/vcs"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 )
 )
 
 
-func NewTestLogger() *text.Logger {
+func newTestLogger() *text.Logger {
 	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
 	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
 }
 }
 
 
@@ -134,7 +134,7 @@ func TestInstaller_InstallNeeded(t *testing.T) {
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-				parser.RebuildModeNo, false, NewTestLogger())
+				parser.RebuildModeNo, false, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddArg("needed")
 			cmdArgs.AddArg("needed")
@@ -408,7 +408,7 @@ func TestInstaller_InstallMixedSourcesAndLayers(t *testing.T) {
 
 
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
-			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, NewTestLogger())
+			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddTarget("yay")
 			cmdArgs.AddTarget("yay")
@@ -462,7 +462,7 @@ func TestInstaller_RunPostHooks(t *testing.T) {
 	cmdBuilder.Runner = mockRunner
 	cmdBuilder.Runner = mockRunner
 
 
 	installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 	installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-		parser.RebuildModeNo, false, NewTestLogger())
+		parser.RebuildModeNo, false, newTestLogger())
 
 
 	called := false
 	called := false
 	hook := func(ctx context.Context) error {
 	hook := func(ctx context.Context) error {
@@ -593,7 +593,7 @@ func TestInstaller_CompileFailed(t *testing.T) {
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-				parser.RebuildModeNo, false, NewTestLogger())
+				parser.RebuildModeNo, false, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddArg("needed")
 			cmdArgs.AddArg("needed")
@@ -609,10 +609,11 @@ func TestInstaller_CompileFailed(t *testing.T) {
 			} else {
 			} else {
 				require.NoError(td, errI)
 				require.NoError(td, errI)
 			}
 			}
-			err := installer.CompileFailedAndIgnored()
+			failed, err := installer.CompileFailedAndIgnored()
 			if tc.wantErrCompile {
 			if tc.wantErrCompile {
 				require.Error(td, err)
 				require.Error(td, err)
 				assert.ErrorContains(td, err, "yay")
 				assert.ErrorContains(td, err, "yay")
+				assert.Len(t, failed, len(tc.targets))
 			} else {
 			} else {
 				require.NoError(td, err)
 				require.NoError(td, err)
 			}
 			}
@@ -752,7 +753,7 @@ func TestInstaller_InstallSplitPackage(t *testing.T) {
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-				parser.RebuildModeNo, false, NewTestLogger())
+				parser.RebuildModeNo, false, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddTarget("jellyfin")
 			cmdArgs.AddTarget("jellyfin")
@@ -891,7 +892,7 @@ func TestInstaller_InstallDownloadOnly(t *testing.T) {
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-				parser.RebuildModeNo, true, NewTestLogger())
+				parser.RebuildModeNo, true, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddTarget("yay")
 			cmdArgs.AddTarget("yay")
@@ -995,7 +996,7 @@ func TestInstaller_InstallGroup(t *testing.T) {
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-				parser.RebuildModeNo, true, NewTestLogger())
+				parser.RebuildModeNo, true, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddTarget("kubernetes-tools")
 			cmdArgs.AddTarget("kubernetes-tools")
@@ -1213,7 +1214,7 @@ func TestInstaller_InstallRebuild(t *testing.T) {
 			cmdBuilder.Runner = mockRunner
 			cmdBuilder.Runner = mockRunner
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny,
-				tc.rebuildOption, false, NewTestLogger())
+				tc.rebuildOption, false, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddTarget("yay")
 			cmdArgs.AddTarget("yay")
@@ -1298,7 +1299,7 @@ func TestInstaller_InstallUpgrade(t *testing.T) {
 			}
 			}
 
 
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, tc.targetMode,
 			installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, tc.targetMode,
-				parser.RebuildModeNo, false, NewTestLogger())
+				parser.RebuildModeNo, false, newTestLogger())
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddArg("u", "upgrades") // Make sure both args are removed
 			cmdArgs.AddArg("u", "upgrades") // Make sure both args are removed

+ 56 - 122
install.go

@@ -1,4 +1,4 @@
-package main
+package build
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,6 +16,61 @@ import (
 	"github.com/Jguer/yay/v12/pkg/vcs"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 )
 )
 
 
+func installPkgArchive(ctx context.Context,
+	cmdBuilder exe.ICmdBuilder,
+	mode parser.TargetMode,
+	vcsStore vcs.Store,
+	cmdArgs *parser.Arguments,
+	pkgArchives []string,
+	noConfirm bool,
+) error {
+	if len(pkgArchives) == 0 {
+		return nil
+	}
+
+	arguments := cmdArgs.Copy()
+	arguments.ClearTargets()
+	arguments.Op = "U"
+	arguments.DelArg("confirm")
+	arguments.DelArg("noconfirm")
+	arguments.DelArg("c", "clean")
+	arguments.DelArg("i", "install")
+	arguments.DelArg("q", "quiet")
+	arguments.DelArg("y", "refresh")
+	arguments.DelArg("u", "sysupgrade")
+	arguments.DelArg("w", "downloadonly")
+	arguments.DelArg("asdeps", "asdep")
+	arguments.DelArg("asexplicit", "asexp")
+
+	arguments.AddTarget(pkgArchives...)
+
+	if errShow := cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
+		arguments, mode, noConfirm)); errShow != nil {
+		return errShow
+	}
+
+	if errStore := vcsStore.Save(); errStore != nil {
+		fmt.Fprintln(os.Stderr, errStore)
+	}
+
+	return nil
+}
+
+func setInstallReason(ctx context.Context,
+	cmdBuilder exe.ICmdBuilder, mode parser.TargetMode,
+	cmdArgs *parser.Arguments, deps, exps []string,
+) error {
+	if len(deps)+len(exps) == 0 {
+		return nil
+	}
+
+	if errDeps := asdeps(ctx, cmdBuilder, mode, cmdArgs, deps); errDeps != nil {
+		return errDeps
+	}
+
+	return asexp(ctx, cmdBuilder, mode, cmdArgs, exps)
+}
+
 func setPkgReason(ctx context.Context,
 func setPkgReason(ctx context.Context,
 	cmdBuilder exe.ICmdBuilder,
 	cmdBuilder exe.ICmdBuilder,
 	mode parser.TargetMode,
 	mode parser.TargetMode,
@@ -69,43 +124,6 @@ func asexp(ctx context.Context,
 	return setPkgReason(ctx, cmdBuilder, mode, cmdArgs, pkgs, true)
 	return setPkgReason(ctx, cmdBuilder, mode, cmdArgs, pkgs, true)
 }
 }
 
 
-func removeMake(ctx context.Context, config *settings.Configuration,
-	cmdBuilder exe.ICmdBuilder, makeDeps []string, cmdArgs *parser.Arguments,
-) error {
-	removeArguments := cmdArgs.CopyGlobal()
-
-	err := removeArguments.AddArg("R", "s", "u")
-	if err != nil {
-		return err
-	}
-
-	for _, pkg := range makeDeps {
-		removeArguments.AddTarget(pkg)
-	}
-
-	oldValue := settings.NoConfirm
-	settings.NoConfirm = true
-	err = cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
-		removeArguments, config.Mode, settings.NoConfirm))
-	settings.NoConfirm = oldValue
-
-	return err
-}
-
-func earlyRefresh(ctx context.Context, cfg *settings.Configuration, cmdBuilder exe.ICmdBuilder, cmdArgs *parser.Arguments) error {
-	arguments := cmdArgs.Copy()
-	if cfg.CombinedUpgrade {
-		arguments.DelArg("u", "sysupgrade")
-	}
-	arguments.DelArg("s", "search")
-	arguments.DelArg("i", "info")
-	arguments.DelArg("l", "list")
-	arguments.ClearTargets()
-
-	return cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
-		arguments, cfg.Mode, settings.NoConfirm))
-}
-
 func parsePackageList(ctx context.Context, cmdBuilder exe.ICmdBuilder,
 func parsePackageList(ctx context.Context, cmdBuilder exe.ICmdBuilder,
 	dir string,
 	dir string,
 ) (pkgdests map[string]string, pkgVersion string, err error) {
 ) (pkgdests map[string]string, pkgVersion string, err error) {
@@ -144,87 +162,3 @@ func parsePackageList(ctx context.Context, cmdBuilder exe.ICmdBuilder,
 
 
 	return pkgdests, pkgVersion, nil
 	return pkgdests, pkgVersion, nil
 }
 }
-
-func gitMerge(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) error {
-	_, stderr, err := cmdBuilder.Capture(
-		cmdBuilder.BuildGitCmd(ctx,
-			dir, "reset", "--hard", "HEAD"))
-	if err != nil {
-		return errors.New(gotext.Get("error resetting %s: %s", dir, stderr))
-	}
-
-	_, stderr, err = cmdBuilder.Capture(
-		cmdBuilder.BuildGitCmd(ctx,
-			dir, "merge", "--no-edit", "--ff"))
-	if err != nil {
-		return errors.New(gotext.Get("error merging %s: %s", dir, stderr))
-	}
-
-	return nil
-}
-
-func mergePkgbuilds(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string) error {
-	for _, dir := range pkgbuildDirs {
-		err := gitMerge(ctx, cmdBuilder, dir)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func installPkgArchive(ctx context.Context,
-	cmdBuilder exe.ICmdBuilder,
-	mode parser.TargetMode,
-	vcsStore vcs.Store,
-	cmdArgs *parser.Arguments,
-	pkgArchives []string,
-	noConfirm bool,
-) error {
-	if len(pkgArchives) == 0 {
-		return nil
-	}
-
-	arguments := cmdArgs.Copy()
-	arguments.ClearTargets()
-	arguments.Op = "U"
-	arguments.DelArg("confirm")
-	arguments.DelArg("noconfirm")
-	arguments.DelArg("c", "clean")
-	arguments.DelArg("i", "install")
-	arguments.DelArg("q", "quiet")
-	arguments.DelArg("y", "refresh")
-	arguments.DelArg("u", "sysupgrade")
-	arguments.DelArg("w", "downloadonly")
-	arguments.DelArg("asdeps", "asdep")
-	arguments.DelArg("asexplicit", "asexp")
-
-	arguments.AddTarget(pkgArchives...)
-
-	if errShow := cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
-		arguments, mode, noConfirm)); errShow != nil {
-		return errShow
-	}
-
-	if errStore := vcsStore.Save(); errStore != nil {
-		fmt.Fprintln(os.Stderr, errStore)
-	}
-
-	return nil
-}
-
-func setInstallReason(ctx context.Context,
-	cmdBuilder exe.ICmdBuilder, mode parser.TargetMode,
-	cmdArgs *parser.Arguments, deps, exps []string,
-) error {
-	if len(deps)+len(exps) == 0 {
-		return nil
-	}
-
-	if errDeps := asdeps(ctx, cmdBuilder, mode, cmdArgs, deps); errDeps != nil {
-		return errDeps
-	}
-
-	return asexp(ctx, cmdBuilder, mode, cmdArgs, exps)
-}

+ 10 - 13
pkg/pgp/keys.go

@@ -4,8 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"context"
 	"context"
 	"errors"
 	"errors"
-	"fmt"
-	"os"
 	"os/exec"
 	"os/exec"
 	"strings"
 	"strings"
 
 
@@ -50,7 +48,7 @@ type GPGCmdBuilder interface {
 
 
 // CheckPgpKeys iterates through the keys listed in the PKGBUILDs and if needed,
 // CheckPgpKeys iterates through the keys listed in the PKGBUILDs and if needed,
 // asks the user whether yay should try to import them.
 // asks the user whether yay should try to import them.
-func CheckPgpKeys(ctx context.Context, pkgbuildDirsByBase map[string]string, srcinfos map[string]*gosrc.Srcinfo,
+func CheckPgpKeys(ctx context.Context, logger *text.Logger, pkgbuildDirsByBase map[string]string, srcinfos map[string]*gosrc.Srcinfo,
 	cmdBuilder GPGCmdBuilder, noConfirm bool,
 	cmdBuilder GPGCmdBuilder, noConfirm bool,
 ) ([]string, error) {
 ) ([]string, error) {
 	// Let's check the keys individually, and then we can offer to import
 	// Let's check the keys individually, and then we can offer to import
@@ -80,24 +78,23 @@ func CheckPgpKeys(ctx context.Context, pkgbuildDirsByBase map[string]string, src
 		return []string{}, nil
 		return []string{}, nil
 	}
 	}
 
 
-	str, err := formatKeysToImport(problematic)
+	str, err := formatKeysToImport(logger, problematic)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	fmt.Println()
-	fmt.Println(str)
+	logger.Println("\n", str)
 
 
-	if text.ContinueTask(os.Stdin, gotext.Get("Import?"), true, noConfirm) {
-		return problematic.toSlice(), importKeys(ctx, cmdBuilder, problematic.toSlice())
+	if logger.ContinueTask(gotext.Get("Import?"), true, noConfirm) {
+		return problematic.toSlice(), importKeys(ctx, logger, cmdBuilder, problematic.toSlice())
 	}
 	}
 
 
 	return problematic.toSlice(), nil
 	return problematic.toSlice(), nil
 }
 }
 
 
 // importKeys tries to import the list of keys specified in its argument.
 // importKeys tries to import the list of keys specified in its argument.
-func importKeys(ctx context.Context, cmdBuilder GPGCmdBuilder, keys []string) error {
-	text.OperationInfoln(gotext.Get("Importing keys with gpg..."))
+func importKeys(ctx context.Context, logger *text.Logger, cmdBuilder GPGCmdBuilder, keys []string) error {
+	logger.OperationInfoln(gotext.Get("Importing keys with gpg..."))
 
 
 	if err := cmdBuilder.Show(cmdBuilder.BuildGPGCmd(ctx, append([]string{"--recv-keys"}, keys...)...)); err != nil {
 	if err := cmdBuilder.Show(cmdBuilder.BuildGPGCmd(ctx, append([]string{"--recv-keys"}, keys...)...)); err != nil {
 		return errors.New(gotext.Get("problem importing keys"))
 		return errors.New(gotext.Get("problem importing keys"))
@@ -108,14 +105,14 @@ func importKeys(ctx context.Context, cmdBuilder GPGCmdBuilder, keys []string) er
 
 
 // formatKeysToImport receives a set of keys and returns a string containing the
 // formatKeysToImport receives a set of keys and returns a string containing the
 // question asking the user wants to import the problematic keys.
 // question asking the user wants to import the problematic keys.
-func formatKeysToImport(keys pgpKeySet) (string, error) {
+func formatKeysToImport(logger *text.Logger, keys pgpKeySet) (string, error) {
 	if len(keys) == 0 {
 	if len(keys) == 0 {
 		return "", errors.New(gotext.Get("no keys to import"))
 		return "", errors.New(gotext.Get("no keys to import"))
 	}
 	}
 
 
 	var buffer bytes.Buffer
 	var buffer bytes.Buffer
 
 
-	buffer.WriteString(text.SprintOperationInfo(gotext.Get("PGP keys need importing:")))
+	buffer.WriteString(logger.SprintOperationInfo(gotext.Get("PGP keys need importing:")))
 
 
 	for key, bases := range keys {
 	for key, bases := range keys {
 		pkglist := ""
 		pkglist := ""
@@ -124,7 +121,7 @@ func formatKeysToImport(keys pgpKeySet) (string, error) {
 		}
 		}
 
 
 		pkglist = strings.TrimRight(pkglist, " ")
 		pkglist = strings.TrimRight(pkglist, " ")
-		buffer.WriteString("\n" + text.SprintWarn(gotext.Get("%s, required by: %s", text.Cyan(key), text.Cyan(pkglist))))
+		buffer.WriteString("\n" + logger.SprintWarn(gotext.Get("%s, required by: %s", text.Cyan(key), text.Cyan(pkglist))))
 	}
 	}
 
 
 	return buffer.String(), nil
 	return buffer.String(), nil

+ 7 - 1
pkg/pgp/keys_test.go

@@ -6,6 +6,7 @@ package pgp
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
+	"io"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"sort"
 	"sort"
@@ -17,8 +18,13 @@ import (
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/require"
 
 
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
+	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
 func makeSrcinfo(pkgbase string, pgpkeys ...string) *gosrc.Srcinfo {
 func makeSrcinfo(pkgbase string, pgpkeys ...string) *gosrc.Srcinfo {
 	srcinfo := gosrc.Srcinfo{}
 	srcinfo := gosrc.Srcinfo{}
 	srcinfo.Pkgbase = pkgbase
 	srcinfo.Pkgbase = pkgbase
@@ -228,7 +234,7 @@ func TestCheckPgpKeys(t *testing.T) {
 				GPGFlags: []string{"--homedir /tmp"},
 				GPGFlags: []string{"--homedir /tmp"},
 				Runner:   mockRunner,
 				Runner:   mockRunner,
 			}
 			}
-			problematic, err := CheckPgpKeys(context.Background(), tt.pkgs, tt.srcinfos, &cmdBuilder, true)
+			problematic, err := CheckPgpKeys(context.Background(), newTestLogger(), tt.pkgs, tt.srcinfos, &cmdBuilder, true)
 
 
 			require.Len(t, mockRunner.ShowCalls, len(tt.wantShow))
 			require.Len(t, mockRunner.ShowCalls, len(tt.wantShow))
 			require.Len(t, mockRunner.CaptureCalls, len(tt.wantCapture))
 			require.Len(t, mockRunner.CaptureCalls, len(tt.wantCapture))

pkg/pgp/testdata/11E521D646982372EB577A1F8F0871F202119294 → pkg/sync/srcinfo/pgp/testdata/11E521D646982372EB577A1F8F0871F202119294


pkg/pgp/testdata/487EACC08557AD082088DABA1EB2638FF56C0C53 → pkg/sync/srcinfo/pgp/testdata/487EACC08557AD082088DABA1EB2638FF56C0C53


pkg/pgp/testdata/647F28654894E3BD457199BE38DBBDC86092693E → pkg/sync/srcinfo/pgp/testdata/647F28654894E3BD457199BE38DBBDC86092693E


pkg/pgp/testdata/A314827C4E4250A204CE6E13284FC34C8E4B1A25 → pkg/sync/srcinfo/pgp/testdata/A314827C4E4250A204CE6E13284FC34C8E4B1A25


pkg/pgp/testdata/ABAF11C65A2970B130ABE3C479BE3E4300411886 → pkg/sync/srcinfo/pgp/testdata/ABAF11C65A2970B130ABE3C479BE3E4300411886


pkg/pgp/testdata/B6C8F98282B944E3B0D5C2530FC3042E345AD05D → pkg/sync/srcinfo/pgp/testdata/B6C8F98282B944E3B0D5C2530FC3042E345AD05D


pkg/pgp/testdata/C52048C0C0748FEE227D47A2702353E0F7E48EDB → pkg/sync/srcinfo/pgp/testdata/C52048C0C0748FEE227D47A2702353E0F7E48EDB


+ 13 - 12
pkg/srcinfo/service.go

@@ -11,28 +11,28 @@ import (
 
 
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
-	"github.com/Jguer/yay/v12/pkg/pgp"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
+	"github.com/Jguer/yay/v12/pkg/sync/srcinfo/pgp"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 	"github.com/Jguer/yay/v12/pkg/vcs"
 )
 )
 
 
-// TODO: add tests
 type Service struct {
 type Service struct {
 	dbExecutor db.Executor
 	dbExecutor db.Executor
 	cfg        *settings.Configuration
 	cfg        *settings.Configuration
-	cmdBuilder exe.ICmdBuilder
+	cmdBuilder pgp.GPGCmdBuilder
 	vcsStore   vcs.Store
 	vcsStore   vcs.Store
+	log        *text.Logger
 
 
 	pkgBuildDirs map[string]string
 	pkgBuildDirs map[string]string
 	srcInfos     map[string]*gosrc.Srcinfo
 	srcInfos     map[string]*gosrc.Srcinfo
 }
 }
 
 
-func NewService(dbExecutor db.Executor, cfg *settings.Configuration,
+func NewService(dbExecutor db.Executor, cfg *settings.Configuration, logger *text.Logger,
 	cmdBuilder exe.ICmdBuilder, vcsStore vcs.Store, pkgBuildDirs map[string]string,
 	cmdBuilder exe.ICmdBuilder, vcsStore vcs.Store, pkgBuildDirs map[string]string,
 ) (*Service, error) {
 ) (*Service, error) {
-	srcinfos, err := ParseSrcinfoFilesByBase(pkgBuildDirs, true)
+	srcinfos, err := ParseSrcinfoFilesByBase(logger, pkgBuildDirs, true)
 	if err != nil {
 	if err != nil {
 		panic(err)
 		panic(err)
 	}
 	}
@@ -43,6 +43,7 @@ func NewService(dbExecutor db.Executor, cfg *settings.Configuration,
 		vcsStore:     vcsStore,
 		vcsStore:     vcsStore,
 		pkgBuildDirs: pkgBuildDirs,
 		pkgBuildDirs: pkgBuildDirs,
 		srcInfos:     srcinfos,
 		srcInfos:     srcinfos,
+		log:          logger,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -68,7 +69,7 @@ nextpkg:
 }
 }
 
 
 func (s *Service) CheckPGPKeys(ctx context.Context) error {
 func (s *Service) CheckPGPKeys(ctx context.Context) error {
-	_, errCPK := pgp.CheckPgpKeys(ctx, s.pkgBuildDirs, s.srcInfos, s.cmdBuilder, settings.NoConfirm)
+	_, errCPK := pgp.CheckPgpKeys(ctx, s.log.Child("pgp"), s.pkgBuildDirs, s.srcInfos, s.cmdBuilder, settings.NoConfirm)
 	return errCPK
 	return errCPK
 }
 }
 
 
@@ -83,15 +84,15 @@ func (s *Service) UpdateVCSStore(ctx context.Context, targets []map[string]*dep.
 		for i := range srcinfo.Packages {
 		for i := range srcinfo.Packages {
 			for j := range targets {
 			for j := range targets {
 				if _, ok := targets[j][srcinfo.Packages[i].Pkgname]; !ok {
 				if _, ok := targets[j][srcinfo.Packages[i].Pkgname]; !ok {
-					text.Debugln("skipping VCS update for", srcinfo.Packages[i].Pkgname, "not in targets")
+					s.log.Debugln("skipping VCS update for", srcinfo.Packages[i].Pkgname, "not in targets")
 					continue
 					continue
 				}
 				}
 				if _, ok := ignore[srcinfo.Packages[i].Pkgname]; ok {
 				if _, ok := ignore[srcinfo.Packages[i].Pkgname]; ok {
-					text.Debugln("skipping VCS update for", srcinfo.Packages[i].Pkgname, "due to install error")
+					s.log.Debugln("skipping VCS update for", srcinfo.Packages[i].Pkgname, "due to install error")
 					continue
 					continue
 				}
 				}
 
 
-				text.Debugln("checking VCS entry for", srcinfo.Packages[i].Pkgname, fmt.Sprintf("source: %v", srcinfo.Source))
+				s.log.Debugln("checking VCS entry for", srcinfo.Packages[i].Pkgname, fmt.Sprintf("source: %v", srcinfo.Source))
 				s.vcsStore.Update(ctx, srcinfo.Packages[i].Pkgname, srcinfo.Source)
 				s.vcsStore.Update(ctx, srcinfo.Packages[i].Pkgname, srcinfo.Source)
 			}
 			}
 		}
 		}
@@ -100,17 +101,17 @@ func (s *Service) UpdateVCSStore(ctx context.Context, targets []map[string]*dep.
 	return nil
 	return nil
 }
 }
 
 
-func ParseSrcinfoFilesByBase(pkgBuildDirs map[string]string, errIsFatal bool) (map[string]*gosrc.Srcinfo, error) {
+func ParseSrcinfoFilesByBase(logger *text.Logger, pkgBuildDirs map[string]string, errIsFatal bool) (map[string]*gosrc.Srcinfo, error) {
 	srcinfos := make(map[string]*gosrc.Srcinfo)
 	srcinfos := make(map[string]*gosrc.Srcinfo)
 
 
 	k := 0
 	k := 0
 	for base, dir := range pkgBuildDirs {
 	for base, dir := range pkgBuildDirs {
-		text.OperationInfoln(gotext.Get("(%d/%d) Parsing SRCINFO: %s", k+1, len(pkgBuildDirs), text.Cyan(base)))
+		logger.OperationInfoln(gotext.Get("(%d/%d) Parsing SRCINFO: %s", k+1, len(pkgBuildDirs), text.Cyan(base)))
 
 
 		pkgbuild, err := gosrc.ParseFile(filepath.Join(dir, ".SRCINFO"))
 		pkgbuild, err := gosrc.ParseFile(filepath.Join(dir, ".SRCINFO"))
 		if err != nil {
 		if err != nil {
 			if !errIsFatal {
 			if !errIsFatal {
-				text.Warnln(gotext.Get("failed to parse %s -- skipping: %s", base, err))
+				logger.Warnln(gotext.Get("failed to parse %s -- skipping: %s", base, err))
 				continue
 				continue
 			}
 			}
 
 

+ 132 - 0
pkg/sync/srcinfo/service_test.go

@@ -0,0 +1,132 @@
+package srcinfo
+
+import (
+	"context"
+	"io"
+	"strings"
+	"testing"
+
+	gosrc "github.com/Morganamilo/go-srcinfo"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/Jguer/yay/v12/pkg/db/mock"
+	"github.com/Jguer/yay/v12/pkg/dep"
+	"github.com/Jguer/yay/v12/pkg/settings"
+	"github.com/Jguer/yay/v12/pkg/settings/exe"
+	"github.com/Jguer/yay/v12/pkg/text"
+	"github.com/Jguer/yay/v12/pkg/vcs"
+)
+
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
+func TestNewService(t *testing.T) {
+	dbExecutor := &mock.DBExecutor{}
+	cfg := &settings.Configuration{}
+	cmdBuilder := &exe.MockBuilder{}
+	vcsStore := &vcs.Mock{}
+	pkgBuildDirs := map[string]string{
+		"jellyfin": "../../../testdata/jfin",
+		"cephbin":  "../../../testdata/cephbin",
+	}
+
+	srv, err := NewService(dbExecutor, cfg, newTestLogger(), cmdBuilder, vcsStore, pkgBuildDirs)
+	assert.NoError(t, err)
+	assert.NotNil(t, srv)
+	assert.Equal(t, dbExecutor, srv.dbExecutor)
+	assert.Equal(t, cfg, srv.cfg)
+	assert.Equal(t, cmdBuilder, srv.cmdBuilder)
+	assert.Equal(t, vcsStore, srv.vcsStore)
+	assert.Equal(t, pkgBuildDirs, srv.pkgBuildDirs)
+	assert.NotNil(t, srv.srcInfos)
+}
+
+func TestService_IncompatiblePkgs(t *testing.T) {
+	srv := &Service{
+		dbExecutor: &mock.DBExecutor{AlpmArchitecturesFn: func() ([]string, error) {
+			return []string{"x86_64"}, nil
+		}},
+		srcInfos: map[string]*gosrc.Srcinfo{
+			"pkg1": {
+				Package: gosrc.Package{
+					Arch: []string{"x86_64", "any"},
+				},
+			},
+			"pkg2": {
+				Package: gosrc.Package{
+					Arch: []string{"any"},
+				},
+			},
+			"pkg3": {
+				Package: gosrc.Package{
+					Arch: []string{"armv7h"},
+				},
+			},
+			"pkg4": {
+				Package: gosrc.Package{
+					Arch: []string{"i683", "x86_64"},
+				},
+			},
+		},
+	}
+
+	incompatible, err := srv.IncompatiblePkgs(context.Background())
+	assert.NoError(t, err)
+	assert.ElementsMatch(t, []string{"pkg3"}, incompatible)
+}
+
+func TestService_CheckPGPKeys(t *testing.T) {
+	srv := &Service{
+		log: newTestLogger(),
+		pkgBuildDirs: map[string]string{
+			"pkg1": "/path/to/pkg1",
+			"pkg2": "/path/to/pkg2",
+		},
+		srcInfos: map[string]*gosrc.Srcinfo{
+			"pkg1": {
+				Packages: []gosrc.Package{
+					{Pkgname: "pkg1"},
+				},
+			},
+			"pkg2": {
+				Packages: []gosrc.Package{
+					{Pkgname: "pkg2"},
+				},
+			},
+		},
+	}
+
+	err := srv.CheckPGPKeys(context.Background())
+	assert.NoError(t, err)
+}
+
+func TestService_UpdateVCSStore(t *testing.T) {
+	srv := &Service{
+		srcInfos: map[string]*gosrc.Srcinfo{
+			"pkg1": {
+				Packages: []gosrc.Package{
+					{Pkgname: "pkg1"},
+				},
+			},
+			"pkg2": {
+				Packages: []gosrc.Package{
+					{Pkgname: "pkg2"},
+				},
+			},
+		},
+		vcsStore: &vcs.Mock{},
+	}
+
+	targets := []map[string]*dep.InstallInfo{
+		{
+			"pkg1": {},
+			"pkg2": {},
+		},
+	}
+	ignore := map[string]error{}
+
+	err := srv.UpdateVCSStore(context.Background(), targets, ignore)
+	assert.NoError(t, err)
+}

+ 138 - 0
pkg/sync/sync.go

@@ -0,0 +1,138 @@
+package sync
+
+import (
+	"context"
+
+	"github.com/Jguer/yay/v12/pkg/completion"
+	"github.com/Jguer/yay/v12/pkg/db"
+	"github.com/Jguer/yay/v12/pkg/dep"
+	"github.com/Jguer/yay/v12/pkg/multierror"
+	"github.com/Jguer/yay/v12/pkg/runtime"
+	"github.com/Jguer/yay/v12/pkg/settings"
+	"github.com/Jguer/yay/v12/pkg/settings/parser"
+	"github.com/Jguer/yay/v12/pkg/sync/build"
+	"github.com/Jguer/yay/v12/pkg/sync/srcinfo"
+	"github.com/Jguer/yay/v12/pkg/sync/workdir"
+	"github.com/Jguer/yay/v12/pkg/text"
+
+	"github.com/leonelquinteros/gotext"
+)
+
+type OperationService struct {
+	ctx        context.Context
+	cfg        *settings.Configuration
+	dbExecutor db.Executor
+	logger     *text.Logger
+}
+
+func NewOperationService(ctx context.Context,
+	dbExecutor db.Executor,
+	run *runtime.Runtime,
+) *OperationService {
+	return &OperationService{
+		ctx:        ctx,
+		cfg:        run.Cfg,
+		dbExecutor: dbExecutor,
+		logger:     run.Logger.Child("operation"),
+	}
+}
+
+func (o *OperationService) Run(ctx context.Context, run *runtime.Runtime,
+	cmdArgs *parser.Arguments,
+	targets []map[string]*dep.InstallInfo, excluded []string,
+) error {
+	if len(targets) == 0 {
+		o.logger.Println("", gotext.Get("there is nothing to do"))
+		return nil
+	}
+	preparer := workdir.NewPreparer(o.dbExecutor, run.CmdBuilder, o.cfg, o.logger.Child("workdir"))
+	installer := build.NewInstaller(o.dbExecutor, run.CmdBuilder,
+		run.VCSStore, o.cfg.Mode, o.cfg.ReBuild,
+		cmdArgs.ExistsArg("w", "downloadonly"), run.Logger.Child("installer"))
+
+	pkgBuildDirs, errInstall := preparer.Run(ctx, run, targets)
+	if errInstall != nil {
+		return errInstall
+	}
+
+	if cleanFunc := preparer.ShouldCleanMakeDeps(run, cmdArgs); cleanFunc != nil {
+		installer.AddPostInstallHook(cleanFunc)
+	}
+
+	if cleanAURDirsFunc := preparer.ShouldCleanAURDirs(run, pkgBuildDirs); cleanAURDirsFunc != nil {
+		installer.AddPostInstallHook(cleanAURDirsFunc)
+	}
+
+	go func() {
+		errComp := completion.Update(ctx, run.HTTPClient, o.dbExecutor,
+			o.cfg.AURURL, o.cfg.CompletionPath, o.cfg.CompletionInterval, false)
+		if errComp != nil {
+			o.logger.Warnln(errComp)
+		}
+	}()
+
+	srcInfo, errInstall := srcinfo.NewService(o.dbExecutor, o.cfg,
+		o.logger.Child("srcinfo"), run.CmdBuilder, run.VCSStore, pkgBuildDirs)
+	if errInstall != nil {
+		return errInstall
+	}
+
+	incompatible, errInstall := srcInfo.IncompatiblePkgs(ctx)
+	if errInstall != nil {
+		return errInstall
+	}
+
+	if errIncompatible := confirmIncompatible(o.logger, incompatible); errIncompatible != nil {
+		return errIncompatible
+	}
+
+	if errPGP := srcInfo.CheckPGPKeys(ctx); errPGP != nil {
+		return errPGP
+	}
+
+	if errInstall := installer.Install(ctx, cmdArgs, targets, pkgBuildDirs,
+		excluded, o.manualConfirmRequired(cmdArgs)); errInstall != nil {
+		return errInstall
+	}
+
+	var multiErr multierror.MultiError
+
+	failedAndIgnored, err := installer.CompileFailedAndIgnored()
+	if err != nil {
+		multiErr.Add(err)
+	}
+
+	if !cmdArgs.ExistsArg("w", "downloadonly") {
+		if err := srcInfo.UpdateVCSStore(ctx, targets, failedAndIgnored); err != nil {
+			o.logger.Warnln(err)
+		}
+	}
+
+	if err := installer.RunPostInstallHooks(ctx); err != nil {
+		multiErr.Add(err)
+	}
+
+	return multiErr.Return()
+}
+
+func (o *OperationService) manualConfirmRequired(cmdArgs *parser.Arguments) bool {
+	return (!cmdArgs.ExistsArg("u", "sysupgrade") && cmdArgs.Op != "Y") || o.cfg.DoubleConfirm
+}
+
+func confirmIncompatible(logger *text.Logger, incompatible []string) error {
+	if len(incompatible) > 0 {
+		logger.Warnln(gotext.Get("The following packages are not compatible with your architecture:"))
+
+		for _, pkg := range incompatible {
+			logger.Print("  " + text.Cyan(pkg))
+		}
+
+		logger.Println()
+
+		if !logger.ContinueTask(gotext.Get("Try to build them anyway?"), true, settings.NoConfirm) {
+			return &settings.ErrUserAbort{}
+		}
+	}
+
+	return nil
+}

+ 1 - 1
aur_source.go

@@ -1,4 +1,4 @@
-package main
+package workdir
 
 
 import (
 import (
 	"context"
 	"context"

+ 1 - 1
aur_source_test.go

@@ -1,7 +1,7 @@
 //go:build !integration
 //go:build !integration
 // +build !integration
 // +build !integration
 
 
-package main
+package workdir
 
 
 import (
 import (
 	"context"
 	"context"

+ 62 - 0
pkg/sync/workdir/clean.go

@@ -0,0 +1,62 @@
+package workdir
+
+import (
+	"context"
+
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v12/pkg/runtime"
+	"github.com/Jguer/yay/v12/pkg/settings"
+	"github.com/Jguer/yay/v12/pkg/settings/exe"
+	"github.com/Jguer/yay/v12/pkg/settings/parser"
+	"github.com/Jguer/yay/v12/pkg/text"
+)
+
+func removeMake(ctx context.Context, config *settings.Configuration,
+	cmdBuilder exe.ICmdBuilder, makeDeps []string, cmdArgs *parser.Arguments,
+) error {
+	removeArguments := cmdArgs.CopyGlobal()
+
+	err := removeArguments.AddArg("R", "s", "u")
+	if err != nil {
+		return err
+	}
+
+	for _, pkg := range makeDeps {
+		removeArguments.AddTarget(pkg)
+	}
+
+	oldValue := settings.NoConfirm
+	settings.NoConfirm = true
+	err = cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
+		removeArguments, config.Mode, settings.NoConfirm))
+	settings.NoConfirm = oldValue
+
+	return err
+}
+
+func cleanAfter(ctx context.Context, run *runtime.Runtime,
+	cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string,
+) {
+	run.Logger.Println(gotext.Get("removing untracked AUR files from cache..."))
+
+	i := 0
+	for _, dir := range pkgbuildDirs {
+		run.Logger.OperationInfoln(gotext.Get("Cleaning (%d/%d): %s", i+1, len(pkgbuildDirs), text.Cyan(dir)))
+
+		_, stderr, err := cmdBuilder.Capture(
+			cmdBuilder.BuildGitCmd(
+				ctx, dir, "reset", "--hard", "HEAD"))
+		if err != nil {
+			run.Logger.Errorln(gotext.Get("error resetting %s: %s", dir, stderr))
+		}
+
+		if err := run.CmdBuilder.Show(
+			run.CmdBuilder.BuildGitCmd(
+				ctx, dir, "clean", "-fx", "--exclude", "*.pkg.*")); err != nil {
+			run.Logger.Errorln(err)
+		}
+
+		i++
+	}
+}

+ 39 - 0
pkg/sync/workdir/merge.go

@@ -0,0 +1,39 @@
+package workdir
+
+import (
+	"context"
+	"errors"
+
+	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v12/pkg/settings/exe"
+)
+
+func gitMerge(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) error {
+	_, stderr, err := cmdBuilder.Capture(
+		cmdBuilder.BuildGitCmd(ctx,
+			dir, "reset", "--hard", "HEAD"))
+	if err != nil {
+		return errors.New(gotext.Get("error resetting %s: %s", dir, stderr))
+	}
+
+	_, stderr, err = cmdBuilder.Capture(
+		cmdBuilder.BuildGitCmd(ctx,
+			dir, "merge", "--no-edit", "--ff"))
+	if err != nil {
+		return errors.New(gotext.Get("error merging %s: %s", dir, stderr))
+	}
+
+	return nil
+}
+
+func mergePkgbuilds(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string) error {
+	for _, dir := range pkgbuildDirs {
+		err := gitMerge(ctx, cmdBuilder, dir)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 29 - 23
preparer.go

@@ -1,4 +1,4 @@
-package main
+package workdir
 
 
 import (
 import (
 	"context"
 	"context"
@@ -12,9 +12,11 @@ import (
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/download"
 	"github.com/Jguer/yay/v12/pkg/download"
 	"github.com/Jguer/yay/v12/pkg/menus"
 	"github.com/Jguer/yay/v12/pkg/menus"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
+	"github.com/Jguer/yay/v12/pkg/sync/build"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 
 
 	gosrc "github.com/Morganamilo/go-srcinfo"
 	gosrc "github.com/Morganamilo/go-srcinfo"
@@ -29,7 +31,7 @@ const (
 	PreDownloadSourcesHook HookType = "pre-download-sources"
 	PreDownloadSourcesHook HookType = "pre-download-sources"
 )
 )
 
 
-type HookFn func(ctx context.Context, config *settings.Configuration, w io.Writer,
+type HookFn func(ctx context.Context, run *runtime.Runtime, w io.Writer,
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 	pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
 ) error
 ) error
 
 
@@ -45,12 +47,13 @@ type Preparer struct {
 	cfg             *settings.Configuration
 	cfg             *settings.Configuration
 	hooks           []Hook
 	hooks           []Hook
 	downloadSources bool
 	downloadSources bool
+	log             *text.Logger
 
 
 	makeDeps []string
 	makeDeps []string
 }
 }
 
 
 func NewPreparerWithoutHooks(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
 func NewPreparerWithoutHooks(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
-	cfg *settings.Configuration, downloadSources bool,
+	cfg *settings.Configuration, logger *text.Logger, downloadSources bool,
 ) *Preparer {
 ) *Preparer {
 	return &Preparer{
 	return &Preparer{
 		dbExecutor:      dbExecutor,
 		dbExecutor:      dbExecutor,
@@ -58,13 +61,14 @@ func NewPreparerWithoutHooks(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
 		cfg:             cfg,
 		cfg:             cfg,
 		hooks:           []Hook{},
 		hooks:           []Hook{},
 		downloadSources: downloadSources,
 		downloadSources: downloadSources,
+		log:             logger,
 	}
 	}
 }
 }
 
 
 func NewPreparer(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
 func NewPreparer(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
-	cfg *settings.Configuration,
+	cfg *settings.Configuration, logger *text.Logger,
 ) *Preparer {
 ) *Preparer {
-	preper := NewPreparerWithoutHooks(dbExecutor, cmdBuilder, cfg, true)
+	preper := NewPreparerWithoutHooks(dbExecutor, cmdBuilder, cfg, logger, true)
 
 
 	if cfg.CleanMenu {
 	if cfg.CleanMenu {
 		preper.hooks = append(preper.hooks, Hook{
 		preper.hooks = append(preper.hooks, Hook{
@@ -93,20 +97,20 @@ func NewPreparer(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
 	return preper
 	return preper
 }
 }
 
 
-func (preper *Preparer) ShouldCleanAURDirs(pkgBuildDirs map[string]string) PostInstallHookFunc {
+func (preper *Preparer) ShouldCleanAURDirs(run *runtime.Runtime, pkgBuildDirs map[string]string) build.PostInstallHookFunc {
 	if !preper.cfg.CleanAfter || len(pkgBuildDirs) == 0 {
 	if !preper.cfg.CleanAfter || len(pkgBuildDirs) == 0 {
 		return nil
 		return nil
 	}
 	}
 
 
-	text.Debugln("added post install hook to clean up AUR dirs", pkgBuildDirs)
+	preper.log.Debugln("added post install hook to clean up AUR dirs", pkgBuildDirs)
 
 
 	return func(ctx context.Context) error {
 	return func(ctx context.Context) error {
-		cleanAfter(ctx, preper.cfg, preper.cfg.Runtime.CmdBuilder, pkgBuildDirs)
+		cleanAfter(ctx, run, run.CmdBuilder, pkgBuildDirs)
 		return nil
 		return nil
 	}
 	}
 }
 }
 
 
-func (preper *Preparer) ShouldCleanMakeDeps(cmdArgs *parser.Arguments) PostInstallHookFunc {
+func (preper *Preparer) ShouldCleanMakeDeps(run *runtime.Runtime, cmdArgs *parser.Arguments) build.PostInstallHookFunc {
 	if len(preper.makeDeps) == 0 {
 	if len(preper.makeDeps) == 0 {
 		return nil
 		return nil
 	}
 	}
@@ -118,25 +122,25 @@ func (preper *Preparer) ShouldCleanMakeDeps(cmdArgs *parser.Arguments) PostInsta
 		return nil
 		return nil
 	default:
 	default:
 		isYesDefault := preper.cfg.RemoveMake == "askyes"
 		isYesDefault := preper.cfg.RemoveMake == "askyes"
-		if !text.ContinueTask(os.Stdin, gotext.Get("Remove make dependencies after install?"),
+		if !preper.log.ContinueTask(gotext.Get("Remove make dependencies after install?"),
 			isYesDefault, settings.NoConfirm) {
 			isYesDefault, settings.NoConfirm) {
 			return nil
 			return nil
 		}
 		}
 	}
 	}
 
 
-	text.Debugln("added post install hook to clean up AUR makedeps", preper.makeDeps)
+	preper.log.Debugln("added post install hook to clean up AUR makedeps", preper.makeDeps)
 
 
 	return func(ctx context.Context) error {
 	return func(ctx context.Context) error {
-		return removeMake(ctx, preper.cfg, preper.cfg.Runtime.CmdBuilder, preper.makeDeps, cmdArgs)
+		return removeMake(ctx, preper.cfg, run.CmdBuilder, preper.makeDeps, cmdArgs)
 	}
 	}
 }
 }
 
 
-func (preper *Preparer) Run(ctx context.Context,
-	w io.Writer, targets []map[string]*dep.InstallInfo,
+func (preper *Preparer) Run(ctx context.Context, run *runtime.Runtime,
+	targets []map[string]*dep.InstallInfo,
 ) (pkgbuildDirsByBase map[string]string, err error) {
 ) (pkgbuildDirsByBase map[string]string, err error) {
-	preper.Present(w, targets)
+	preper.Present(targets)
 
 
-	pkgBuildDirs, err := preper.PrepareWorkspace(ctx, targets)
+	pkgBuildDirs, err := preper.PrepareWorkspace(ctx, run, targets)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -144,7 +148,7 @@ func (preper *Preparer) Run(ctx context.Context,
 	return pkgBuildDirs, nil
 	return pkgBuildDirs, nil
 }
 }
 
 
-func (preper *Preparer) Present(w io.Writer, targets []map[string]*dep.InstallInfo) {
+func (preper *Preparer) Present(targets []map[string]*dep.InstallInfo) {
 	pkgsBySourceAndReason := map[string]map[string][]string{}
 	pkgsBySourceAndReason := map[string]map[string][]string{}
 
 
 	for _, layer := range targets {
 	for _, layer := range targets {
@@ -173,7 +177,7 @@ func (preper *Preparer) Present(w io.Writer, targets []map[string]*dep.InstallIn
 
 
 	for source, pkgsByReason := range pkgsBySourceAndReason {
 	for source, pkgsByReason := range pkgsBySourceAndReason {
 		for reason, pkgs := range pkgsByReason {
 		for reason, pkgs := range pkgsByReason {
-			fmt.Fprintf(w, text.Bold("%s %s (%d):")+" %s\n",
+			preper.log.Printf(text.Bold("%s %s (%d):")+" %s\n",
 				source,
 				source,
 				reason,
 				reason,
 				len(pkgs),
 				len(pkgs),
@@ -182,7 +186,9 @@ func (preper *Preparer) Present(w io.Writer, targets []map[string]*dep.InstallIn
 	}
 	}
 }
 }
 
 
-func (preper *Preparer) PrepareWorkspace(ctx context.Context, targets []map[string]*dep.InstallInfo) (map[string]string, error) {
+func (preper *Preparer) PrepareWorkspace(ctx context.Context,
+	run *runtime.Runtime, targets []map[string]*dep.InstallInfo,
+) (map[string]string, error) {
 	aurBasesToClone := mapset.NewThreadUnsafeSet[string]()
 	aurBasesToClone := mapset.NewThreadUnsafeSet[string]()
 	pkgBuildDirsByBase := make(map[string]string, len(targets))
 	pkgBuildDirsByBase := make(map[string]string, len(targets))
 
 
@@ -203,7 +209,7 @@ func (preper *Preparer) PrepareWorkspace(ctx context.Context, targets []map[stri
 	}
 	}
 
 
 	if _, errA := download.AURPKGBUILDRepos(ctx,
 	if _, errA := download.AURPKGBUILDRepos(ctx,
-		preper.cmdBuilder, aurBasesToClone.ToSlice(),
+		preper.cmdBuilder, preper.log.Child("download"), aurBasesToClone.ToSlice(),
 		preper.cfg.AURURL, preper.cfg.BuildDir, false); errA != nil {
 		preper.cfg.AURURL, preper.cfg.BuildDir, false); errA != nil {
 		return nil, errA
 		return nil, errA
 	}
 	}
@@ -220,7 +226,7 @@ func (preper *Preparer) PrepareWorkspace(ctx context.Context, targets []map[stri
 	remoteNamesCache := mapset.NewThreadUnsafeSet(remoteNames...)
 	remoteNamesCache := mapset.NewThreadUnsafeSet(remoteNames...)
 	for _, hookFn := range preper.hooks {
 	for _, hookFn := range preper.hooks {
 		if hookFn.Type == PreDownloadSourcesHook {
 		if hookFn.Type == PreDownloadSourcesHook {
-			if err := hookFn.Hookfn(ctx, preper.cfg, os.Stdout, pkgBuildDirsByBase, remoteNamesCache); err != nil {
+			if err := hookFn.Hookfn(ctx, run, os.Stdout, pkgBuildDirsByBase, remoteNamesCache); err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 		}
 		}
@@ -228,7 +234,7 @@ func (preper *Preparer) PrepareWorkspace(ctx context.Context, targets []map[stri
 
 
 	if errP := downloadPKGBUILDSourceFanout(ctx, preper.cmdBuilder,
 	if errP := downloadPKGBUILDSourceFanout(ctx, preper.cmdBuilder,
 		pkgBuildDirsByBase, false, preper.cfg.MaxConcurrentDownloads); errP != nil {
 		pkgBuildDirsByBase, false, preper.cfg.MaxConcurrentDownloads); errP != nil {
-		text.Errorln(errP)
+		preper.log.Errorln(errP)
 	}
 	}
 
 
 	return pkgBuildDirsByBase, nil
 	return pkgBuildDirsByBase, nil
@@ -242,7 +248,7 @@ func (preper *Preparer) needToCloneAURBase(installInfo *dep.InstallInfo, pkgbuil
 	srcinfoFile := filepath.Join(pkgbuildDir, ".SRCINFO")
 	srcinfoFile := filepath.Join(pkgbuildDir, ".SRCINFO")
 	if pkgbuild, err := gosrc.ParseFile(srcinfoFile); err == nil {
 	if pkgbuild, err := gosrc.ParseFile(srcinfoFile); err == nil {
 		if db.VerCmp(pkgbuild.Version(), installInfo.Version) >= 0 {
 		if db.VerCmp(pkgbuild.Version(), installInfo.Version) >= 0 {
-			text.OperationInfoln(
+			preper.log.OperationInfoln(
 				gotext.Get("PKGBUILD up to date, skipping download: %s",
 				gotext.Get("PKGBUILD up to date, skipping download: %s",
 					text.Cyan(*installInfo.AURBase)))
 					text.Cyan(*installInfo.AURBase)))
 			return false
 			return false

+ 9 - 2
preparer_test.go

@@ -1,16 +1,23 @@
 //go:build !integration
 //go:build !integration
 // +build !integration
 // +build !integration
 
 
-package main
+package workdir
 
 
 import (
 import (
+	"io"
+	"strings"
 	"testing"
 	"testing"
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 
 
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
+	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
 // Test order of pre-download-sources hooks
 // Test order of pre-download-sources hooks
 func TestPreDownloadSourcesHooks(t *testing.T) {
 func TestPreDownloadSourcesHooks(t *testing.T) {
 	testCases := []struct {
 	testCases := []struct {
@@ -49,7 +56,7 @@ func TestPreDownloadSourcesHooks(t *testing.T) {
 
 
 	for _, tc := range testCases {
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
-			preper := NewPreparer(nil, nil, tc.cfg)
+			preper := NewPreparer(nil, nil, tc.cfg, newTestLogger())
 
 
 			assert.Len(t, preper.hooks, len(tc.wantHook))
 			assert.Len(t, preper.hooks, len(tc.wantHook))
 
 

+ 51 - 5
pkg/text/input.go

@@ -3,14 +3,18 @@ package text
 import (
 import (
 	"bufio"
 	"bufio"
 	"fmt"
 	"fmt"
-	"io"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/leonelquinteros/gotext"
 )
 )
 
 
 func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) {
 func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) {
-	Info()
+	l.Info()
 
 
 	if defaultValue != "" || noConfirm {
 	if defaultValue != "" || noConfirm {
-		fmt.Println(defaultValue)
+		l.Println(defaultValue)
 		return defaultValue, nil
 		return defaultValue, nil
 	}
 	}
 
 
@@ -28,6 +32,48 @@ func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) {
 	return string(buf), nil
 	return string(buf), nil
 }
 }
 
 
-func GetInput(r io.Reader, defaultValue string, noConfirm bool) (string, error) {
-	return GlobalLogger.GetInput(defaultValue, noConfirm)
+// ContinueTask prompts if user wants to continue task.
+// If NoConfirm is set the action will continue without user input.
+func (l *Logger) ContinueTask(s string, preset, noConfirm bool) bool {
+	if noConfirm {
+		return preset
+	}
+
+	var (
+		response string
+		postFix  string
+		n        string
+		y        string
+		yes      = gotext.Get("yes")
+		no       = gotext.Get("no")
+	)
+
+	// Only use localized "y" and "n" if they are latin characters.
+	if nRune, _ := utf8.DecodeRuneInString(no); unicode.Is(unicode.Latin, nRune) {
+		n = string(nRune)
+	} else {
+		n = nDefault
+	}
+
+	if yRune, _ := utf8.DecodeRuneInString(yes); unicode.Is(unicode.Latin, yRune) {
+		y = string(yRune)
+	} else {
+		y = yDefault
+	}
+
+	if preset { // If default behavior is true, use y as default.
+		postFix = fmt.Sprintf(" [%s/%s] ", strings.ToUpper(y), n)
+	} else { // If default behavior is anything else, use n as default.
+		postFix = fmt.Sprintf(" [%s/%s] ", y, strings.ToUpper(n))
+	}
+
+	l.OperationInfo(Bold(s), Bold(postFix))
+
+	if _, err := fmt.Fscanln(l.r, &response); err != nil {
+		return preset
+	}
+
+	return strings.EqualFold(response, yes) ||
+		strings.EqualFold(response, y) ||
+		(!strings.EqualFold(yDefault, n) && strings.EqualFold(response, yDefault))
 }
 }

+ 0 - 139
pkg/text/print.go

@@ -1,139 +0,0 @@
-package text
-
-import (
-	"fmt"
-	"os"
-	"strconv"
-	"strings"
-	"syscall"
-	"unicode"
-
-	"github.com/leonelquinteros/gotext"
-	"golang.org/x/sys/unix"
-)
-
-const (
-	arrow      = "==>"
-	smallArrow = " ->"
-	opSymbol   = "::"
-)
-
-var (
-	cachedColumnCount = -1
-	GlobalLogger      = NewLogger(os.Stdout, os.Stderr, os.Stdin, false, "global")
-)
-
-func Debugln(a ...interface{}) {
-	GlobalLogger.Debugln(a...)
-}
-
-func OperationInfoln(a ...interface{}) {
-	GlobalLogger.OperationInfoln(a...)
-}
-
-func OperationInfo(a ...interface{}) {
-	GlobalLogger.OperationInfo(a...)
-}
-
-func SprintOperationInfo(a ...interface{}) string {
-	return GlobalLogger.SprintOperationInfo(a...)
-}
-
-func Info(a ...interface{}) {
-	GlobalLogger.Info(a...)
-}
-
-func Infoln(a ...interface{}) {
-	GlobalLogger.Infoln(a...)
-}
-
-func SprintWarn(a ...interface{}) string {
-	return GlobalLogger.SprintWarn(a...)
-}
-
-func Warn(a ...interface{}) {
-	GlobalLogger.Warn(a...)
-}
-
-func Warnln(a ...interface{}) {
-	GlobalLogger.Warnln(a...)
-}
-
-func SprintError(a ...interface{}) string {
-	return GlobalLogger.SprintError(a...)
-}
-
-func Error(a ...interface{}) {
-	GlobalLogger.Error(a...)
-}
-
-func Errorln(a ...interface{}) {
-	GlobalLogger.Errorln(a...)
-}
-
-func getColumnCount() int {
-	if cachedColumnCount > 0 {
-		return cachedColumnCount
-	}
-
-	if count, err := strconv.Atoi(os.Getenv("COLUMNS")); err == nil {
-		cachedColumnCount = count
-		return cachedColumnCount
-	}
-
-	if ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ); err == nil {
-		cachedColumnCount = int(ws.Col)
-		return cachedColumnCount
-	}
-
-	return 80
-}
-
-func PrintInfoValue(key string, values ...string) {
-	const (
-		keyLength  = 32
-		delimCount = 2
-	)
-
-	specialWordsCount := 0
-
-	for _, runeValue := range key {
-		// CJK handling: the character 'ー' is Katakana
-		// but if use unicode.Katakana, it will return false
-		if unicode.IsOneOf([]*unicode.RangeTable{
-			unicode.Han,
-			unicode.Hiragana,
-			unicode.Katakana,
-			unicode.Hangul,
-		}, runeValue) || runeValue == 'ー' {
-			specialWordsCount++
-		}
-	}
-
-	keyTextCount := specialWordsCount - keyLength + delimCount
-	str := fmt.Sprintf(Bold("%-*s: "), keyTextCount, key)
-
-	if len(values) == 0 || (len(values) == 1 && values[0] == "") {
-		fmt.Fprintf(os.Stdout, "%s%s\n", str, gotext.Get("None"))
-		return
-	}
-
-	maxCols := getColumnCount()
-	cols := keyLength + len(values[0])
-	str += values[0]
-
-	for _, value := range values[1:] {
-		if maxCols > keyLength && cols+len(value)+delimCount >= maxCols {
-			cols = keyLength
-			str += "\n" + strings.Repeat(" ", keyLength)
-		} else if cols != keyLength {
-			str += strings.Repeat(" ", delimCount)
-			cols += delimCount
-		}
-
-		str += value
-		cols += len(value)
-	}
-
-	fmt.Println(str)
-}

+ 6 - 0
pkg/text/service.go

@@ -5,6 +5,12 @@ import (
 	"io"
 	"io"
 )
 )
 
 
+const (
+	arrow      = "==>"
+	smallArrow = " ->"
+	opSymbol   = "::"
+)
+
 type Logger struct {
 type Logger struct {
 	name   string
 	name   string
 	Debug  bool
 	Debug  bool

+ 0 - 51
pkg/text/text.go

@@ -1,13 +1,8 @@
 package text
 package text
 
 
 import (
 import (
-	"fmt"
-	"io"
 	"strings"
 	"strings"
 	"unicode"
 	"unicode"
-	"unicode/utf8"
-
-	"github.com/leonelquinteros/gotext"
 )
 )
 
 
 const (
 const (
@@ -52,49 +47,3 @@ func LessRunes(iRunes, jRunes []rune) bool {
 
 
 	return len(iRunes) < len(jRunes)
 	return len(iRunes) < len(jRunes)
 }
 }
-
-// ContinueTask prompts if user wants to continue task.
-// If NoConfirm is set the action will continue without user input.
-func ContinueTask(input io.Reader, s string, preset, noConfirm bool) bool {
-	if noConfirm {
-		return preset
-	}
-
-	var (
-		response string
-		postFix  string
-		n        string
-		y        string
-		yes      = gotext.Get("yes")
-		no       = gotext.Get("no")
-	)
-
-	// Only use localized "y" and "n" if they are latin characters.
-	if nRune, _ := utf8.DecodeRuneInString(no); unicode.Is(unicode.Latin, nRune) {
-		n = string(nRune)
-	} else {
-		n = nDefault
-	}
-
-	if yRune, _ := utf8.DecodeRuneInString(yes); unicode.Is(unicode.Latin, yRune) {
-		y = string(yRune)
-	} else {
-		y = yDefault
-	}
-
-	if preset { // If default behavior is true, use y as default.
-		postFix = fmt.Sprintf(" [%s/%s] ", strings.ToUpper(y), n)
-	} else { // If default behavior is anything else, use n as default.
-		postFix = fmt.Sprintf(" [%s/%s] ", y, strings.ToUpper(n))
-	}
-
-	OperationInfo(Bold(s), Bold(postFix))
-
-	if _, err := fmt.Fscanln(input, &response); err != nil {
-		return preset
-	}
-
-	return strings.EqualFold(response, yes) ||
-		strings.EqualFold(response, y) ||
-		(!strings.EqualFold(yDefault, n) && strings.EqualFold(response, yDefault))
-}

+ 7 - 3
pkg/text/text_test.go

@@ -4,6 +4,7 @@
 package text
 package text
 
 
 import (
 import (
+	"io"
 	"os"
 	"os"
 	"path"
 	"path"
 	"strings"
 	"strings"
@@ -74,7 +75,8 @@ func TestContinueTask(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			// create io.Reader with value of input
 			// create io.Reader with value of input
 			in := strings.NewReader(tt.args.input)
 			in := strings.NewReader(tt.args.input)
-			got := ContinueTask(in, tt.args.s, tt.args.preset, tt.args.noConfirm)
+			logger := NewLogger(io.Discard, io.Discard, in, false, "test")
+			got := logger.ContinueTask(tt.args.s, tt.args.preset, tt.args.noConfirm)
 			require.Equal(t, tt.want, got)
 			require.Equal(t, tt.want, got)
 		})
 		})
 	}
 	}
@@ -120,7 +122,8 @@ msgstr "да"
 	for _, tt := range tests {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			in := strings.NewReader(tt.args.input)
 			in := strings.NewReader(tt.args.input)
-			got := ContinueTask(in, tt.args.s, tt.args.preset, tt.args.noConfirm)
+			logger := NewLogger(io.Discard, io.Discard, in, false, "test")
+			got := logger.ContinueTask(tt.args.s, tt.args.preset, tt.args.noConfirm)
 			require.Equal(t, tt.want, got)
 			require.Equal(t, tt.want, got)
 		})
 		})
 	}
 	}
@@ -168,7 +171,8 @@ msgstr "ja"
 	for _, tt := range tests {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			in := strings.NewReader(tt.args.input)
 			in := strings.NewReader(tt.args.input)
-			got := ContinueTask(in, tt.args.s, tt.args.preset, tt.args.noConfirm)
+			logger := NewLogger(io.Discard, io.Discard, in, false, "test")
+			got := logger.ContinueTask(tt.args.s, tt.args.preset, tt.args.noConfirm)
 			require.Equal(t, tt.want, got)
 			require.Equal(t, tt.want, got)
 		})
 		})
 	}
 	}

+ 8 - 6
pkg/vcs/vcs_test.go

@@ -7,7 +7,6 @@ import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
-	"fmt"
 	"io"
 	"io"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
@@ -24,6 +23,10 @@ import (
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
+func newTestLogger() *text.Logger {
+	return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test")
+}
+
 func TestParsing(t *testing.T) {
 func TestParsing(t *testing.T) {
 	t.Parallel()
 	t.Parallel()
 	type source struct {
 	type source struct {
@@ -232,7 +235,7 @@ func TestInfoStoreToUpgrade(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
 			t.Parallel()
 			v := &InfoStore{
 			v := &InfoStore{
-				logger:     text.GlobalLogger,
+				logger:     newTestLogger(),
 				CmdBuilder: tt.fields.CmdBuilder,
 				CmdBuilder: tt.fields.CmdBuilder,
 				OriginsByPackage: map[string]OriginInfoByURL{
 				OriginsByPackage: map[string]OriginInfoByURL{
 					"yay": tt.args.infos,
 					"yay": tt.args.infos,
@@ -365,7 +368,7 @@ func TestInfoStore_NeedsUpdate(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
 			t.Parallel()
 			v := &InfoStore{
 			v := &InfoStore{
-				logger:     text.GlobalLogger,
+				logger:     newTestLogger(),
 				CmdBuilder: tt.fields.CmdBuilder,
 				CmdBuilder: tt.fields.CmdBuilder,
 			}
 			}
 			got := v.needsUpdate(context.Background(), tt.args.infos)
 			got := v.needsUpdate(context.Background(), tt.args.infos)
@@ -415,7 +418,7 @@ func TestInfoStore_Update(t *testing.T) {
 			t.Parallel()
 			t.Parallel()
 			v := &InfoStore{
 			v := &InfoStore{
 				OriginsByPackage: tt.fields.OriginsByPackage,
 				OriginsByPackage: tt.fields.OriginsByPackage,
-				logger:           text.GlobalLogger,
+				logger:           newTestLogger(),
 				FilePath:         filePath,
 				FilePath:         filePath,
 				CmdBuilder:       tt.fields.CmdBuilder,
 				CmdBuilder:       tt.fields.CmdBuilder,
 			}
 			}
@@ -429,7 +432,6 @@ func TestInfoStore_Update(t *testing.T) {
 			cupaloy.SnapshotT(t, marshalledinfo)
 			cupaloy.SnapshotT(t, marshalledinfo)
 
 
 			v.Load()
 			v.Load()
-			fmt.Println(v.OriginsByPackage)
 			assert.Len(t, tt.fields.OriginsByPackage, 1)
 			assert.Len(t, tt.fields.OriginsByPackage, 1)
 
 
 			marshalledinfo, err = json.MarshalIndent(tt.fields.OriginsByPackage, "", "\t")
 			marshalledinfo, err = json.MarshalIndent(tt.fields.OriginsByPackage, "", "\t")
@@ -479,7 +481,7 @@ func TestInfoStore_Remove(t *testing.T) {
 			t.Parallel()
 			t.Parallel()
 			v := &InfoStore{
 			v := &InfoStore{
 				OriginsByPackage: tt.fields.OriginsByPackage,
 				OriginsByPackage: tt.fields.OriginsByPackage,
-				logger:           text.GlobalLogger,
+				logger:           newTestLogger(),
 				FilePath:         filePath,
 				FilePath:         filePath,
 			}
 			}
 			v.RemovePackages(tt.args.pkgs)
 			v.RemovePackages(tt.args.pkgs)

+ 129 - 55
print.go

@@ -6,14 +6,19 @@ import (
 	"io"
 	"io"
 	"os"
 	"os"
 	"strconv"
 	"strconv"
+	"strings"
+	"syscall"
+	"unicode"
 
 
 	aur "github.com/Jguer/aur"
 	aur "github.com/Jguer/aur"
 	mapset "github.com/deckarep/golang-set/v2"
 	mapset "github.com/deckarep/golang-set/v2"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
+	"golang.org/x/sys/unix"
 
 
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/query"
 	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
@@ -21,47 +26,47 @@ import (
 )
 )
 
 
 // printInfo prints package info like pacman -Si.
 // printInfo prints package info like pacman -Si.
-func printInfo(config *settings.Configuration, a *aur.Pkg, extendedInfo bool) {
-	text.PrintInfoValue(gotext.Get("Repository"), "aur")
-	text.PrintInfoValue(gotext.Get("Name"), a.Name)
-	text.PrintInfoValue(gotext.Get("Version"), a.Version)
-	text.PrintInfoValue(gotext.Get("Description"), a.Description)
-	text.PrintInfoValue(gotext.Get("URL"), a.URL)
-	text.PrintInfoValue(gotext.Get("Licenses"), a.License...)
-	text.PrintInfoValue(gotext.Get("Groups"), a.Groups...)
-	text.PrintInfoValue(gotext.Get("Provides"), a.Provides...)
-	text.PrintInfoValue(gotext.Get("Depends On"), a.Depends...)
-	text.PrintInfoValue(gotext.Get("Optional Deps"), a.OptDepends...)
-	text.PrintInfoValue(gotext.Get("Make Deps"), a.MakeDepends...)
-	text.PrintInfoValue(gotext.Get("Check Deps"), a.CheckDepends...)
-	text.PrintInfoValue(gotext.Get("Conflicts With"), a.Conflicts...)
-	text.PrintInfoValue(gotext.Get("Replaces"), a.Replaces...)
-	text.PrintInfoValue(gotext.Get("AUR URL"), config.AURURL+"/packages/"+a.Name)
-	text.PrintInfoValue(gotext.Get("First Submitted"), text.FormatTimeQuery(a.FirstSubmitted))
-	text.PrintInfoValue(gotext.Get("Keywords"), a.Keywords...)
-	text.PrintInfoValue(gotext.Get("Last Modified"), text.FormatTimeQuery(a.LastModified))
-	text.PrintInfoValue(gotext.Get("Maintainer"), a.Maintainer)
-	text.PrintInfoValue(gotext.Get("Popularity"), fmt.Sprintf("%f", a.Popularity))
-	text.PrintInfoValue(gotext.Get("Votes"), fmt.Sprintf("%d", a.NumVotes))
+func printInfo(logger *text.Logger, config *settings.Configuration, a *aur.Pkg, extendedInfo bool) {
+	printInfoValue(logger, gotext.Get("Repository"), "aur")
+	printInfoValue(logger, gotext.Get("Name"), a.Name)
+	printInfoValue(logger, gotext.Get("Version"), a.Version)
+	printInfoValue(logger, gotext.Get("Description"), a.Description)
+	printInfoValue(logger, gotext.Get("URL"), a.URL)
+	printInfoValue(logger, gotext.Get("Licenses"), a.License...)
+	printInfoValue(logger, gotext.Get("Groups"), a.Groups...)
+	printInfoValue(logger, gotext.Get("Provides"), a.Provides...)
+	printInfoValue(logger, gotext.Get("Depends On"), a.Depends...)
+	printInfoValue(logger, gotext.Get("Optional Deps"), a.OptDepends...)
+	printInfoValue(logger, gotext.Get("Make Deps"), a.MakeDepends...)
+	printInfoValue(logger, gotext.Get("Check Deps"), a.CheckDepends...)
+	printInfoValue(logger, gotext.Get("Conflicts With"), a.Conflicts...)
+	printInfoValue(logger, gotext.Get("Replaces"), a.Replaces...)
+	printInfoValue(logger, gotext.Get("AUR URL"), config.AURURL+"/packages/"+a.Name)
+	printInfoValue(logger, gotext.Get("First Submitted"), text.FormatTimeQuery(a.FirstSubmitted))
+	printInfoValue(logger, gotext.Get("Keywords"), a.Keywords...)
+	printInfoValue(logger, gotext.Get("Last Modified"), text.FormatTimeQuery(a.LastModified))
+	printInfoValue(logger, gotext.Get("Maintainer"), a.Maintainer)
+	printInfoValue(logger, gotext.Get("Popularity"), fmt.Sprintf("%f", a.Popularity))
+	printInfoValue(logger, gotext.Get("Votes"), fmt.Sprintf("%d", a.NumVotes))
 
 
 	if a.OutOfDate != 0 {
 	if a.OutOfDate != 0 {
-		text.PrintInfoValue(gotext.Get("Out-of-date"), text.FormatTimeQuery(a.OutOfDate))
+		printInfoValue(logger, gotext.Get("Out-of-date"), text.FormatTimeQuery(a.OutOfDate))
 	} else {
 	} else {
-		text.PrintInfoValue(gotext.Get("Out-of-date"), "No")
+		printInfoValue(logger, gotext.Get("Out-of-date"), "No")
 	}
 	}
 
 
 	if extendedInfo {
 	if extendedInfo {
-		text.PrintInfoValue("ID", fmt.Sprintf("%d", a.ID))
-		text.PrintInfoValue(gotext.Get("Package Base ID"), fmt.Sprintf("%d", a.PackageBaseID))
-		text.PrintInfoValue(gotext.Get("Package Base"), a.PackageBase)
-		text.PrintInfoValue(gotext.Get("Snapshot URL"), config.AURURL+a.URLPath)
+		printInfoValue(logger, "ID", fmt.Sprintf("%d", a.ID))
+		printInfoValue(logger, gotext.Get("Package Base ID"), fmt.Sprintf("%d", a.PackageBaseID))
+		printInfoValue(logger, gotext.Get("Package Base"), a.PackageBase)
+		printInfoValue(logger, gotext.Get("Snapshot URL"), config.AURURL+a.URLPath)
 	}
 	}
 
 
-	fmt.Println()
+	logger.Println()
 }
 }
 
 
 // BiggestPackages prints the name of the ten biggest packages in the system.
 // BiggestPackages prints the name of the ten biggest packages in the system.
-func biggestPackages(dbExecutor db.Executor) {
+func biggestPackages(logger *text.Logger, dbExecutor db.Executor) {
 	pkgS := dbExecutor.BiggestPackages()
 	pkgS := dbExecutor.BiggestPackages()
 
 
 	if len(pkgS) < 10 {
 	if len(pkgS) < 10 {
@@ -69,34 +74,34 @@ func biggestPackages(dbExecutor db.Executor) {
 	}
 	}
 
 
 	for i := 0; i < 10; i++ {
 	for i := 0; i < 10; i++ {
-		fmt.Printf("%s: %s\n", text.Bold(pkgS[i].Name()), text.Cyan(text.Human(pkgS[i].ISize())))
+		logger.Printf("%s: %s\n", text.Bold(pkgS[i].Name()), text.Cyan(text.Human(pkgS[i].ISize())))
 	}
 	}
 }
 }
 
 
 // localStatistics prints installed packages statistics.
 // localStatistics prints installed packages statistics.
-func localStatistics(ctx context.Context, cfg *settings.Configuration, dbExecutor db.Executor) error {
-	info := statistics(cfg, dbExecutor)
+func localStatistics(ctx context.Context, run *runtime.Runtime, dbExecutor db.Executor) error {
+	info := statistics(run, dbExecutor)
 
 
 	remoteNames := dbExecutor.InstalledRemotePackageNames()
 	remoteNames := dbExecutor.InstalledRemotePackageNames()
 	remote := dbExecutor.InstalledRemotePackages()
 	remote := dbExecutor.InstalledRemotePackages()
-	text.Infoln(gotext.Get("Yay version v%s", yayVersion))
-	fmt.Println(text.Bold(text.Cyan("===========================================")))
-	text.Infoln(gotext.Get("Total installed packages: %s", text.Cyan(strconv.Itoa(info.Totaln))))
-	text.Infoln(gotext.Get("Foreign installed packages: %s", text.Cyan(strconv.Itoa(len(remoteNames)))))
-	text.Infoln(gotext.Get("Explicitly installed packages: %s", text.Cyan(strconv.Itoa(info.Expln))))
-	text.Infoln(gotext.Get("Total Size occupied by packages: %s", text.Cyan(text.Human(info.TotalSize))))
+	run.Logger.Infoln(gotext.Get("Yay version v%s", yayVersion))
+	run.Logger.Println(text.Bold(text.Cyan("===========================================")))
+	run.Logger.Infoln(gotext.Get("Total installed packages: %s", text.Cyan(strconv.Itoa(info.Totaln))))
+	run.Logger.Infoln(gotext.Get("Foreign installed packages: %s", text.Cyan(strconv.Itoa(len(remoteNames)))))
+	run.Logger.Infoln(gotext.Get("Explicitly installed packages: %s", text.Cyan(strconv.Itoa(info.Expln))))
+	run.Logger.Infoln(gotext.Get("Total Size occupied by packages: %s", text.Cyan(text.Human(info.TotalSize))))
 
 
 	for path, size := range info.pacmanCaches {
 	for path, size := range info.pacmanCaches {
-		text.Infoln(gotext.Get("Size of pacman cache %s: %s", path, text.Cyan(text.Human(size))))
+		run.Logger.Infoln(gotext.Get("Size of pacman cache %s: %s", path, text.Cyan(text.Human(size))))
 	}
 	}
 
 
-	text.Infoln(gotext.Get("Size of yay cache %s: %s", cfg.BuildDir, text.Cyan(text.Human(info.yayCache))))
-	fmt.Println(text.Bold(text.Cyan("===========================================")))
-	text.Infoln(gotext.Get("Ten biggest packages:"))
-	biggestPackages(dbExecutor)
-	fmt.Println(text.Bold(text.Cyan("===========================================")))
+	run.Logger.Infoln(gotext.Get("Size of yay cache %s: %s", run.Cfg.BuildDir, text.Cyan(text.Human(info.yayCache))))
+	run.Logger.Println(text.Bold(text.Cyan("===========================================")))
+	run.Logger.Infoln(gotext.Get("Ten biggest packages:"))
+	biggestPackages(run.Logger, dbExecutor)
+	run.Logger.Println(text.Bold(text.Cyan("===========================================")))
 
 
-	aurData, err := cfg.Runtime.AURClient.Get(ctx, &aur.Query{
+	aurData, err := run.AURClient.Get(ctx, &aur.Query{
 		Needles: remoteNames,
 		Needles: remoteNames,
 		By:      aur.Name,
 		By:      aur.Name,
 	})
 	})
@@ -104,7 +109,7 @@ func localStatistics(ctx context.Context, cfg *settings.Configuration, dbExecuto
 		return err
 		return err
 	}
 	}
 
 
-	warnings := query.NewWarnings(cfg.Runtime.Logger.Child("print"))
+	warnings := query.NewWarnings(run.Logger.Child("warnings"))
 	for i := range aurData {
 	for i := range aurData {
 		warnings.AddToWarnings(remote, &aurData[i])
 		warnings.AddToWarnings(remote, &aurData[i])
 	}
 	}
@@ -114,13 +119,13 @@ func localStatistics(ctx context.Context, cfg *settings.Configuration, dbExecuto
 	return nil
 	return nil
 }
 }
 
 
-func printUpdateList(ctx context.Context, cfg *settings.Configuration, cmdArgs *parser.Arguments,
+func printUpdateList(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments,
 	dbExecutor db.Executor, enableDowngrade bool, filter upgrade.Filter,
 	dbExecutor db.Executor, enableDowngrade bool, filter upgrade.Filter,
 ) error {
 ) error {
 	quietMode := cmdArgs.ExistsArg("q", "quiet")
 	quietMode := cmdArgs.ExistsArg("q", "quiet")
 
 
 	// TODO: handle quiet mode in a better way
 	// TODO: handle quiet mode in a better way
-	logger := text.NewLogger(io.Discard, os.Stderr, os.Stdin, cfg.Debug, "update-list")
+	logger := text.NewLogger(io.Discard, os.Stderr, os.Stdin, run.Cfg.Debug, "update-list")
 	dbExecutor.SetLogger(logger.Child("db"))
 	dbExecutor.SetLogger(logger.Child("db"))
 	oldNoConfirm := settings.NoConfirm
 	oldNoConfirm := settings.NoConfirm
 	settings.NoConfirm = true
 	settings.NoConfirm = true
@@ -128,12 +133,12 @@ func printUpdateList(ctx context.Context, cfg *settings.Configuration, cmdArgs *
 	defer func() { settings.NoConfirm = oldNoConfirm }()
 	defer func() { settings.NoConfirm = oldNoConfirm }()
 
 
 	targets := mapset.NewThreadUnsafeSet(cmdArgs.Targets...)
 	targets := mapset.NewThreadUnsafeSet(cmdArgs.Targets...)
-	grapher := dep.NewGrapher(dbExecutor, cfg.Runtime.AURClient, false, true,
+	grapher := dep.NewGrapher(dbExecutor, run.AURClient, false, true,
 		false, false, cmdArgs.ExistsArg("needed"), logger.Child("grapher"))
 		false, false, cmdArgs.ExistsArg("needed"), logger.Child("grapher"))
 
 
 	upService := upgrade.NewUpgradeService(
 	upService := upgrade.NewUpgradeService(
-		grapher, cfg.Runtime.AURClient, dbExecutor, cfg.Runtime.VCSStore,
-		cfg, true, logger.Child("upgrade"))
+		grapher, run.AURClient, dbExecutor, run.VCSStore,
+		run.Cfg, true, logger.Child("upgrade"))
 
 
 	graph, errSysUp := upService.GraphUpgrades(ctx, nil,
 	graph, errSysUp := upService.GraphUpgrades(ctx, nil,
 		enableDowngrade, filter)
 		enableDowngrade, filter)
@@ -163,9 +168,9 @@ func printUpdateList(ctx context.Context, cfg *settings.Configuration, cmdArgs *
 			}
 			}
 
 
 			if quietMode {
 			if quietMode {
-				fmt.Printf("%s\n", pkgName)
+				run.Logger.Printf("%s\n", pkgName)
 			} else {
 			} else {
-				fmt.Printf("%s %s -> %s\n", text.Bold(pkgName), text.Bold(text.Green(ii.LocalVersion)),
+				run.Logger.Printf("%s %s -> %s\n", text.Bold(pkgName), text.Bold(text.Green(ii.LocalVersion)),
 					text.Bold(text.Green(ii.Version)))
 					text.Bold(text.Green(ii.Version)))
 			}
 			}
 
 
@@ -179,7 +184,7 @@ func printUpdateList(ctx context.Context, cfg *settings.Configuration, cmdArgs *
 	missing := false
 	missing := false
 	targets.Each(func(pkgName string) bool {
 	targets.Each(func(pkgName string) bool {
 		if dbExecutor.LocalPackage(pkgName) == nil {
 		if dbExecutor.LocalPackage(pkgName) == nil {
-			cfg.Runtime.Logger.Errorln(gotext.Get("package '%s' was not found", pkgName))
+			run.Logger.Errorln(gotext.Get("package '%s' was not found", pkgName))
 			missing = true
 			missing = true
 		}
 		}
 		return false
 		return false
@@ -191,3 +196,72 @@ func printUpdateList(ctx context.Context, cfg *settings.Configuration, cmdArgs *
 
 
 	return nil
 	return nil
 }
 }
+
+func printInfoValue(logger *text.Logger, key string, values ...string) {
+	const (
+		keyLength  = 32
+		delimCount = 2
+	)
+
+	specialWordsCount := 0
+
+	for _, runeValue := range key {
+		// CJK handling: the character 'ー' is Katakana
+		// but if use unicode.Katakana, it will return false
+		if unicode.IsOneOf([]*unicode.RangeTable{
+			unicode.Han,
+			unicode.Hiragana,
+			unicode.Katakana,
+			unicode.Hangul,
+		}, runeValue) || runeValue == 'ー' {
+			specialWordsCount++
+		}
+	}
+
+	keyTextCount := specialWordsCount - keyLength + delimCount
+	str := fmt.Sprintf(text.Bold("%-*s: "), keyTextCount, key)
+
+	if len(values) == 0 || (len(values) == 1 && values[0] == "") {
+		logger.Printf("%s%s\n", str, gotext.Get("None"))
+		return
+	}
+
+	maxCols := getColumnCount()
+	cols := keyLength + len(values[0])
+	str += values[0]
+
+	for _, value := range values[1:] {
+		if maxCols > keyLength && cols+len(value)+delimCount >= maxCols {
+			cols = keyLength
+			str += "\n" + strings.Repeat(" ", keyLength)
+		} else if cols != keyLength {
+			str += strings.Repeat(" ", delimCount)
+			cols += delimCount
+		}
+
+		str += value
+		cols += len(value)
+	}
+
+	logger.Println(str)
+}
+
+var cachedColumnCount = -1
+
+func getColumnCount() int {
+	if cachedColumnCount > 0 {
+		return cachedColumnCount
+	}
+
+	if count, err := strconv.Atoi(os.Getenv("COLUMNS")); err == nil {
+		cachedColumnCount = count
+		return cachedColumnCount
+	}
+
+	if ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ); err == nil {
+		cachedColumnCount = int(ws.Col)
+		return cachedColumnCount
+	}
+
+	return 80
+}

+ 13 - 13
print_test.go

@@ -20,6 +20,7 @@ import (
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
@@ -271,29 +272,28 @@ func TestPrintUpdateList(t *testing.T) {
 				SudoLoopEnabled:  false,
 				SudoLoopEnabled:  false,
 			}
 			}
 
 
-			cfg := &settings.Configuration{
-				RemoveMake: "no",
-				Runtime: &settings.Runtime{
-					Logger:     NewTestLogger(),
-					CmdBuilder: cmdBuilder,
-					VCSStore:   &vcs.Mock{},
-					AURClient:  tc.mockData.aurCache,
+			r, w, _ := os.Pipe()
+
+			logger := text.NewLogger(w, io.Discard, strings.NewReader(""), true, "test")
+
+			run := &runtime.Runtime{
+				Cfg: &settings.Configuration{
+					RemoveMake: "no",
 				},
 				},
+				Logger:     logger,
+				CmdBuilder: cmdBuilder,
+				VCSStore:   &vcs.Mock{},
+				AURClient:  tc.mockData.aurCache,
 			}
 			}
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
 			cmdArgs.AddArg(tc.args...)
 			cmdArgs.AddArg(tc.args...)
 			cmdArgs.AddTarget(tc.targets...)
 			cmdArgs.AddTarget(tc.targets...)
 
 
-			rescueStdout := os.Stdout
-			r, w, _ := os.Pipe()
-			os.Stdout = w
-
-			err = handleCmd(context.Background(), cfg, cmdArgs, tc.mockData.db)
+			err = handleCmd(context.Background(), run, cmdArgs, tc.mockData.db)
 
 
 			w.Close()
 			w.Close()
 			out, _ := io.ReadAll(r)
 			out, _ := io.ReadAll(r)
-			os.Stdout = rescueStdout
 
 
 			if tc.wantErr {
 			if tc.wantErr {
 				require.Error(t, err)
 				require.Error(t, err)

+ 12 - 11
query.go

@@ -12,6 +12,7 @@ import (
 
 
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/query"
 	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/text"
 	"github.com/Jguer/yay/v12/pkg/text"
@@ -32,7 +33,7 @@ func syncSearch(ctx context.Context, pkgS []string,
 }
 }
 
 
 // SyncInfo serves as a pacman -Si for repo packages and AUR packages.
 // SyncInfo serves as a pacman -Si for repo packages and AUR packages.
-func syncInfo(ctx context.Context, cfg *settings.Configuration,
+func syncInfo(ctx context.Context, run *runtime.Runtime,
 	cmdArgs *parser.Arguments, pkgS []string, dbExecutor db.Executor,
 	cmdArgs *parser.Arguments, pkgS []string, dbExecutor db.Executor,
 ) error {
 ) error {
 	var (
 	var (
@@ -41,8 +42,8 @@ func syncInfo(ctx context.Context, cfg *settings.Configuration,
 		missing = false
 		missing = false
 	)
 	)
 
 
-	pkgS = query.RemoveInvalidTargets(pkgS, cfg.Mode)
-	aurS, repoS := packageSlices(pkgS, cfg, dbExecutor)
+	pkgS = query.RemoveInvalidTargets(run.Logger, pkgS, run.Cfg.Mode)
+	aurS, repoS := packageSlices(pkgS, run.Cfg, dbExecutor)
 
 
 	if len(aurS) != 0 {
 	if len(aurS) != 0 {
 		noDB := make([]string, 0, len(aurS))
 		noDB := make([]string, 0, len(aurS))
@@ -52,14 +53,14 @@ func syncInfo(ctx context.Context, cfg *settings.Configuration,
 			noDB = append(noDB, name)
 			noDB = append(noDB, name)
 		}
 		}
 
 
-		info, err = cfg.Runtime.AURClient.Get(ctx, &aur.Query{
+		info, err = run.AURClient.Get(ctx, &aur.Query{
 			Needles: noDB,
 			Needles: noDB,
 			By:      aur.Name,
 			By:      aur.Name,
 		})
 		})
 		if err != nil {
 		if err != nil {
 			missing = true
 			missing = true
 
 
-			cfg.Runtime.Logger.Errorln(err)
+			run.Logger.Errorln(err)
 		}
 		}
 	}
 	}
 
 
@@ -68,8 +69,8 @@ func syncInfo(ctx context.Context, cfg *settings.Configuration,
 		arguments.ClearTargets()
 		arguments.ClearTargets()
 		arguments.AddTarget(repoS...)
 		arguments.AddTarget(repoS...)
 
 
-		err = cfg.Runtime.CmdBuilder.Show(cfg.Runtime.CmdBuilder.BuildPacmanCmd(ctx,
-			arguments, cfg.Mode, settings.NoConfirm))
+		err = run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx,
+			arguments, run.Cfg.Mode, settings.NoConfirm))
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -81,7 +82,7 @@ func syncInfo(ctx context.Context, cfg *settings.Configuration,
 
 
 	if len(info) != 0 {
 	if len(info) != 0 {
 		for i := range info {
 		for i := range info {
-			printInfo(cfg, &info[i], cmdArgs.ExistsDouble("i"))
+			printInfo(run.Logger, run.Cfg, &info[i], cmdArgs.ExistsDouble("i"))
 		}
 		}
 	}
 	}
 
 
@@ -220,7 +221,7 @@ func getFolderSize(path string) (size int64) {
 }
 }
 
 
 // Statistics returns statistics about packages installed in system.
 // Statistics returns statistics about packages installed in system.
-func statistics(cfg *settings.Configuration, dbExecutor db.Executor) (res struct {
+func statistics(run *runtime.Runtime, dbExecutor db.Executor) (res struct {
 	Totaln       int
 	Totaln       int
 	Expln        int
 	Expln        int
 	TotalSize    int64
 	TotalSize    int64
@@ -238,11 +239,11 @@ func statistics(cfg *settings.Configuration, dbExecutor db.Executor) (res struct
 	}
 	}
 
 
 	res.pacmanCaches = make(map[string]int64)
 	res.pacmanCaches = make(map[string]int64)
-	for _, path := range cfg.Runtime.PacmanConf.CacheDir {
+	for _, path := range run.PacmanConf.CacheDir {
 		res.pacmanCaches[path] = getFolderSize(path)
 		res.pacmanCaches[path] = getFolderSize(path)
 	}
 	}
 
 
-	res.yayCache = getFolderSize(cfg.BuildDir)
+	res.yayCache = getFolderSize(run.Cfg.BuildDir)
 
 
 	return
 	return
 }
 }

+ 17 - 16
query_test.go

@@ -16,6 +16,7 @@ import (
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	"github.com/Jguer/yay/v12/pkg/query"
 	"github.com/Jguer/yay/v12/pkg/query"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
@@ -125,12 +126,12 @@ func TestSyncInfo(t *testing.T) {
 				Runner:           mockRunner,
 				Runner:           mockRunner,
 				SudoLoopEnabled:  false,
 				SudoLoopEnabled:  false,
 			}
 			}
-			cfg := &settings.Configuration{
-				Runtime: &settings.Runtime{
-					CmdBuilder: cmdBuilder,
-					AURClient:  mockAUR,
-					Logger:     NewTestLogger(),
-				},
+
+			run := &runtime.Runtime{
+				CmdBuilder: cmdBuilder,
+				AURClient:  mockAUR,
+				Logger:     newTestLogger(),
+				Cfg:        &settings.Configuration{},
 			}
 			}
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
@@ -138,7 +139,7 @@ func TestSyncInfo(t *testing.T) {
 			cmdArgs.AddTarget(tc.targets...)
 			cmdArgs.AddTarget(tc.targets...)
 
 
 			err := handleCmd(context.Background(),
 			err := handleCmd(context.Background(),
-				cfg, cmdArgs, dbExc,
+				run, cmdArgs, dbExc,
 			)
 			)
 
 
 			if tc.wantErr {
 			if tc.wantErr {
@@ -266,14 +267,14 @@ func TestSyncSearchAURDB(t *testing.T) {
 				Runner:           mockRunner,
 				Runner:           mockRunner,
 				SudoLoopEnabled:  false,
 				SudoLoopEnabled:  false,
 			}
 			}
-			cfg := &settings.Configuration{
-				Runtime: &settings.Runtime{
-					CmdBuilder: cmdBuilder,
-					AURClient:  mockAUR,
-					QueryBuilder: query.NewSourceQueryBuilder(mockAUR, NewTestLogger(), "votes", parser.ModeAny, "name",
-						tc.bottomUp, tc.singleLine, tc.mixed),
-					Logger: NewTestLogger(),
-				},
+
+			run := &runtime.Runtime{
+				CmdBuilder: cmdBuilder,
+				AURClient:  mockAUR,
+				QueryBuilder: query.NewSourceQueryBuilder(mockAUR, newTestLogger(), "votes", parser.ModeAny, "name",
+					tc.bottomUp, tc.singleLine, tc.mixed),
+				Logger: newTestLogger(),
+				Cfg:    &settings.Configuration{},
 			}
 			}
 
 
 			cmdArgs := parser.MakeArguments()
 			cmdArgs := parser.MakeArguments()
@@ -281,7 +282,7 @@ func TestSyncSearchAURDB(t *testing.T) {
 			cmdArgs.AddTarget(tc.targets...)
 			cmdArgs.AddTarget(tc.targets...)
 
 
 			err := handleCmd(context.Background(),
 			err := handleCmd(context.Background(),
-				cfg, cmdArgs, dbExc,
+				run, cmdArgs, dbExc,
 			)
 			)
 
 
 			if tc.wantErr {
 			if tc.wantErr {

+ 26 - 125
sync.go

@@ -3,37 +3,36 @@ package main
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"os"
 	"strings"
 	"strings"
 
 
-	"github.com/Jguer/yay/v12/pkg/completion"
+	"github.com/leonelquinteros/gotext"
+
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/multierror"
 	"github.com/Jguer/yay/v12/pkg/multierror"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
+	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
-	"github.com/Jguer/yay/v12/pkg/srcinfo"
-	"github.com/Jguer/yay/v12/pkg/text"
+	"github.com/Jguer/yay/v12/pkg/sync"
 	"github.com/Jguer/yay/v12/pkg/upgrade"
 	"github.com/Jguer/yay/v12/pkg/upgrade"
-
-	"github.com/leonelquinteros/gotext"
 )
 )
 
 
 func syncInstall(ctx context.Context,
 func syncInstall(ctx context.Context,
-	cfg *settings.Configuration,
+	run *runtime.Runtime,
 	cmdArgs *parser.Arguments,
 	cmdArgs *parser.Arguments,
 	dbExecutor db.Executor,
 	dbExecutor db.Executor,
 ) error {
 ) error {
-	aurCache := cfg.Runtime.AURClient
+	aurCache := run.AURClient
 	refreshArg := cmdArgs.ExistsArg("y", "refresh")
 	refreshArg := cmdArgs.ExistsArg("y", "refresh")
 	noDeps := cmdArgs.ExistsArg("d", "nodeps")
 	noDeps := cmdArgs.ExistsArg("d", "nodeps")
-	noCheck := strings.Contains(cfg.MFlags, "--nocheck")
+	noCheck := strings.Contains(run.Cfg.MFlags, "--nocheck")
 	if noDeps {
 	if noDeps {
-		cfg.Runtime.CmdBuilder.AddMakepkgFlag("-d")
+		run.CmdBuilder.AddMakepkgFlag("-d")
 	}
 	}
 
 
-	if refreshArg && cfg.Mode.AtLeastRepo() {
-		if errR := earlyRefresh(ctx, cfg, cfg.Runtime.CmdBuilder, cmdArgs); errR != nil {
+	if refreshArg && run.Cfg.Mode.AtLeastRepo() {
+		if errR := earlyRefresh(ctx, run.Cfg, run.CmdBuilder, cmdArgs); errR != nil {
 			return fmt.Errorf("%s - %w", gotext.Get("error refreshing databases"), errR)
 			return fmt.Errorf("%s - %w", gotext.Get("error refreshing databases"), errR)
 		}
 		}
 
 
@@ -45,7 +44,7 @@ func syncInstall(ctx context.Context,
 	}
 	}
 
 
 	grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm,
 	grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm,
-		noDeps, noCheck, cmdArgs.ExistsArg("needed"), cfg.Runtime.Logger.Child("grapher"))
+		noDeps, noCheck, cmdArgs.ExistsArg("needed"), run.Logger.Child("grapher"))
 
 
 	graph, err := grapher.GraphFromTargets(ctx, nil, cmdArgs.Targets)
 	graph, err := grapher.GraphFromTargets(ctx, nil, cmdArgs.Targets)
 	if err != nil {
 	if err != nil {
@@ -57,8 +56,8 @@ func syncInstall(ctx context.Context,
 		var errSysUp error
 		var errSysUp error
 
 
 		upService := upgrade.NewUpgradeService(
 		upService := upgrade.NewUpgradeService(
-			grapher, aurCache, dbExecutor, cfg.Runtime.VCSStore,
-			cfg, settings.NoConfirm, cfg.Runtime.Logger.Child("upgrade"))
+			grapher, aurCache, dbExecutor, run.VCSStore,
+			run.Cfg, settings.NoConfirm, run.Logger.Child("upgrade"))
 
 
 		graph, errSysUp = upService.GraphUpgrades(ctx,
 		graph, errSysUp = upService.GraphUpgrades(ctx,
 			graph, cmdArgs.ExistsDouble("u", "sysupgrade"),
 			graph, cmdArgs.ExistsDouble("u", "sysupgrade"),
@@ -75,7 +74,7 @@ func syncInstall(ctx context.Context,
 		}
 		}
 	}
 	}
 
 
-	opService := NewOperationService(ctx, cfg, dbExecutor)
+	opService := sync.NewOperationService(ctx, dbExecutor, run)
 	multiErr := &multierror.MultiError{}
 	multiErr := &multierror.MultiError{}
 	targets := graph.TopoSortedLayerMap(func(s string, ii *dep.InstallInfo) error {
 	targets := graph.TopoSortedLayerMap(func(s string, ii *dep.InstallInfo) error {
 		if ii.Source == dep.Missing {
 		if ii.Source == dep.Missing {
@@ -88,117 +87,19 @@ func syncInstall(ctx context.Context,
 		return err
 		return err
 	}
 	}
 
 
-	return opService.Run(ctx, cmdArgs, targets, excluded)
-}
-
-type OperationService struct {
-	ctx        context.Context
-	cfg        *settings.Configuration
-	dbExecutor db.Executor
-}
-
-func NewOperationService(ctx context.Context, cfg *settings.Configuration, dbExecutor db.Executor) *OperationService {
-	return &OperationService{
-		ctx:        ctx,
-		cfg:        cfg,
-		dbExecutor: dbExecutor,
-	}
-}
-
-func (o *OperationService) Run(ctx context.Context,
-	cmdArgs *parser.Arguments,
-	targets []map[string]*dep.InstallInfo, excluded []string,
-) error {
-	if len(targets) == 0 {
-		fmt.Fprintln(os.Stdout, "", gotext.Get("there is nothing to do"))
-		return nil
-	}
-	preparer := NewPreparer(o.dbExecutor, o.cfg.Runtime.CmdBuilder, o.cfg)
-	installer := NewInstaller(o.dbExecutor, o.cfg.Runtime.CmdBuilder,
-		o.cfg.Runtime.VCSStore, o.cfg.Mode, o.cfg.ReBuild,
-		cmdArgs.ExistsArg("w", "downloadonly"), o.cfg.Runtime.Logger.Child("installer"))
-
-	pkgBuildDirs, errInstall := preparer.Run(ctx, os.Stdout, targets)
-	if errInstall != nil {
-		return errInstall
-	}
-
-	if cleanFunc := preparer.ShouldCleanMakeDeps(cmdArgs); cleanFunc != nil {
-		installer.AddPostInstallHook(cleanFunc)
-	}
-
-	if cleanAURDirsFunc := preparer.ShouldCleanAURDirs(pkgBuildDirs); cleanAURDirsFunc != nil {
-		installer.AddPostInstallHook(cleanAURDirsFunc)
-	}
-
-	go func() {
-		errComp := completion.Update(ctx, o.cfg.Runtime.HTTPClient, o.dbExecutor,
-			o.cfg.AURURL, o.cfg.CompletionPath, o.cfg.CompletionInterval, false)
-		if errComp != nil {
-			text.Warnln(errComp)
-		}
-	}()
-
-	srcInfo, errInstall := srcinfo.NewService(o.dbExecutor, o.cfg, o.cfg.Runtime.CmdBuilder, o.cfg.Runtime.VCSStore, pkgBuildDirs)
-	if errInstall != nil {
-		return errInstall
-	}
-
-	incompatible, errInstall := srcInfo.IncompatiblePkgs(ctx)
-	if errInstall != nil {
-		return errInstall
-	}
-
-	if errIncompatible := confirmIncompatible(incompatible); errIncompatible != nil {
-		return errIncompatible
-	}
-
-	if errPGP := srcInfo.CheckPGPKeys(ctx); errPGP != nil {
-		return errPGP
-	}
-
-	if errInstall := installer.Install(ctx, cmdArgs, targets, pkgBuildDirs,
-		excluded, o.manualConfirmRequired(cmdArgs)); errInstall != nil {
-		return errInstall
-	}
-
-	var multiErr multierror.MultiError
-
-	if err := installer.CompileFailedAndIgnored(); err != nil {
-		multiErr.Add(err)
-	}
-
-	if !cmdArgs.ExistsArg("w", "downloadonly") {
-		if err := srcInfo.UpdateVCSStore(ctx, targets, installer.failedAndIgnored); err != nil {
-			text.Warnln(err)
-		}
-	}
-
-	if err := installer.RunPostInstallHooks(ctx); err != nil {
-		multiErr.Add(err)
-	}
-
-	return multiErr.Return()
-}
-
-func (o *OperationService) manualConfirmRequired(cmdArgs *parser.Arguments) bool {
-	return (!cmdArgs.ExistsArg("u", "sysupgrade") && cmdArgs.Op != "Y") || o.cfg.DoubleConfirm
+	return opService.Run(ctx, run, cmdArgs, targets, excluded)
 }
 }
 
 
-func confirmIncompatible(incompatible []string) error {
-	if len(incompatible) > 0 {
-		text.Warnln(gotext.Get("The following packages are not compatible with your architecture:"))
-
-		for _, pkg := range incompatible {
-			fmt.Print("  " + text.Cyan(pkg))
-		}
-
-		fmt.Println()
-
-		if !text.ContinueTask(os.Stdin, gotext.Get("Try to build them anyway?"), true, settings.NoConfirm) {
-			return &settings.ErrUserAbort{}
-		}
+func earlyRefresh(ctx context.Context, cfg *settings.Configuration, cmdBuilder exe.ICmdBuilder, cmdArgs *parser.Arguments) error {
+	arguments := cmdArgs.Copy()
+	if cfg.CombinedUpgrade {
+		arguments.DelArg("u", "sysupgrade")
 	}
 	}
+	arguments.DelArg("s", "search")
+	arguments.DelArg("i", "info")
+	arguments.DelArg("l", "list")
+	arguments.ClearTargets()
 
 
-	return nil
+	return cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
+		arguments, cfg.Mode, settings.NoConfirm))
 }
 }

+ 72 - 73
sync_test.go

@@ -23,6 +23,7 @@ import (
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	"github.com/Jguer/yay/v12/pkg/db/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
 	mockaur "github.com/Jguer/yay/v12/pkg/dep/mock"
+	"github.com/Jguer/yay/v12/pkg/runtime"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/exe"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
 	"github.com/Jguer/yay/v12/pkg/settings/parser"
@@ -106,21 +107,20 @@ func TestSyncUpgrade(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	cfg := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n"), true, "test"),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n"), true, "test"),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
-
-	err = handleCmd(context.Background(), cfg, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	wantCapture := []string{}
 	wantCapture := []string{}
@@ -219,21 +219,20 @@ func TestSyncUpgrade_IgnoreAll(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	cfg := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
-
-	err = handleCmd(context.Background(), cfg, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	wantCapture := []string{}
 	wantCapture := []string{}
@@ -349,21 +348,21 @@ func TestSyncUpgrade_IgnoreOne(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	cfg := &settings.Configuration{
-		RemoveMake: "no",
-		Runtime: &settings.Runtime{
-			Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{}, nil
-				},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			RemoveMake: "no",
+		},
+		Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), cfg, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	wantCapture := []string{}
 	wantCapture := []string{}
@@ -512,37 +511,37 @@ pkgname = python-vosk
 		},
 		},
 	}
 	}
 
 
-	cfg := &settings.Configuration{
-		DoubleConfirm: true,
-		RemoveMake:    "no",
-		BuildDir:      tmpDir,
-		Runtime: &settings.Runtime{
-			Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n\n\n\n"), true, "test"),
-			CmdBuilder: cmdBuilder,
-			VCSStore:   &vcs.Mock{},
-			AURClient: &mockaur.MockAUR{
-				GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-					return []aur.Pkg{
-						{
-							Name:        "vosk-api",
-							PackageBase: "vosk-api",
-							Version:     "0.3.45-1",
-						},
-						{
-							Name:        "python-vosk",
-							PackageBase: "vosk-api",
-							Version:     "0.3.45-1",
-							Depends: []string{
-								"vosk-api=0.3.45",
-							},
+	run := &runtime.Runtime{
+		Cfg: &settings.Configuration{
+			DoubleConfirm: true,
+			RemoveMake:    "no",
+			BuildDir:      tmpDir,
+		},
+		Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n\n\n\n"), true, "test"),
+		CmdBuilder: cmdBuilder,
+		VCSStore:   &vcs.Mock{},
+		AURClient: &mockaur.MockAUR{
+			GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+				return []aur.Pkg{
+					{
+						Name:        "vosk-api",
+						PackageBase: "vosk-api",
+						Version:     "0.3.45-1",
+					},
+					{
+						Name:        "python-vosk",
+						PackageBase: "vosk-api",
+						Version:     "0.3.45-1",
+						Depends: []string{
+							"vosk-api=0.3.45",
 						},
 						},
-					}, nil
-				},
+					},
+				}, nil
 			},
 			},
 		},
 		},
 	}
 	}
 
 
-	err = handleCmd(context.Background(), cfg, cmdArgs, db)
+	err = handleCmd(context.Background(), run, cmdArgs, db)
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	wantCapture := []string{
 	wantCapture := []string{
@@ -697,22 +696,22 @@ func TestSyncUpgrade_NoCombinedUpgrade(t *testing.T) {
 				},
 				},
 			}
 			}
 
 
-			cfg := &settings.Configuration{
-				RemoveMake:      "no",
-				CombinedUpgrade: false,
-				Runtime: &settings.Runtime{
-					Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"),
-					CmdBuilder: cmdBuilder,
-					VCSStore:   &vcs.Mock{},
-					AURClient: &mockaur.MockAUR{
-						GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
-							return []aur.Pkg{}, nil
-						},
+			run := &runtime.Runtime{
+				Cfg: &settings.Configuration{
+					RemoveMake:      "no",
+					CombinedUpgrade: false,
+				},
+				Logger:     text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"),
+				CmdBuilder: cmdBuilder,
+				VCSStore:   &vcs.Mock{},
+				AURClient: &mockaur.MockAUR{
+					GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
+						return []aur.Pkg{}, nil
 					},
 					},
 				},
 				},
 			}
 			}
 
 
-			err = handleCmd(context.Background(), cfg, cmdArgs, db)
+			err = handleCmd(context.Background(), run, cmdArgs, db)
 			require.NoError(t, err)
 			require.NoError(t, err)
 
 
 			require.Len(t, mockRunner.ShowCalls, len(tc.want))
 			require.Len(t, mockRunner.ShowCalls, len(tc.want))

+ 11 - 12
vcs.go

@@ -2,7 +2,6 @@ package main
 
 
 import (
 import (
 	"context"
 	"context"
-	"os"
 	"sync"
 	"sync"
 
 
 	"github.com/Jguer/aur"
 	"github.com/Jguer/aur"
@@ -10,9 +9,9 @@ import (
 
 
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/db"
 	"github.com/Jguer/yay/v12/pkg/dep"
 	"github.com/Jguer/yay/v12/pkg/dep"
-	"github.com/Jguer/yay/v12/pkg/settings"
-	"github.com/Jguer/yay/v12/pkg/srcinfo"
-	"github.com/Jguer/yay/v12/pkg/text"
+	"github.com/Jguer/yay/v12/pkg/runtime"
+	"github.com/Jguer/yay/v12/pkg/sync/srcinfo"
+	"github.com/Jguer/yay/v12/pkg/sync/workdir"
 )
 )
 
 
 func infoToInstallInfo(info []aur.Pkg) []map[string]*dep.InstallInfo {
 func infoToInstallInfo(info []aur.Pkg) []map[string]*dep.InstallInfo {
@@ -31,11 +30,11 @@ func infoToInstallInfo(info []aur.Pkg) []map[string]*dep.InstallInfo {
 }
 }
 
 
 // createDevelDB forces yay to create a DB of the existing development packages.
 // createDevelDB forces yay to create a DB of the existing development packages.
-func createDevelDB(ctx context.Context, cfg *settings.Configuration, dbExecutor db.Executor) error {
+func createDevelDB(ctx context.Context, run *runtime.Runtime, dbExecutor db.Executor) error {
 	remoteNames := dbExecutor.InstalledRemotePackageNames()
 	remoteNames := dbExecutor.InstalledRemotePackageNames()
 
 
-	cfg.Runtime.QueryBuilder.Execute(ctx, dbExecutor, remoteNames)
-	info, err := cfg.Runtime.AURClient.Get(ctx, &aur.Query{
+	run.QueryBuilder.Execute(ctx, dbExecutor, remoteNames)
+	info, err := run.AURClient.Get(ctx, &aur.Query{
 		Needles:  remoteNames,
 		Needles:  remoteNames,
 		By:       aur.Name,
 		By:       aur.Name,
 		Contains: false,
 		Contains: false,
@@ -44,15 +43,15 @@ func createDevelDB(ctx context.Context, cfg *settings.Configuration, dbExecutor
 		return err
 		return err
 	}
 	}
 
 
-	preper := NewPreparerWithoutHooks(dbExecutor, cfg.Runtime.CmdBuilder, cfg, false)
+	preper := workdir.NewPreparerWithoutHooks(dbExecutor, run.CmdBuilder, run.Cfg, run.Logger.Child("workdir"), false)
 
 
 	mapInfo := infoToInstallInfo(info)
 	mapInfo := infoToInstallInfo(info)
-	pkgBuildDirsByBase, err := preper.Run(ctx, os.Stdout, mapInfo)
+	pkgBuildDirsByBase, err := preper.Run(ctx, run, mapInfo)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	srcinfos, err := srcinfo.ParseSrcinfoFilesByBase(pkgBuildDirsByBase, false)
+	srcinfos, err := srcinfo.ParseSrcinfoFilesByBase(run.Logger.Child("srcinfo"), pkgBuildDirsByBase, false)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -63,14 +62,14 @@ func createDevelDB(ctx context.Context, cfg *settings.Configuration, dbExecutor
 			wg.Add(1)
 			wg.Add(1)
 
 
 			go func(i string, iP int) {
 			go func(i string, iP int) {
-				cfg.Runtime.VCSStore.Update(ctx, srcinfos[i].Packages[iP].Pkgname, srcinfos[i].Source)
+				run.VCSStore.Update(ctx, srcinfos[i].Packages[iP].Pkgname, srcinfos[i].Source)
 				wg.Done()
 				wg.Done()
 			}(i, iP)
 			}(i, iP)
 		}
 		}
 	}
 	}
 
 
 	wg.Wait()
 	wg.Wait()
-	text.OperationInfoln(gotext.Get("GenDB finished. No packages were installed"))
+	run.Logger.OperationInfoln(gotext.Get("GenDB finished. No packages were installed"))
 
 
 	return err
 	return err
 }
 }

+ 4 - 3
vote.go

@@ -3,11 +3,12 @@ package main
 import (
 import (
 	"context"
 	"context"
 	"errors"
 	"errors"
-	"fmt"
 
 
 	"github.com/Jguer/aur"
 	"github.com/Jguer/aur"
 	"github.com/Jguer/votar/pkg/vote"
 	"github.com/Jguer/votar/pkg/vote"
 	"github.com/leonelquinteros/gotext"
 	"github.com/leonelquinteros/gotext"
+
+	"github.com/Jguer/yay/v12/pkg/text"
 )
 )
 
 
 type ErrAURVote struct {
 type ErrAURVote struct {
@@ -20,7 +21,7 @@ func (e *ErrAURVote) Error() string {
 }
 }
 
 
 func handlePackageVote(ctx context.Context,
 func handlePackageVote(ctx context.Context,
-	targets []string, aurClient aur.QueryClient,
+	targets []string, aurClient aur.QueryClient, logger *text.Logger,
 	voteClient *vote.Client, upvote bool,
 	voteClient *vote.Client, upvote bool,
 ) error {
 ) error {
 	infos, err := aurClient.Get(ctx, &aur.Query{
 	infos, err := aurClient.Get(ctx, &aur.Query{
@@ -32,7 +33,7 @@ func handlePackageVote(ctx context.Context,
 	}
 	}
 
 
 	if len(infos) == 0 {
 	if len(infos) == 0 {
-		fmt.Println(gotext.Get(" there is nothing to do"))
+		logger.Println(gotext.Get(" there is nothing to do"))
 		return nil
 		return nil
 	}
 	}