From fcd454691fe8c831d76f9c9a0e9e5be69fb02233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=A5=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D1=8B=D1=87=D0=AA=20=D0=A5=D1=80=D0=B0=D0=BC=D0=BE?= =?UTF-8?q?=D0=B2?= Date: Mon, 23 Feb 2026 16:02:51 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B4=D0=BE=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=BC=D0=BD=D1=8B=D1=85=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=D0=B5=20install?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен метод ListAvailable в интерфейс менеджера пакетов - Реализован поиск доступных пакетов для всех менеджеров (apt, apt-rpm, dnf, yum, pacman, apk, zypper) - Вынесена общая функция для apt и apt-rpm во избежание дублирования - Автодополнение теперь выводит и ALR-пакеты, и системные с дедупликацией - Добавлена фильтрация по префиксу для производительности --- install.go | 33 ++++++++++++++++++++-- internal/manager/apk.go | 42 ++++++++++++++++++++++++++++ internal/manager/apt.go | 4 +++ internal/manager/apt_common.go | 51 ++++++++++++++++++++++++++++++++++ internal/manager/apt_rpm.go | 4 +++ internal/manager/dnf.go | 37 ++++++++++++++++++++++++ internal/manager/managers.go | 3 ++ internal/manager/pacman.go | 36 ++++++++++++++++++++++++ internal/manager/yum.go | 37 ++++++++++++++++++++++++ internal/manager/zypper.go | 51 ++++++++++++++++++++++++++++++++++ 10 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 internal/manager/apt_common.go diff --git a/install.go b/install.go index abdfdc4..0aeb876 100644 --- a/install.go +++ b/install.go @@ -21,6 +21,8 @@ package main import ( "fmt" + "log/slog" + "strings" "github.com/leonelquinteros/gotext" "github.com/urfave/cli/v2" @@ -110,25 +112,52 @@ func InstallCmd() *cli.Command { return nil }), BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { - ctx := c.Context deps, err := appbuilder. New(ctx). WithConfig(). WithDB(). + WithManager(). Build() if err != nil { return err } defer deps.Defer() + seen := make(map[string]struct{}) + + var prefix string + if c.Args().Len() > 0 { + prefix = c.Args().Get(c.Args().Len() - 1) + if strings.HasPrefix(prefix, "-") { + prefix = "" + } + } + result, err := deps.DB.GetPkgs(c.Context, "true") if err != nil { return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) } for _, pkg := range result { - fmt.Println(pkg.Name) + if prefix == "" || strings.HasPrefix(pkg.Name, prefix) { + if _, ok := seen[pkg.Name]; !ok { + seen[pkg.Name] = struct{}{} + fmt.Println(pkg.Name) + } + } + } + + sysPkgs, err := deps.Manager.ListAvailable(prefix) + if err != nil { + slog.Debug("failed to list system packages", "err", err) + } else { + for _, name := range sysPkgs { + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + fmt.Println(name) + } + } } return nil diff --git a/internal/manager/apk.go b/internal/manager/apk.go index c809156..c8ee149 100644 --- a/internal/manager/apk.go +++ b/internal/manager/apk.go @@ -116,6 +116,48 @@ func (a *APK) UpgradeAll(opts *Opts) error { return a.Upgrade(opts) } +func (a *APK) ListAvailable(prefix string) ([]string, error) { + cmd := exec.Command("apk", "search", "-q", prefix) + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("apk: listavailable: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("apk: listavailable: %w", err) + } + + seen := make(map[string]struct{}) + var pkgs []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + if line == "" { + continue + } + // apk search -q returns "name-version", extract name + lastDash := strings.LastIndex(line, "-") + name := line + if lastDash > 0 { + name = line[:lastDash] + } + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + pkgs = append(pkgs, name) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("apk: listavailable: %w", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("apk: listavailable: %w", err) + } + + return pkgs, nil +} + func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) { out := map[string]string{} cmd := exec.Command("apk", "list", "-I") diff --git a/internal/manager/apt.go b/internal/manager/apt.go index 72b1709..14d2db8 100644 --- a/internal/manager/apt.go +++ b/internal/manager/apt.go @@ -196,6 +196,10 @@ func (a *APT) IsInstalled(pkg string) (bool, error) { return strings.Contains(status, "install ok installed"), nil } +func (a *APT) ListAvailable(prefix string) ([]string, error) { + return aptCacheListAvailable(prefix) +} + func (a *APT) GetInstalledVersion(pkg string) (string, error) { resolved := a.resolvePackageName(pkg) cmd := exec.Command("dpkg-query", "-f", "${Version}", "-W", resolved) diff --git a/internal/manager/apt_common.go b/internal/manager/apt_common.go new file mode 100644 index 0000000..f23be8e --- /dev/null +++ b/internal/manager/apt_common.go @@ -0,0 +1,51 @@ +// ALR - Any Linux Repository +// Copyright (C) 2025 The ALR Authors +// +// 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" +) + +func aptCacheListAvailable(prefix string) ([]string, error) { + cmd := exec.Command("apt-cache", "pkgnames", prefix) + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("apt-cache: listavailable: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("apt-cache: listavailable: %w", err) + } + + var pkgs []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + pkgs = append(pkgs, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("apt-cache: listavailable: %w", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("apt-cache: listavailable: %w", err) + } + + return pkgs, nil +} diff --git a/internal/manager/apt_rpm.go b/internal/manager/apt_rpm.go index c3144df..ed39807 100644 --- a/internal/manager/apt_rpm.go +++ b/internal/manager/apt_rpm.go @@ -100,6 +100,10 @@ func (a *APTRpm) Upgrade(opts *Opts, pkgs ...string) error { return a.Install(opts, pkgs...) } +func (a *APTRpm) ListAvailable(prefix string) ([]string, error) { + return aptCacheListAvailable(prefix) +} + func (a *APTRpm) UpgradeAll(opts *Opts) error { opts = ensureOpts(opts) cmd := a.getCmd(opts, "apt-get", "dist-upgrade") diff --git a/internal/manager/dnf.go b/internal/manager/dnf.go index 05e9b34..3fe64f9 100644 --- a/internal/manager/dnf.go +++ b/internal/manager/dnf.go @@ -20,6 +20,7 @@ package manager import ( + "bufio" "fmt" "os/exec" ) @@ -107,6 +108,42 @@ func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error { return nil } +func (d *DNF) ListAvailable(prefix string) ([]string, error) { + cmd := exec.Command("dnf", "repoquery", "--qf", "%{name}\n", "--quiet", prefix+"*") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("dnf: listavailable: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("dnf: listavailable: %w", err) + } + + seen := make(map[string]struct{}) + var pkgs []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + name := scanner.Text() + if name == "" { + continue + } + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + pkgs = append(pkgs, name) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("dnf: listavailable: %w", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("dnf: listavailable: %w", err) + } + + return pkgs, nil +} + // UpgradeAll обновляет все установленные пакеты func (d *DNF) UpgradeAll(opts *Opts) error { opts = ensureOpts(opts) diff --git a/internal/manager/managers.go b/internal/manager/managers.go index 648bae8..dd5e7c2 100644 --- a/internal/manager/managers.go +++ b/internal/manager/managers.go @@ -79,6 +79,9 @@ type Manager interface { // GetInstalledVersion returns the version of an installed package. // Returns empty string and no error if package is not installed. GetInstalledVersion(string) (string, error) + // ListAvailable returns names of available packages matching the given prefix. + // The prefix is used for filtering to avoid returning all packages. + ListAvailable(prefix string) ([]string, error) } // Detect returns the package manager detected on the system diff --git a/internal/manager/pacman.go b/internal/manager/pacman.go index daaef00..f79391b 100644 --- a/internal/manager/pacman.go +++ b/internal/manager/pacman.go @@ -115,6 +115,42 @@ func (p *Pacman) UpgradeAll(opts *Opts) error { return nil } +func (p *Pacman) ListAvailable(prefix string) ([]string, error) { + cmd := exec.Command("pacman", "-Ssq", "^"+prefix) + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("pacman: listavailable: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("pacman: listavailable: %w", err) + } + + seen := make(map[string]struct{}) + var pkgs []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + name := scanner.Text() + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + pkgs = append(pkgs, name) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("pacman: listavailable: %w", err) + } + + if err := cmd.Wait(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + return nil, nil + } + return nil, fmt.Errorf("pacman: listavailable: %w", err) + } + + return pkgs, nil +} + func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) { out := map[string]string{} cmd := exec.Command("pacman", "-Q") diff --git a/internal/manager/yum.go b/internal/manager/yum.go index a3867dd..4d37778 100644 --- a/internal/manager/yum.go +++ b/internal/manager/yum.go @@ -20,6 +20,7 @@ package manager import ( + "bufio" "fmt" "os/exec" ) @@ -103,6 +104,42 @@ func (y *YUM) Upgrade(opts *Opts, pkgs ...string) error { return nil } +func (y *YUM) ListAvailable(prefix string) ([]string, error) { + cmd := exec.Command("yum", "repoquery", "--qf", "%{name}\n", "--quiet", prefix+"*") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("yum: listavailable: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("yum: listavailable: %w", err) + } + + seen := make(map[string]struct{}) + var pkgs []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + name := scanner.Text() + if name == "" { + continue + } + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + pkgs = append(pkgs, name) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("yum: listavailable: %w", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("yum: listavailable: %w", err) + } + + return pkgs, nil +} + func (y *YUM) UpgradeAll(opts *Opts) error { opts = ensureOpts(opts) cmd := y.getCmd(opts, "yum", "upgrade") diff --git a/internal/manager/zypper.go b/internal/manager/zypper.go index 51de062..9524354 100644 --- a/internal/manager/zypper.go +++ b/internal/manager/zypper.go @@ -20,8 +20,10 @@ package manager import ( + "bufio" "fmt" "os/exec" + "strings" ) // Zypper represents the Zypper package manager @@ -103,6 +105,55 @@ func (z *Zypper) Upgrade(opts *Opts, pkgs ...string) error { return nil } +func (z *Zypper) ListAvailable(prefix string) ([]string, error) { + cmd := exec.Command("zypper", "--quiet", "search", "--type", "package", prefix+"*") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("zypper: listavailable: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("zypper: listavailable: %w", err) + } + + seen := make(map[string]struct{}) + var pkgs []string + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + // zypper table format: "S | Name | Summary | Type" + // Skip separator lines and headers + if !strings.Contains(line, "|") { + continue + } + fields := strings.Split(line, "|") + if len(fields) < 2 { + continue + } + name := strings.TrimSpace(fields[1]) + if name == "" || name == "Name" { + continue + } + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + pkgs = append(pkgs, name) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("zypper: listavailable: %w", err) + } + + if err := cmd.Wait(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 104 { + return nil, nil + } + return nil, fmt.Errorf("zypper: listavailable: %w", err) + } + + return pkgs, nil +} + func (z *Zypper) UpgradeAll(opts *Opts) error { opts = ensureOpts(opts) cmd := z.getCmd(opts, "zypper", "update", "-y") -- 2.49.1 From 16dd798f107f621b89d3a4621bd60095b5379f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=A5=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D1=8B=D1=87=D0=AA=20=D0=A5=D1=80=D0=B0=D0=BC=D0=BE?= =?UTF-8?q?=D0=B2?= Date: Mon, 23 Feb 2026 16:11:48 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Исправлено применение logLevel из конфига (значение в lowercase не распознавалось) - Добавлено предупреждение при некорректном значении logLevel - Исправлен уровень отладочных сообщений с Info на Debug (12 мест) --- internal/build/build.go | 18 +++++++++--------- main.go | 6 +++++- pkg/alrsh/alrsh.go | 7 +++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/internal/build/build.go b/internal/build/build.go index 9caa8a0..b2519f6 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -614,9 +614,9 @@ func (b *Builder) BuildALRDeps( allPkgs = append(allPkgs, p.pkg) } - slog.Info("DEBUG: allPkgs count", "count", len(allPkgs)) + slog.Debug("allPkgs count", "count", len(allPkgs)) for _, p := range allPkgsWithKeys { - slog.Info("DEBUG: package in depTree", "key", p.key, "name", p.pkg.Name, "repo", p.pkg.Repository) + slog.Debug("package in depTree", "key", p.key, "name", p.pkg.Name, "repo", p.pkg.Repository) } needBuildPkgs, err := b.installerExecutor.FilterPackagesByVersion(ctx, allPkgs, input.OSRelease()) @@ -630,9 +630,9 @@ func (b *Builder) BuildALRDeps( needBuildNames[pkg.Name] = true } - slog.Info("DEBUG: needBuildPkgs count", "count", len(needBuildPkgs)) + slog.Debug("needBuildPkgs count", "count", len(needBuildPkgs)) for _, pkg := range needBuildPkgs { - slog.Info("DEBUG: package needs build", "name", pkg.Name) + slog.Debug("package needs build", "name", pkg.Name) } // Строим needBuildSet по КЛЮЧАМ depTree, а не по pkg.Name @@ -647,13 +647,13 @@ func (b *Builder) BuildALRDeps( // Шаг 3: Группируем подпакеты по basePkgName для оптимизации сборки // Если несколько подпакетов из одного мультипакета, собираем их вместе - slog.Info("DEBUG: sortedPkgs", "pkgs", sortedPkgs) + slog.Debug("sortedPkgs", "pkgs", sortedPkgs) // Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш for _, pkgName := range sortedPkgs { node := depTree[pkgName] if node == nil { - slog.Info("DEBUG: node is nil", "pkgName", pkgName) + slog.Debug("node is nil", "pkgName", pkgName) continue } @@ -662,7 +662,7 @@ func (b *Builder) BuildALRDeps( // Пропускаем уже установленные пакеты if !needBuildSet[pkgName] { - slog.Info("DEBUG: skipping (not in needBuildSet)", "pkgName", pkgName) + slog.Debug("skipping (not in needBuildSet)", "pkgName", pkgName) continue } @@ -687,12 +687,12 @@ func (b *Builder) BuildALRDeps( if allInCache { // Подпакет в кеше, используем его - slog.Info("DEBUG: using cached package", "pkgName", pkgName) + slog.Debug("using cached package", "pkgName", pkgName) buildDeps = append(buildDeps, cachedDeps...) continue } - slog.Info("DEBUG: building package", "pkgName", pkgName) + slog.Debug("building package", "pkgName", pkgName) // Собираем только запрошенный подпакет // SkipDepsBuilding: true предотвращает рекурсивный вызов BuildALRDeps diff --git a/main.go b/main.go index 48c15ce..cbe1749 100644 --- a/main.go +++ b/main.go @@ -122,7 +122,7 @@ func GetApp() *cli.App { func setLogLevel(newLevel string) { level := slog.LevelInfo - switch newLevel { + switch strings.ToUpper(newLevel) { case "DEBUG": level = slog.LevelDebug case "INFO": @@ -131,6 +131,10 @@ func setLogLevel(newLevel string) { level = slog.LevelWarn case "ERROR": level = slog.LevelError + default: + if newLevel != "" { + slog.Warn("unknown logLevel value, falling back to INFO", "value", newLevel) + } } logger, ok := slog.Default().Handler().(*logger.Logger) if !ok { diff --git a/pkg/alrsh/alrsh.go b/pkg/alrsh/alrsh.go index e0dfeb8..a68f307 100644 --- a/pkg/alrsh/alrsh.go +++ b/pkg/alrsh/alrsh.go @@ -172,14 +172,13 @@ func (s *ScriptFile) createPackageFromMeta( return nil, err } - // DEBUG: Выводим что в metaRunner.Vars и dec.Runner.Vars для deps_debian if depsDebianMeta, ok := metaRunner.Vars["deps_debian"]; ok { - slog.Info("DEBUG createPackageFromMeta: metaRunner.Vars[deps_debian]", "value", depsDebianMeta.String(), "list", depsDebianMeta.List) + slog.Debug("createPackageFromMeta: metaRunner.Vars[deps_debian]", "value", depsDebianMeta.String(), "list", depsDebianMeta.List) } else { - slog.Info("DEBUG createPackageFromMeta: metaRunner.Vars[deps_debian] NOT FOUND") + slog.Debug("createPackageFromMeta: metaRunner.Vars[deps_debian] NOT FOUND") } if depsDebianParent, ok := dec.Runner.Vars["deps_debian"]; ok { - slog.Info("DEBUG createPackageFromMeta: parent Vars[deps_debian]", "value", depsDebianParent.String(), "list", depsDebianParent.List) + slog.Debug("createPackageFromMeta: parent Vars[deps_debian]", "value", depsDebianParent.String(), "list", depsDebianParent.List) } // Сливаем переменные родительского runner'а с переменными мета-функции. -- 2.49.1