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
This commit is contained in:
Jo
2023-08-06 21:39:41 +02:00
committed by GitHub
parent 7483393377
commit 8916cd174b
74 changed files with 1475 additions and 1367 deletions

View File

@@ -8,6 +8,7 @@ import (
"github.com/Jguer/yay/v12/pkg/db/ialpm"
"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/parser"
"github.com/Jguer/yay/v12/pkg/text"
@@ -17,44 +18,45 @@ import (
"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 {
return err
}
cmdArgs := parser.MakeArguments()
if errP := config.ParseCommandLine(cmdArgs); errP != nil {
if errP := cfg.ParseCommandLine(cmdArgs); errP != nil {
return errP
}
pacmanConf, _, err := settings.RetrievePacmanConfig(cmdArgs, config.PacmanConf)
run, err := runtime.NewRuntime(cfg, cmdArgs, "1.0.0")
if err != nil {
return err
}
dbExecutor, err := ialpm.NewExecutor(pacmanConf, text.GlobalLogger)
dbExecutor, err := ialpm.NewExecutor(run.PacmanConf, logger)
if err != nil {
return err
}
aurCache, err := metadata.New(
metadata.WithCacheFilePath(
filepath.Join(config.BuildDir, "aur.json")))
filepath.Join(cfg.BuildDir, "aur.json")))
if err != nil {
return errors.Wrap(err, gotext.Get("failed to retrieve aur Cache"))
}
grapher := dep.NewGrapher(dbExecutor, aurCache, true, settings.NoConfirm,
cmdArgs.ExistsDouble("d", "nodeps"), false, false,
config.Runtime.Logger.Child("grapher"))
run.Logger.Child("grapher"))
return graphPackage(context.Background(), grapher, cmdArgs.Targets)
}
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)
}
}

View File

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

View File

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

View File

@@ -158,7 +158,7 @@ func TestAURPKGBUILDRepos(t *testing.T) {
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.EqualValues(t, map[string]bool{"yay": true, "yay-bin": false, "yay-git": true}, cloned)

View File

@@ -81,8 +81,8 @@ func getURLName(pkg db.IPackage) string {
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) {
pkgbuilds := make(map[string][]byte, len(targets))
@@ -96,7 +96,7 @@ func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *htt
for _, target := range targets {
// 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 {
continue
}
@@ -136,7 +136,7 @@ func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *htt
}
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,
) (map[string]bool, error) {
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 {
// 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 {
continue
}
@@ -184,11 +184,11 @@ func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.Que
}
if aur {
text.OperationInfoln(
logger.OperationInfoln(
gotext.Get("(%d/%d) Downloaded PKGBUILD: %s",
progress, len(targets), text.Cyan(pkgName)))
} else {
text.OperationInfoln(
logger.OperationInfoln(
gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s",
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.
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, name := text.SplitDBFromName(target)
if dbName != "aur" && mode.AtLeastRepo() {
@@ -239,7 +239,7 @@ func getPackageUsableName(dbExecutor DBSearcher, aurClient aur.QueryClient,
Needles: []string{name},
})
if err != nil {
text.Warnln(err)
logger.Warnln(err)
return dbName, name, true, true
}

View File

@@ -5,6 +5,7 @@ package download
import (
"context"
"io"
"net/http"
"os"
"path/filepath"
@@ -22,6 +23,10 @@ import (
"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 package in repo is already present
// WHEN defining package db as a target
@@ -56,7 +61,7 @@ func TestPKGBUILDReposDefinedDBPull(t *testing.T) {
absPackagesDB: map[string]string{"yay": "core"},
}
cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
cmdBuilder,
cmdBuilder, newTestLogger(),
targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
assert.NoError(t, err)
@@ -90,7 +95,7 @@ func TestPKGBUILDReposDefinedDBClone(t *testing.T) {
absPackagesDB: map[string]string{"yay": "core"},
}
cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
cmdBuilder,
cmdBuilder, newTestLogger(),
targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
assert.NoError(t, err)
@@ -124,7 +129,7 @@ func TestPKGBUILDReposClone(t *testing.T) {
absPackagesDB: map[string]string{"yay": "core"},
}
cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
cmdBuilder,
cmdBuilder, newTestLogger(),
targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
assert.NoError(t, err)
@@ -158,7 +163,7 @@ func TestPKGBUILDReposNotFound(t *testing.T) {
absPackagesDB: map[string]string{"yay": "core"},
}
cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
cmdBuilder,
cmdBuilder, newTestLogger(),
targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
assert.NoError(t, err)
@@ -192,7 +197,7 @@ func TestPKGBUILDReposRepoMode(t *testing.T) {
absPackagesDB: map[string]string{"yay": "core"},
}
cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
cmdBuilder,
cmdBuilder, newTestLogger(),
targets, parser.ModeRepo, "https://aur.archlinux.org", dir, false)
assert.NoError(t, err)
@@ -230,7 +235,7 @@ func TestPKGBUILDFull(t *testing.T) {
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)
assert.NoError(t, err)
@@ -268,7 +273,7 @@ func TestPKGBUILDReposMissingAUR(t *testing.T) {
absPackagesDB: map[string]string{"yay": "core"},
}
cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient,
cmdBuilder,
cmdBuilder, newTestLogger(),
targets, parser.ModeAny, "https://aur.archlinux.org", dir, false)
assert.NoError(t, err)

View File

@@ -9,6 +9,7 @@ import (
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v12/pkg/runtime"
"github.com/Jguer/yay/v12/pkg/settings"
"github.com/Jguer/yay/v12/pkg/text"
)
@@ -23,7 +24,7 @@ func anyExistInCache(pkgbuildDirs map[string]string) bool {
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],
) error {
if len(pkgbuildDirsByBase) == 0 {
@@ -49,25 +50,25 @@ func CleanFn(ctx context.Context, config *settings.Configuration, w io.Writer,
bases = append(bases, pkg)
}
toClean, errClean := selectionMenu(w, pkgbuildDirsByBase, bases, installed,
toClean, errClean := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed,
gotext.Get("Packages to cleanBuild?"),
settings.NoConfirm, config.AnswerClean, skipFunc)
settings.NoConfirm, run.Cfg.AnswerClean, skipFunc)
if errClean != nil {
return errClean
}
for i, base := range toClean {
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
}
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
}

View File

@@ -5,13 +5,13 @@ import (
"context"
"fmt"
"io"
"os"
"strings"
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
"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/exe"
"github.com/Jguer/yay/v12/pkg/text"
@@ -22,7 +22,7 @@ const (
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,
) error {
var errMulti multierror.MultiError
@@ -46,7 +46,7 @@ func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder,
}
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
}
@@ -145,7 +145,7 @@ func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgb
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],
) error {
if len(pkgbuildDirsByBase) == 0 {
@@ -157,23 +157,23 @@ func DiffFn(ctx context.Context, config *settings.Configuration, w io.Writer,
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 {
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
}
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{}
}
if errUpd := updatePkgbuildSeenRef(ctx, config.Runtime.CmdBuilder, pkgbuildDirsByBase, toDiff); errUpd != nil {
if errUpd := updatePkgbuildSeenRef(ctx, run.CmdBuilder, pkgbuildDirsByBase, toDiff); errUpd != nil {
return errUpd
}

View File

@@ -14,6 +14,7 @@ import (
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v12/pkg/runtime"
"github.com/Jguer/yay/v12/pkg/settings"
"github.com/Jguer/yay/v12/pkg/text"
)
@@ -59,7 +60,7 @@ func editor(log *text.Logger, editorConfig, editorFlags string, noConfirm bool)
for {
log.Infoln(gotext.Get("Edit PKGBUILD with?"))
editorInput, err := text.GetInput(os.Stdin, "", noConfirm)
editorInput, err := log.GetInput("", noConfirm)
if err != nil {
log.Errorln(err)
continue
@@ -113,7 +114,7 @@ func editPkgbuilds(log *text.Logger, pkgbuildDirs map[string]string, bases []str
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],
) error {
if len(pkgbuildDirsByBase) == 0 {
@@ -125,21 +126,21 @@ func EditFn(ctx context.Context, cfg *settings.Configuration, w io.Writer,
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 {
return errMenu
}
// 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
}
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{}
}

View File

@@ -2,7 +2,6 @@ package menus
import (
"fmt"
"io"
"os"
"github.com/leonelquinteros/gotext"
@@ -14,7 +13,9 @@ import (
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 := ""
for n, pkgBase := range bases {
@@ -34,20 +35,20 @@ func pkgbuildNumberMenu(w io.Writer, pkgbuildDirs map[string]string, bases []str
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,
) ([]string, error) {
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 {
return nil, err
}

View File

@@ -4,11 +4,9 @@ import (
"bytes"
"context"
"encoding/xml"
"fmt"
"html"
"io"
"net/http"
"os"
"strings"
"time"
@@ -23,13 +21,13 @@ type item struct {
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
date, err := time.Parse(time.RFC1123Z, item.PubDate)
if err != nil {
fmt.Fprintln(os.Stderr, err)
logger.Errorln(err)
} else {
fd = text.FormatTime(int(date.Unix()))
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 {
desc := strings.TrimSpace(parseNews(item.Description))
fmt.Println(desc)
logger.Println(desc)
}
}
@@ -60,7 +58,9 @@ type rss struct {
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)
if err != nil {
return err
@@ -87,11 +87,11 @@ func PrintNewsFeed(ctx context.Context, client *http.Client, cutOffDate time.Tim
if bottomUp {
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 {
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)
}
}

View File

@@ -8,12 +8,15 @@ import (
"io"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/bradleyjkemp/cupaloy"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
"github.com/Jguer/yay/v12/pkg/text"
)
const lastNews = `
@@ -135,17 +138,16 @@ func TestPrintNewsFeed(t *testing.T) {
defer gock.Off()
rescueStdout := os.Stdout
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)
w.Close()
out, _ := io.ReadAll(r)
cupaloy.SnapshotT(t, out)
os.Stdout = rescueStdout
})
}
}
@@ -164,15 +166,14 @@ func TestPrintNewsFeedSameDay(t *testing.T) {
defer gock.Off()
rescueStdout := os.Stdout
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)
w.Close()
out, _ := io.ReadAll(r)
cupaloy.SnapshotT(t, out)
os.Stdout = rescueStdout
}

View File

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

View File

@@ -7,19 +7,19 @@ import (
"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)
for _, target := range targets {
dbName, _ := text.SplitDBFromName(target)
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
}
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
}

View File

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

View File

@@ -1,4 +1,4 @@
package settings
package runtime
import (
"fmt"
@@ -10,7 +10,7 @@ import (
"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 := "/"
if value, _, exists := cmdArgs.GetArg("root", "r"); exists {
root = value

View File

@@ -1,7 +1,7 @@
//go:build !integration
// +build !integration
package settings
package runtime
import (
"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.NotNil(t, pacmanConf)
assert.Equal(t, color, false)

View File

@@ -1,4 +1,4 @@
package settings
package runtime
import (
"context"
@@ -9,8 +9,8 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v12/pkg/db"
"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/parser"
"github.com/Jguer/yay/v12/pkg/text"
@@ -24,6 +24,7 @@ import (
)
type Runtime struct {
Cfg *settings.Configuration
QueryBuilder query.Builder
PacmanConf *pacmanconf.Config
VCSStore vcs.Store
@@ -31,13 +32,12 @@ type Runtime struct {
HTTPClient *http.Client
VoteClient *vote.Client
AURClient aur.QueryClient
DBExecutor db.Executor
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")
cmdBuilder := cfg.CmdBuilder(nil)
runner := exe.NewOSRunner(logger.Child("runner"))
httpClient := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
@@ -86,6 +86,16 @@ func BuildRuntime(cfg *Configuration, cmdArgs *parser.Arguments, version string)
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(
cfg.VCSFilePath, cmdBuilder,
logger.Child("vcs"))
@@ -94,17 +104,23 @@ func BuildRuntime(cfg *Configuration, cmdArgs *parser.Arguments, version string)
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,
CmdBuilder: cmdBuilder,
HTTPClient: &http.Client{},
VoteClient: voteClient,
AURClient: aurCache,
DBExecutor: nil,
Logger: text.NewLogger(os.Stdout, os.Stderr, os.Stdin, cfg.Debug, "runtime"),
Logger: logger,
}
return runtime, nil
return run, nil
}

View File

@@ -1,16 +1,17 @@
//go:build !integration
// +build !integration
package settings_test
package runtime_test
import (
"testing"
"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/parser"
"github.com/Jguer/yay/v12/pkg/text"
)
func TestBuildRuntime(t *testing.T) {
@@ -23,24 +24,23 @@ func TestBuildRuntime(t *testing.T) {
AURRPCURL: "https://aur.archlinux.org/rpc",
BuildDir: "/tmp",
VCSFilePath: "",
Runtime: &settings.Runtime{Logger: text.NewLogger(nil, nil, nil, false, "")},
PacmanConf: "../../testdata/pacman.conf",
}
cmdArgs := parser.MakeArguments()
version := "1.0.0"
// 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.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)
}

View File

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

View File

@@ -9,7 +9,6 @@ import (
"path/filepath"
"strings"
"github.com/Jguer/yay/v12/pkg/settings/exe"
"github.com/Jguer/yay/v12/pkg/settings/parser"
"github.com/Jguer/yay/v12/pkg/text"
@@ -24,53 +23,52 @@ var NoConfirm = false
// Configuration stores yay's config.
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:"-"`
VCSFilePath string `json:"-"`
@@ -237,19 +235,16 @@ func DefaultConfig(version string) *Configuration {
Debug: false,
UseRPC: 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)
cacheHome, errCache := getCacheHome()
if errCache != nil {
text.Errorln(errCache)
if errCache != nil && logger != nil {
logger.Errorln(errCache)
}
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"),
}
}

View File

@@ -36,7 +36,7 @@ func TestNewConfig(t *testing.T) {
_, err = f.WriteString(string(configJSON))
assert.NoError(t, err)
newConfig, err := NewConfig(GetConfigPath(), "v1.0.0")
newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0")
assert.NoError(t, err)
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))
assert.NoError(t, err)
newConfig, err := NewConfig(GetConfigPath(), "v1.0.0")
newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0")
assert.NoError(t, err)
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))
assert.NoError(t, err)
newConfig, err := NewConfig(GetConfigPath(), "v1.0.0")
newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0")
assert.NoError(t, err)
assert.Equal(t, filepath.Join(homeDir, "test-build-dir"), newConfig.BuildDir)

View File

@@ -15,6 +15,7 @@ import (
mapset "github.com/deckarep/golang-set/v2"
"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/text"
)
@@ -38,7 +39,6 @@ type ICmdBuilder interface {
BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd
BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd
AddMakepkgFlag(string)
SetPacmanDBPath(string)
SudoLoop()
}
@@ -60,6 +60,26 @@ type CmdBuilder struct {
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 {
args := make([]string, len(c.GPGFlags), len(c.GPGFlags)+len(extraArgs))
copy(args, c.GPGFlags)
@@ -135,10 +155,6 @@ func (c *CmdBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs
return cmd
}
func (c *CmdBuilder) SetPacmanDBPath(dbPath string) {
c.PacmanDBPath = dbPath
}
// deElevateCommand, `systemd-run` code based on pikaur.
func (c *CmdBuilder) deElevateCommand(ctx context.Context, cmd *exec.Cmd) *exec.Cmd {
if os.Geteuid() != 0 {
@@ -242,7 +258,7 @@ func (c *CmdBuilder) waitLock(dbPath string) {
time.Sleep(3 * time.Second)
if _, err := os.Stat(lockDBPath); err != nil {
fmt.Println()
c.Log.Println()
return
}

View File

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

View File

@@ -36,6 +36,10 @@ type MockRunner struct {
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 {
var res *exec.Cmd
if m.BuildMakepkgCmdFn != nil {

View File

@@ -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,
) error {
saveConfig := false
@@ -53,7 +53,7 @@ func (c *Configuration) RunMigrations(migrations []configMigration,
for _, migration := range migrations {
if db.VerCmp(migration.TargetVersion(), c.Version) > 0 {
if migration.Do(c) {
text.Infoln("Config migration executed (",
logger.Infoln("Config migration executed (",
migration.TargetVersion(), "):", migration)
saveConfig = true

View File

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

62
pkg/sync/build/errors.go Normal file
View File

@@ -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)
}

448
pkg/sync/build/installer.go Normal file
View File

@@ -0,0 +1,448 @@
package build
import (
"context"
"fmt"
"os"
"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/settings"
"github.com/Jguer/yay/v12/pkg/settings/exe"
"github.com/Jguer/yay/v12/pkg/settings/parser"
"github.com/Jguer/yay/v12/pkg/text"
"github.com/Jguer/yay/v12/pkg/vcs"
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
)
type (
PostInstallHookFunc func(ctx context.Context) error
Installer struct {
dbExecutor db.Executor
postInstallHooks []PostInstallHookFunc
failedAndIgnored map[string]error
exeCmd exe.ICmdBuilder
vcsStore vcs.Store
targetMode parser.TargetMode
rebuildMode parser.RebuildMode
origTargets mapset.Set[string]
downloadOnly bool
log *text.Logger
manualConfirmRequired bool
}
)
func NewInstaller(dbExecutor db.Executor,
exeCmd exe.ICmdBuilder, vcsStore vcs.Store, targetMode parser.TargetMode,
rebuildMode parser.RebuildMode, downloadOnly bool, logger *text.Logger,
) *Installer {
return &Installer{
dbExecutor: dbExecutor,
postInstallHooks: []PostInstallHookFunc{},
failedAndIgnored: map[string]error{},
exeCmd: exeCmd,
vcsStore: vcsStore,
targetMode: targetMode,
rebuildMode: rebuildMode,
downloadOnly: downloadOnly,
log: logger,
manualConfirmRequired: true,
}
}
func (installer *Installer) CompileFailedAndIgnored() (map[string]error, error) {
if len(installer.failedAndIgnored) == 0 {
return installer.failedAndIgnored, nil
}
return installer.failedAndIgnored, &FailedIgnoredPkgError{
pkgErrors: installer.failedAndIgnored,
}
}
func (installer *Installer) AddPostInstallHook(hook PostInstallHookFunc) {
if hook == nil {
return
}
installer.postInstallHooks = append(installer.postInstallHooks, hook)
}
func (installer *Installer) RunPostInstallHooks(ctx context.Context) error {
var errMulti multierror.MultiError
for _, hook := range installer.postInstallHooks {
if err := hook(ctx); err != nil {
errMulti.Add(err)
}
}
return errMulti.Return()
}
func (installer *Installer) Install(ctx context.Context,
cmdArgs *parser.Arguments,
targets []map[string]*dep.InstallInfo,
pkgBuildDirs map[string]string,
excluded []string,
manualConfirmRequired bool,
) error {
installer.log.Debugln("manualConfirmRequired:", manualConfirmRequired)
installer.manualConfirmRequired = manualConfirmRequired
installer.origTargets = mapset.NewThreadUnsafeSet[string]()
for _, targetString := range cmdArgs.Targets {
installer.origTargets.Add(dep.ToTarget(targetString).Name)
}
installer.log.Debugln("origTargets:", installer.origTargets)
// Reorganize targets into layers of dependencies
var errMulti multierror.MultiError
for i := len(targets) - 1; i >= 0; i-- {
lastLayer := i == 0
errI := installer.handleLayer(ctx, cmdArgs, targets[i], pkgBuildDirs, lastLayer, excluded)
if errI == nil && lastLayer {
// success after rollups
return nil
}
if errI != nil {
errMulti.Add(errI)
if lastLayer {
break
}
// rollup
installer.log.Warnln(gotext.Get("Failed to install layer, rolling up to next layer."), "error:", errI)
targets[i-1] = mergeLayers(targets[i-1], targets[i])
}
}
return errMulti.Return()
}
func mergeLayers(layer1, layer2 map[string]*dep.InstallInfo) map[string]*dep.InstallInfo {
for name, info := range layer2 {
layer1[name] = info
}
return layer1
}
func (installer *Installer) appendNoConfirm() bool {
return !installer.manualConfirmRequired || settings.NoConfirm
}
func (installer *Installer) handleLayer(ctx context.Context,
cmdArgs *parser.Arguments,
layer map[string]*dep.InstallInfo,
pkgBuildDirs map[string]string,
lastLayer bool,
excluded []string,
) error {
// Install layer
nameToBaseMap := make(map[string]string, 0)
syncDeps, syncExp, syncGroups := mapset.NewThreadUnsafeSet[string](),
mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
aurDeps, aurExp := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()
upgradeSync := false
for name, info := range layer {
switch info.Source {
case dep.AUR, dep.SrcInfo:
nameToBaseMap[name] = *info.AURBase
switch info.Reason {
case dep.Explicit:
if cmdArgs.ExistsArg("asdeps", "asdep") {
aurDeps.Add(name)
} else {
aurExp.Add(name)
}
case dep.Dep, dep.MakeDep, dep.CheckDep:
aurDeps.Add(name)
}
case dep.Sync:
if info.Upgrade {
upgradeSync = true
continue // do not add to targets, let pacman handle it
}
compositePkgName := fmt.Sprintf("%s/%s", *info.SyncDBName, name)
if info.IsGroup {
syncGroups.Add(compositePkgName)
continue
}
switch info.Reason {
case dep.Explicit:
if cmdArgs.ExistsArg("asdeps", "asdep") {
syncDeps.Add(compositePkgName)
} else {
syncExp.Add(compositePkgName)
}
case dep.Dep, dep.MakeDep, dep.CheckDep:
syncDeps.Add(compositePkgName)
}
}
}
installer.log.Debugln("syncDeps", syncDeps, "SyncExp", syncExp,
"aurDeps", aurDeps, "aurExp", aurExp, "upgrade", upgradeSync)
errShow := installer.installSyncPackages(ctx, cmdArgs, syncDeps, syncExp, syncGroups,
excluded, upgradeSync, installer.appendNoConfirm())
if errShow != nil {
return ErrInstallRepoPkgs
}
errAur := installer.installAURPackages(ctx, cmdArgs, aurDeps, aurExp,
nameToBaseMap, pkgBuildDirs, true, lastLayer, installer.appendNoConfirm())
return errAur
}
func (installer *Installer) installAURPackages(ctx context.Context,
cmdArgs *parser.Arguments,
aurDepNames, aurExpNames mapset.Set[string],
nameToBase, pkgBuildDirsByBase map[string]string,
installIncompatible bool,
lastLayer bool,
noConfirm bool,
) error {
all := aurDepNames.Union(aurExpNames).ToSlice()
if len(all) == 0 {
return nil
}
deps, exps := make([]string, 0, aurDepNames.Cardinality()), make([]string, 0, aurExpNames.Cardinality())
pkgArchives := make([]string, 0, len(exps)+len(deps))
for _, name := range all {
base := nameToBase[name]
dir := pkgBuildDirsByBase[base]
pkgdests, errMake := installer.buildPkg(ctx, dir, base,
installIncompatible, cmdArgs.ExistsArg("needed"), installer.origTargets.Contains(name))
if errMake != nil {
if !lastLayer {
return fmt.Errorf("%s - %w", gotext.Get("error making: %s", base), errMake)
}
installer.failedAndIgnored[name] = errMake
installer.log.Errorln(gotext.Get("error making: %s", base), "-", errMake)
continue
}
if len(pkgdests) == 0 {
installer.log.Warnln(gotext.Get("nothing to install for %s", text.Cyan(base)))
continue
}
newPKGArchives, hasDebug, err := installer.getNewTargets(pkgdests, name)
if err != nil {
return err
}
pkgArchives = append(pkgArchives, newPKGArchives...)
if isDep := installer.isDep(cmdArgs, aurExpNames, name); isDep {
deps = append(deps, name)
} else {
exps = append(exps, name)
}
if hasDebug {
deps = append(deps, name+"-debug")
}
}
if err := installPkgArchive(ctx, installer.exeCmd, installer.targetMode,
installer.vcsStore, cmdArgs, pkgArchives, noConfirm); err != nil {
return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err)
}
if err := setInstallReason(ctx, installer.exeCmd, installer.targetMode, cmdArgs, deps, exps); err != nil {
return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err)
}
return nil
}
func (installer *Installer) buildPkg(ctx context.Context,
dir, base string,
installIncompatible, needed, isTarget bool,
) (map[string]string, error) {
args := []string{"--nobuild", "-fC"}
if installIncompatible {
args = append(args, "--ignorearch")
}
// pkgver bump
if err := installer.exeCmd.Show(
installer.exeCmd.BuildMakepkgCmd(ctx, dir, args...)); err != nil {
return nil, err
}
pkgdests, pkgVersion, errList := parsePackageList(ctx, installer.exeCmd, dir)
if errList != nil {
return nil, errList
}
switch {
case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly:
args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
pkgdests = map[string]string{}
installer.log.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion)))
case installer.skipAlreadyBuiltPkg(isTarget, pkgdests):
args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"}
installer.log.Warnln(gotext.Get("%s already made -- skipping build", text.Cyan(base+"-"+pkgVersion)))
default:
args = []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"}
if installIncompatible {
args = append(args, "--ignorearch")
}
}
errMake := installer.exeCmd.Show(
installer.exeCmd.BuildMakepkgCmd(ctx,
dir, args...))
if errMake != nil {
return nil, errMake
}
if installer.downloadOnly {
return map[string]string{}, nil
}
return pkgdests, nil
}
func (installer *Installer) pkgsAreAlreadyInstalled(pkgdests map[string]string, pkgVersion string) bool {
for pkgName := range pkgdests {
if !installer.dbExecutor.IsCorrectVersionInstalled(pkgName, pkgVersion) {
return false
}
}
return true
}
func pkgsAreBuilt(logger *text.Logger, pkgdests map[string]string) bool {
for _, pkgdest := range pkgdests {
if _, err := os.Stat(pkgdest); err != nil {
logger.Debugln("pkgIsBuilt:", pkgdest, "does not exist")
return false
}
}
return true
}
func (installer *Installer) skipAlreadyBuiltPkg(isTarget bool, pkgdests map[string]string) bool {
switch installer.rebuildMode {
case parser.RebuildModeNo:
return pkgsAreBuilt(installer.log, pkgdests)
case parser.RebuildModeYes:
return !isTarget && pkgsAreBuilt(installer.log, pkgdests)
// case parser.RebuildModeTree: // TODO
// case parser.RebuildModeAll: // TODO
default:
// same as RebuildModeNo
return pkgsAreBuilt(installer.log, pkgdests)
}
}
func (*Installer) isDep(cmdArgs *parser.Arguments, aurExpNames mapset.Set[string], name string) bool {
switch {
case cmdArgs.ExistsArg("asdeps", "asdep"):
return true
case cmdArgs.ExistsArg("asexplicit", "asexp"):
return false
case aurExpNames.Contains(name):
return false
}
return true
}
func (installer *Installer) getNewTargets(pkgdests map[string]string, name string,
) (archives []string, good bool, err error) {
pkgdest, ok := pkgdests[name]
if !ok {
return nil, false, &PkgDestNotInListError{name: name}
}
pkgArchives := make([]string, 0, 2)
if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) {
return nil, false, &FindPkgDestError{name: name, pkgDest: pkgdest}
}
pkgArchives = append(pkgArchives, pkgdest)
debugName := name + "-debug"
pkgdestDebug, ok := pkgdests[debugName]
if ok {
if _, errStat := os.Stat(pkgdestDebug); errStat == nil {
pkgArchives = append(pkgArchives, pkgdestDebug)
} else {
ok = false
}
}
return pkgArchives, ok, nil
}
func (installer *Installer) installSyncPackages(ctx context.Context, cmdArgs *parser.Arguments,
syncDeps, // repo targets that are deps
syncExp mapset.Set[string], // repo targets that are exp
syncGroups mapset.Set[string], // repo targets that are groups
excluded []string,
upgrade bool, // run even without targets
noConfirm bool,
) error {
repoTargets := syncDeps.Union(syncExp).Union(syncGroups).ToSlice()
if len(repoTargets) == 0 && !upgrade {
return nil
}
arguments := cmdArgs.Copy()
arguments.DelArg("asdeps", "asdep")
arguments.DelArg("asexplicit", "asexp")
arguments.DelArg("i", "install")
arguments.Op = "S"
arguments.ClearTargets()
arguments.AddTarget(repoTargets...)
// Don't upgrade all repo packages if only AUR upgrades are specified
if installer.targetMode == parser.ModeAUR {
arguments.DelArg("u", "upgrades")
}
if len(excluded) > 0 {
arguments.CreateOrAppendOption("ignore", excluded...)
}
errShow := installer.exeCmd.Show(installer.exeCmd.BuildPacmanCmd(ctx,
arguments, installer.targetMode, noConfirm))
if errShow != nil {
return errShow
}
if errD := asdeps(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncDeps.ToSlice()); errD != nil {
return errD
}
if errE := asexp(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncExp.ToSlice()); errE != nil {
return errE
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,164 @@
package build
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/leonelquinteros/gotext"
"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/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,
cmdBuilder exe.ICmdBuilder,
mode parser.TargetMode,
cmdArgs *parser.Arguments, pkgs []string, exp bool,
) error {
if len(pkgs) == 0 {
return nil
}
cmdArgs = cmdArgs.CopyGlobal()
if exp {
if err := cmdArgs.AddArg("q", "D", "asexplicit"); err != nil {
return err
}
} else {
if err := cmdArgs.AddArg("q", "D", "asdeps"); err != nil {
return err
}
}
for _, compositePkgName := range pkgs {
pkgSplit := strings.Split(compositePkgName, "/")
pkgName := pkgSplit[0]
if len(pkgSplit) > 1 {
pkgName = pkgSplit[1]
}
cmdArgs.AddTarget(pkgName)
}
if err := cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx,
cmdArgs, mode, settings.NoConfirm)); err != nil {
return &SetPkgReasonError{exp: exp}
}
return nil
}
func asdeps(ctx context.Context,
cmdBuilder exe.ICmdBuilder,
mode parser.TargetMode, cmdArgs *parser.Arguments, pkgs []string,
) error {
return setPkgReason(ctx, cmdBuilder, mode, cmdArgs, pkgs, false)
}
func asexp(ctx context.Context,
cmdBuilder exe.ICmdBuilder,
mode parser.TargetMode, cmdArgs *parser.Arguments, pkgs []string,
) error {
return setPkgReason(ctx, cmdBuilder, mode, cmdArgs, pkgs, true)
}
func parsePackageList(ctx context.Context, cmdBuilder exe.ICmdBuilder,
dir string,
) (pkgdests map[string]string, pkgVersion string, err error) {
stdout, stderr, err := cmdBuilder.Capture(
cmdBuilder.BuildMakepkgCmd(ctx, dir, "--packagelist"))
if err != nil {
return nil, "", fmt.Errorf("%s %w", stderr, err)
}
lines := strings.Split(stdout, "\n")
pkgdests = make(map[string]string)
for _, line := range lines {
if line == "" {
continue
}
fileName := filepath.Base(line)
split := strings.Split(fileName, "-")
if len(split) < 4 {
return nil, "", errors.New(gotext.Get("cannot find package name: %v", split))
}
// pkgname-pkgver-pkgrel-arch.pkgext
// This assumes 3 dashes after the pkgname, Will cause an error
// if the PKGEXT contains a dash. Please no one do that.
pkgName := strings.Join(split[:len(split)-3], "-")
pkgVersion = strings.Join(split[len(split)-3:len(split)-1], "-")
pkgdests[pkgName] = line
}
if len(pkgdests) == 0 {
return nil, "", &NoPkgDestsFoundError{dir}
}
return pkgdests, pkgVersion, nil
}

View File

@@ -4,8 +4,6 @@ import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
@@ -50,7 +48,7 @@ type GPGCmdBuilder interface {
// CheckPgpKeys iterates through the keys listed in the PKGBUILDs and if needed,
// 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,
) ([]string, error) {
// 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
}
str, err := formatKeysToImport(problematic)
str, err := formatKeysToImport(logger, problematic)
if err != nil {
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
}
// 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 {
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
// 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 {
return "", errors.New(gotext.Get("no keys to import"))
}
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 {
pkglist := ""
@@ -124,7 +121,7 @@ func formatKeysToImport(keys pgpKeySet) (string, error) {
}
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

View File

@@ -6,6 +6,7 @@ package pgp
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"sort"
@@ -17,8 +18,13 @@ import (
"github.com/stretchr/testify/require"
"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 {
srcinfo := gosrc.Srcinfo{}
srcinfo.Pkgbase = pkgbase
@@ -228,7 +234,7 @@ func TestCheckPgpKeys(t *testing.T) {
GPGFlags: []string{"--homedir /tmp"},
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.CaptureCalls, len(tt.wantCapture))

View File

@@ -11,28 +11,28 @@ import (
"github.com/Jguer/yay/v12/pkg/db"
"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/exe"
"github.com/Jguer/yay/v12/pkg/sync/srcinfo/pgp"
"github.com/Jguer/yay/v12/pkg/text"
"github.com/Jguer/yay/v12/pkg/vcs"
)
// TODO: add tests
type Service struct {
dbExecutor db.Executor
cfg *settings.Configuration
cmdBuilder exe.ICmdBuilder
cmdBuilder pgp.GPGCmdBuilder
vcsStore vcs.Store
log *text.Logger
pkgBuildDirs map[string]string
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,
) (*Service, error) {
srcinfos, err := ParseSrcinfoFilesByBase(pkgBuildDirs, true)
srcinfos, err := ParseSrcinfoFilesByBase(logger, pkgBuildDirs, true)
if err != nil {
panic(err)
}
@@ -43,6 +43,7 @@ func NewService(dbExecutor db.Executor, cfg *settings.Configuration,
vcsStore: vcsStore,
pkgBuildDirs: pkgBuildDirs,
srcInfos: srcinfos,
log: logger,
}, nil
}
@@ -68,7 +69,7 @@ nextpkg:
}
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
}
@@ -83,15 +84,15 @@ func (s *Service) UpdateVCSStore(ctx context.Context, targets []map[string]*dep.
for i := range srcinfo.Packages {
for j := range targets {
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
}
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
}
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)
}
}
@@ -100,17 +101,17 @@ func (s *Service) UpdateVCSStore(ctx context.Context, targets []map[string]*dep.
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)
k := 0
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"))
if err != nil {
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
}

View File

@@ -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
pkg/sync/sync.go Normal file
View File

@@ -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
}

View File

@@ -0,0 +1,136 @@
package workdir
import (
"context"
"fmt"
"runtime"
"sync"
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v12/pkg/multierror"
"github.com/Jguer/yay/v12/pkg/settings/exe"
"github.com/Jguer/yay/v12/pkg/text"
)
type ErrDownloadSource struct {
inner error
pkgName string
errOut string
}
func (e ErrDownloadSource) Error() string {
return fmt.Sprintln(gotext.Get("error downloading sources: %s", text.Cyan(e.pkgName)),
"\n\t context:", e.inner.Error(), "\n\t", e.errOut)
}
func (e *ErrDownloadSource) Unwrap() error {
return e.inner
}
func downloadPKGBUILDSource(ctx context.Context,
cmdBuilder exe.ICmdBuilder, pkgBuildDir string, installIncompatible bool,
) error {
args := []string{"--verifysource", "--skippgpcheck", "-Ccf"}
if installIncompatible {
args = append(args, "--ignorearch")
}
err := cmdBuilder.Show(
cmdBuilder.BuildMakepkgCmd(ctx, pkgBuildDir, args...))
if err != nil {
return ErrDownloadSource{inner: err, pkgName: pkgBuildDir}
}
return nil
}
func downloadPKGBUILDSourceWorker(ctx context.Context, wg *sync.WaitGroup,
dirChannel <-chan string, valOut chan<- string, errOut chan<- error,
cmdBuilder exe.ICmdBuilder, incompatible bool,
) {
for pkgBuildDir := range dirChannel {
err := downloadPKGBUILDSource(ctx, cmdBuilder, pkgBuildDir, incompatible)
if err != nil {
errOut <- ErrDownloadSource{inner: err, pkgName: pkgBuildDir, errOut: ""}
} else {
valOut <- pkgBuildDir
}
}
wg.Done()
}
func downloadPKGBUILDSourceFanout(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgBuildDirs map[string]string,
incompatible bool, maxConcurrentDownloads int,
) error {
if len(pkgBuildDirs) == 0 {
return nil // no work to do
}
if len(pkgBuildDirs) == 1 {
for _, pkgBuildDir := range pkgBuildDirs {
return downloadPKGBUILDSource(ctx, cmdBuilder, pkgBuildDir, incompatible)
}
}
var (
numOfWorkers = runtime.NumCPU()
wg = &sync.WaitGroup{}
c = make(chan string)
fanInChanValues = make(chan string)
fanInChanErrors = make(chan error)
)
if maxConcurrentDownloads != 0 {
numOfWorkers = maxConcurrentDownloads
}
dedupSet := mapset.NewThreadUnsafeSet[string]()
go func() {
for _, pkgbuildDir := range pkgBuildDirs {
if !dedupSet.Contains(pkgbuildDir) {
c <- pkgbuildDir
dedupSet.Add(pkgbuildDir)
}
}
close(c)
}()
// Launch Workers
wg.Add(numOfWorkers)
for s := 0; s < numOfWorkers; s++ {
go downloadPKGBUILDSourceWorker(ctx, wg, c,
fanInChanValues, fanInChanErrors, cmdBuilder, incompatible)
}
go func() {
wg.Wait()
close(fanInChanValues)
close(fanInChanErrors)
}()
returnErr := multierror.MultiError{}
receiver:
for {
select {
case _, ok := <-fanInChanValues:
if !ok {
break receiver
}
case err, ok := <-fanInChanErrors:
if !ok {
break receiver
}
returnErr.Add(err)
}
}
return returnErr.Return()
}

View File

@@ -0,0 +1,158 @@
//go:build !integration
// +build !integration
package workdir
import (
"context"
"fmt"
"os/exec"
"path/filepath"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/Jguer/yay/v12/pkg/multierror"
"github.com/Jguer/yay/v12/pkg/settings/exe"
)
type TestMakepkgBuilder struct {
exe.ICmdBuilder
parentBuilder *exe.CmdBuilder
test *testing.T
passes uint32
want string
wantDir string
showError error
}
func (z *TestMakepkgBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd {
cmd := z.parentBuilder.BuildMakepkgCmd(ctx, dir, extraArgs...)
if z.want != "" {
assert.Contains(z.test, cmd.String(), z.want)
}
if z.wantDir != "" {
assert.Equal(z.test, z.wantDir, cmd.Dir)
}
atomic.AddUint32(&z.passes, 1)
return cmd
}
func (z *TestMakepkgBuilder) Show(cmd *exec.Cmd) error {
return z.showError
}
// GIVEN 1 package
// WHEN downloadPKGBUILDSource is called
// THEN 1 call should be made to makepkg with the specified parameters and dir
func Test_downloadPKGBUILDSource(t *testing.T) {
t.Parallel()
cmdBuilder := &TestMakepkgBuilder{
parentBuilder: &exe.CmdBuilder{MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg"},
test: t,
want: "makepkg --nocheck --config /etc/not.conf --verifysource --skippgpcheck -Ccf",
wantDir: "/tmp/yay-bin",
}
err := downloadPKGBUILDSource(context.Background(), cmdBuilder, filepath.Join("/tmp", "yay-bin"), false)
assert.NoError(t, err)
assert.Equal(t, 1, int(cmdBuilder.passes))
}
// GIVEN 1 package
// WHEN downloadPKGBUILDSource is called
// THEN 1 call should be made to makepkg which should return error
func Test_downloadPKGBUILDSourceError(t *testing.T) {
t.Parallel()
cmdBuilder := &TestMakepkgBuilder{
parentBuilder: &exe.CmdBuilder{MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg"},
test: t,
want: "makepkg --nocheck --config /etc/not.conf --verifysource --skippgpcheck -Ccf",
wantDir: "/tmp/yay-bin",
showError: &exec.ExitError{},
}
err := downloadPKGBUILDSource(context.Background(), cmdBuilder, filepath.Join("/tmp", "yay-bin"), false)
assert.Error(t, err)
assert.EqualError(t, err, "error downloading sources: \x1b[36m/tmp/yay-bin\x1b[0m \n\t context: <nil> \n\t \n")
}
// GIVEN 5 packages
// WHEN downloadPKGBUILDSourceFanout is called
// THEN 5 calls should be made to makepkg
func Test_downloadPKGBUILDSourceFanout(t *testing.T) {
t.Parallel()
pkgBuildDirs := map[string]string{
"yay": "/tmp/yay",
"yay-bin": "/tmp/yay-bin",
"yay-git": "/tmp/yay-git",
"yay-v11": "/tmp/yay-v11",
"yay-v12": "/tmp/yay-v12",
}
for _, maxConcurrentDownloads := range []int{0, 3} {
t.Run(fmt.Sprintf("maxconcurrentdownloads set to %d", maxConcurrentDownloads), func(t *testing.T) {
cmdBuilder := &TestMakepkgBuilder{
parentBuilder: &exe.CmdBuilder{
MakepkgConfPath: "/etc/not.conf",
MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg",
},
test: t,
}
err := downloadPKGBUILDSourceFanout(context.Background(), cmdBuilder, pkgBuildDirs, true, maxConcurrentDownloads)
assert.NoError(t, err)
assert.Equal(t, 5, int(cmdBuilder.passes))
})
}
}
// GIVEN 1 package
// WHEN downloadPKGBUILDSourceFanout is called
// THEN 1 calls should be made to makepkg without concurrency
func Test_downloadPKGBUILDSourceFanoutNoCC(t *testing.T) {
t.Parallel()
cmdBuilder := &TestMakepkgBuilder{
parentBuilder: &exe.CmdBuilder{
MakepkgConfPath: "/etc/not.conf",
MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg",
},
test: t,
}
pkgBuildDirs := map[string]string{"yay": "/tmp/yay"}
err := downloadPKGBUILDSourceFanout(context.Background(), cmdBuilder, pkgBuildDirs, false, 0)
assert.NoError(t, err)
assert.Equal(t, 1, int(cmdBuilder.passes))
}
// GIVEN 5 packages
// WHEN downloadPKGBUILDSourceFanout is called
// THEN 5 calls should be made to makepkg
func Test_downloadPKGBUILDSourceFanoutError(t *testing.T) {
t.Parallel()
cmdBuilder := &TestMakepkgBuilder{
parentBuilder: &exe.CmdBuilder{
MakepkgConfPath: "/etc/not.conf",
MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg",
},
test: t,
showError: &exec.ExitError{},
}
pkgBuildDirs := map[string]string{
"yay": "/tmp/yay",
"yay-bin": "/tmp/yay-bin",
"yay-git": "/tmp/yay-git",
"yay-v11": "/tmp/yay-v11",
"yay-v12": "/tmp/yay-v12",
}
err := downloadPKGBUILDSourceFanout(context.Background(), cmdBuilder, pkgBuildDirs, false, 0)
assert.Error(t, err)
assert.Equal(t, 5, int(cmdBuilder.passes))
assert.Len(t, err.(*multierror.MultiError).Errors, 5)
}

62
pkg/sync/workdir/clean.go Normal file
View File

@@ -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
pkg/sync/workdir/merge.go Normal file
View File

@@ -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
}

View File

@@ -0,0 +1,259 @@
package workdir
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/Jguer/yay/v12/pkg/db"
"github.com/Jguer/yay/v12/pkg/dep"
"github.com/Jguer/yay/v12/pkg/download"
"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/exe"
"github.com/Jguer/yay/v12/pkg/settings/parser"
"github.com/Jguer/yay/v12/pkg/sync/build"
"github.com/Jguer/yay/v12/pkg/text"
gosrc "github.com/Morganamilo/go-srcinfo"
mapset "github.com/deckarep/golang-set/v2"
"github.com/leonelquinteros/gotext"
)
type HookType string
const (
// PreDownloadSourcesHook is called before sourcing a package
PreDownloadSourcesHook HookType = "pre-download-sources"
)
type HookFn func(ctx context.Context, run *runtime.Runtime, w io.Writer,
pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
) error
type Hook struct {
Name string
Hookfn HookFn
Type HookType
}
type Preparer struct {
dbExecutor db.Executor
cmdBuilder exe.ICmdBuilder
cfg *settings.Configuration
hooks []Hook
downloadSources bool
log *text.Logger
makeDeps []string
}
func NewPreparerWithoutHooks(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
cfg *settings.Configuration, logger *text.Logger, downloadSources bool,
) *Preparer {
return &Preparer{
dbExecutor: dbExecutor,
cmdBuilder: cmdBuilder,
cfg: cfg,
hooks: []Hook{},
downloadSources: downloadSources,
log: logger,
}
}
func NewPreparer(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder,
cfg *settings.Configuration, logger *text.Logger,
) *Preparer {
preper := NewPreparerWithoutHooks(dbExecutor, cmdBuilder, cfg, logger, true)
if cfg.CleanMenu {
preper.hooks = append(preper.hooks, Hook{
Name: "clean",
Hookfn: menus.CleanFn,
Type: PreDownloadSourcesHook,
})
}
if cfg.DiffMenu {
preper.hooks = append(preper.hooks, Hook{
Name: "diff",
Hookfn: menus.DiffFn,
Type: PreDownloadSourcesHook,
})
}
if cfg.EditMenu {
preper.hooks = append(preper.hooks, Hook{
Name: "edit",
Hookfn: menus.EditFn,
Type: PreDownloadSourcesHook,
})
}
return preper
}
func (preper *Preparer) ShouldCleanAURDirs(run *runtime.Runtime, pkgBuildDirs map[string]string) build.PostInstallHookFunc {
if !preper.cfg.CleanAfter || len(pkgBuildDirs) == 0 {
return nil
}
preper.log.Debugln("added post install hook to clean up AUR dirs", pkgBuildDirs)
return func(ctx context.Context) error {
cleanAfter(ctx, run, run.CmdBuilder, pkgBuildDirs)
return nil
}
}
func (preper *Preparer) ShouldCleanMakeDeps(run *runtime.Runtime, cmdArgs *parser.Arguments) build.PostInstallHookFunc {
if len(preper.makeDeps) == 0 {
return nil
}
switch preper.cfg.RemoveMake {
case "yes":
break
case "no":
return nil
default:
isYesDefault := preper.cfg.RemoveMake == "askyes"
if !preper.log.ContinueTask(gotext.Get("Remove make dependencies after install?"),
isYesDefault, settings.NoConfirm) {
return nil
}
}
preper.log.Debugln("added post install hook to clean up AUR makedeps", preper.makeDeps)
return func(ctx context.Context) error {
return removeMake(ctx, preper.cfg, run.CmdBuilder, preper.makeDeps, cmdArgs)
}
}
func (preper *Preparer) Run(ctx context.Context, run *runtime.Runtime,
targets []map[string]*dep.InstallInfo,
) (pkgbuildDirsByBase map[string]string, err error) {
preper.Present(targets)
pkgBuildDirs, err := preper.PrepareWorkspace(ctx, run, targets)
if err != nil {
return nil, err
}
return pkgBuildDirs, nil
}
func (preper *Preparer) Present(targets []map[string]*dep.InstallInfo) {
pkgsBySourceAndReason := map[string]map[string][]string{}
for _, layer := range targets {
for pkgName, info := range layer {
source := dep.SourceNames[info.Source]
reason := dep.ReasonNames[info.Reason]
var pkgStr string
if info.Version != "" {
pkgStr = text.Cyan(fmt.Sprintf("%s-%s", pkgName, info.Version))
} else {
pkgStr = text.Cyan(pkgName)
}
if _, ok := pkgsBySourceAndReason[source]; !ok {
pkgsBySourceAndReason[source] = map[string][]string{}
}
pkgsBySourceAndReason[source][reason] = append(pkgsBySourceAndReason[source][reason], pkgStr)
if info.Reason == dep.MakeDep {
preper.makeDeps = append(preper.makeDeps, pkgName)
}
}
}
for source, pkgsByReason := range pkgsBySourceAndReason {
for reason, pkgs := range pkgsByReason {
preper.log.Printf(text.Bold("%s %s (%d):")+" %s\n",
source,
reason,
len(pkgs),
strings.Join(pkgs, ", "))
}
}
}
func (preper *Preparer) PrepareWorkspace(ctx context.Context,
run *runtime.Runtime, targets []map[string]*dep.InstallInfo,
) (map[string]string, error) {
aurBasesToClone := mapset.NewThreadUnsafeSet[string]()
pkgBuildDirsByBase := make(map[string]string, len(targets))
for _, layer := range targets {
for _, info := range layer {
if info.Source == dep.AUR {
pkgBase := *info.AURBase
pkgBuildDir := filepath.Join(preper.cfg.BuildDir, pkgBase)
if preper.needToCloneAURBase(info, pkgBuildDir) {
aurBasesToClone.Add(pkgBase)
}
pkgBuildDirsByBase[pkgBase] = pkgBuildDir
} else if info.Source == dep.SrcInfo {
pkgBase := *info.AURBase
pkgBuildDirsByBase[pkgBase] = *info.SrcinfoPath
}
}
}
if _, errA := download.AURPKGBUILDRepos(ctx,
preper.cmdBuilder, preper.log.Child("download"), aurBasesToClone.ToSlice(),
preper.cfg.AURURL, preper.cfg.BuildDir, false); errA != nil {
return nil, errA
}
if !preper.downloadSources {
return pkgBuildDirsByBase, nil
}
if err := mergePkgbuilds(ctx, preper.cmdBuilder, pkgBuildDirsByBase); err != nil {
return nil, err
}
remoteNames := preper.dbExecutor.InstalledRemotePackageNames()
remoteNamesCache := mapset.NewThreadUnsafeSet(remoteNames...)
for _, hookFn := range preper.hooks {
if hookFn.Type == PreDownloadSourcesHook {
if err := hookFn.Hookfn(ctx, run, os.Stdout, pkgBuildDirsByBase, remoteNamesCache); err != nil {
return nil, err
}
}
}
if errP := downloadPKGBUILDSourceFanout(ctx, preper.cmdBuilder,
pkgBuildDirsByBase, false, preper.cfg.MaxConcurrentDownloads); errP != nil {
preper.log.Errorln(errP)
}
return pkgBuildDirsByBase, nil
}
func (preper *Preparer) needToCloneAURBase(installInfo *dep.InstallInfo, pkgbuildDir string) bool {
if preper.cfg.ReDownload == "all" {
return true
}
srcinfoFile := filepath.Join(pkgbuildDir, ".SRCINFO")
if pkgbuild, err := gosrc.ParseFile(srcinfoFile); err == nil {
if db.VerCmp(pkgbuild.Version(), installInfo.Version) >= 0 {
preper.log.OperationInfoln(
gotext.Get("PKGBUILD up to date, skipping download: %s",
text.Cyan(*installInfo.AURBase)))
return false
}
}
return true
}

View File

@@ -0,0 +1,72 @@
//go:build !integration
// +build !integration
package workdir
import (
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"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
func TestPreDownloadSourcesHooks(t *testing.T) {
testCases := []struct {
name string
cfg *settings.Configuration
wantHook []string
}{
{
name: "clean, diff, edit",
cfg: &settings.Configuration{
CleanMenu: true,
DiffMenu: true,
EditMenu: true,
},
wantHook: []string{"clean", "diff", "edit"},
},
{
name: "clean, edit",
cfg: &settings.Configuration{
CleanMenu: true,
DiffMenu: false,
EditMenu: true,
},
wantHook: []string{"clean", "edit"},
},
{
name: "clean, diff",
cfg: &settings.Configuration{
CleanMenu: true,
DiffMenu: true,
EditMenu: false,
},
wantHook: []string{"clean", "diff"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
preper := NewPreparer(nil, nil, tc.cfg, newTestLogger())
assert.Len(t, preper.hooks, len(tc.wantHook))
got := make([]string, 0, len(preper.hooks))
for _, hook := range preper.hooks {
got = append(got, hook.Name)
}
assert.Equal(t, tc.wantHook, got)
})
}
}

View File

@@ -3,14 +3,18 @@ package text
import (
"bufio"
"fmt"
"io"
"strings"
"unicode"
"unicode/utf8"
"github.com/leonelquinteros/gotext"
)
func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) {
Info()
l.Info()
if defaultValue != "" || noConfirm {
fmt.Println(defaultValue)
l.Println(defaultValue)
return defaultValue, nil
}
@@ -28,6 +32,48 @@ func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) {
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))
}

View File

@@ -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)
}

View File

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

View File

@@ -1,13 +1,8 @@
package text
import (
"fmt"
"io"
"strings"
"unicode"
"unicode/utf8"
"github.com/leonelquinteros/gotext"
)
const (
@@ -52,49 +47,3 @@ func LessRunes(iRunes, jRunes []rune) bool {
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))
}

View File

@@ -4,6 +4,7 @@
package text
import (
"io"
"os"
"path"
"strings"
@@ -74,7 +75,8 @@ func TestContinueTask(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// create io.Reader with value of 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)
})
}
@@ -120,7 +122,8 @@ msgstr "да"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
})
}
@@ -168,7 +171,8 @@ msgstr "ja"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
})
}

View File

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