mirror of
https://github.com/Jguer/yay.git
synced 2024-11-07 17:47:21 +01:00
20a98de384
=== RUN TestSearch --- PASS: TestSearch (0.38s) === RUN TestInfo --- PASS: TestInfo (0.08s) === RUN TestUpdate :: Starting AUR upgrade... ==> arduino: 1:1.6.12-3 -> 1:1.6.12-4 ==> Proceed with upgrade (Y/n) ==> Installing arduino ==> Edit PKGBUILD? (y/N) --- PASS: TestUpdate (1.15s) === RUN TestUpgrade :: Starting AUR upgrade... ==> arduino: 1:1.6.12-4 -> 1:1.6.12-3 ==> Proceed with upgrade (Y/n) ==> Installing arduino ==> Edit PKGBUILD? (y/N) --- PASS: TestUpgrade (1.07s) PASS BenchMark Update 2 1022581207 ns/op 2034108 B/op 14345 allocs/op BenchMark Upgrade 2 956533245 ns/op 662720 B/op 11738 allocs/op
509 lines
12 KiB
Go
509 lines
12 KiB
Go
package aur
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"sort"
|
|
"strings"
|
|
|
|
alpm "github.com/demizer/go-alpm"
|
|
"github.com/jguer/yay/pacman"
|
|
)
|
|
|
|
var version = "undefined"
|
|
|
|
// TarBin describes the default installation point of tar command.
|
|
const TarBin string = "/usr/bin/tar"
|
|
|
|
// BaseURL givers the AUR default address.
|
|
const BaseURL string = "https://aur.archlinux.org"
|
|
|
|
// MakepkgBin describes the default installation point of makepkg command.
|
|
const MakepkgBin string = "/usr/bin/makepkg"
|
|
|
|
// SearchMode is search without numbers.
|
|
const SearchMode int = -1
|
|
|
|
// Result describes an AUR package.
|
|
type Result struct {
|
|
ID int `json:"ID"`
|
|
Name string `json:"Name"`
|
|
PackageBaseID int `json:"PackageBaseID"`
|
|
PackageBase string `json:"PackageBase"`
|
|
Version string `json:"Version"`
|
|
Description string `json:"Description"`
|
|
URL string `json:"URL"`
|
|
NumVotes int `json:"NumVotes"`
|
|
Popularity float32 `json:"Popularity"`
|
|
OutOfDate int `json:"OutOfDate"`
|
|
Maintainer string `json:"Maintainer"`
|
|
FirstSubmitted int `json:"FirstSubmitted"`
|
|
LastModified int64 `json:"LastModified"`
|
|
URLPath string `json:"URLPath"`
|
|
Depends []string `json:"Depends"`
|
|
MakeDepends []string `json:"MakeDepends"`
|
|
OptDepends []string `json:"OptDepends"`
|
|
Conflicts []string `json:"Conflicts"`
|
|
License []string `json:"License"`
|
|
Keywords []string `json:"Keywords"`
|
|
Installed bool
|
|
}
|
|
|
|
// Query is a collection of Results
|
|
type Query []Result
|
|
|
|
func (q Query) Len() int {
|
|
return len(q)
|
|
}
|
|
|
|
func (q Query) Less(i, j int) bool {
|
|
return q[i].NumVotes < q[j].NumVotes
|
|
}
|
|
|
|
func (q Query) Swap(i, j int) {
|
|
q[i], q[j] = q[j], q[i]
|
|
}
|
|
|
|
// PrintSearch handles printing search results in a given format
|
|
func (q Query) PrintSearch(start int) {
|
|
for i, res := range q {
|
|
switch {
|
|
case start != SearchMode && res.Installed == true:
|
|
fmt.Printf("%d \x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[0m(%d) \x1b[32;40mInstalled\x1b[0m\n%s\n",
|
|
start+i, "aur", res.Name, res.Version, res.NumVotes, res.Description)
|
|
case start != SearchMode && res.Installed != true:
|
|
fmt.Printf("%d \x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[0m(%d)\n%s\n",
|
|
start+i, "aur", res.Name, res.Version, res.NumVotes, res.Description)
|
|
case start == SearchMode && res.Installed == true:
|
|
fmt.Printf("\x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[32;40mInstalled\x1b[0m\n%s\n",
|
|
"aur", res.Name, res.Version, res.Description)
|
|
case start == SearchMode && res.Installed != true:
|
|
fmt.Printf("\x1b[1m%s/\x1b[33m%s \x1b[36m%s\x1b[0m\n%s\n",
|
|
"aur", res.Name, res.Version, res.Description)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search returns an AUR search
|
|
func Search(pkg string, sortS bool) (Query, int, error) {
|
|
type returned struct {
|
|
Results Query `json:"results"`
|
|
ResultCount int `json:"resultcount"`
|
|
}
|
|
r := returned{}
|
|
err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=search&arg="+pkg, &r)
|
|
|
|
if sortS {
|
|
sort.Sort(r.Results)
|
|
}
|
|
|
|
// for _, res := range r.Results {
|
|
// res.Installed, err = IspkgInstalled(res.Name)
|
|
// }
|
|
return r.Results, r.ResultCount, err
|
|
}
|
|
|
|
// Info returns an AUR search with package details
|
|
func Info(pkg string) (Query, int, error) {
|
|
type returned struct {
|
|
Results Query `json:"results"`
|
|
ResultCount int `json:"resultcount"`
|
|
}
|
|
r := returned{}
|
|
|
|
err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=info&arg[]="+pkg, &r)
|
|
|
|
return r.Results, r.ResultCount, err
|
|
}
|
|
|
|
// MultiInfo takes a slice of strings and returns a slice with the info of each package
|
|
func MultiInfo(pkgS []string) (Query, int, error) {
|
|
type returned struct {
|
|
Results Query `json:"results"`
|
|
ResultCount int `json:"resultcount"`
|
|
}
|
|
r := returned{}
|
|
|
|
var pkg string
|
|
for _, pkgn := range pkgS {
|
|
pkg += "&arg[]=" + pkgn
|
|
}
|
|
|
|
err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=info"+pkg, &r)
|
|
|
|
return r.Results, r.ResultCount, err
|
|
}
|
|
|
|
// Install sends system commands to make and install a package from pkgName
|
|
func Install(pkg string, baseDir string, conf *alpm.PacmanConfig, flags []string) (err error) {
|
|
q, n, err := Info(pkg)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if n == 0 {
|
|
return fmt.Errorf("Package %s does not exist", pkg)
|
|
}
|
|
|
|
q[0].Install(baseDir, conf, flags)
|
|
return err
|
|
}
|
|
|
|
// Upgrade tries to update every foreign package installed in the system
|
|
func Upgrade(baseDir string, conf *alpm.PacmanConfig, flags []string) error {
|
|
fmt.Println("\x1b[1;36;1m::\x1b[0m\x1b[1m Starting AUR upgrade...\x1b[0m")
|
|
|
|
foreign, n, err := pacman.ForeignPackages()
|
|
if err != nil || n == 0 {
|
|
return err
|
|
}
|
|
|
|
keys := make([]string, len(foreign))
|
|
|
|
i := 0
|
|
for k := range foreign {
|
|
keys[i] = k
|
|
i++
|
|
}
|
|
|
|
q, _, err := MultiInfo(keys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
outdated := q[:0]
|
|
for _, res := range q {
|
|
if _, ok := foreign[res.Name]; ok {
|
|
// Leaving this here for now, warn about downgrades later
|
|
if res.LastModified > foreign[res.Name].Date {
|
|
// o[i] = o[len(o)-1]
|
|
// o[len(o)-1] = Result{} // Trying to help the GC, not sure if necessary. Time will tell
|
|
// o = o[:len(o)-1]
|
|
fmt.Printf("\x1b[1m\x1b[32m==>\x1b[33;1m %s: \x1b[0m%s \x1b[33;1m-> \x1b[0m%s\n",
|
|
res.Name, res.Version, foreign[res.Name].Version)
|
|
outdated = append(outdated, res)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//If there are no outdated packages, don't prompt
|
|
if len(outdated) == 0 {
|
|
fmt.Println(" there is nothing to do")
|
|
return nil
|
|
}
|
|
|
|
// Install updated packages
|
|
if NoConfirm(flags) == false {
|
|
fmt.Println("\x1b[1m\x1b[32m==> Proceed with upgrade\x1b[0m\x1b[1m (Y/n)\x1b[0m")
|
|
var response string
|
|
fmt.Scanln(&response)
|
|
if strings.ContainsAny(response, "n & N") {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for _, pkg := range outdated {
|
|
pkg.Install(baseDir, conf, flags)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdatePackages handles AUR updates
|
|
func UpdatePackages(baseDir string, conf *alpm.PacmanConfig, flags []string) error {
|
|
|
|
fmt.Println("\x1b[1;36;1m::\x1b[0m\x1b[1m Starting AUR upgrade...\x1b[0m")
|
|
|
|
h, err := conf.CreateHandle()
|
|
defer h.Release()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
localDb, err := h.LocalDb()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbList, err := h.SyncDbs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var foreign []alpm.Package
|
|
|
|
// Find foreign packages in system
|
|
for _, pkg := range localDb.PkgCache().Slice() {
|
|
// Change to more effective method
|
|
found := false
|
|
for _, db := range dbList.Slice() {
|
|
_, err = db.PkgByName(pkg.Name())
|
|
if err == nil {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
foreign = append(foreign, pkg)
|
|
}
|
|
}
|
|
|
|
// Find outdated packages
|
|
type Outdated struct {
|
|
res *Result
|
|
pkgVersion string
|
|
er error
|
|
}
|
|
|
|
r := make(chan *Outdated) // Allocate a channel.
|
|
for _, pkg := range foreign {
|
|
// fmt.Println("Checking number", i, pkg.Name())
|
|
go func(pkg alpm.Package) {
|
|
q, n, err := Info(pkg.Name())
|
|
if err != nil {
|
|
r <- nil
|
|
return
|
|
}
|
|
|
|
if n == 0 {
|
|
r <- nil
|
|
return
|
|
}
|
|
|
|
// Leaving this here for now, warn about downgrades later
|
|
if int64(q[0].LastModified) > pkg.InstallDate().Unix() {
|
|
r <- &Outdated{&q[0], pkg.Version(), err}
|
|
} else {
|
|
r <- nil
|
|
}
|
|
}(pkg)
|
|
}
|
|
|
|
var outdated []*Result
|
|
var checkedPkg *Outdated
|
|
for i := 0; i < len(foreign); i++ {
|
|
// fmt.Println("Wait Cycle", i)
|
|
checkedPkg = <-r
|
|
// fmt.Println(checkedPkg)
|
|
if checkedPkg != nil {
|
|
fmt.Printf("\x1b[1m\x1b[32m==>\x1b[33;1m %s: \x1b[0m%s \x1b[33;1m-> \x1b[0m%s\n",
|
|
checkedPkg.res.Name, checkedPkg.pkgVersion, checkedPkg.res.Version)
|
|
outdated = append(outdated, checkedPkg.res)
|
|
}
|
|
}
|
|
|
|
//If there are no outdated packages, don't prompt
|
|
if len(outdated) == 0 {
|
|
fmt.Println(" there is nothing to do")
|
|
return nil
|
|
}
|
|
|
|
// Install updated packages
|
|
if NoConfirm(flags) == false {
|
|
fmt.Println("\x1b[1m\x1b[32m==> Proceed with upgrade\x1b[0m\x1b[1m (Y/n)\x1b[0m")
|
|
var response string
|
|
fmt.Scanln(&response)
|
|
if strings.ContainsAny(response, "n & N") {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for _, pkg := range outdated {
|
|
pkg.Install(baseDir, conf, flags)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Install handles install from Result
|
|
func (a *Result) Install(baseDir string, conf *alpm.PacmanConfig, flags []string) (err error) {
|
|
fmt.Printf("\x1b[1m\x1b[32m==> Installing\x1b[33m %s\x1b[0m\n", a.Name)
|
|
|
|
// No need to use filepath.separators because it won't run on inferior platforms
|
|
err = os.MkdirAll(baseDir+"builds", 0755)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
tarLocation := baseDir + a.Name + ".tar.gz"
|
|
defer os.Remove(baseDir + a.Name + ".tar.gz")
|
|
|
|
err = downloadFile(tarLocation, BaseURL+a.URLPath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = exec.Command(TarBin, "-xf", tarLocation, "-C", baseDir).Run()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer os.RemoveAll(baseDir + a.Name)
|
|
|
|
var response string
|
|
var dir bytes.Buffer
|
|
dir.WriteString(baseDir)
|
|
dir.WriteString(a.Name)
|
|
dir.WriteString("/")
|
|
|
|
if NoConfirm(flags) == false {
|
|
fmt.Println("\x1b[1m\x1b[32m==> Edit PKGBUILD?\x1b[0m\x1b[1m (y/N)\x1b[0m")
|
|
fmt.Scanln(&response)
|
|
if strings.ContainsAny(response, "y & Y") {
|
|
editcmd := exec.Command(Editor, dir.String()+"PKGBUILD")
|
|
editcmd.Stdout = os.Stdout
|
|
editcmd.Stderr = os.Stderr
|
|
editcmd.Stdin = os.Stdin
|
|
err = editcmd.Run()
|
|
}
|
|
}
|
|
depS, err := a.Dependencies(conf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, dep := range depS {
|
|
q, n, errD := Info(dep)
|
|
if errD != nil {
|
|
return errD
|
|
}
|
|
|
|
if n != 0 {
|
|
q[0].Install(baseDir, conf, []string{"--asdeps"})
|
|
}
|
|
}
|
|
|
|
err = os.Chdir(dir.String())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var makepkgcmd *exec.Cmd
|
|
var args []string
|
|
args = append(args, "-sri")
|
|
args = append(args, flags...)
|
|
makepkgcmd = exec.Command(MakepkgBin, args...)
|
|
makepkgcmd.Stdout = os.Stdout
|
|
makepkgcmd.Stderr = os.Stderr
|
|
makepkgcmd.Stdin = os.Stdin
|
|
err = makepkgcmd.Run()
|
|
|
|
return
|
|
}
|
|
|
|
// Dependencies returns package dependencies splitting between AUR results and Repo Results not installed
|
|
func (a *Result) Dependencies(conf *alpm.PacmanConfig) (final []string, err error) {
|
|
h, err := conf.CreateHandle()
|
|
defer h.Release()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
dbList, err := h.SyncDbs()
|
|
localDb, err := h.LocalDb()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
f := func(c rune) bool {
|
|
return c == '>' || c == '<' || c == '=' || c == ' '
|
|
}
|
|
q, n, err := Info(a.Name)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if n == 0 {
|
|
return final, fmt.Errorf("Failed to get deps from RPC")
|
|
}
|
|
|
|
deps := append(q[0].MakeDepends, q[0].Depends...)
|
|
for _, dep := range deps {
|
|
fields := strings.FieldsFunc(dep, f)
|
|
// If package is installed let it go.
|
|
_, err = localDb.PkgByName(fields[0])
|
|
if err == nil {
|
|
continue
|
|
}
|
|
|
|
// If package is in repo let it be installed by makepkg.
|
|
found := false
|
|
for _, db := range dbList.Slice() {
|
|
_, err = db.PkgByName(fields[0])
|
|
if err == nil {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if found {
|
|
continue
|
|
}
|
|
|
|
_, nd, err := Info(fields[0])
|
|
if err != nil {
|
|
return final, err
|
|
}
|
|
|
|
if nd == 0 {
|
|
return final, fmt.Errorf("Unable to find dependency in repos and AUR.")
|
|
}
|
|
|
|
final = append(final, fields[0])
|
|
}
|
|
return
|
|
}
|
|
|
|
// IspkgInstalled returns true if pkgName is installed
|
|
func IspkgInstalled(pkgName string) (bool, error) {
|
|
h, err := alpm.Init("/", "/var/lib/pacman")
|
|
defer h.Release()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
localDb, err := h.LocalDb()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
_, err = localDb.PkgByName(pkgName)
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// IspkgInRepo returns true if pkgName is in a synced repo
|
|
func IspkgInRepo(pkgName string, conf *alpm.PacmanConfig) (bool, error) {
|
|
h, err := conf.CreateHandle()
|
|
defer h.Release()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
dbList, _ := h.SyncDbs()
|
|
for _, db := range dbList.Slice() {
|
|
_, err = db.PkgByName(pkgName)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// NoConfirm returns true if prompts should be ignored
|
|
func NoConfirm(flags []string) bool {
|
|
noconf := false
|
|
for _, flag := range flags {
|
|
if strings.Contains(flag, "noconfirm") {
|
|
noconf = true
|
|
break
|
|
}
|
|
}
|
|
|
|
return noconf
|
|
}
|