forked from Plemya-x/ALR
		
	fix: removeAlreadyInstalled before FindPkgs
This commit is contained in:
		
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @@ -56,6 +56,7 @@ require ( | |||||||
| 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect | 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect | ||||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | ||||||
| 	github.com/cyphar/filepath-securejoin v0.2.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/dlclark/regexp2 v1.10.0 // indirect | ||||||
| 	github.com/dsnet/compress v0.0.1 // indirect | 	github.com/dsnet/compress v0.0.1 // indirect | ||||||
| 	github.com/dustin/go-humanize v1.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/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect | ||||||
| 	github.com/pierrec/lz4/v4 v4.1.15 // indirect | 	github.com/pierrec/lz4/v4 v4.1.15 // indirect | ||||||
| 	github.com/pjbgf/sha1cd v0.3.0 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||||||
| 	github.com/rivo/uniseg v0.4.4 // indirect | 	github.com/rivo/uniseg v0.4.4 // indirect | ||||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // 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/shopspring/decimal v1.2.0 // indirect | ||||||
| 	github.com/skeema/knownhosts v1.2.2 // indirect | 	github.com/skeema/knownhosts v1.2.2 // indirect | ||||||
| 	github.com/spf13/cast v1.6.0 // 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/therootcompany/xz v1.0.1 // indirect | ||||||
| 	github.com/ulikunitz/xz v0.5.12 // indirect | 	github.com/ulikunitz/xz v0.5.12 // indirect | ||||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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.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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | 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 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | ||||||
| github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= | github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= | ||||||
| github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= | ||||||
|   | |||||||
| @@ -65,6 +65,7 @@ import ( | |||||||
| // Один содержит пути к собранным пакетам, другой - имена собранных пакетов. | // Один содержит пути к собранным пакетам, другой - имена собранных пакетов. | ||||||
| func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) { | func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) { | ||||||
| 	log := loggerctx.From(ctx) | 	log := loggerctx.From(ctx) | ||||||
|  | 	reposInstance := repos.GetInstance(ctx) | ||||||
|  |  | ||||||
| 	info, err := distro.ParseOSRelease(ctx) | 	info, err := distro.ParseOSRelease(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -133,12 +134,12 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string | |||||||
| 		return nil, nil, err | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	buildDeps, err := installBuildDeps(ctx, vars, opts, installed) // Устанавливаем зависимости для сборки | 	buildDeps, err := installBuildDeps(ctx, reposInstance, vars, opts) // Устанавливаем зависимости для сборки | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, nil, err | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = installOptDeps(ctx, vars, opts, installed) // Устанавливаем опциональные зависимости | 	err = installOptDeps(ctx, reposInstance, vars, opts) // Устанавливаем опциональные зависимости | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, nil, err | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
| @@ -329,18 +330,25 @@ func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, | |||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type PackageFinder interface { | ||||||
|  | 	FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) | ||||||
|  | } | ||||||
|  |  | ||||||
| // Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает | // Функция 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) | 	log := loggerctx.From(ctx) | ||||||
| 	var buildDeps []string | 	var buildDeps []string | ||||||
| 	if len(vars.BuildDepends) > 0 { | 	if len(vars.BuildDepends) > 0 { | ||||||
| 		found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends) // Находим пакеты-зависимости | 		deps, err := removeAlreadyInstalled(opts, vars.BuildDepends) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			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() // Логгируем установку зависимостей | 		log.Info("Installing build dependencies").Send() // Логгируем установку зависимостей | ||||||
|  |  | ||||||
| @@ -353,9 +361,13 @@ func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.Bui | |||||||
|  |  | ||||||
| // Функция installOptDeps спрашивает у пользователя, какие, если таковые имеются, опциональные зависимости он хочет установить. | // Функция installOptDeps спрашивает у пользователя, какие, если таковые имеются, опциональные зависимости он хочет установить. | ||||||
| // Если пользователь решает установить какие-либо опциональные зависимости, выполняется их установка. | // Если пользователь решает установить какие-либо опциональные зависимости, выполняется их установка. | ||||||
| func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) error { | func installOptDeps(ctx context.Context, repos PackageFinder, vars *types.BuildVars, opts types.BuildOpts) error { | ||||||
| 	if len(vars.OptDepends) > 0 { | 	optDeps, err := removeAlreadyInstalled(opts, vars.OptDepends) | ||||||
| 		optDeps, err := cliutils.ChooseOptDepends(ctx, vars.OptDepends, "install", opts.Interactive) // Пользователя просят выбрать опциональные зависимости | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if len(optDeps) > 0 { | ||||||
|  | 		optDeps, err := cliutils.ChooseOptDepends(ctx, optDeps, "install", opts.Interactive) // Пользователя просят выбрать опциональные зависимости | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -369,7 +381,6 @@ func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.Build | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости |  | ||||||
| 		flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) | 		flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) | ||||||
| 		InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем выбранные пакеты | 		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) | 	return r.Run(ctx, fl) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Функция removeAlreadyInstalled возвращает карту без каких-либо зависимостей, которые уже установлены. | // Returns not installed dependencies | ||||||
| func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package { | func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) { | ||||||
| 	filteredPackages := make(map[string][]db.Package) | 	filteredPackages := []string{} | ||||||
|  |  | ||||||
| 	for name, pkgList := range found { | 	for _, dep := range dependencies { | ||||||
| 		filteredPkgList := []db.Package{} | 		installed, err := opts.Manager.IsInstalled(dep) | ||||||
| 		for _, pkg := range pkgList { | 		if err != nil { | ||||||
| 			if _, isInstalled := installed[pkg.Name]; !isInstalled { | 			return nil, err | ||||||
| 				filteredPkgList = append(filteredPkgList, pkg) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 		filteredPackages[name] = filteredPkgList | 		if installed { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		filteredPackages = append(filteredPackages, dep) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return filteredPackages | 	return filteredPackages, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Функция packageNames возвращает имена всех предоставленных пакетов. | // Функция packageNames возвращает имена всех предоставленных пакетов. | ||||||
|   | |||||||
							
								
								
									
										225
									
								
								pkg/build/build_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								pkg/build/build_internal_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -149,6 +149,21 @@ func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) { | |||||||
| 	return out, nil | 	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 { | func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if opts.AsRoot { | 	if opts.AsRoot { | ||||||
|   | |||||||
| @@ -135,6 +135,21 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) { | |||||||
| 	return out, nil | 	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 { | func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if opts.AsRoot { | 	if opts.AsRoot { | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ | |||||||
| package manager | package manager | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -25,6 +24,7 @@ import ( | |||||||
|  |  | ||||||
| // APTRpm represents the APT-RPM package manager | // APTRpm represents the APT-RPM package manager | ||||||
| type APTRpm struct { | type APTRpm struct { | ||||||
|  | 	CommonRPM | ||||||
| 	rootCmd string | 	rootCmd string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -106,38 +106,6 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error { | |||||||
| 	return nil | 	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 { | func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if opts.AsRoot { | 	if opts.AsRoot { | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								pkg/manager/common_rpm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								pkg/manager/common_rpm.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | } | ||||||
| @@ -19,14 +19,13 @@ | |||||||
| package manager | package manager | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // DNF представляет менеджер пакетов DNF | // DNF представляет менеджер пакетов DNF | ||||||
| type DNF struct { | type DNF struct { | ||||||
|  | 	CommonRPM | ||||||
| 	rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root | 	rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -120,39 +119,6 @@ func (d *DNF) UpgradeAll(opts *Opts) error { | |||||||
| 	return nil | 	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 | // getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF | ||||||
| func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
|   | |||||||
| @@ -80,6 +80,8 @@ type Manager interface { | |||||||
| 	UpgradeAll(*Opts) error | 	UpgradeAll(*Opts) error | ||||||
| 	// ListInstalled returns all installed packages mapped to their versions | 	// ListInstalled returns all installed packages mapped to their versions | ||||||
| 	ListInstalled(*Opts) (map[string]string, error) | 	ListInstalled(*Opts) (map[string]string, error) | ||||||
|  | 	// | ||||||
|  | 	IsInstalled(string) (bool, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Detect returns the package manager detected on the system | // Detect returns the package manager detected on the system | ||||||
|   | |||||||
| @@ -142,6 +142,21 @@ func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) { | |||||||
| 	return out, nil | 	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 { | func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if opts.AsRoot { | 	if opts.AsRoot { | ||||||
|   | |||||||
| @@ -20,14 +20,14 @@ | |||||||
| package manager | package manager | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // YUM represents the YUM package manager | // YUM represents the YUM package manager | ||||||
| type YUM struct { | type YUM struct { | ||||||
|  | 	CommonRPM | ||||||
|  |  | ||||||
| 	rootCmd string | 	rootCmd string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -111,38 +111,6 @@ func (y *YUM) UpgradeAll(opts *Opts) error { | |||||||
| 	return nil | 	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 { | func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if opts.AsRoot { | 	if opts.AsRoot { | ||||||
|   | |||||||
| @@ -20,14 +20,13 @@ | |||||||
| package manager | package manager | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Zypper represents the Zypper package manager | // Zypper represents the Zypper package manager | ||||||
| type Zypper struct { | type Zypper struct { | ||||||
|  | 	CommonRPM | ||||||
| 	rootCmd string | 	rootCmd string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -111,38 +110,6 @@ func (z *Zypper) UpgradeAll(opts *Opts) error { | |||||||
| 	return nil | 	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 { | func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if opts.AsRoot { | 	if opts.AsRoot { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user