feat(config): refactor config to allow running without config

This commit is contained in:
jguer committed 2020-08-21 02:39:52 +02:00
1 parent c5af6f8189
commit 72f120b0f3
19 files changed
+441 -605

No files matched your search

-21
View File
@@ -16,27 +16,6 @@ import (
"github.com/Jguer/yay/v10/pkg/text"
)
// GetPkgbuild gets the pkgbuild of the package 'pkg' trying the ABS first and then the AUR trying the ABS first and then the AUR.
// RemovePackage removes package from VCS information
func removeVCSPackage(pkgs []string, localCache vcsInfo) {
updated := false
for _, pkgName := range pkgs {
if _, ok := localCache[pkgName]; ok {
delete(localCache, pkgName)
updated = true
}
}
if updated {
err := saveVCSInfo(config.Runtime.VCSPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
}
// CleanDependencies removes all dangling dependencies in system
func cleanDependencies(cmdArgs *settings.Arguments, dbExecutor db.Executor, removeOptional bool) error {
hanging := hangingPackages(removeOptional, dbExecutor)
+5 -5
View File
@@ -16,6 +16,7 @@ import (
"github.com/Jguer/yay/v10/pkg/query"
"github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/text"
"github.com/Jguer/yay/v10/pkg/vcs"
)
func usage() {
@@ -199,8 +200,7 @@ func handleVersion() {
func handlePrint(cmdArgs *settings.Arguments, dbExecutor db.Executor) (err error) {
switch {
case cmdArgs.ExistsArg("d", "defaultconfig"):
tmpConfig := settings.MakeConfig()
tmpConfig.ExpandEnv()
tmpConfig := settings.DefaultConfig()
fmt.Printf("%v", tmpConfig)
case cmdArgs.ExistsArg("g", "currentconfig"):
fmt.Printf("%v", config)
@@ -224,7 +224,7 @@ func handlePrint(cmdArgs *settings.Arguments, dbExecutor db.Executor) (err error
func handleYay(cmdArgs *settings.Arguments, dbExecutor db.Executor) error {
if cmdArgs.ExistsArg("gendb") {
return createDevelDB(config.Runtime.VCSPath, dbExecutor)
return createDevelDB(config, dbExecutor)
}
if cmdArgs.ExistsDouble("c") {
return cleanDependencies(cmdArgs, dbExecutor, true)
@@ -285,10 +285,10 @@ func handleSync(cmdArgs *settings.Arguments, dbExecutor db.Executor) error {
return nil
}
func handleRemove(cmdArgs *settings.Arguments, localCache vcsInfo) error {
func handleRemove(cmdArgs *settings.Arguments, localCache vcs.InfoStore) error {
err := config.Runtime.CmdRunner.Show(passToPacman(cmdArgs))
if err == nil {
removeVCSPackage(cmdArgs.Targets, localCache)
localCache.RemovePackage(cmdArgs.Targets)
}
return err
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/text"
"github.com/Jguer/yay/v10/pkg/vcs"
)
// Verbosity settings for search
@@ -25,7 +26,7 @@ var yayVersion = "10.0.0"
var localePath = "/usr/share/locale"
// savedInfo holds the current vcs info
var savedInfo vcsInfo
var savedInfo vcs.InfoStore
// YayConf holds the current config values for yay.
var config *settings.Configuration
+1
View File
@@ -1,5 +1,6 @@
github.com/Jguer/go-alpm v0.0.0-20200405152916-a3feea4322e9 h1:lLQSUe6iRdtFrP0zkDV7n8I8XKSxRHQTEU1KRh4IOLg=
github.com/Jguer/go-alpm v0.0.0-20200405152916-a3feea4322e9/go.mod h1:D5SUcIS9Yiz/L8cjRzq/992eERnx6ugYmGlc4e7xdus=
github.com/Jguer/yay v8.1173.0+incompatible h1:1IO32DoPD6Ds4YeqER6ppNjUtQ75cbhuBoVzQI762yk=
github.com/Morganamilo/go-pacmanconf v0.0.0-20180910220353-9c5265e1b14f h1:ptFKynTV1p8JCzqk81NcMj0DV0Xle+PdKxfHjPbdIOU=
github.com/Morganamilo/go-pacmanconf v0.0.0-20180910220353-9c5265e1b14f/go.mod h1:Hk55m330jNiwxRodIlMCvw5iEyoRUCIY64W1p9D+tHc=
github.com/Morganamilo/go-srcinfo v1.0.0 h1:Wh4nEF+HJWo+29hnxM18Q2hi+DUf0GejS13+Wg+dzmI=
+3 -4
View File
@@ -977,9 +977,8 @@ func buildInstallPkgbuilds(
return errShow
}
err = saveVCSInfo(config.Runtime.VCSPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
if errStore := config.Runtime.VCSStore.Save(); err != nil {
fmt.Fprintln(os.Stderr, errStore)
}
if errDeps := asdeps(cmdArgs, deps); err != nil {
@@ -1160,7 +1159,7 @@ func buildInstallPkgbuilds(
var wg sync.WaitGroup
for _, pkg := range base {
wg.Add(1)
go updateVCSData(config.Runtime.VCSPath, pkg.Name, srcinfo.Source, &mux, &wg)
go config.Runtime.VCSStore.Update(pkg.Name, srcinfo.Source, &mux, &wg)
}
wg.Wait()
+12 -94
View File
@@ -1,8 +1,6 @@
package main // import "github.com/Jguer/yay"
import (
"encoding/json"
"errors"
"fmt"
"os"
@@ -24,57 +22,6 @@ func initGotext() {
gotext.Configure(localePath, os.Getenv("LANG"), "yay")
}
func initConfig(configPath string) error {
cfile, err := os.Open(configPath)
if !os.IsNotExist(err) && err != nil {
return errors.New(gotext.Get("failed to open config file '%s': %s", configPath, err))
}
defer cfile.Close()
if !os.IsNotExist(err) {
decoder := json.NewDecoder(cfile)
if err = decoder.Decode(&config); err != nil {
return errors.New(gotext.Get("failed to read config file '%s': %s", configPath, err))
}
}
aurdest := os.Getenv("AURDEST")
if aurdest != "" {
config.BuildDir = aurdest
}
return nil
}
func initVCS(vcsFilePath string) error {
vfile, err := os.Open(vcsFilePath)
if !os.IsNotExist(err) && err != nil {
return errors.New(gotext.Get("failed to open vcs file '%s': %s", vcsFilePath, err))
}
defer vfile.Close()
if !os.IsNotExist(err) {
decoder := json.NewDecoder(vfile)
if err = decoder.Decode(&savedInfo); err != nil {
return errors.New(gotext.Get("failed to read vcs file '%s': %s", vcsFilePath, err))
}
}
return nil
}
func initBuildDir() error {
if _, err := os.Stat(config.BuildDir); os.IsNotExist(err) {
if err = os.MkdirAll(config.BuildDir, 0o755); err != nil {
return errors.New(gotext.Get("failed to create BuildDir directory '%s': %s", config.BuildDir, err))
}
} else if err != nil {
return err
}
return nil
}
func initAlpm(cmdArgs *settings.Arguments, pacmanConfigPath string) (*pacmanconf.Config, bool, error) {
root := "/"
if value, _, exists := cmdArgs.GetArg("root", "r"); exists {
@@ -124,6 +71,7 @@ func initAlpm(cmdArgs *settings.Arguments, pacmanConfigPath string) (*pacmanconf
}
func main() {
var err error
ret := 0
defer func() { os.Exit(ret) }()
initGotext()
@@ -131,28 +79,16 @@ func main() {
text.Warnln(gotext.Get("Avoid running yay as root/sudo."))
}
config, err = settings.NewConfig()
if err != nil {
if str := err.Error(); str != "" {
fmt.Fprintln(os.Stderr, str)
}
ret = 1
return
}
cmdArgs := settings.MakeArguments()
runtime, err := settings.MakeRuntime()
if err != nil {
if str := err.Error(); str != "" {
fmt.Fprintln(os.Stderr, str)
}
ret = 1
return
}
config = settings.MakeConfig()
config.Runtime = runtime
err = initConfig(runtime.ConfigPath)
if err != nil {
if str := err.Error(); str != "" {
fmt.Fprintln(os.Stderr, str)
}
ret = 1
return
}
err = cmdArgs.ParseCommandLine(config)
if err != nil {
if str := err.Error(); str != "" {
@@ -163,30 +99,12 @@ func main() {
}
if config.Runtime.SaveConfig {
errS := config.SaveConfig(runtime.ConfigPath)
errS := config.Save(config.Runtime.ConfigPath)
if errS != nil {
fmt.Fprintln(os.Stderr, err)
}
}
config.ExpandEnv()
err = initBuildDir()
if err != nil {
if str := err.Error(); str != "" {
fmt.Fprintln(os.Stderr, str)
}
ret = 1
return
}
err = initVCS(runtime.VCSPath)
if err != nil {
if str := err.Error(); str != "" {
fmt.Fprintln(os.Stderr, str)
}
ret = 1
return
}
var useColor bool
config.Runtime.PacmanConf, useColor, err = initAlpm(cmdArgs, config.PacmanConf)
if err != nil {
@@ -199,7 +117,7 @@ func main() {
text.UseColor = useColor
dbExecutor, err := ialpm.NewExecutor(runtime.PacmanConf)
dbExecutor, err := ialpm.NewExecutor(config.Runtime.PacmanConf)
if err != nil {
if str := err.Error(); str != "" {
fmt.Fprintln(os.Stderr, str)
+5 -3
View File
@@ -10,7 +10,8 @@ import (
)
func TestPacmanConf(t *testing.T) {
expectedPacmanConf := &pacmanconf.Config{RootDir: "/",
expectedPacmanConf := &pacmanconf.Config{
RootDir: "/",
DBPath: "//var/lib/pacman/",
CacheDir: []string{"/cachedir/", "/another/"},
HookDir: []string{"/hookdir/"},
@@ -36,12 +37,13 @@ func TestPacmanConf(t *testing.T) {
DisableDownloadTimeout: false,
Repos: []pacmanconf.Repository{
{Name: "repo1", Servers: []string{"repo1"}, SigLevel: []string(nil), Usage: []string{"All"}},
{Name: "repo2", Servers: []string{"repo2"}, SigLevel: []string(nil), Usage: []string{"All"}}}}
{Name: "repo2", Servers: []string{"repo2"}, SigLevel: []string(nil), Usage: []string{"All"}},
},
}
pacmanConf, color, err := initAlpm(settings.MakeArguments(), "testdata/pacman.conf")
assert.Nil(t, err)
assert.NotNil(t, pacmanConf)
assert.Equal(t, color, false)
assert.EqualValues(t, expectedPacmanConf, pacmanConf)
}
+5 -2
View File
@@ -8,7 +8,8 @@ import (
)
func TestAlpmExecutor(t *testing.T) {
pacmanConf := &pacmanconf.Config{RootDir: "/",
pacmanConf := &pacmanconf.Config{
RootDir: "/",
DBPath: "/var/lib/pacman/",
CacheDir: []string{"/cachedir/", "/another/"},
HookDir: []string{"/hookdir/"},
@@ -34,7 +35,9 @@ func TestAlpmExecutor(t *testing.T) {
DisableDownloadTimeout: false,
Repos: []pacmanconf.Repository{
{Name: "repo1", Servers: []string{"repo1"}, SigLevel: []string(nil), Usage: []string{"All"}},
{Name: "repo2", Servers: []string{"repo2"}, SigLevel: []string(nil), Usage: []string{"All"}}}}
{Name: "repo2", Servers: []string{"repo2"}, SigLevel: []string(nil), Usage: []string{"All"}},
},
}
aExec, err := NewExecutor(pacmanConf)
assert.NoError(t, err)
+9
View File
@@ -22,30 +22,39 @@ type Package struct {
func (p *Package) Base() string {
return p.PBase
}
func (p *Package) BuildDate() time.Time {
return p.PBuildDate
}
func (p *Package) DB() *alpm.DB {
return p.PDB
}
func (p *Package) Description() string {
return p.PDescription
}
func (p *Package) ISize() int64 {
return p.PISize
}
func (p *Package) Name() string {
return p.PName
}
func (p *Package) ShouldIgnore() bool {
return p.PShouldIgnore
}
func (p *Package) Size() int64 {
return p.PSize
}
func (p *Package) Version() string {
return p.PVersion
}
func (p *Package) Reason() alpm.PkgReason {
return p.PReason
}
+93 -37
View File
@@ -5,6 +5,12 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v10/pkg/settings/exe"
"github.com/Jguer/yay/v10/pkg/vcs"
)
const (
@@ -67,11 +73,12 @@ type Configuration struct {
}
// SaveConfig writes yay config to file.
func (config *Configuration) SaveConfig(configPath string) error {
marshalledinfo, err := json.MarshalIndent(config, "", "\t")
func (c *Configuration) Save(configPath string) error {
marshalledinfo, err := json.MarshalIndent(c, "", "\t")
if err != nil {
return err
}
// https://github.com/Jguer/yay/issues/1325
marshalledinfo = append(marshalledinfo, '\n')
in, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
@@ -85,49 +92,49 @@ func (config *Configuration) SaveConfig(configPath string) error {
return in.Sync()
}
func (config *Configuration) ExpandEnv() {
config.AURURL = os.ExpandEnv(config.AURURL)
config.ABSDir = os.ExpandEnv(config.ABSDir)
config.BuildDir = os.ExpandEnv(config.BuildDir)
config.Editor = os.ExpandEnv(config.Editor)
config.EditorFlags = os.ExpandEnv(config.EditorFlags)
config.MakepkgBin = os.ExpandEnv(config.MakepkgBin)
config.MakepkgConf = os.ExpandEnv(config.MakepkgConf)
config.PacmanBin = os.ExpandEnv(config.PacmanBin)
config.PacmanConf = os.ExpandEnv(config.PacmanConf)
config.GpgFlags = os.ExpandEnv(config.GpgFlags)
config.MFlags = os.ExpandEnv(config.MFlags)
config.GitFlags = os.ExpandEnv(config.GitFlags)
config.SortBy = os.ExpandEnv(config.SortBy)
config.SearchBy = os.ExpandEnv(config.SearchBy)
config.GitBin = os.ExpandEnv(config.GitBin)
config.GpgBin = os.ExpandEnv(config.GpgBin)
config.SudoBin = os.ExpandEnv(config.SudoBin)
config.SudoFlags = os.ExpandEnv(config.SudoFlags)
config.ReDownload = os.ExpandEnv(config.ReDownload)
config.ReBuild = os.ExpandEnv(config.ReBuild)
config.AnswerClean = os.ExpandEnv(config.AnswerClean)
config.AnswerDiff = os.ExpandEnv(config.AnswerDiff)
config.AnswerEdit = os.ExpandEnv(config.AnswerEdit)
config.AnswerUpgrade = os.ExpandEnv(config.AnswerUpgrade)
config.RemoveMake = os.ExpandEnv(config.RemoveMake)
func (c *Configuration) expandEnv() {
c.AURURL = os.ExpandEnv(c.AURURL)
c.ABSDir = os.ExpandEnv(c.ABSDir)
c.BuildDir = os.ExpandEnv(c.BuildDir)
c.Editor = os.ExpandEnv(c.Editor)
c.EditorFlags = os.ExpandEnv(c.EditorFlags)
c.MakepkgBin = os.ExpandEnv(c.MakepkgBin)
c.MakepkgConf = os.ExpandEnv(c.MakepkgConf)
c.PacmanBin = os.ExpandEnv(c.PacmanBin)
c.PacmanConf = os.ExpandEnv(c.PacmanConf)
c.GpgFlags = os.ExpandEnv(c.GpgFlags)
c.MFlags = os.ExpandEnv(c.MFlags)
c.GitFlags = os.ExpandEnv(c.GitFlags)
c.SortBy = os.ExpandEnv(c.SortBy)
c.SearchBy = os.ExpandEnv(c.SearchBy)
c.GitBin = os.ExpandEnv(c.GitBin)
c.GpgBin = os.ExpandEnv(c.GpgBin)
c.SudoBin = os.ExpandEnv(c.SudoBin)
c.SudoFlags = os.ExpandEnv(c.SudoFlags)
c.ReDownload = os.ExpandEnv(c.ReDownload)
c.ReBuild = os.ExpandEnv(c.ReBuild)
c.AnswerClean = os.ExpandEnv(c.AnswerClean)
c.AnswerDiff = os.ExpandEnv(c.AnswerDiff)
c.AnswerEdit = os.ExpandEnv(c.AnswerEdit)
c.AnswerUpgrade = os.ExpandEnv(c.AnswerUpgrade)
c.RemoveMake = os.ExpandEnv(c.RemoveMake)
}
func (config *Configuration) String() string {
func (c *Configuration) String() string {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetIndent("", "\t")
if err := enc.Encode(config); err != nil {
if err := enc.Encode(c); err != nil {
fmt.Fprintln(os.Stderr, err)
}
return buf.String()
}
func MakeConfig() *Configuration {
newConfig := &Configuration{
func DefaultConfig() *Configuration {
return &Configuration{
AURURL: "https://aur.archlinux.org",
BuildDir: "$HOME/.cache/yay",
ABSDir: "$HOME/.cache/yay/abs",
BuildDir: os.ExpandEnv("$HOME/.cache/yay"),
ABSDir: os.ExpandEnv("$HOME/.cache/yay/abs"),
CleanAfter: false,
Editor: "",
EditorFlags: "",
@@ -167,10 +174,59 @@ func MakeConfig() *Configuration {
UseAsk: false,
CombinedUpgrade: false,
}
}
if os.Getenv("XDG_CACHE_HOME") != "" {
newConfig.BuildDir = "$XDG_CACHE_HOME/yay"
func NewConfig() (*Configuration, error) {
newConfig := DefaultConfig()
cacheHome := getCacheHome()
newConfig.BuildDir = cacheHome
configPath := getConfigPath()
newConfig.load(configPath)
if aurdest := os.Getenv("AURDEST"); aurdest != "" {
newConfig.BuildDir = aurdest
}
return newConfig
newConfig.expandEnv()
newConfig.Runtime = &Runtime{
Mode: ModeAny,
SaveConfig: false,
CompletionPath: filepath.Join(cacheHome, completionFileName),
CmdRunner: &exe.OSRunner{},
CmdBuilder: exe.NewCmdBuilder(newConfig.GitBin, newConfig.GitFlags),
PacmanConf: nil,
VCSStore: nil,
}
newConfig.Runtime.VCSStore = vcs.NewInfoStore(filepath.Join(cacheHome, vcsFileName),
newConfig.Runtime.CmdRunner, newConfig.Runtime.CmdBuilder)
if err := initDir(newConfig.BuildDir); err != nil {
return nil, err
}
err := newConfig.Runtime.VCSStore.Load()
return newConfig, err
}
func (c *Configuration) load(configPath string) {
cfile, err := os.Open(configPath)
if !os.IsNotExist(err) && err != nil {
fmt.Fprintln(os.Stderr,
gotext.Get("failed to open config file '%s': %s", configPath, err))
return
}
defer cfile.Close()
if !os.IsNotExist(err) {
decoder := json.NewDecoder(cfile)
if err = decoder.Decode(c); err != nil {
fmt.Fprintln(os.Stderr,
gotext.Get("failed to read config file '%s': %s", configPath, err))
}
}
}
+61
View File
@@ -0,0 +1,61 @@
package settings
import (
"os"
"path/filepath"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
)
// configFileName holds the name of the config file.
const configFileName string = "config.json"
// vcsFileName holds the name of the vcs file.
const vcsFileName string = "vcs.json"
const completionFileName string = "completion.cache"
func getConfigPath() string {
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
if err := initDir(configHome); err == nil {
return filepath.Join(configHome, "yay", configFileName)
}
}
if configHome := os.Getenv("HOME"); configHome != "" {
if err := initDir(configHome); err == nil {
return filepath.Join(configHome, ".config", "yay", configFileName)
}
}
return ""
}
func getCacheHome() string {
if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
if err := initDir(cacheHome); err == nil {
return filepath.Join(cacheHome, "yay")
}
}
if cacheHome := os.Getenv("HOME"); cacheHome != "" {
if err := initDir(cacheHome); err == nil {
return filepath.Join(cacheHome, ".cache", "yay")
}
}
return "/tmp"
}
func initDir(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0o755); err != nil {
return errors.New(gotext.Get("failed to create config directory '%s': %s", dir, err))
}
} else if err != nil {
return err
}
return nil
}
+68
View File
@@ -0,0 +1,68 @@
package exe
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/Jguer/yay/v10/pkg/text"
)
type Runner interface {
Capture(cmd *exec.Cmd, timeout int64) (stdout string, stderr string, err error)
Show(cmd *exec.Cmd) error
}
type OSRunner struct {
}
func (r *OSRunner) Show(cmd *exec.Cmd) error {
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("")
}
return nil
}
func (r *OSRunner) Capture(cmd *exec.Cmd, timeout int64) (stdout, stderr string, err error) {
var outbuf, errbuf bytes.Buffer
var timer *time.Timer
timedOut := false
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
err = cmd.Start()
if err != nil {
return "", "", err
}
if timeout != 0 {
timer = time.AfterFunc(time.Duration(timeout)*time.Second, func() {
err = cmd.Process.Kill()
if err != nil {
text.Errorln(err)
}
timedOut = true
})
}
err = cmd.Wait()
if timeout != 0 {
timer.Stop()
}
if err != nil {
return "", "", err
}
stdout = strings.TrimSpace(outbuf.String())
stderr = strings.TrimSpace(errbuf.String())
if timedOut {
err = fmt.Errorf("command timed out")
}
return stdout, stderr, err
}
+33
View File
@@ -0,0 +1,33 @@
package exe
import (
"os"
"os/exec"
"strings"
)
type CmdBuilder struct {
GitBin string
GitFlags []string
}
func NewCmdBuilder(gitBin, gitFlags string) *CmdBuilder {
c := &CmdBuilder{GitBin: gitBin, GitFlags: strings.Fields(gitFlags)}
return c
}
func (c *CmdBuilder) BuildGitCmd(dir string, extraArgs ...string) *exec.Cmd {
args := make([]string, 0, len(c.GitFlags))
copy(args, c.GitFlags)
if dir != "" {
args = append(args, "-C", dir)
}
args = append(args, extraArgs...)
cmd := exec.Command(c.GitBin, args...)
cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
return cmd
}
+5 -131
View File
@@ -1,153 +1,27 @@
package settings
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/Morganamilo/go-pacmanconf"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
"github.com/Jguer/yay/v10/pkg/text"
"github.com/Jguer/yay/v10/pkg/settings/exe"
"github.com/Jguer/yay/v10/pkg/vcs"
)
type TargetMode int
// configFileName holds the name of the config file.
const configFileName string = "config.json"
// vcsFileName holds the name of the vcs file.
const vcsFileName string = "vcs.json"
const completionFileName string = "completion.cache"
const (
ModeAny TargetMode = iota
ModeAUR
ModeRepo
)
type Runner interface {
Capture(cmd *exec.Cmd, timeout int64) (stdout string, stderr string, err error)
Show(cmd *exec.Cmd) error
}
type OSRunner struct {
}
func (r *OSRunner) Show(cmd *exec.Cmd) error {
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("")
}
return nil
}
func (r *OSRunner) Capture(cmd *exec.Cmd, timeout int64) (stdout, stderr string, err error) {
var outbuf, errbuf bytes.Buffer
var timer *time.Timer
timedOut := false
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
err = cmd.Start()
if err != nil {
return "", "", err
}
if timeout != 0 {
timer = time.AfterFunc(time.Duration(timeout)*time.Second, func() {
err = cmd.Process.Kill()
if err != nil {
text.Errorln(err)
}
timedOut = true
})
}
err = cmd.Wait()
if timeout != 0 {
timer.Stop()
}
if err != nil {
return "", "", err
}
stdout = strings.TrimSpace(outbuf.String())
stderr = strings.TrimSpace(errbuf.String())
if timedOut {
err = fmt.Errorf("command timed out")
}
return stdout, stderr, err
}
type Runtime struct {
Mode TargetMode
SaveConfig bool
CompletionPath string
ConfigPath string
VCSPath string
PacmanConf *pacmanconf.Config
CmdRunner Runner
}
func MakeRuntime() (*Runtime, error) {
cacheHome := ""
configHome := ""
runtime := &Runtime{
Mode: ModeAny,
SaveConfig: false,
CompletionPath: "",
CmdRunner: &OSRunner{},
}
if configHome = os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
configHome = filepath.Join(configHome, "yay")
} else if configHome = os.Getenv("HOME"); configHome != "" {
configHome = filepath.Join(configHome, ".config", "yay")
} else {
return nil, errors.New(gotext.Get("%s and %s unset", "XDG_CONFIG_HOME", "HOME"))
}
if err := initDir(configHome); err != nil {
return nil, err
}
if cacheHome = os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
cacheHome = filepath.Join(cacheHome, "yay")
} else if cacheHome = os.Getenv("HOME"); cacheHome != "" {
cacheHome = filepath.Join(cacheHome, ".cache", "yay")
} else {
return nil, errors.New(gotext.Get("%s and %s unset", "XDG_CACHE_HOME", "HOME"))
}
if err := initDir(cacheHome); err != nil {
return runtime, err
}
runtime.ConfigPath = filepath.Join(configHome, configFileName)
runtime.VCSPath = filepath.Join(cacheHome, vcsFileName)
runtime.CompletionPath = filepath.Join(cacheHome, completionFileName)
return runtime, nil
}
func initDir(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0o755); err != nil {
return errors.New(gotext.Get("failed to create config directory '%s': %s", dir, err))
}
} else if err != nil {
return err
}
return nil
VCSStore *vcs.InfoStore
CmdBuilder *exe.CmdBuilder
CmdRunner exe.Runner
}
+2 -1
View File
@@ -46,7 +46,8 @@ func NewInfoStore(filePath string, runner exe.Runner, cmdBuilder *exe.CmdBuilder
CmdBuilder: cmdBuilder,
FilePath: filePath,
OriginsByPackage: map[string]OriginInfoByURL{},
Runner: runner}
Runner: runner,
}
return infoStore
}
+13 -9
View File
@@ -15,6 +15,7 @@ import (
"github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/text"
"github.com/Jguer/yay/v10/pkg/upgrade"
"github.com/Jguer/yay/v10/pkg/vcs"
rpc "github.com/mikkeloscar/aur"
@@ -97,20 +98,23 @@ func upList(warnings *query.AURWarnings, dbExecutor db.Executor, enableDowngrade
return aurUp, repoUp, errs.Return()
}
func upDevel(remote []db.RepoPackage, aurdata map[string]*rpc.Pkg, localCache vcsInfo) upgrade.UpSlice {
func upDevel(
remote []db.RepoPackage,
aurdata map[string]*rpc.Pkg,
localCache vcs.InfoStore) upgrade.UpSlice {
toUpdate := make([]db.RepoPackage, 0, len(aurdata))
toRemove := make([]string, 0)
var mux1, mux2 sync.Mutex
var wg sync.WaitGroup
checkUpdate := func(vcsName string, e shaInfos) {
checkUpdate := func(pkgName string, e vcs.OriginInfoByURL) {
defer wg.Done()
if e.needsUpdate() {
if _, ok := aurdata[vcsName]; ok {
if localCache.NeedsUpdate(e) {
if _, ok := aurdata[pkgName]; ok {
for _, pkg := range remote {
if pkg.Name() == vcsName {
if pkg.Name() == pkgName {
mux1.Lock()
toUpdate = append(toUpdate, pkg)
mux1.Unlock()
@@ -120,14 +124,14 @@ func upDevel(remote []db.RepoPackage, aurdata map[string]*rpc.Pkg, localCache vc
}
mux2.Lock()
toRemove = append(toRemove, vcsName)
toRemove = append(toRemove, pkgName)
mux2.Unlock()
}
}
for vcsName, e := range localCache {
for pkgName, e := range localCache.OriginsByPackage {
wg.Add(1)
go checkUpdate(vcsName, e)
go checkUpdate(pkgName, e)
}
wg.Wait()
@@ -147,7 +151,7 @@ func upDevel(remote []db.RepoPackage, aurdata map[string]*rpc.Pkg, localCache vc
}
}
removeVCSPackage(toRemove, localCache)
localCache.RemovePackage(toRemove)
return toUpgrade
}
+121 -67
View File
@@ -17,6 +17,7 @@ import (
"github.com/Jguer/yay/v10/pkg/db/mock"
"github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/upgrade"
"github.com/Jguer/yay/v10/pkg/vcs"
)
func Test_upAUR(t *testing.T) {
@@ -30,32 +31,40 @@ func Test_upAUR(t *testing.T) {
args args
want upgrade.UpSlice
}{
{name: "No Updates",
{
name: "No Updates",
args: args{
remote: []db.RepoPackage{
&mock.Package{PName: "hello", PVersion: "2.0.0"},
&mock.Package{PName: "local_pkg", PVersion: "1.1.0"},
&mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}},
&mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true},
},
aurdata: map[string]*rpc.Pkg{
"hello": {Version: "2.0.0", Name: "hello"},
"ignored": {Version: "2.0.0", Name: "ignored"}},
"ignored": {Version: "2.0.0", Name: "ignored"},
},
timeUpdate: false,
},
want: upgrade.UpSlice{}},
{name: "Simple Update",
want: upgrade.UpSlice{},
},
{
name: "Simple Update",
args: args{
remote: []db.RepoPackage{&mock.Package{PName: "hello", PVersion: "2.0.0"}},
aurdata: map[string]*rpc.Pkg{"hello": {Version: "2.1.0", Name: "hello"}},
timeUpdate: false,
},
want: upgrade.UpSlice{upgrade.Upgrade{Name: "hello", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "2.1.0"}}},
{name: "Time Update",
want: upgrade.UpSlice{upgrade.Upgrade{Name: "hello", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "2.1.0"}},
},
{
name: "Time Update",
args: args{
remote: []db.RepoPackage{&mock.Package{PName: "hello", PVersion: "2.0.0", PBuildDate: time.Now()}},
aurdata: map[string]*rpc.Pkg{"hello": {Version: "2.0.0", Name: "hello", LastModified: int(time.Now().AddDate(0, 0, 2).Unix())}},
timeUpdate: true,
},
want: upgrade.UpSlice{upgrade.Upgrade{Name: "hello", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "2.0.0"}}},
want: upgrade.UpSlice{upgrade.Upgrade{Name: "hello", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "2.0.0"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -96,8 +105,10 @@ func (r *MockRunner) Capture(cmd *exec.Cmd, timeout int64) (stdout, stderr strin
}
func Test_upDevel(t *testing.T) {
config = settings.MakeConfig()
config.Runtime, _ = settings.MakeRuntime()
var err error
config, err = settings.NewConfig()
assert.NoError(t, err)
config.Runtime.CmdRunner = &MockRunner{
Returned: []string{
"7f4c277ce7149665d1c79b76ca8fbb832a65a03b HEAD",
@@ -111,7 +122,7 @@ func Test_upDevel(t *testing.T) {
type args struct {
remote []db.RepoPackage
aurdata map[string]*rpc.Pkg
cached vcsInfo
cached vcs.InfoStore
}
tests := []struct {
name string
@@ -119,102 +130,145 @@ func Test_upDevel(t *testing.T) {
want upgrade.UpSlice
finalLen int
}{
{name: "No Updates",
{
name: "No Updates",
args: args{
cached: vcsInfo{},
cached: vcs.InfoStore{
Runner: config.Runtime.CmdRunner,
CmdBuilder: config.Runtime.CmdBuilder,
},
remote: []db.RepoPackage{
&mock.Package{PName: "hello", PVersion: "2.0.0"},
&mock.Package{PName: "local_pkg", PVersion: "1.1.0"},
&mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}},
&mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true},
},
aurdata: map[string]*rpc.Pkg{
"hello": {Version: "2.0.0", Name: "hello"},
"ignored": {Version: "2.0.0", Name: "ignored"}},
"ignored": {Version: "2.0.0", Name: "ignored"},
},
},
want: upgrade.UpSlice{}},
{name: "Simple Update",
want: upgrade.UpSlice{},
},
{
name: "Simple Update",
finalLen: 3,
args: args{
cached: vcsInfo{
"hello": shaInfos{
"github.com/Jguer/z.git": shaInfo{
Protocols: []string{"https"},
Branch: "0",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1"}},
"hello-non-existant": shaInfos{
"github.com/Jguer/y.git": shaInfo{
Protocols: []string{"https"},
Branch: "0",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1"}},
"hello2": shaInfos{
"github.com/Jguer/a.git": shaInfo{
Protocols: []string{"https"},
Branch: "1",
SHA: "7f4c277ce7149665d1c79b76ca8fbb832a65a03b"}},
"hello4": shaInfos{
"github.com/Jguer/b.git": shaInfo{
Protocols: []string{"https"},
Branch: "2",
SHA: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
"github.com/Jguer/c.git": shaInfo{
Protocols: []string{"https"},
Branch: "3",
SHA: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
cached: vcs.InfoStore{
Runner: config.Runtime.CmdRunner,
CmdBuilder: config.Runtime.CmdBuilder,
OriginsByPackage: map[string]vcs.OriginInfoByURL{
"hello": {
"github.com/Jguer/z.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "0",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1",
},
},
"hello-non-existant": {
"github.com/Jguer/y.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "0",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1",
},
},
"hello2": {
"github.com/Jguer/a.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "1",
SHA: "7f4c277ce7149665d1c79b76ca8fbb832a65a03b",
},
},
"hello4": {
"github.com/Jguer/b.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "2",
SHA: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
"github.com/Jguer/c.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "3",
SHA: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
},
},
},
},
remote: []db.RepoPackage{
&mock.Package{PName: "hello", PVersion: "2.0.0"},
&mock.Package{PName: "hello2", PVersion: "3.0.0"},
&mock.Package{PName: "hello4", PVersion: "4.0.0"}},
&mock.Package{PName: "hello4", PVersion: "4.0.0"},
},
aurdata: map[string]*rpc.Pkg{
"hello": {Version: "2.0.0", Name: "hello"},
"hello2": {Version: "2.0.0", Name: "hello2"},
"hello4": {Version: "2.0.0", Name: "hello4"},
},
},
want: upgrade.UpSlice{upgrade.Upgrade{
Name: "hello",
Repository: "devel",
LocalVersion: "2.0.0",
RemoteVersion: "latest-commit"},
want: upgrade.UpSlice{
upgrade.Upgrade{
Name: "hello",
Repository: "devel",
LocalVersion: "2.0.0",
RemoteVersion: "latest-commit",
},
upgrade.Upgrade{
Name: "hello4",
Repository: "devel",
LocalVersion: "4.0.0",
RemoteVersion: "latest-commit"},
}},
{name: "No update returned",
RemoteVersion: "latest-commit",
},
},
},
{
name: "No update returned",
finalLen: 1,
args: args{
cached: vcsInfo{
"hello": shaInfos{
"github.com/Jguer/d.git": shaInfo{
Protocols: []string{"https"},
Branch: "4",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1"}}},
cached: vcs.InfoStore{
Runner: config.Runtime.CmdRunner,
CmdBuilder: config.Runtime.CmdBuilder,
OriginsByPackage: map[string]vcs.OriginInfoByURL{
"hello": {
"github.com/Jguer/d.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "4",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1",
},
},
},
},
remote: []db.RepoPackage{&mock.Package{PName: "hello", PVersion: "2.0.0"}},
aurdata: map[string]*rpc.Pkg{"hello": {Version: "2.0.0", Name: "hello"}},
},
want: upgrade.UpSlice{}},
{name: "No update returned - ignored",
want: upgrade.UpSlice{},
},
{
name: "No update returned - ignored",
finalLen: 1,
args: args{
cached: vcsInfo{
"hello": shaInfos{
"github.com/Jguer/e.git": shaInfo{
Protocols: []string{"https"},
Branch: "3",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1"}}},
cached: vcs.InfoStore{
Runner: config.Runtime.CmdRunner,
CmdBuilder: config.Runtime.CmdBuilder,
OriginsByPackage: map[string]vcs.OriginInfoByURL{
"hello": {
"github.com/Jguer/e.git": vcs.OriginInfo{
Protocols: []string{"https"},
Branch: "3",
SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1",
},
},
},
},
remote: []db.RepoPackage{&mock.Package{PName: "hello", PVersion: "2.0.0", PShouldIgnore: true}},
aurdata: map[string]*rpc.Pkg{"hello": {Version: "2.0.0", Name: "hello"}},
},
want: upgrade.UpSlice{}},
want: upgrade.UpSlice{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config.Runtime.CmdRunner.(*MockRunner).t = t
got := upDevel(tt.args.remote, tt.args.aurdata, tt.args.cached)
assert.ElementsMatch(t, tt.want, got)
assert.Equal(t, tt.finalLen, len(tt.args.cached))
assert.Equal(t, tt.finalLen, len(tt.args.cached.OriginsByPackage))
})
}
}
+3 -188
View File
@@ -1,35 +1,20 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"sync"
gosrc "github.com/Morganamilo/go-srcinfo"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v10/pkg/db"
"github.com/Jguer/yay/v10/pkg/dep"
"github.com/Jguer/yay/v10/pkg/query"
"github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/stringset"
"github.com/Jguer/yay/v10/pkg/text"
)
// Info contains the last commit sha of a repo
type (
vcsInfo map[string]shaInfos
shaInfos map[string]shaInfo
shaInfo struct {
Protocols []string `json:"protocols"`
Branch string `json:"branch"`
SHA string `json:"sha"`
}
)
// createDevelDB forces yay to create a DB of the existing development packages
func createDevelDB(vcsFilePath string, dbExecutor db.Executor) error {
func createDevelDB(config *settings.Configuration, dbExecutor db.Executor) error {
var mux sync.Mutex
var wg sync.WaitGroup
@@ -58,7 +43,7 @@ func createDevelDB(vcsFilePath string, dbExecutor db.Executor) error {
for i := range srcinfos {
for iP := range srcinfos[i].Packages {
wg.Add(1)
go updateVCSData(vcsFilePath, srcinfos[i].Packages[iP].Pkgname, srcinfos[i].Source, &mux, &wg)
go savedInfo.Update(srcinfos[i].Packages[iP].Pkgname, srcinfos[i].Source, &mux, &wg)
}
}
@@ -66,173 +51,3 @@ func createDevelDB(vcsFilePath string, dbExecutor db.Executor) error {
text.OperationInfoln(gotext.Get("GenDB finished. No packages were installed"))
return err
}
// parseSource returns the git url, default branch and protocols it supports
func parseSource(source string) (url, branch string, protocols []string) {
split := strings.Split(source, "::")
source = split[len(split)-1]
split = strings.SplitN(source, "://", 2)
if len(split) != 2 {
return "", "", nil
}
protocols = strings.SplitN(split[0], "+", 2)
git := false
for _, protocol := range protocols {
if protocol == "git" {
git = true
break
}
}
protocols = protocols[len(protocols)-1:]
if !git {
return "", "", nil
}
split = strings.SplitN(split[1], "#", 2)
if len(split) == 2 {
secondSplit := strings.SplitN(split[1], "=", 2)
if secondSplit[0] != "branch" {
// source has #commit= or #tag= which makes them not vcs
// packages because they reference a specific point
return "", "", nil
}
if len(secondSplit) == 2 {
url = split[0]
branch = secondSplit[1]
}
} else {
url = split[0]
branch = "HEAD"
}
url = strings.Split(url, "?")[0]
branch = strings.Split(branch, "?")[0]
return url, branch, protocols
}
func updateVCSData(vcsFilePath, pkgName string, sources []gosrc.ArchString, mux sync.Locker, wg *sync.WaitGroup) {
defer wg.Done()
if savedInfo == nil {
mux.Lock()
savedInfo = make(vcsInfo)
mux.Unlock()
}
info := make(shaInfos)
checkSource := func(source gosrc.ArchString) {
defer wg.Done()
url, branch, protocols := parseSource(source.Value)
if url == "" || branch == "" {
return
}
commit := getCommit(url, branch, protocols)
if commit == "" {
return
}
mux.Lock()
info[url] = shaInfo{
protocols,
branch,
commit,
}
savedInfo[pkgName] = info
text.Warnln(gotext.Get("Found git repo: %s", text.Cyan(url)))
err := saveVCSInfo(vcsFilePath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
mux.Unlock()
}
for _, source := range sources {
wg.Add(1)
go checkSource(source)
}
}
func getCommit(url, branch string, protocols []string) string {
if len(protocols) > 0 {
protocol := protocols[len(protocols)-1]
cmd := passToGit("", "ls-remote", protocol+"://"+url, branch)
stdout, _, err := config.Runtime.CmdRunner.Capture(cmd, 5)
if err != nil {
text.Warnln(err)
return ""
}
split := strings.Fields(stdout)
if len(split) < 2 {
return ""
}
commit := split[0]
return commit
}
return ""
}
func (infos shaInfos) needsUpdate() bool {
// used to signal we have gone through all sources and found nothing
finished := make(chan struct{})
alive := 0
// if we find an update we use this to exit early and return true
hasUpdate := make(chan struct{})
checkHash := func(url string, info shaInfo) {
hash := getCommit(url, info.Branch, info.Protocols)
if hash != "" && hash != info.SHA {
hasUpdate <- struct{}{}
} else {
finished <- struct{}{}
}
}
for url, info := range infos {
alive++
go checkHash(url, info)
}
for {
select {
case <-hasUpdate:
return true
case <-finished:
alive--
if alive == 0 {
return false
}
}
}
}
func saveVCSInfo(vcsFilePath string) error {
marshalledinfo, err := json.MarshalIndent(savedInfo, "", "\t")
if err != nil || string(marshalledinfo) == "null" {
return err
}
in, err := os.OpenFile(vcsFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
defer in.Close()
_, err = in.Write(marshalledinfo)
if err != nil {
return err
}
err = in.Sync()
return err
}
-42
View File
@@ -1,42 +0,0 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParsing(t *testing.T) {
type source struct {
URL string
Branch string
Protocols []string
}
urls := []string{
"git+https://github.com/neovim/neovim.git",
"git://github.com/jguer/yay.git#branch=master",
"git://github.com/davidgiven/ack",
"git://github.com/jguer/yay.git#tag=v3.440",
"git://github.com/jguer/yay.git#commit=e5470c88c6e2f9e0f97deb4728659ffa70ef5d0c",
"a+b+c+d+e+f://github.com/jguer/yay.git#branch=foo",
}
sources := []source{
{"github.com/neovim/neovim.git", "HEAD", []string{"https"}},
{"github.com/jguer/yay.git", "master", []string{"git"}},
{"github.com/davidgiven/ack", "HEAD", []string{"git"}},
{"", "", nil},
{"", "", nil},
{"", "", nil},
}
for n, url := range urls {
url, branch, protocols := parseSource(url)
compare := sources[n]
assert.Equal(t, compare.URL, url)
assert.Equal(t, compare.Branch, branch)
assert.Equal(t, compare.Protocols, protocols)
}
}