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:
2026-02-25 20:35:45 +00:00
parent 6cbfc96e3d
commit 0d6c5d9e17
14 changed files with 198 additions and 52 deletions

View File

@@ -8,7 +8,9 @@ import (
)
// 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.
func New() *Apt {
@@ -19,27 +21,37 @@ func New() *Apt {
func (a *Apt) Name() string { return "apt" }
// 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...)
}
// 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...)
}
// 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...)
}
// Upgrade all installed packages.
func (a *Apt) Upgrade(ctx context.Context, opts ...snack.Option) error {
a.Lock()
defer a.Unlock()
return upgrade(ctx, opts...)
}
// Update refreshes the package index.
func (a *Apt) Update(ctx context.Context) error {
a.Lock()
defer a.Unlock()
return update(ctx)
}

View File

@@ -17,7 +17,21 @@ func available() bool {
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...)
var args []string
if o.Sudo {
@@ -30,11 +44,17 @@ func buildArgs(command string, pkgs []string, opts ...snack.Option) []string {
if o.DryRun {
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
}
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...)
var cmd *exec.Cmd
if args[0] == "sudo" {
@@ -57,15 +77,15 @@ func runAptGet(ctx context.Context, command string, pkgs []string, opts ...snack
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...)
}
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...)
}
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...)
}

View File

@@ -10,15 +10,15 @@ import (
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
}
func remove(_ context.Context, _ []string, _ ...snack.Option) error {
func remove(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
return snack.ErrUnsupportedPlatform
}
func purge(_ context.Context, _ []string, _ ...snack.Option) error {
func purge(_ context.Context, _ []snack.Target, _ ...snack.Option) error {
return snack.ErrUnsupportedPlatform
}