Files
snack/apk/capabilities_linux.go
Tai Groot 934c6610c5 feat: add Homebrew provider, implement NameNormalizer across all managers
- Add brew package for Homebrew support on macOS and Linux
- Implement NameNormalizer interface (NormalizeName, ParseArch) for all providers
- Add darwin platform detection with Homebrew as default
- Consolidate capabilities by removing separate *_linux.go/*_other.go files
- Update tests for new capability expectations
- Add comprehensive tests for AUR and brew providers
- Update README with capability matrix and modern Target API usage

💘 Generated with Crush

Assisted-by: AWS Claude Opus 4.5 via Crush <crush@charm.land>
2026-03-05 20:40:32 -05:00

133 lines
3.6 KiB
Go

//go:build linux
package apk
import (
"context"
"fmt"
"os/exec"
"strings"
"github.com/gogrlx/snack"
)
func latestVersion(ctx context.Context, pkg string) (string, error) {
// Use `apk search -e` for exact match, output is "pkg-version"
c := exec.CommandContext(ctx, "apk", "search", "-e", pkg)
out, err := c.CombinedOutput()
if err != nil {
return "", fmt.Errorf("apk latestVersion %s: %w", pkg, snack.ErrNotFound)
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
if len(lines) == 0 || lines[0] == "" {
return "", fmt.Errorf("apk latestVersion %s: %w", pkg, snack.ErrNotFound)
}
// Output: "pkg-1.2.3-r0"
_, ver := splitNameVersion(lines[0])
if ver == "" {
return "", fmt.Errorf("apk latestVersion %s: %w", pkg, snack.ErrNotFound)
}
return ver, nil
}
func listUpgrades(ctx context.Context) ([]snack.Package, error) {
c := exec.CommandContext(ctx, "apk", "upgrade", "--simulate")
out, err := c.CombinedOutput()
if err != nil {
// If simulate fails, it could mean no upgrades or an error
outStr := strings.TrimSpace(string(out))
if outStr == "" || strings.Contains(outStr, "OK") {
return nil, nil
}
return nil, fmt.Errorf("apk listUpgrades: %w", err)
}
return parseUpgradeSimulation(string(out)), nil
}
func upgradeAvailable(ctx context.Context, pkg string) (bool, error) {
upgrades, err := listUpgrades(ctx)
if err != nil {
return false, err
}
for _, u := range upgrades {
if u.Name == pkg {
return true, nil
}
}
return false, nil
}
func versionCmp(ctx context.Context, ver1, ver2 string) (int, error) {
c := exec.CommandContext(ctx, "apk", "version", "-t", ver1, ver2)
out, err := c.CombinedOutput()
if err != nil {
return 0, fmt.Errorf("apk versionCmp: %w", err)
}
result := strings.TrimSpace(string(out))
switch result {
case "<":
return -1, nil
case ">":
return 1, nil
case "=":
return 0, nil
default:
return 0, fmt.Errorf("apk versionCmp: unexpected output %q", result)
}
}
// autoremove is a no-op for apk. Alpine's apk does not have a direct
// autoremove equivalent. `apk fix` repairs packages but does not remove
// unused dependencies.
func autoremove(_ context.Context, _ ...snack.Option) error {
return nil
}
func clean(ctx context.Context) error {
c := exec.CommandContext(ctx, "apk", "cache", "clean")
out, err := c.CombinedOutput()
if err != nil {
return fmt.Errorf("apk clean: %s: %w", strings.TrimSpace(string(out)), err)
}
return nil
}
func fileList(ctx context.Context, pkg string) ([]string, error) {
c := exec.CommandContext(ctx, "apk", "info", "-L", pkg)
out, err := c.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("apk fileList %s: %w", pkg, snack.ErrNotInstalled)
}
var files []string
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// First line is "pkg-version contains:" — skip it
if strings.Contains(line, " contains:") {
continue
}
files = append(files, line)
}
return files, nil
}
func owner(ctx context.Context, path string) (string, error) {
c := exec.CommandContext(ctx, "apk", "info", "--who-owns", path)
out, err := c.CombinedOutput()
if err != nil {
return "", fmt.Errorf("apk owner %s: %w", path, snack.ErrNotFound)
}
// Output: "/path is owned by pkg-version"
outStr := strings.TrimSpace(string(out))
if idx := strings.Index(outStr, "is owned by "); idx != -1 {
nameVer := strings.TrimSpace(outStr[idx+len("is owned by "):])
name, _ := splitNameVersion(nameVer)
if name != "" {
return name, nil
}
}
return "", fmt.Errorf("apk owner %s: unexpected output %q", path, outStr)
}