Улучшения обработки зависимостей и фильтрации установленных пакетов

- Добавлена поддержка версионных ограничений при установке пакетов
- Улучшена логика фильтрации уже установленных пакетов
- Добавлен метод GetInstalledVersion для всех менеджеров пакетов
- Активированы тесты для систем archlinux, alpine, opensuse-leap
- Улучшена обработка переменных в скриптах

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-01-16 01:01:01 +03:00
parent b649a459b8
commit 3d9f4a0985
25 changed files with 1330 additions and 45 deletions

View File

@@ -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")

View File

@@ -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()

View File

@@ -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{}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}