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