mirror of
https://github.com/gogrlx/snack.git
synced 2026-04-02 05:08:42 -07:00
feat: add per-provider mutex and Target-aware implementations
- Add snack.Locker embed for per-provider mutex serialization - Update all providers (pacman, apk, apt, dpkg) to use []Target with version pinning support (pkg=version syntax) - Add lock/unlock to all mutating operations (Install, Remove, Purge, Upgrade, Update) - Add snack.TargetNames helper and formatTargets per provider - apt: add FromRepo (-t) and Reinstall support - dpkg: use Target.Source for .deb file paths in Install
This commit is contained in:
20
apk/apk.go
20
apk/apk.go
@@ -8,7 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Apk wraps apk-tools operations.
|
// Apk wraps apk-tools operations.
|
||||||
type Apk struct{}
|
type Apk struct {
|
||||||
|
snack.Locker
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a new Apk manager.
|
// New returns a new Apk manager.
|
||||||
func New() *Apk {
|
func New() *Apk {
|
||||||
@@ -25,27 +27,37 @@ func (a *Apk) Name() string { return "apk" }
|
|||||||
func (a *Apk) Available() bool { return available() }
|
func (a *Apk) Available() bool { return available() }
|
||||||
|
|
||||||
// Install one or more packages.
|
// Install one or more packages.
|
||||||
func (a *Apk) Install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (a *Apk) Install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return install(ctx, pkgs, opts...)
|
return install(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove one or more packages.
|
// Remove one or more packages.
|
||||||
func (a *Apk) Remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (a *Apk) Remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return remove(ctx, pkgs, opts...)
|
return remove(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge removes packages including config files.
|
// Purge removes packages including config files.
|
||||||
func (a *Apk) Purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (a *Apk) Purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return purge(ctx, pkgs, opts...)
|
return purge(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade all installed packages.
|
// Upgrade all installed packages.
|
||||||
func (a *Apk) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
func (a *Apk) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return upgrade(ctx, opts...)
|
return upgrade(ctx, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update refreshes the package index.
|
// Update refreshes the package index.
|
||||||
func (a *Apk) Update(ctx context.Context) error {
|
func (a *Apk) Update(ctx context.Context) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return update(ctx)
|
return update(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,20 +52,34 @@ func run(ctx context.Context, base []string, opts ...snack.Option) (string, erro
|
|||||||
return strings.TrimSpace(string(out)), nil
|
return strings.TrimSpace(string(out)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
// formatTargets converts targets to apk CLI arguments.
|
||||||
args := append([]string{"add"}, pkgs...)
|
// apk uses "pkg=version" for version pinning.
|
||||||
|
func formatTargets(targets []snack.Target) []string {
|
||||||
|
args := make([]string, 0, len(targets))
|
||||||
|
for _, t := range targets {
|
||||||
|
if t.Version != "" {
|
||||||
|
args = append(args, t.Name+"="+t.Version)
|
||||||
|
} else {
|
||||||
|
args = append(args, t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
args := append([]string{"add"}, formatTargets(pkgs)...)
|
||||||
_, err := run(ctx, args, opts...)
|
_, err := run(ctx, args, opts...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
args := append([]string{"del"}, pkgs...)
|
args := append([]string{"del"}, snack.TargetNames(pkgs)...)
|
||||||
_, err := run(ctx, args, opts...)
|
_, err := run(ctx, args, opts...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
args := append([]string{"del", "--purge"}, pkgs...)
|
args := append([]string{"del", "--purge"}, snack.TargetNames(pkgs)...)
|
||||||
_, err := run(ctx, args, opts...)
|
_, err := run(ctx, args, opts...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
|
|
||||||
func available() bool { return false }
|
func available() bool { return false }
|
||||||
|
|
||||||
func install(_ context.Context, _ []string, _ ...snack.Option) error {
|
func install(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(_ context.Context, _ []string, _ ...snack.Option) error {
|
func remove(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(_ context.Context, _ []string, _ ...snack.Option) error {
|
func purge(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
apt/apt.go
20
apt/apt.go
@@ -8,7 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Apt implements the snack.Manager interface using apt-get and apt-cache.
|
// Apt implements the snack.Manager interface using apt-get and apt-cache.
|
||||||
type Apt struct{}
|
type Apt struct {
|
||||||
|
snack.Locker
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a new Apt manager.
|
// New returns a new Apt manager.
|
||||||
func New() *Apt {
|
func New() *Apt {
|
||||||
@@ -19,27 +21,37 @@ func New() *Apt {
|
|||||||
func (a *Apt) Name() string { return "apt" }
|
func (a *Apt) Name() string { return "apt" }
|
||||||
|
|
||||||
// Install one or more packages.
|
// Install one or more packages.
|
||||||
func (a *Apt) Install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (a *Apt) Install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return install(ctx, pkgs, opts...)
|
return install(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove one or more packages.
|
// Remove one or more packages.
|
||||||
func (a *Apt) Remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (a *Apt) Remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return remove(ctx, pkgs, opts...)
|
return remove(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge one or more packages including config files.
|
// Purge one or more packages including config files.
|
||||||
func (a *Apt) Purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (a *Apt) Purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return purge(ctx, pkgs, opts...)
|
return purge(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade all installed packages.
|
// Upgrade all installed packages.
|
||||||
func (a *Apt) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
func (a *Apt) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return upgrade(ctx, opts...)
|
return upgrade(ctx, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update refreshes the package index.
|
// Update refreshes the package index.
|
||||||
func (a *Apt) Update(ctx context.Context) error {
|
func (a *Apt) Update(ctx context.Context) error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
return update(ctx)
|
return update(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,21 @@ func available() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildArgs(command string, pkgs []string, opts ...snack.Option) []string {
|
// formatTargets converts targets to apt CLI arguments.
|
||||||
|
// apt uses "pkg=version" for version pinning.
|
||||||
|
func formatTargets(targets []snack.Target) []string {
|
||||||
|
args := make([]string, 0, len(targets))
|
||||||
|
for _, t := range targets {
|
||||||
|
if t.Version != "" {
|
||||||
|
args = append(args, t.Name+"="+t.Version)
|
||||||
|
} else {
|
||||||
|
args = append(args, t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildArgs(command string, pkgs []snack.Target, opts ...snack.Option) []string {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
var args []string
|
var args []string
|
||||||
if o.Sudo {
|
if o.Sudo {
|
||||||
@@ -30,11 +44,17 @@ func buildArgs(command string, pkgs []string, opts ...snack.Option) []string {
|
|||||||
if o.DryRun {
|
if o.DryRun {
|
||||||
args = append(args, "--dry-run")
|
args = append(args, "--dry-run")
|
||||||
}
|
}
|
||||||
args = append(args, pkgs...)
|
if o.FromRepo != "" {
|
||||||
|
args = append(args, "-t", o.FromRepo)
|
||||||
|
}
|
||||||
|
if o.Reinstall && command == "install" {
|
||||||
|
args = append(args, "--reinstall")
|
||||||
|
}
|
||||||
|
args = append(args, formatTargets(pkgs)...)
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAptGet(ctx context.Context, command string, pkgs []string, opts ...snack.Option) error {
|
func runAptGet(ctx context.Context, command string, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
args := buildArgs(command, pkgs, opts...)
|
args := buildArgs(command, pkgs, opts...)
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if args[0] == "sudo" {
|
if args[0] == "sudo" {
|
||||||
@@ -57,15 +77,15 @@ func runAptGet(ctx context.Context, command string, pkgs []string, opts ...snack
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
return runAptGet(ctx, "install", pkgs, opts...)
|
return runAptGet(ctx, "install", pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
return runAptGet(ctx, "remove", pkgs, opts...)
|
return runAptGet(ctx, "remove", pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
return runAptGet(ctx, "purge", pkgs, opts...)
|
return runAptGet(ctx, "purge", pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
|
|
||||||
func available() bool { return false }
|
func available() bool { return false }
|
||||||
|
|
||||||
func install(_ context.Context, _ []string, _ ...snack.Option) error {
|
func install(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(_ context.Context, _ []string, _ ...snack.Option) error {
|
func remove(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(_ context.Context, _ []string, _ ...snack.Option) error {
|
func purge(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
dpkg/dpkg.go
16
dpkg/dpkg.go
@@ -8,7 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Dpkg implements the snack.Manager interface using dpkg and dpkg-query.
|
// Dpkg implements the snack.Manager interface using dpkg and dpkg-query.
|
||||||
type Dpkg struct{}
|
type Dpkg struct {
|
||||||
|
snack.Locker
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a new Dpkg manager.
|
// New returns a new Dpkg manager.
|
||||||
func New() *Dpkg {
|
func New() *Dpkg {
|
||||||
@@ -19,17 +21,23 @@ func New() *Dpkg {
|
|||||||
func (d *Dpkg) Name() string { return "dpkg" }
|
func (d *Dpkg) Name() string { return "dpkg" }
|
||||||
|
|
||||||
// Install one or more .deb files.
|
// Install one or more .deb files.
|
||||||
func (d *Dpkg) Install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (d *Dpkg) Install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
return install(ctx, pkgs, opts...)
|
return install(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove one or more packages.
|
// Remove one or more packages.
|
||||||
func (d *Dpkg) Remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (d *Dpkg) Remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
return remove(ctx, pkgs, opts...)
|
return remove(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge one or more packages including config files.
|
// Purge one or more packages including config files.
|
||||||
func (d *Dpkg) Purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (d *Dpkg) Purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
return purge(ctx, pkgs, opts...)
|
return purge(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func available() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
var args []string
|
var args []string
|
||||||
if o.Sudo {
|
if o.Sudo {
|
||||||
@@ -27,7 +27,14 @@ func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
|||||||
if o.DryRun {
|
if o.DryRun {
|
||||||
args = append(args, "--simulate")
|
args = append(args, "--simulate")
|
||||||
}
|
}
|
||||||
args = append(args, pkgs...)
|
// dpkg -i takes file paths; use Source if set, otherwise Name
|
||||||
|
for _, t := range pkgs {
|
||||||
|
if t.Source != "" {
|
||||||
|
args = append(args, t.Source)
|
||||||
|
} else {
|
||||||
|
args = append(args, t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
@@ -41,7 +48,7 @@ func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
var args []string
|
var args []string
|
||||||
if o.Sudo {
|
if o.Sudo {
|
||||||
@@ -51,7 +58,7 @@ func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
|||||||
if o.DryRun {
|
if o.DryRun {
|
||||||
args = append(args, "--simulate")
|
args = append(args, "--simulate")
|
||||||
}
|
}
|
||||||
args = append(args, pkgs...)
|
args = append(args, snack.TargetNames(pkgs)...)
|
||||||
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
@@ -61,7 +68,7 @@ func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
var args []string
|
var args []string
|
||||||
if o.Sudo {
|
if o.Sudo {
|
||||||
@@ -71,7 +78,7 @@ func purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
|||||||
if o.DryRun {
|
if o.DryRun {
|
||||||
args = append(args, "--simulate")
|
args = append(args, "--simulate")
|
||||||
}
|
}
|
||||||
args = append(args, pkgs...)
|
args = append(args, snack.TargetNames(pkgs)...)
|
||||||
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
|
|
||||||
func available() bool { return false }
|
func available() bool { return false }
|
||||||
|
|
||||||
func install(_ context.Context, _ []string, _ ...snack.Option) error {
|
func install(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(_ context.Context, _ []string, _ ...snack.Option) error {
|
func remove(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(_ context.Context, _ []string, _ ...snack.Option) error {
|
func purge(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
mutex.go
Normal file
27
mutex.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package snack
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// Locker is an embeddable type that provides per-provider mutex locking.
|
||||||
|
// Each package manager backend should embed this in its struct to serialize
|
||||||
|
// mutating operations (Install, Remove, Purge, Upgrade, Update).
|
||||||
|
//
|
||||||
|
// Read-only operations (List, Search, Info, IsInstalled, Version) generally
|
||||||
|
// don't need the lock, but backends may choose to lock them if the underlying
|
||||||
|
// tool doesn't support concurrent reads.
|
||||||
|
//
|
||||||
|
// Different providers use independent locks, so an apt Install and a snap
|
||||||
|
// Install can run concurrently, but two apt Installs will serialize.
|
||||||
|
type Locker struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock acquires the provider lock. Call before mutating operations.
|
||||||
|
func (l *Locker) Lock() {
|
||||||
|
l.mu.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock releases the provider lock. Defer after Lock.
|
||||||
|
func (l *Locker) Unlock() {
|
||||||
|
l.mu.Unlock()
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Pacman wraps the pacman package manager CLI.
|
// Pacman wraps the pacman package manager CLI.
|
||||||
type Pacman struct{}
|
type Pacman struct {
|
||||||
|
snack.Locker
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a new Pacman manager.
|
// New returns a new Pacman manager.
|
||||||
func New() *Pacman {
|
func New() *Pacman {
|
||||||
@@ -22,27 +24,37 @@ func (p *Pacman) Name() string { return "pacman" }
|
|||||||
func (p *Pacman) Available() bool { return available() }
|
func (p *Pacman) Available() bool { return available() }
|
||||||
|
|
||||||
// Install one or more packages.
|
// Install one or more packages.
|
||||||
func (p *Pacman) Install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (p *Pacman) Install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
return install(ctx, pkgs, opts...)
|
return install(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove one or more packages.
|
// Remove one or more packages.
|
||||||
func (p *Pacman) Remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (p *Pacman) Remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
return remove(ctx, pkgs, opts...)
|
return remove(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge removes packages including configuration files.
|
// Purge removes packages including configuration files.
|
||||||
func (p *Pacman) Purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func (p *Pacman) Purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
return purge(ctx, pkgs, opts...)
|
return purge(ctx, pkgs, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade all installed packages to their latest versions.
|
// Upgrade all installed packages to their latest versions.
|
||||||
func (p *Pacman) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
func (p *Pacman) Upgrade(ctx context.Context, opts ...snack.Option) error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
return upgrade(ctx, opts...)
|
return upgrade(ctx, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update refreshes the package database.
|
// Update refreshes the package database.
|
||||||
func (p *Pacman) Update(ctx context.Context) error {
|
func (p *Pacman) Update(ctx context.Context) error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
return update(ctx)
|
return update(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,23 +58,47 @@ func run(ctx context.Context, baseArgs []string, opts snack.Options) (string, er
|
|||||||
return stdout.String(), nil
|
return stdout.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
// formatTargets converts targets to pacman CLI arguments.
|
||||||
|
// Pacman uses "pkg=version" for version pinning.
|
||||||
|
func formatTargets(targets []snack.Target) []string {
|
||||||
|
args := make([]string, 0, len(targets))
|
||||||
|
for _, t := range targets {
|
||||||
|
if t.Version != "" {
|
||||||
|
args = append(args, t.Name+"="+t.Version)
|
||||||
|
} else {
|
||||||
|
args = append(args, t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func install(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
args := append([]string{"-S", "--noconfirm"}, pkgs...)
|
base := []string{"-S", "--noconfirm"}
|
||||||
|
if o.Refresh {
|
||||||
|
base = []string{"-Sy", "--noconfirm"}
|
||||||
|
}
|
||||||
|
for _, t := range pkgs {
|
||||||
|
if t.FromRepo != "" || o.FromRepo != "" {
|
||||||
|
// Not directly supported by pacman CLI; user should configure repos
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args := append(base, formatTargets(pkgs)...)
|
||||||
_, err := run(ctx, args, o)
|
_, err := run(ctx, args, o)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func remove(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
args := append([]string{"-R", "--noconfirm"}, pkgs...)
|
args := append([]string{"-R", "--noconfirm"}, snack.TargetNames(pkgs)...)
|
||||||
_, err := run(ctx, args, o)
|
_, err := run(ctx, args, o)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(ctx context.Context, pkgs []string, opts ...snack.Option) error {
|
func purge(ctx context.Context, pkgs []snack.Target, opts ...snack.Option) error {
|
||||||
o := snack.ApplyOptions(opts...)
|
o := snack.ApplyOptions(opts...)
|
||||||
args := append([]string{"-Rns", "--noconfirm"}, pkgs...)
|
args := append([]string{"-Rns", "--noconfirm"}, snack.TargetNames(pkgs)...)
|
||||||
_, err := run(ctx, args, o)
|
_, err := run(ctx, args, o)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
|
|
||||||
func available() bool { return false }
|
func available() bool { return false }
|
||||||
|
|
||||||
func install(_ context.Context, _ []string, _ ...snack.Option) error {
|
func install(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(_ context.Context, _ []string, _ ...snack.Option) error {
|
func remove(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func purge(_ context.Context, _ []string, _ ...snack.Option) error {
|
func purge(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
|
||||||
return snack.ErrUnsupportedPlatform
|
return snack.ErrUnsupportedPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user