Улучшения обработки зависимостей и фильтрации установленных пакетов
- Добавлена поддержка версионных ограничений при установке пакетов - Улучшена логика фильтрации уже установленных пакетов - Добавлен метод GetInstalledVersion для всех менеджеров пакетов - Активированы тесты для систем archlinux, alpine, opensuse-leap - Улучшена обработка переменных в скриптах Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -596,22 +596,73 @@ func (b *Builder) BuildALRDeps(
|
||||
return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Шаг 2.5: Фильтруем уже установленные пакеты
|
||||
// Собираем пакеты вместе с их ключами (именами поиска)
|
||||
type pkgWithKey struct {
|
||||
key string
|
||||
pkg alrsh.Package
|
||||
}
|
||||
var allPkgsWithKeys []pkgWithKey
|
||||
for key, node := range depTree {
|
||||
if node.Package != nil {
|
||||
allPkgsWithKeys = append(allPkgsWithKeys, pkgWithKey{key: key, pkg: *node.Package})
|
||||
}
|
||||
}
|
||||
|
||||
var allPkgs []alrsh.Package
|
||||
for _, p := range allPkgsWithKeys {
|
||||
allPkgs = append(allPkgs, p.pkg)
|
||||
}
|
||||
|
||||
slog.Info("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)
|
||||
}
|
||||
|
||||
needBuildPkgs, err := b.installerExecutor.FilterPackagesByVersion(ctx, allPkgs, input.OSRelease())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to filter packages: %w", err)
|
||||
}
|
||||
|
||||
// Создаём множество имён пакетов, которые нужно собрать
|
||||
needBuildNames := make(map[string]bool)
|
||||
for _, pkg := range needBuildPkgs {
|
||||
needBuildNames[pkg.Name] = true
|
||||
}
|
||||
|
||||
slog.Info("DEBUG: needBuildPkgs count", "count", len(needBuildPkgs))
|
||||
for _, pkg := range needBuildPkgs {
|
||||
slog.Info("DEBUG: package needs build", "name", pkg.Name)
|
||||
}
|
||||
|
||||
// Строим needBuildSet по КЛЮЧАМ depTree, а не по pkg.Name
|
||||
// Это важно, т.к. ключ может быть именем из Provides (python3-pyside6),
|
||||
// а pkg.Name - фактическое имя пакета (python3-shiboken6)
|
||||
needBuildSet := make(map[string]bool)
|
||||
for _, p := range allPkgsWithKeys {
|
||||
if needBuildNames[p.pkg.Name] {
|
||||
needBuildSet[p.key] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Шаг 3: Группируем подпакеты по basePkgName для оптимизации сборки
|
||||
// Если несколько подпакетов из одного мультипакета, собираем их вместе
|
||||
builtBasePkgs := make(map[string]bool) // Уже собранные базовые пакеты
|
||||
slog.Info("DEBUG: sortedPkgs", "pkgs", sortedPkgs)
|
||||
|
||||
// Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
|
||||
for _, pkgName := range sortedPkgs {
|
||||
node := depTree[pkgName]
|
||||
if node == nil {
|
||||
slog.Info("DEBUG: node is nil", "pkgName", pkgName)
|
||||
continue
|
||||
}
|
||||
|
||||
pkg := node.Package
|
||||
basePkgName := node.BasePkgName
|
||||
|
||||
// Если базовый пакет уже собран, пропускаем
|
||||
if builtBasePkgs[basePkgName] {
|
||||
// Пропускаем уже установленные пакеты
|
||||
if !needBuildSet[pkgName] {
|
||||
slog.Info("DEBUG: skipping (not in needBuildSet)", "pkgName", pkgName)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -636,10 +687,13 @@ func (b *Builder) BuildALRDeps(
|
||||
|
||||
if allInCache {
|
||||
// Подпакет в кеше, используем его
|
||||
slog.Info("DEBUG: using cached package", "pkgName", pkgName)
|
||||
buildDeps = append(buildDeps, cachedDeps...)
|
||||
continue
|
||||
}
|
||||
|
||||
slog.Info("DEBUG: building package", "pkgName", pkgName)
|
||||
|
||||
// Собираем только запрошенный подпакет
|
||||
// SkipDepsBuilding: true предотвращает рекурсивный вызов BuildALRDeps
|
||||
res, err := b.BuildPackageFromDb(
|
||||
@@ -660,7 +714,6 @@ func (b *Builder) BuildALRDeps(
|
||||
}
|
||||
|
||||
buildDeps = append(buildDeps, res...)
|
||||
builtBasePkgs[basePkgName] = true
|
||||
}
|
||||
|
||||
buildDeps = removeDuplicates(buildDeps)
|
||||
@@ -846,8 +899,9 @@ func (i *Builder) InstallPkgs(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Отслеживание установки пакетов из репозитория
|
||||
|
||||
_ = i.installerExecutor.CheckVersionsAfterInstall(ctx, repoDeps)
|
||||
|
||||
for _, pkg := range repoDeps {
|
||||
if stats.ShouldTrackPackage(pkg) {
|
||||
stats.TrackInstallation(ctx, pkg, "install")
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
@@ -44,7 +45,12 @@ func (b *Builder) ResolveDependencyTree(
|
||||
) (map[string]*DependencyNode, []string, error) {
|
||||
resolved := make(map[string]*DependencyNode)
|
||||
visited := make(map[string]bool)
|
||||
systemDeps := make(map[string]bool) // Для дедупликации системных зависимостей
|
||||
systemDeps := make(map[string]bool)
|
||||
|
||||
overrideNames, err := overrides.Resolve(input.OSRelease(), overrides.DefaultOpts)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to resolve overrides: %w", err)
|
||||
}
|
||||
|
||||
var resolve func(pkgNames []string) error
|
||||
resolve = func(pkgNames []string) error {
|
||||
@@ -77,20 +83,17 @@ func (b *Builder) ResolveDependencyTree(
|
||||
|
||||
pkg := pkgList[0]
|
||||
|
||||
// Определяем базовое имя пакета
|
||||
alrsh.ResolvePackage(&pkg, overrideNames)
|
||||
|
||||
baseName := pkg.BasePkgName
|
||||
if baseName == "" {
|
||||
baseName = pkg.Name
|
||||
}
|
||||
|
||||
// Используем имя конкретного подпакета как ключ (не basePkgName)
|
||||
// Это позволяет собирать только запрошенный подпакет, а не весь мультипакет
|
||||
if resolved[pkgName] != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Получаем зависимости для этого дистрибутива
|
||||
// Пакет из БД уже содержит разрешенные значения для текущего дистрибутива
|
||||
deps := pkg.Depends.Resolved()
|
||||
buildDeps := pkg.BuildDepends.Resolved()
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package finddeps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
|
||||
@@ -39,10 +41,9 @@ func New(info *distro.OSRelease, pkgFormat string) *ProvReqService {
|
||||
finder: &EmptyFindProvReq{},
|
||||
}
|
||||
if pkgFormat == "rpm" {
|
||||
switch info.ID {
|
||||
case "altlinux":
|
||||
if _, err := os.Stat("/usr/lib/rpm/find-provides"); err == nil {
|
||||
s.finder = &ALTLinuxFindProvReq{}
|
||||
case "fedora":
|
||||
} else if _, err := exec.LookPath("/usr/lib/rpm/rpmdeps"); err == nil {
|
||||
s.finder = &FedoraFindProvReq{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/depver"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||
)
|
||||
|
||||
@@ -43,7 +44,14 @@ func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *mana
|
||||
}
|
||||
|
||||
func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
||||
return i.mgr.Install(opts, pkgs...)
|
||||
// Convert dependencies to manager-specific format
|
||||
converted := make([]string, len(pkgs))
|
||||
for idx, pkg := range pkgs {
|
||||
dep := depver.Parse(pkg)
|
||||
converted[idx] = dep.ForManager(i.mgr.Name())
|
||||
}
|
||||
|
||||
return i.mgr.Install(opts, converted...)
|
||||
}
|
||||
|
||||
func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
||||
@@ -54,19 +62,71 @@ func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) (
|
||||
filteredPackages := []string{}
|
||||
|
||||
for _, dep := range pkgs {
|
||||
installed, err := i.mgr.IsInstalled(dep)
|
||||
parsed := depver.Parse(dep)
|
||||
|
||||
// Check if package is installed
|
||||
installed, err := i.mgr.IsInstalled(parsed.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if installed {
|
||||
|
||||
if !installed {
|
||||
filteredPackages = append(filteredPackages, dep)
|
||||
continue
|
||||
}
|
||||
filteredPackages = append(filteredPackages, dep)
|
||||
|
||||
// If there's a version constraint, check if installed version satisfies it
|
||||
if parsed.HasVersionConstraint() {
|
||||
installedVer, err := i.mgr.GetInstalledVersion(parsed.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !parsed.Satisfies(installedVer) {
|
||||
// Installed version doesn't satisfy constraint - need to upgrade
|
||||
slog.Debug("installed version doesn't satisfy constraint",
|
||||
"package", parsed.Name,
|
||||
"required", dep,
|
||||
"installed", installedVer)
|
||||
filteredPackages = append(filteredPackages, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredPackages, nil
|
||||
}
|
||||
|
||||
func (i *Installer) CheckVersionsAfterInstall(ctx context.Context, pkgs []string) error {
|
||||
for _, pkg := range pkgs {
|
||||
parsed := depver.Parse(pkg)
|
||||
if !parsed.HasVersionConstraint() {
|
||||
continue
|
||||
}
|
||||
|
||||
installedVer, err := i.mgr.GetInstalledVersion(parsed.Name)
|
||||
if err != nil {
|
||||
slog.Warn(gotext.Get("Failed to get installed version"),
|
||||
"package", parsed.Name,
|
||||
"error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if installedVer == "" {
|
||||
slog.Warn(gotext.Get("Package was not installed"),
|
||||
"package", parsed.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !parsed.Satisfies(installedVer) {
|
||||
slog.Warn(gotext.Get("Installed version doesn't satisfy requirement"),
|
||||
"package", parsed.Name,
|
||||
"required", pkg,
|
||||
"installed", installedVer)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Installer) FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error) {
|
||||
installedPkgs, err := i.mgr.ListInstalled(nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,6 +36,7 @@ type InstallerExecutor interface {
|
||||
Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
|
||||
RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
|
||||
FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error)
|
||||
CheckVersionsAfterInstall(ctx context.Context, pkgs []string) error
|
||||
}
|
||||
|
||||
type ScriptExecutor interface {
|
||||
|
||||
@@ -238,6 +238,33 @@ func (s *InstallerExecutorRPCServer) FilterPackagesByVersion(args *InstallerExec
|
||||
return nil
|
||||
}
|
||||
|
||||
type InstallerExecutorCheckVersionsAfterInstallArgs struct {
|
||||
Pkgs []string
|
||||
}
|
||||
|
||||
type InstallerExecutorCheckVersionsAfterInstallResp struct {
|
||||
}
|
||||
|
||||
func (s *InstallerExecutorRPC) CheckVersionsAfterInstall(ctx context.Context, pkgs []string) error {
|
||||
var resp *InstallerExecutorCheckVersionsAfterInstallResp
|
||||
err := s.client.Call("Plugin.CheckVersionsAfterInstall", &InstallerExecutorCheckVersionsAfterInstallArgs{
|
||||
Pkgs: pkgs,
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InstallerExecutorRPCServer) CheckVersionsAfterInstall(args *InstallerExecutorCheckVersionsAfterInstallArgs, resp *InstallerExecutorCheckVersionsAfterInstallResp) error {
|
||||
err := s.Impl.CheckVersionsAfterInstall(context.Background(), args.Pkgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = InstallerExecutorCheckVersionsAfterInstallResp{}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ScriptExecutorReadScriptArgs struct {
|
||||
ScriptPath string
|
||||
}
|
||||
|
||||
@@ -167,3 +167,33 @@ func (a *APK) IsInstalled(pkg string) (bool, error) {
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *APK) GetInstalledVersion(pkg string) (string, 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 "", nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("apk: getinstalledversion: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
// Output format: "package-version" (e.g., "curl-8.5.0-r0")
|
||||
// We need to extract just the version part
|
||||
line := strings.TrimSpace(string(output))
|
||||
if line == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Find the last hyphen that separates name from version
|
||||
// Alpine package names can contain hyphens, version starts after last one
|
||||
lastDash := strings.LastIndex(line, "-")
|
||||
if lastDash == -1 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return line[lastDash+1:], nil
|
||||
}
|
||||
|
||||
@@ -82,8 +82,15 @@ func (a *APT) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
|
||||
func (a *APT) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
|
||||
resolvedPkgs := make([]string, 0, len(pkgs))
|
||||
for _, pkg := range pkgs {
|
||||
resolved := a.resolvePackageName(pkg)
|
||||
resolvedPkgs = append(resolvedPkgs, resolved)
|
||||
}
|
||||
|
||||
cmd := a.getCmd(opts, "apt", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
cmd.Args = append(cmd.Args, resolvedPkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -92,6 +99,39 @@ func (a *APT) Remove(opts *Opts, pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APT) resolvePackageName(pkg string) string {
|
||||
cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", pkg)
|
||||
output, err := cmd.Output()
|
||||
if err == nil && strings.Contains(string(output), "install ok installed") {
|
||||
return pkg
|
||||
}
|
||||
|
||||
cmd = exec.Command("dpkg-query", "-W", "-f", "${Package}\t${Provides}\n")
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
return pkg
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
parts := strings.SplitN(line, "\t", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
pkgName := parts[0]
|
||||
provides := parts[1]
|
||||
|
||||
for _, p := range strings.Split(provides, ", ") {
|
||||
p = strings.TrimSpace(p)
|
||||
provName := strings.Split(p, " ")[0]
|
||||
if provName == pkg {
|
||||
return pkgName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func (a *APT) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return a.Install(opts, pkgs...)
|
||||
@@ -140,11 +180,11 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
}
|
||||
|
||||
func (a *APT) IsInstalled(pkg string) (bool, error) {
|
||||
cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", pkg)
|
||||
resolved := a.resolvePackageName(pkg)
|
||||
cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", resolved)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
// Код выхода 1 означает что пакет не найден
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
@@ -153,6 +193,21 @@ func (a *APT) IsInstalled(pkg string) (bool, error) {
|
||||
}
|
||||
|
||||
status := strings.TrimSpace(string(output))
|
||||
// Проверяем что пакет действительно установлен (статус должен содержать "install ok installed")
|
||||
return strings.Contains(status, "install ok installed"), nil
|
||||
}
|
||||
|
||||
func (a *APT) GetInstalledVersion(pkg string) (string, error) {
|
||||
resolved := a.resolvePackageName(pkg)
|
||||
cmd := exec.Command("dpkg-query", "-f", "${Version}", "-W", resolved)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("apt: getinstalledversion: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
@@ -70,3 +70,21 @@ func (a *CommonRPM) IsInstalled(pkg string) (bool, error) {
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *CommonRPM) GetInstalledVersion(pkg string) (string, error) {
|
||||
cmd := exec.Command("rpm", "-q", "--queryformat", "%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}", pkg)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("rpm: getinstalledversion: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
version := strings.TrimSpace(string(output))
|
||||
// Remove epoch 0: prefix if present
|
||||
version = strings.TrimPrefix(version, "0:")
|
||||
return version, nil
|
||||
}
|
||||
|
||||
59
internal/manager/manager_test.go
Normal file
59
internal/manager/manager_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package manager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewZypperReturnsCorrectType(t *testing.T) {
|
||||
z := NewZypper()
|
||||
if z == nil {
|
||||
t.Fatal("NewZypper() returned nil")
|
||||
}
|
||||
if z.Name() != "zypper" {
|
||||
t.Errorf("Expected name 'zypper', got '%s'", z.Name())
|
||||
}
|
||||
if z.Format() != "rpm" {
|
||||
t.Errorf("Expected format 'rpm', got '%s'", z.Format())
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagersOrder(t *testing.T) {
|
||||
// Проверяем, что APT-RPM идёт раньше APT в списке менеджеров
|
||||
aptRpmIndex := -1
|
||||
aptIndex := -1
|
||||
|
||||
for i, m := range managers {
|
||||
switch m.Name() {
|
||||
case "apt-rpm":
|
||||
aptRpmIndex = i
|
||||
case "apt":
|
||||
aptIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
if aptRpmIndex == -1 {
|
||||
t.Fatal("APT-RPM not found in managers list")
|
||||
}
|
||||
if aptIndex == -1 {
|
||||
t.Fatal("APT not found in managers list")
|
||||
}
|
||||
if aptRpmIndex >= aptIndex {
|
||||
t.Errorf("APT-RPM (index %d) should come before APT (index %d)", aptRpmIndex, aptIndex)
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,12 @@ var DefaultOpts = &Opts{
|
||||
|
||||
var managers = []Manager{
|
||||
NewPacman(),
|
||||
NewAPTRpm(), // APT-RPM должен проверяться раньше APT, т.к. на ALT Linux есть оба
|
||||
NewAPT(),
|
||||
NewDNF(),
|
||||
NewYUM(),
|
||||
NewAPK(),
|
||||
NewZypper(),
|
||||
NewAPTRpm(),
|
||||
}
|
||||
|
||||
// Register registers a new package manager
|
||||
@@ -74,8 +74,11 @@ type Manager interface {
|
||||
UpgradeAll(*Opts) error
|
||||
// ListInstalled returns all installed packages mapped to their versions
|
||||
ListInstalled(*Opts) (map[string]string, error)
|
||||
//
|
||||
// IsInstalled checks if a package is installed
|
||||
IsInstalled(string) (bool, error)
|
||||
// GetInstalledVersion returns the version of an installed package.
|
||||
// Returns empty string and no error if package is not installed.
|
||||
GetInstalledVersion(string) (string, error)
|
||||
}
|
||||
|
||||
// Detect returns the package manager detected on the system
|
||||
|
||||
@@ -160,3 +160,24 @@ func (p *Pacman) IsInstalled(pkg string) (bool, error) {
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (p *Pacman) GetInstalledVersion(pkg string) (string, 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 "", nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("pacman: getinstalledversion: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
// Output format: "package-name version"
|
||||
_, version, ok := strings.Cut(strings.TrimSpace(string(output)), " ")
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ type Zypper struct {
|
||||
CommonRPM
|
||||
}
|
||||
|
||||
func NewZypper() *YUM {
|
||||
return &YUM{
|
||||
func NewZypper() *Zypper {
|
||||
return &Zypper{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "-y",
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/depver"
|
||||
)
|
||||
|
||||
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
|
||||
@@ -36,39 +37,49 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse version constraint from package name
|
||||
dep := depver.Parse(pkgName)
|
||||
searchName := dep.Name
|
||||
|
||||
var result []alrsh.Package
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case strings.Contains(pkgName, "/"):
|
||||
case strings.Contains(searchName, "/"):
|
||||
// repo/pkg
|
||||
parts := strings.SplitN(pkgName, "/", 2)
|
||||
parts := strings.SplitN(searchName, "/", 2)
|
||||
repo := parts[0]
|
||||
name := parts[1]
|
||||
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
||||
|
||||
case strings.Contains(pkgName, "+"):
|
||||
case strings.Contains(searchName, "+"):
|
||||
// pkg+repo
|
||||
parts := strings.SplitN(pkgName, "+", 2)
|
||||
parts := strings.SplitN(searchName, "+", 2)
|
||||
name := parts[0]
|
||||
repo := parts[1]
|
||||
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
||||
|
||||
default:
|
||||
result, err = rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
|
||||
// Сначала ищем по точному имени пакета
|
||||
result, err = rs.db.GetPkgs(ctx, "name = ?", searchName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by name: %w", err)
|
||||
}
|
||||
|
||||
// Затем по provides
|
||||
if len(result) == 0 {
|
||||
result, err = rs.db.GetPkgs(ctx, "basepkg_name = ?", pkgName)
|
||||
result, err = rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", searchName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by basepkg_name: %w", err)
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// В последнюю очередь по basepkg_name (для мультипакетов)
|
||||
if len(result) == 0 {
|
||||
result, err = rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
||||
result, err = rs.db.GetPkgs(ctx, "basepkg_name = ?", searchName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by basepkg_name: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +87,11 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
||||
return nil, nil, fmt.Errorf("FindPkgs: lookup for %q failed: %w", pkgName, err)
|
||||
}
|
||||
|
||||
// Filter by version if constraint is specified
|
||||
if dep.HasVersionConstraint() && len(result) > 0 {
|
||||
result = filterByVersion(result, dep)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
notFound = append(notFound, pkgName)
|
||||
} else {
|
||||
@@ -85,3 +101,14 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
||||
|
||||
return found, notFound, nil
|
||||
}
|
||||
|
||||
// filterByVersion filters packages by version constraint.
|
||||
func filterByVersion(pkgs []alrsh.Package, dep depver.Dependency) []alrsh.Package {
|
||||
var filtered []alrsh.Package
|
||||
for _, pkg := range pkgs {
|
||||
if dep.Satisfies(pkg.Version) {
|
||||
filtered = append(filtered, pkg)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||
return &Decoder{info, runner, true, len(info.Like) > 0}
|
||||
}
|
||||
|
||||
func (d *Decoder) Info() *distro.OSRelease {
|
||||
return d.info
|
||||
}
|
||||
|
||||
// DecodeVar decodes a variable to val using reflection.
|
||||
// Structs should use the "sh" struct tag.
|
||||
func (d *Decoder) DecodeVar(name string, val any) error {
|
||||
|
||||
Reference in New Issue
Block a user