feat(download): download PKGBUILD repos interface

This commit is contained in:
jguer 2021-08-02 18:00:50 +02:00 committed by J Guerreiro
parent 50c0ece9a7
commit 79b44fd544
11 changed files with 258 additions and 220 deletions

View file

@ -54,7 +54,6 @@ linters:
- gocritic - gocritic
- gofmt - gofmt
- goimports - goimports
- golint
- goprintffuncname - goprintffuncname
- gosec - gosec
- gosimple - gosimple
@ -63,6 +62,8 @@ linters:
- lll - lll
- misspell - misspell
- nakedret - nakedret
- prealloc
- revive
- rowserrcheck - rowserrcheck
- staticcheck - staticcheck
- structcheck - structcheck
@ -73,7 +74,6 @@ linters:
- unused - unused
- varcheck - varcheck
- whitespace - whitespace
- prealloc
# disabled want to fix # disabled want to fix
#- scopelint #- scopelint

4
cmd.go
View file

@ -277,7 +277,9 @@ func handleGetpkgbuild(cmdArgs *settings.Arguments, dbExecutor db.Executor) erro
if cmdArgs.ExistsArg("p", "print") { if cmdArgs.ExistsArg("p", "print") {
return printPkgbuilds(dbExecutor, config.Runtime.HTTPClient, cmdArgs.Targets) return printPkgbuilds(dbExecutor, config.Runtime.HTTPClient, cmdArgs.Targets)
} }
return getPkgbuilds(cmdArgs.Targets, dbExecutor, cmdArgs.ExistsArg("f", "force")) return getPkgbuilds(dbExecutor,
config.Runtime.CmdRunner, config.Runtime.CmdBuilder,
cmdArgs.Targets, config.Runtime.Mode, config.AURURL, cmdArgs.ExistsArg("f", "force"))
} }
func handleYogurt(cmdArgs *settings.Arguments, dbExecutor db.Executor) error { func handleYogurt(cmdArgs *settings.Arguments, dbExecutor db.Executor) error {

View file

@ -3,20 +3,15 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
alpm "github.com/Jguer/go-alpm/v2"
"github.com/Jguer/yay/v10/pkg/db" "github.com/Jguer/yay/v10/pkg/db"
"github.com/Jguer/yay/v10/pkg/dep" "github.com/Jguer/yay/v10/pkg/download"
"github.com/Jguer/yay/v10/pkg/multierror" "github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/query" "github.com/Jguer/yay/v10/pkg/settings/exe"
"github.com/Jguer/yay/v10/pkg/text" "github.com/Jguer/yay/v10/pkg/text"
) )
@ -80,34 +75,6 @@ func gitHasDiff(path, name string) (bool, error) {
return true, nil return true, nil
} }
// TODO: yay-next passes args through the header, use that to unify ABS and AUR
func gitDownloadABS(url, path, name string) (bool, error) {
if err := os.MkdirAll(path, 0o755); err != nil {
return false, err
}
if _, errExist := os.Stat(filepath.Join(path, name)); os.IsNotExist(errExist) {
cmd := config.Runtime.CmdBuilder.BuildGitCmd(path, "clone", "--no-progress", "--single-branch",
"-b", "packages/"+name, url, name)
_, stderr, err := config.Runtime.CmdRunner.Capture(cmd, 0)
if err != nil {
return false, fmt.Errorf(gotext.Get("error cloning %s: %s", name, stderr))
}
return true, nil
} else if errExist != nil {
return false, fmt.Errorf(gotext.Get("error reading %s", filepath.Join(path, name, ".git")))
}
cmd := config.Runtime.CmdBuilder.BuildGitCmd(filepath.Join(path, name), "pull", "--ff-only")
_, stderr, err := config.Runtime.CmdRunner.Capture(cmd, 0)
if err != nil {
return false, fmt.Errorf(gotext.Get("error fetching %s: %s", name, stderr))
}
return true, nil
}
func gitDownload(url, path, name string) (bool, error) { func gitDownload(url, path, name string) (bool, error) {
_, err := os.Stat(filepath.Join(path, name, ".git")) _, err := os.Stat(filepath.Join(path, name, ".git"))
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -149,173 +116,33 @@ func gitMerge(path, name string) error {
return nil return nil
} }
func getPkgbuilds(pkgs []string, dbExecutor db.Executor, force bool) error { func getPkgbuilds(dbExecutor db.Executor,
missing := false cmdRunner exe.Runner,
cmdBuilder exe.GitCmdBuilder, targets []string,
mode settings.TargetMode,
aurURL string,
force bool) error {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return err return err
} }
pkgs = query.RemoveInvalidTargets(pkgs, config.Runtime.Mode) cloned, errD := download.PKGBUILDRepos(dbExecutor, cmdRunner, cmdBuilder, targets, mode, aurURL, wd, force)
aur, repo := packageSlices(pkgs, dbExecutor) if errD != nil {
text.Errorln(errD)
for n := range aur {
_, pkg := text.SplitDBFromName(aur[n])
aur[n] = pkg
} }
info, err := query.AURInfoPrint(config.Runtime.AURClient, aur, config.RequestSplitN) if len(targets) != len(cloned) {
if err != nil { missing := []string{}
return err for _, target := range targets {
if _, ok := cloned[target]; !ok {
missing = append(missing, target)
} }
}
text.Warnln(gotext.Get("Unable to find the following packages:"), strings.Join(missing, ", "))
if len(repo) > 0 {
missing, err = getPkgbuildsfromABS(repo, wd, dbExecutor, force)
if err != nil {
return err
}
}
if len(aur) > 0 {
allBases := dep.GetBases(info)
bases := make([]dep.Base, 0)
for _, base := range allBases {
name := base.Pkgbase()
pkgDest := filepath.Join(wd, name)
_, err = os.Stat(pkgDest)
if os.IsNotExist(err) {
bases = append(bases, base)
} else if err != nil {
text.Errorln(err)
continue
} else {
if force {
if err = os.RemoveAll(pkgDest); err != nil {
text.Errorln(err)
continue
}
bases = append(bases, base)
} else {
text.Warnln(gotext.Get("%s already exists. Use -f/--force to overwrite", pkgDest))
continue
}
}
}
if _, err = downloadPkgbuilds(bases, nil, wd); err != nil {
return err
}
missing = missing || len(aur) != len(info)
}
if missing {
err = fmt.Errorf("") err = fmt.Errorf("")
} }
return err return err
} }
// GetPkgbuild downloads pkgbuild from the ABS.
func getPkgbuildsfromABS(pkgs []string, path string, dbExecutor db.Executor, force bool) (bool, error) {
var wg sync.WaitGroup
var mux sync.Mutex
var errs multierror.MultiError
names := make(map[string]string)
missing := make([]string, 0)
downloaded := 0
for _, pkgN := range pkgs {
var pkg alpm.IPackage
var err error
var url string
pkgDB, name := text.SplitDBFromName(pkgN)
if pkgDB != "" {
pkg = dbExecutor.SatisfierFromDB(name, pkgDB)
} else {
pkg = dbExecutor.SyncSatisfier(name)
}
if pkg == nil {
missing = append(missing, name)
continue
}
name = pkg.Base()
if name == "" {
name = pkg.Name()
}
// TODO: Check existence with ls-remote
// https://git.archlinux.org/svntogit/packages.git
switch pkg.DB().Name() {
case "core", "extra", "testing":
url = "https://github.com/archlinux/svntogit-packages.git"
case "community", "multilib", "community-testing", "multilib-testing":
url = "https://github.com/archlinux/svntogit-community.git"
default:
missing = append(missing, name)
continue
}
_, err = os.Stat(filepath.Join(path, name))
switch {
case err != nil && !os.IsNotExist(err):
text.Errorln(err)
continue
case os.IsNotExist(err), force:
if err = os.RemoveAll(filepath.Join(path, name)); err != nil {
text.Errorln(err)
continue
}
default:
text.Warn(gotext.Get("%s already downloaded -- use -f to overwrite", text.Cyan(name)))
continue
}
names[name] = url
}
if len(missing) != 0 {
text.Warnln(gotext.Get("Missing ABS packages:"),
text.Cyan(strings.Join(missing, ", ")))
}
download := func(pkg string, url string) {
defer wg.Done()
if _, err := gitDownloadABS(url, config.ABSDir, pkg); err != nil {
errs.Add(errors.New(gotext.Get("failed to get pkgbuild: %s: %s", text.Cyan(pkg), err.Error())))
return
}
_, stderr, err := config.Runtime.CmdRunner.Capture(
exec.Command(
"cp", "-r",
filepath.Join(config.ABSDir, pkg, "trunk"),
filepath.Join(path, pkg)), 0)
mux.Lock()
downloaded++
if err != nil {
errs.Add(errors.New(gotext.Get("failed to link %s: %s", text.Cyan(pkg), stderr)))
} else {
fmt.Fprintln(os.Stdout, gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s", downloaded, len(names), text.Cyan(pkg)))
}
mux.Unlock()
}
count := 0
for name, url := range names {
wg.Add(1)
go download(name, url)
count++
if count%25 == 0 {
wg.Wait()
}
}
wg.Wait()
return len(missing) != 0, errs.Return()
}

View file

@ -7,6 +7,8 @@ import (
"net/http" "net/http"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v10/pkg/settings/exe"
) )
const ( const (
@ -21,22 +23,37 @@ var (
ABSCommunityURL = "https://github.com/archlinux/svntogit-community" ABSCommunityURL = "https://github.com/archlinux/svntogit-community"
) )
// Return format for pkgbuild func getRepoURL(db string) (string, error) {
// https://github.com/archlinux/svntogit-community/raw/packages/neovim/trunk/PKGBUILD
func getPackageURL(db, pkgName string) (string, error) {
repoURL := ""
switch db { switch db {
case "core", "extra", "testing": case "core", "extra", "testing":
repoURL = ABSPackageURL return ABSPackageURL, nil
case "community", "multilib", "community-testing", "multilib-testing": case "community", "multilib", "community-testing", "multilib-testing":
repoURL = ABSCommunityURL return ABSCommunityURL, nil
default: }
return "", ErrInvalidRepository return "", ErrInvalidRepository
} }
return fmt.Sprintf(_urlPackagePath, repoURL, pkgName), nil // Return format for pkgbuild
// https://github.com/archlinux/svntogit-community/raw/packages/neovim/trunk/PKGBUILD
func getPackageURL(db, pkgName string) (string, error) {
repoURL, err := getRepoURL(db)
if err != nil {
return "", err
} }
return fmt.Sprintf(_urlPackagePath, repoURL, pkgName), err
}
// Return format for pkgbuild repo
// https://github.com/archlinux/svntogit-community.git
func getPackageRepoURL(db string) (string, error) {
repoURL, err := getRepoURL(db)
return repoURL + ".git", err
}
// GetABSPkgbuild retrieves the PKGBUILD file to a dest directory
func GetABSPkgbuild(httpClient *http.Client, dbName, pkgName string) ([]byte, error) { func GetABSPkgbuild(httpClient *http.Client, dbName, pkgName string) ([]byte, error) {
packageURL, err := getPackageURL(dbName, pkgName) packageURL, err := getPackageURL(dbName, pkgName)
if err != nil { if err != nil {
@ -48,7 +65,7 @@ func GetABSPkgbuild(httpClient *http.Client, dbName, pkgName string) ([]byte, er
return nil, err return nil, err
} }
if resp.StatusCode != 200 { if resp.StatusCode != http.StatusOK {
return nil, ErrABSPackageNotFound return nil, ErrABSPackageNotFound
} }
@ -61,3 +78,14 @@ func GetABSPkgbuild(httpClient *http.Client, dbName, pkgName string) ([]byte, er
return pkgBuild, nil return pkgBuild, nil
} }
// ABSPkgbuildRepo retrieves the PKGBUILD repository to a dest directory
func ABSPkgbuildRepo(cmdRunner exe.Runner, cmdBuilder exe.GitCmdBuilder, dbName, pkgName, dest string, force bool) error {
pkgURL, err := getPackageRepoURL(dbName)
if err != nil {
return err
}
return downloadGitRepo(cmdRunner, cmdBuilder, pkgURL,
pkgName, dest, force, "--single-branch", "-b", "packages/"+pkgName)
}

View file

@ -81,13 +81,11 @@ func Test_getPackageURL(t *testing.T) {
func TestGetABSPkgbuild(t *testing.T) { func TestGetABSPkgbuild(t *testing.T) {
pkgBuildHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pkgBuildHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(gitExtrasPKGBUILD)) w.Write([]byte(gitExtrasPKGBUILD))
}) })
notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404) w.WriteHeader(404)
}) })

View file

@ -1,18 +1,16 @@
package download package download
import ( import (
"errors" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v10/pkg/settings/exe"
) )
var AURPackageURL = "https://aur.archlinux.org/cgit/aur.git" var AURPackageURL = "https://aur.archlinux.org/cgit/aur.git"
var ErrAURPackageNotFound = errors.New(gotext.Get("package not found in AUR"))
func GetAURPkgbuild(httpClient *http.Client, pkgName string) ([]byte, error) { func GetAURPkgbuild(httpClient *http.Client, pkgName string) ([]byte, error) {
values := url.Values{} values := url.Values{}
values.Set("h", pkgName) values.Set("h", pkgName)
@ -22,8 +20,9 @@ func GetAURPkgbuild(httpClient *http.Client, pkgName string) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.StatusCode != 200 {
return nil, ErrAURPackageNotFound if resp.StatusCode != http.StatusOK {
return nil, ErrAURPackageNotFound{pkgName: pkgName}
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -35,3 +34,10 @@ func GetAURPkgbuild(httpClient *http.Client, pkgName string) ([]byte, error) {
return pkgBuild, nil return pkgBuild, nil
} }
// AURPkgbuildRepo retrieves the PKGBUILD repository to a dest directory.
func AURPkgbuildRepo(cmdRunner exe.Runner, cmdBuilder exe.GitCmdBuilder, aurURL, pkgName, dest string, force bool) error {
pkgURL := fmt.Sprintf("%s/%s.git", aurURL, pkgName)
return downloadGitRepo(cmdRunner, cmdBuilder, pkgURL, pkgName, dest, force)
}

View file

@ -10,13 +10,11 @@ import (
func TestGetAURPkgbuild(t *testing.T) { func TestGetAURPkgbuild(t *testing.T) {
pkgBuildHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pkgBuildHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(gitExtrasPKGBUILD)) w.Write([]byte(gitExtrasPKGBUILD))
}) })
notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404) w.WriteHeader(404)
}) })

31
pkg/download/errors.go Normal file
View file

@ -0,0 +1,31 @@
package download
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
// ErrAURPackageNotFound means that package was not found in AUR.
type ErrAURPackageNotFound struct {
pkgName string
}
func (e ErrAURPackageNotFound) Error() string {
return fmt.Sprintln(gotext.Get("package not found in AUR"), ":", e.pkgName)
}
type ErrGetPKGBUILDRepo struct {
inner error
pkgName string
errOut string
}
func (e ErrGetPKGBUILDRepo) Error() string {
return fmt.Sprintln(gotext.Get("error fetching %s: %s", e.pkgName, e.errOut),
"\n\t context:", e.inner.Error())
}
func (e *ErrGetPKGBUILDRepo) Unwrap() error {
return e.inner
}

View file

@ -2,14 +2,61 @@ package download
import ( import (
"net/http" "net/http"
"os"
"path/filepath"
"sync" "sync"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/go-alpm/v2"
"github.com/Jguer/yay/v10/pkg/db" "github.com/Jguer/yay/v10/pkg/db"
"github.com/Jguer/yay/v10/pkg/multierror" "github.com/Jguer/yay/v10/pkg/multierror"
"github.com/Jguer/yay/v10/pkg/settings" "github.com/Jguer/yay/v10/pkg/settings"
"github.com/Jguer/yay/v10/pkg/settings/exe"
"github.com/Jguer/yay/v10/pkg/text" "github.com/Jguer/yay/v10/pkg/text"
) )
func downloadGitRepo(cmdRunner exe.Runner,
cmdBuilder exe.GitCmdBuilder, pkgURL, pkgName, dest string, force bool, gitArgs ...string) error {
gitArgs = append(gitArgs, pkgURL, pkgName)
cloneArgs := make([]string, 0, len(gitArgs)+4)
cloneArgs = append(cloneArgs, "clone", "--no-progress")
cloneArgs = append(cloneArgs, gitArgs...)
finalDir := filepath.Join(dest, pkgName)
if _, err := os.Stat(filepath.Join(finalDir, ".git")); os.IsNotExist(err) {
if _, errD := os.Stat(finalDir); force && errD == nil {
if errR := os.RemoveAll(finalDir); errR != nil {
return ErrGetPKGBUILDRepo{inner: errR, pkgName: pkgName}
}
}
cmd := cmdBuilder.BuildGitCmd(dest, cloneArgs...)
_, stderr, errCapture := cmdRunner.Capture(cmd, 0)
if errCapture != nil {
return ErrGetPKGBUILDRepo{inner: errCapture, pkgName: pkgName, errOut: stderr}
}
} else if err != nil {
return ErrGetPKGBUILDRepo{
inner: err,
pkgName: pkgName,
errOut: gotext.Get("error reading %s", filepath.Join(dest, pkgName, ".git")),
}
} else {
cmd := cmdBuilder.BuildGitCmd(filepath.Join(dest, pkgName), "pull", "--ff-only")
_, stderr, errCmd := cmdRunner.Capture(cmd, 0)
if errCmd != nil {
return ErrGetPKGBUILDRepo{inner: errCmd, pkgName: pkgName, errOut: stderr}
}
}
return nil
}
func getURLName(pkg db.IPackage) string { func getURLName(pkg db.IPackage) string {
name := pkg.Base() name := pkg.Base()
if name == "" { if name == "" {
@ -21,13 +68,18 @@ func getURLName(pkg db.IPackage) string {
func GetPkgbuilds(dbExecutor db.Executor, httpClient *http.Client, targets []string, mode settings.TargetMode) (map[string][]byte, error) { func GetPkgbuilds(dbExecutor db.Executor, httpClient *http.Client, targets []string, mode settings.TargetMode) (map[string][]byte, error) {
pkgbuilds := make(map[string][]byte, len(targets)) pkgbuilds := make(map[string][]byte, len(targets))
var mux sync.Mutex
var errs multierror.MultiError var (
var wg sync.WaitGroup mux sync.Mutex
errs multierror.MultiError
wg sync.WaitGroup
)
sem := make(chan uint8, MaxConcurrentFetch) sem := make(chan uint8, MaxConcurrentFetch)
for _, target := range targets { for _, target := range targets {
aur := true aur := true
dbName, name := text.SplitDBFromName(target) dbName, name := text.SplitDBFromName(target)
if dbName != "aur" && (mode == settings.ModeAny || mode == settings.ModeRepo) { if dbName != "aur" && (mode == settings.ModeAny || mode == settings.ModeRepo) {
if pkg := dbExecutor.SyncPackage(name); pkg != nil { if pkg := dbExecutor.SyncPackage(name); pkg != nil {
@ -43,11 +95,14 @@ func GetPkgbuilds(dbExecutor db.Executor, httpClient *http.Client, targets []str
} }
sem <- 1 sem <- 1
wg.Add(1) wg.Add(1)
go func(target, dbName, pkgName string, aur bool) { go func(target, dbName, pkgName string, aur bool) {
var err error var (
var pkgbuild []byte err error
pkgbuild []byte
)
if aur { if aur {
pkgbuild, err = GetAURPkgbuild(httpClient, pkgName) pkgbuild, err = GetAURPkgbuild(httpClient, pkgName)
@ -72,3 +127,90 @@ func GetPkgbuilds(dbExecutor db.Executor, httpClient *http.Client, targets []str
return pkgbuilds, errs.Return() return pkgbuilds, errs.Return()
} }
func PKGBUILDRepos(dbExecutor db.Executor,
cmdRunner exe.Runner,
cmdBuilder exe.GitCmdBuilder,
targets []string, mode settings.TargetMode, aurURL, dest string, force bool) (map[string]bool, error) {
cloned := make(map[string]bool, len(targets))
var (
mux sync.Mutex
errs multierror.MultiError
wg sync.WaitGroup
)
sem := make(chan uint8, MaxConcurrentFetch)
for _, target := range targets {
aur := true
dbName, name := text.SplitDBFromName(target)
if dbName != "aur" && (mode == settings.ModeAny || mode == settings.ModeRepo) {
var pkg alpm.IPackage
if dbName != "" {
pkg = dbExecutor.SatisfierFromDB(name, dbName)
if pkg == nil {
// if the user precised a db but the package is not in the db
// then it is missing
continue
}
} else {
pkg = dbExecutor.SyncPackage(name)
}
if pkg != nil {
aur = false
name = getURLName(pkg)
dbName = pkg.DB().Name()
}
}
if aur && mode == settings.ModeRepo {
// Mode does not allow AUR packages
continue
}
sem <- 1
wg.Add(1)
go func(target, dbName, pkgName string, aur bool) {
var (
err error
)
if aur {
err = AURPkgbuildRepo(cmdRunner, cmdBuilder, aurURL, pkgName, dest, force)
} else {
err = ABSPkgbuildRepo(cmdRunner, cmdBuilder, dbName, pkgName, dest, force)
}
success := err == nil
if success {
mux.Lock()
cloned[target] = success
mux.Unlock()
}
if !success {
errs.Add(err)
}
if aur {
text.OperationInfoln(
gotext.Get("(%d/%d) Downloaded PKGBUILD: %s",
len(cloned), len(targets), text.Cyan(pkgName)))
} else {
text.OperationInfoln(
gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s",
len(cloned), len(targets), text.Cyan(pkgName)))
}
<-sem
wg.Done()
}(target, dbName, name, aur)
}
wg.Wait()
return cloned, errs.Return()
}

View file

@ -5,6 +5,10 @@ import (
"os/exec" "os/exec"
) )
type GitCmdBuilder interface {
BuildGitCmd(dir string, extraArgs ...string) *exec.Cmd
}
type CmdBuilder struct { type CmdBuilder struct {
GitBin string GitBin string
GitFlags []string GitFlags []string

View file

@ -291,7 +291,9 @@ func printPkgbuilds(dbExecutor db.Executor, httpClient *http.Client, targets []s
missing = append(missing, target) missing = append(missing, target)
} }
} }
text.Warnln("Unable to find the following packages:", strings.Join(missing, ", ")) text.Warnln(gotext.Get("Unable to find the following packages:"), strings.Join(missing, ", "))
return fmt.Errorf("")
} }
return nil return nil