diff --git a/go.mod b/go.mod index 69cc341..e132faf 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -92,6 +93,7 @@ require ( github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -99,6 +101,7 @@ require ( github.com/shopspring/decimal v1.2.0 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/spf13/cast v1.6.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/ulikunitz/xz v0.5.12 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index f7eeec2..eabf92d 100644 --- a/go.sum +++ b/go.sum @@ -327,6 +327,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= diff --git a/pkg/build/build.go b/pkg/build/build.go index 73a0c1e..685edb5 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -65,6 +65,7 @@ import ( // Один содержит пути к собранным пакетам, другой - имена собранных пакетов. func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) { log := loggerctx.From(ctx) + reposInstance := repos.GetInstance(ctx) info, err := distro.ParseOSRelease(ctx) if err != nil { @@ -133,12 +134,12 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string return nil, nil, err } - buildDeps, err := installBuildDeps(ctx, vars, opts, installed) // Устанавливаем зависимости для сборки + buildDeps, err := installBuildDeps(ctx, reposInstance, vars, opts) // Устанавливаем зависимости для сборки if err != nil { return nil, nil, err } - err = installOptDeps(ctx, vars, opts, installed) // Устанавливаем опциональные зависимости + err = installOptDeps(ctx, reposInstance, vars, opts) // Устанавливаем опциональные зависимости if err != nil { return nil, nil, err } @@ -329,18 +330,25 @@ func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, return true, nil } +type PackageFinder interface { + FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) +} + // Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает // срез, содержащий имена всех установленных пакетов. -func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]string, error) { +func installBuildDeps(ctx context.Context, repos PackageFinder, vars *types.BuildVars, opts types.BuildOpts) ([]string, error) { log := loggerctx.From(ctx) var buildDeps []string if len(vars.BuildDepends) > 0 { - found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends) // Находим пакеты-зависимости + deps, err := removeAlreadyInstalled(opts, vars.BuildDepends) if err != nil { return nil, err } - found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости + found, notFound, err := repos.FindPkgs(ctx, deps) // Находим пакеты-зависимости + if err != nil { + return nil, err + } log.Info("Installing build dependencies").Send() // Логгируем установку зависимостей @@ -353,9 +361,13 @@ func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.Bui // Функция installOptDeps спрашивает у пользователя, какие, если таковые имеются, опциональные зависимости он хочет установить. // Если пользователь решает установить какие-либо опциональные зависимости, выполняется их установка. -func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) error { - if len(vars.OptDepends) > 0 { - optDeps, err := cliutils.ChooseOptDepends(ctx, vars.OptDepends, "install", opts.Interactive) // Пользователя просят выбрать опциональные зависимости +func installOptDeps(ctx context.Context, repos PackageFinder, vars *types.BuildVars, opts types.BuildOpts) error { + optDeps, err := removeAlreadyInstalled(opts, vars.OptDepends) + if err != nil { + return err + } + if len(optDeps) > 0 { + optDeps, err := cliutils.ChooseOptDepends(ctx, optDeps, "install", opts.Interactive) // Пользователя просят выбрать опциональные зависимости if err != nil { return err } @@ -369,7 +381,6 @@ func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.Build return err } - found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем выбранные пакеты } @@ -836,21 +847,22 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error { return r.Run(ctx, fl) } -// Функция removeAlreadyInstalled возвращает карту без каких-либо зависимостей, которые уже установлены. -func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package { - filteredPackages := make(map[string][]db.Package) +// Returns not installed dependencies +func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) { + filteredPackages := []string{} - for name, pkgList := range found { - filteredPkgList := []db.Package{} - for _, pkg := range pkgList { - if _, isInstalled := installed[pkg.Name]; !isInstalled { - filteredPkgList = append(filteredPkgList, pkg) - } + for _, dep := range dependencies { + installed, err := opts.Manager.IsInstalled(dep) + if err != nil { + return nil, err } - filteredPackages[name] = filteredPkgList + if installed { + continue + } + filteredPackages = append(filteredPackages, dep) } - return filteredPackages + return filteredPackages, nil } // Функция packageNames возвращает имена всех предоставленных пакетов. diff --git a/pkg/build/build_internal_test.go b/pkg/build/build_internal_test.go new file mode 100644 index 0000000..fd089ae --- /dev/null +++ b/pkg/build/build_internal_test.go @@ -0,0 +1,225 @@ +// ALR - Any Linux Repository +// Copyright (C) 2025 Евгений Храмов +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package build + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "plemya-x.ru/alr/internal/db" + "plemya-x.ru/alr/internal/types" + "plemya-x.ru/alr/pkg/manager" +) + +type TestPackageFinder struct { + FindPkgsFunc func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) +} + +func (pf *TestPackageFinder) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { + if pf.FindPkgsFunc != nil { + return pf.FindPkgsFunc(ctx, pkgs) + } + return map[string][]db.Package{}, []string{}, nil +} + +type TestManager struct { + NameFunc func() string + FormatFunc func() string + ExistsFunc func() bool + SetRootCmdFunc func(cmd string) + SyncFunc func(opts *manager.Opts) error + InstallFunc func(opts *manager.Opts, pkgs ...string) error + RemoveFunc func(opts *manager.Opts, pkgs ...string) error + UpgradeFunc func(opts *manager.Opts, pkgs ...string) error + InstallLocalFunc func(opts *manager.Opts, files ...string) error + UpgradeAllFunc func(opts *manager.Opts) error + ListInstalledFunc func(opts *manager.Opts) (map[string]string, error) + IsInstalledFunc func(pkg string) (bool, error) +} + +func (m *TestManager) Name() string { + if m.NameFunc != nil { + return m.NameFunc() + } + return "TestManager" +} + +func (m *TestManager) Format() string { + if m.FormatFunc != nil { + return m.FormatFunc() + } + return "testpkg" +} + +func (m *TestManager) Exists() bool { + if m.ExistsFunc != nil { + return m.ExistsFunc() + } + return true +} + +func (m *TestManager) SetRootCmd(cmd string) { + if m.SetRootCmdFunc != nil { + m.SetRootCmdFunc(cmd) + } +} + +func (m *TestManager) Sync(opts *manager.Opts) error { + if m.SyncFunc != nil { + return m.SyncFunc(opts) + } + return nil +} + +func (m *TestManager) Install(opts *manager.Opts, pkgs ...string) error { + if m.InstallFunc != nil { + return m.InstallFunc(opts, pkgs...) + } + return nil +} + +func (m *TestManager) Remove(opts *manager.Opts, pkgs ...string) error { + if m.RemoveFunc != nil { + return m.RemoveFunc(opts, pkgs...) + } + return nil +} + +func (m *TestManager) Upgrade(opts *manager.Opts, pkgs ...string) error { + if m.UpgradeFunc != nil { + return m.UpgradeFunc(opts, pkgs...) + } + return nil +} + +func (m *TestManager) InstallLocal(opts *manager.Opts, files ...string) error { + if m.InstallLocalFunc != nil { + return m.InstallLocalFunc(opts, files...) + } + return nil +} + +func (m *TestManager) UpgradeAll(opts *manager.Opts) error { + if m.UpgradeAllFunc != nil { + return m.UpgradeAllFunc(opts) + } + return nil +} + +func (m *TestManager) ListInstalled(opts *manager.Opts) (map[string]string, error) { + if m.ListInstalledFunc != nil { + return m.ListInstalledFunc(opts) + } + return map[string]string{}, nil +} + +func (m *TestManager) IsInstalled(pkg string) (bool, error) { + if m.IsInstalledFunc != nil { + return m.IsInstalledFunc(pkg) + } + return true, nil +} + +func TestInstallBuildDeps(t *testing.T) { + type testEnv struct { + pf PackageFinder + vars *types.BuildVars + opts types.BuildOpts + + // Contains pkgs captured by FindPkgsFunc + capturedPkgs []string + } + + type testCase struct { + Name string + Prepare func() *testEnv + Expected func(t *testing.T, e *testEnv, res []string, err error) + } + + for _, tc := range []testCase{ + { + Name: "install only needed deps", + Prepare: func() *testEnv { + pf := TestPackageFinder{} + vars := types.BuildVars{} + m := TestManager{} + opts := types.BuildOpts{ + Manager: &m, + Interactive: false, + } + + env := &testEnv{ + pf: &pf, + vars: &vars, + opts: opts, + capturedPkgs: []string{}, + } + + pf.FindPkgsFunc = func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { + env.capturedPkgs = append(env.capturedPkgs, pkgs...) + result := make(map[string][]db.Package) + result["bar"] = []db.Package{{ + Name: "bar-pkg", + }} + result["buz"] = []db.Package{{ + Name: "buz-pkg", + }} + + return result, []string{}, nil + } + + vars.BuildDepends = []string{ + "foo", + "bar", + "buz", + } + m.IsInstalledFunc = func(pkg string) (bool, error) { + if pkg == "foo" { + return true, nil + } else { + return false, nil + } + } + + return env + }, + Expected: func(t *testing.T, e *testEnv, res []string, err error) { + assert.NoError(t, err) + assert.Len(t, res, 2) + assert.ElementsMatch(t, res, []string{"bar-pkg", "buz-pkg"}) + + assert.ElementsMatch(t, e.capturedPkgs, []string{"bar", "buz"}) + }, + }, + } { + t.Run(tc.Name, func(tt *testing.T) { + ctx := context.Background() + env := tc.Prepare() + + result, err := installBuildDeps( + ctx, + env.pf, + env.vars, + env.opts, + ) + + tc.Expected(tt, env, result, err) + }) + } +} diff --git a/pkg/manager/apk.go b/pkg/manager/apk.go index 8d71c19..aaf0d11 100644 --- a/pkg/manager/apk.go +++ b/pkg/manager/apk.go @@ -149,6 +149,21 @@ func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) { return out, nil } +func (a *APK) IsInstalled(pkg string) (bool, error) { + cmd := exec.Command("apk", "info", "--installed", pkg) + output, err := cmd.CombinedOutput() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + // Exit code 1 means the package is not installed + if exitErr.ExitCode() == 1 { + return false, nil + } + } + return false, fmt.Errorf("apk: isinstalled: %w, output: %s", err, output) + } + return true, nil +} + func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd if opts.AsRoot { diff --git a/pkg/manager/apt.go b/pkg/manager/apt.go index 6ba1026..dda9e8d 100644 --- a/pkg/manager/apt.go +++ b/pkg/manager/apt.go @@ -135,6 +135,21 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) { return out, nil } +func (a *APT) IsInstalled(pkg string) (bool, error) { + cmd := exec.Command("dpkg-query", "-l", pkg) + output, err := cmd.CombinedOutput() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + // Exit code 1 means the package is not installed + if exitErr.ExitCode() == 1 { + return false, nil + } + } + return false, fmt.Errorf("apt: isinstalled: %w, output: %s", err, output) + } + return true, nil +} + func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd if opts.AsRoot { diff --git a/pkg/manager/apt_rpm.go b/pkg/manager/apt_rpm.go index 2593e89..c3ce1a2 100644 --- a/pkg/manager/apt_rpm.go +++ b/pkg/manager/apt_rpm.go @@ -17,7 +17,6 @@ package manager import ( - "bufio" "fmt" "os/exec" "strings" @@ -25,6 +24,7 @@ import ( // APTRpm represents the APT-RPM package manager type APTRpm struct { + CommonRPM rootCmd string } @@ -106,38 +106,6 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error { return nil } -func (y *APTRpm) ListInstalled(opts *Opts) (map[string]string, error) { - out := map[string]string{} - cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n") - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - err = cmd.Start() - if err != nil { - return nil, err - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - name, version, ok := strings.Cut(scanner.Text(), "\u200b") - if !ok { - continue - } - version = strings.TrimPrefix(version, "0:") - out[name] = version - } - - err = scanner.Err() - if err != nil { - return nil, err - } - - return out, nil -} - func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd if opts.AsRoot { diff --git a/pkg/manager/common_rpm.go b/pkg/manager/common_rpm.go new file mode 100644 index 0000000..a5b1a9c --- /dev/null +++ b/pkg/manager/common_rpm.go @@ -0,0 +1,72 @@ +// ALR - Any Linux Repository +// Copyright (C) 2025 Евгений Храмов +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package manager + +import ( + "bufio" + "fmt" + "os/exec" + "strings" +) + +type CommonRPM struct{} + +func (c *CommonRPM) ListInstalled(opts *Opts) (map[string]string, error) { + out := map[string]string{} + cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n") + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + + err = cmd.Start() + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + name, version, ok := strings.Cut(scanner.Text(), "\u200b") + if !ok { + continue + } + version = strings.TrimPrefix(version, "0:") + out[name] = version + } + + err = scanner.Err() + if err != nil { + return nil, err + } + + return out, nil +} + +func (a *CommonRPM) IsInstalled(pkg string) (bool, error) { + cmd := exec.Command("rpm", "-q", "--whatprovides", pkg) + output, err := cmd.CombinedOutput() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + if exitErr.ExitCode() == 1 { + return false, nil + } + } + return false, fmt.Errorf("rpm: isinstalled: %w, output: %s", err, output) + } + return true, nil +} diff --git a/pkg/manager/dnf.go b/pkg/manager/dnf.go index a3b4dc6..079e967 100644 --- a/pkg/manager/dnf.go +++ b/pkg/manager/dnf.go @@ -19,14 +19,13 @@ package manager import ( - "bufio" "fmt" "os/exec" - "strings" ) // DNF представляет менеджер пакетов DNF type DNF struct { + CommonRPM rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root } @@ -120,39 +119,6 @@ func (d *DNF) UpgradeAll(opts *Opts) error { return nil } -// ListInstalled возвращает список установленных пакетов и их версий -func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) { - out := map[string]string{} - cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n") - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - err = cmd.Start() - if err != nil { - return nil, err - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - name, version, ok := strings.Cut(scanner.Text(), "\u200b") - if !ok { - continue - } - version = strings.TrimPrefix(version, "0:") - out[name] = version - } - - err = scanner.Err() - if err != nil { - return nil, err - } - - return out, nil -} - // getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd diff --git a/pkg/manager/managers.go b/pkg/manager/managers.go index 3467d72..0219bd3 100644 --- a/pkg/manager/managers.go +++ b/pkg/manager/managers.go @@ -80,6 +80,8 @@ type Manager interface { UpgradeAll(*Opts) error // ListInstalled returns all installed packages mapped to their versions ListInstalled(*Opts) (map[string]string, error) + // + IsInstalled(string) (bool, error) } // Detect returns the package manager detected on the system diff --git a/pkg/manager/pacman.go b/pkg/manager/pacman.go index 3e3422b..825bff4 100644 --- a/pkg/manager/pacman.go +++ b/pkg/manager/pacman.go @@ -142,6 +142,21 @@ func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) { return out, nil } +func (p *Pacman) IsInstalled(pkg string) (bool, error) { + cmd := exec.Command("pacman", "-Q", pkg) + output, err := cmd.CombinedOutput() + if err != nil { + // Pacman returns exit code 1 if the package is not found + if exitErr, ok := err.(*exec.ExitError); ok { + if exitErr.ExitCode() == 1 { + return false, nil + } + } + return false, fmt.Errorf("pacman: isinstalled: %w, output: %s", err, output) + } + return true, nil +} + func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd if opts.AsRoot { diff --git a/pkg/manager/yum.go b/pkg/manager/yum.go index c9938ed..65b00e5 100644 --- a/pkg/manager/yum.go +++ b/pkg/manager/yum.go @@ -20,14 +20,14 @@ package manager import ( - "bufio" "fmt" "os/exec" - "strings" ) // YUM represents the YUM package manager type YUM struct { + CommonRPM + rootCmd string } @@ -111,38 +111,6 @@ func (y *YUM) UpgradeAll(opts *Opts) error { return nil } -func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) { - out := map[string]string{} - cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n") - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - err = cmd.Start() - if err != nil { - return nil, err - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - name, version, ok := strings.Cut(scanner.Text(), "\u200b") - if !ok { - continue - } - version = strings.TrimPrefix(version, "0:") - out[name] = version - } - - err = scanner.Err() - if err != nil { - return nil, err - } - - return out, nil -} - func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd if opts.AsRoot { diff --git a/pkg/manager/zypper.go b/pkg/manager/zypper.go index 5a7cd5c..bf7a022 100644 --- a/pkg/manager/zypper.go +++ b/pkg/manager/zypper.go @@ -20,14 +20,13 @@ package manager import ( - "bufio" "fmt" "os/exec" - "strings" ) // Zypper represents the Zypper package manager type Zypper struct { + CommonRPM rootCmd string } @@ -111,38 +110,6 @@ func (z *Zypper) UpgradeAll(opts *Opts) error { return nil } -func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) { - out := map[string]string{} - cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n") - - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - err = cmd.Start() - if err != nil { - return nil, err - } - - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - name, version, ok := strings.Cut(scanner.Text(), "\u200b") - if !ok { - continue - } - version = strings.TrimPrefix(version, "0:") - out[name] = version - } - - err = scanner.Err() - if err != nil { - return nil, err - } - - return out, nil -} - func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { var cmd *exec.Cmd if opts.AsRoot {