Add dry-run flag and implement AUR support
This commit is contained in:
+215
-14
@@ -1,7 +1,11 @@
|
||||
package pacman
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@@ -12,14 +16,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Root = "/"
|
||||
LockFile = "/var/lib/pacman/db.lock"
|
||||
Root = "/"
|
||||
LockFile = "/var/lib/pacman/db.lock"
|
||||
AURInfoURL = "https://aur.archlinux.org/rpc?v=5&type=info"
|
||||
)
|
||||
|
||||
type Pac struct{}
|
||||
type Pac struct {
|
||||
aurCache map[string]AURPackage
|
||||
}
|
||||
|
||||
func New() (*Pac, error) {
|
||||
return &Pac{}, nil
|
||||
return &Pac{aurCache: make(map[string]AURPackage)}, nil
|
||||
}
|
||||
|
||||
func (p *Pac) Close() error {
|
||||
@@ -27,9 +34,21 @@ func (p *Pac) Close() error {
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
Name string
|
||||
InAUR bool
|
||||
Exists bool
|
||||
Name string
|
||||
InAUR bool
|
||||
Exists bool
|
||||
AURInfo *AURPackage
|
||||
}
|
||||
|
||||
type AURResponse struct {
|
||||
Results []AURPackage `json:"results"`
|
||||
}
|
||||
|
||||
type AURPackage struct {
|
||||
Name string `json:"Name"`
|
||||
PackageBase string `json:"PackageBase"`
|
||||
Version string `json:"Version"`
|
||||
URL string `json:"URL"`
|
||||
}
|
||||
|
||||
func (p *Pac) ValidatePackage(name string) (*PackageInfo, error) {
|
||||
@@ -43,9 +62,9 @@ func (p *Pac) ValidatePackage(name string) (*PackageInfo, error) {
|
||||
return &PackageInfo{Name: name, Exists: true, InAUR: false}, nil
|
||||
}
|
||||
|
||||
cmd = exec.Command("aur", "search", name)
|
||||
if out, err := cmd.Output(); err == nil && len(out) > 0 {
|
||||
return &PackageInfo{Name: name, Exists: true, InAUR: true}, nil
|
||||
p.ensureAURCache([]string{name})
|
||||
if aurInfo, ok := p.aurCache[name]; ok {
|
||||
return &PackageInfo{Name: name, Exists: true, InAUR: true, AURInfo: &aurInfo}, nil
|
||||
}
|
||||
|
||||
return &PackageInfo{Name: name, Exists: false, InAUR: false}, nil
|
||||
@@ -105,15 +124,25 @@ func Sync(packages []string) (*output.Result, error) {
|
||||
}
|
||||
}
|
||||
|
||||
pacmanPkgs, aurPkgs := p.categorizePackages(packages)
|
||||
|
||||
for _, pkg := range packages {
|
||||
if err := p.MarkExplicit(pkg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: could not mark %s as explicit: %v\n", pkg, err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = p.SyncPackages(packages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(pacmanPkgs) > 0 {
|
||||
_, err = p.SyncPackages(pacmanPkgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range aurPkgs {
|
||||
if err := p.InstallAUR(pkg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
removed, err := p.CleanupOrphans()
|
||||
@@ -122,7 +151,7 @@ func Sync(packages []string) (*output.Result, error) {
|
||||
}
|
||||
|
||||
after, _ := getInstalledCount()
|
||||
installedCount := max(after - before, 0)
|
||||
installedCount := max(after-before, 0)
|
||||
|
||||
return &output.Result{
|
||||
Installed: installedCount,
|
||||
@@ -130,6 +159,118 @@ func Sync(packages []string) (*output.Result, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Pac) categorizePackages(packages []string) (pacmanPkgs, aurPkgs []string) {
|
||||
var notInPacman []string
|
||||
|
||||
for _, pkg := range packages {
|
||||
info, err := p.ValidatePackage(pkg)
|
||||
if err != nil || !info.Exists {
|
||||
notInPacman = append(notInPacman, pkg)
|
||||
} else if !info.InAUR {
|
||||
pacmanPkgs = append(pacmanPkgs, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(notInPacman) > 0 {
|
||||
p.ensureAURCache(notInPacman)
|
||||
for _, pkg := range notInPacman {
|
||||
if _, ok := p.aurCache[pkg]; ok {
|
||||
aurPkgs = append(aurPkgs, pkg)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "error: package not found: %s\n", pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pacmanPkgs, aurPkgs
|
||||
}
|
||||
|
||||
func (p *Pac) ensureAURCache(packages []string) {
|
||||
if len(packages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var uncached []string
|
||||
for _, pkg := range packages {
|
||||
if _, ok := p.aurCache[pkg]; !ok {
|
||||
uncached = append(uncached, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(uncached) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
p.fetchAURInfo(uncached)
|
||||
}
|
||||
|
||||
func (p *Pac) fetchAURInfo(packages []string) map[string]AURPackage {
|
||||
result := make(map[string]AURPackage)
|
||||
|
||||
if len(packages) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
for _, pkg := range packages {
|
||||
v.Add("arg[]", pkg)
|
||||
}
|
||||
|
||||
resp, err := http.Get(AURInfoURL + "&" + v.Encode())
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
var aurResp AURResponse
|
||||
if err := json.Unmarshal(body, &aurResp); err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
for _, r := range aurResp.Results {
|
||||
p.aurCache[r.Name] = r
|
||||
result[r.Name] = r
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Pac) InstallAUR(pkgName string) error {
|
||||
aurInfo, ok := p.aurCache[pkgName]
|
||||
if !ok {
|
||||
return fmt.Errorf("AUR package not found in cache: %s", pkgName)
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "declpac-aur-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cloneURL := "https://aur.archlinux.org/" + aurInfo.PackageBase + ".git"
|
||||
cloneCmd := exec.Command("git", "clone", cloneURL, tmpDir)
|
||||
cloneCmd.Stdout = os.Stdout
|
||||
cloneCmd.Stderr = os.Stderr
|
||||
if err := cloneCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to clone AUR repo: %w", err)
|
||||
}
|
||||
|
||||
makepkgCmd := exec.Command("makepkg", "-si", "--noconfirm")
|
||||
makepkgCmd.Stdout = os.Stdout
|
||||
makepkgCmd.Stderr = os.Stderr
|
||||
makepkgCmd.Dir = tmpDir
|
||||
if err := makepkgCmd.Run(); err != nil {
|
||||
return fmt.Errorf("makepkg failed to build AUR package: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInstalledCount() (int, error) {
|
||||
cmd := exec.Command("pacman", "-Qq")
|
||||
output, err := cmd.Output()
|
||||
@@ -177,3 +318,63 @@ func (p *Pac) CleanupOrphans() (int, error) {
|
||||
count := strings.Count(orphanList, "\n") + 1
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func DryRun(packages []string) (*output.Result, error) {
|
||||
p, err := New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
current, err := p.GetInstalledPackages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentSet := make(map[string]bool)
|
||||
for _, pkg := range current {
|
||||
currentSet[pkg] = true
|
||||
}
|
||||
|
||||
var toInstall []string
|
||||
var aurPkgs []string
|
||||
for _, pkg := range packages {
|
||||
if !currentSet[pkg] {
|
||||
info, err := p.ValidatePackage(pkg)
|
||||
if err != nil || !info.Exists {
|
||||
return nil, fmt.Errorf("package not found: %s", pkg)
|
||||
}
|
||||
if info.InAUR {
|
||||
aurPkgs = append(aurPkgs, pkg)
|
||||
} else {
|
||||
toInstall = append(toInstall, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orphans, err := p.listOrphans()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &output.Result{
|
||||
Installed: len(toInstall) + len(aurPkgs),
|
||||
Removed: len(orphans),
|
||||
ToInstall: append(toInstall, aurPkgs...),
|
||||
ToRemove: orphans,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Pac) listOrphans() ([]string, error) {
|
||||
cmd := exec.Command("pacman", "-Qdtq")
|
||||
orphans, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list := strings.TrimSpace(string(orphans))
|
||||
if list == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return strings.Split(list, "\n"), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user