From 49785d4dc858d0d56ada56460a470ae3be4ec7b7 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=D0=BE=D0=B2?= Date: Sat, 16 Nov 2024 11:32:47 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=B8=D1=85?= =?UTF-8?q?=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=BE=D0=B2=20package=5F*=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE=20alr.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- pkg/build/build.go | 298 +++++++++++++++++++++++-------------------- pkg/build/install.go | 35 +++-- pkg/gen/funcs.go | 19 ++- pkg/gen/pip.go | 122 ++++++++++-------- pkg/manager/dnf.go | 216 ++++++++++++++++--------------- 6 files changed, 384 insertions(+), 309 deletions(-) diff --git a/.gitignore b/.gitignore index 73728ea..3bca798 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /dist/ /internal/config/version.txt .fleet -.idea \ No newline at end of file +.idea +.gigaide \ No newline at end of file diff --git a/pkg/build/build.go b/pkg/build/build.go index ee1022d..69a8774 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -1,19 +1,29 @@ /* * ALR - Any Linux Repository + * Любая Linux репозитория * Copyright (C) 2024 Евгений Храмов * * 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. + * Это программное обеспечение свободно: вы можете распространять его и/или изменять + * на условиях GNU General Public License, опубликованной Free Software Foundation, + * либо версии 3 лицензии, либо (на ваш выбор) любой более поздней версии. * * 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. + * Это программное обеспечение распространяется в надежде, что оно будет полезным, + * но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии + * КОММЕРЧЕСКОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. См. + * GNU General Public License для более подробной информации. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * Вы должны были получить копию GNU General Public License + * вместе с этой программой. Если нет, см. . */ package build @@ -32,6 +42,7 @@ import ( "strings" "time" + // Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH). _ "github.com/goreleaser/nfpm/v2/apk" _ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/deb" @@ -57,8 +68,8 @@ import ( "mvdan.cc/sh/v3/syntax" ) -// BuildPackage builds the script at the given path. It returns two slices. One contains the paths -// to the built package(s), the other contains the names of the built package(s). +// Функция BuildPackage выполняет сборку скрипта по указанному пути. Возвращает два среза. +// Один содержит пути к собранным пакетам, другой - имена собранных пакетов. func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) { log := loggerctx.From(ctx) @@ -72,9 +83,8 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string return nil, nil, err } - // The first pass is just used to get variable values and runs before - // the script is displayed, so it's restricted so as to prevent malicious - // code from executing. + // Первый проход предназначен для получения значений переменных и выполняется + // до отображения скрипта, чтобы предотвратить выполнение вредоносного кода. vars, err := executeFirstPass(ctx, info, fl, opts.Script) if err != nil { return nil, nil, err @@ -82,8 +92,8 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string dirs := getDirs(ctx, vars, opts.Script) - // If opts.Clean isn't set and we find the package already built, - // just return it rather than rebuilding + // Если флаг opts.Clean не установлен, и пакет уже собран, + // возвращаем его, а не собираем заново. if !opts.Clean { builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir) if err != nil { @@ -95,7 +105,7 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string } } - // Ask the user if they'd like to see the build script + // Спрашиваем у пользователя, хочет ли он увидеть скрипт сборки. err = cliutils.PromptViewScript(ctx, opts.Script, vars.Name, config.Config(ctx).PagerStyle, opts.Interactive) if err != nil { log.Fatal("Failed to prompt user to view build script").Err(err).Send() @@ -103,161 +113,161 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send() - // The second pass will be used to execute the actual code, - // so it's unrestricted. The script has already been displayed - // to the user by this point, so it should be safe + // Второй проход будет использоваться для выполнения реального кода, + // поэтому он не ограничен. Скрипт уже был показан + // пользователю к этому моменту, так что это должно быть безопасно. dec, err := executeSecondPass(ctx, info, fl, dirs) if err != nil { return nil, nil, err } - // Get the installed packages on the system + // Получаем список установленных пакетов в системе installed, err := opts.Manager.ListInstalled(nil) if err != nil { return nil, nil, err } - cont, err := performChecks(ctx, vars, opts.Interactive, installed) + cont, err := performChecks(ctx, vars, opts.Interactive, installed) // Выполняем различные проверки if err != nil { return nil, nil, err } else if !cont { - os.Exit(1) + os.Exit(1) // Если проверки не пройдены, выходим из программы } - // Prepare the directories for building + // Подготавливаем директории для сборки err = prepareDirs(dirs) if err != nil { return nil, nil, err } - buildDeps, err := installBuildDeps(ctx, vars, opts, installed) + buildDeps, err := installBuildDeps(ctx, vars, opts, installed) // Устанавливаем зависимости для сборки if err != nil { return nil, nil, err } - err = installOptDeps(ctx, vars, opts, installed) + err = installOptDeps(ctx, vars, opts, installed) // Устанавливаем опциональные зависимости if err != nil { return nil, nil, err } - builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) + builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) // Собираем зависимости if err != nil { return nil, nil, err } - log.Info("Downloading sources").Send() + log.Info("Downloading sources").Send() // Записываем в лог загрузку источников - err = getSources(ctx, dirs, vars) + err = getSources(ctx, dirs, vars) // Загружаем исходники if err != nil { return nil, nil, err } - err = executeFunctions(ctx, dec, dirs, vars) + err = executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции if err != nil { return nil, nil, err } - log.Info("Building package metadata").Str("name", vars.Name).Send() + log.Info("Building package metadata").Str("name", vars.Name).Send() // Логгируем сборку метаданных пакета - pkgFormat := getPkgFormat(opts.Manager) + pkgFormat := getPkgFormat(opts.Manager) // Получаем формат пакета - pkgInfo, err := buildPkgMetadata(vars, dirs, pkgFormat, append(repoDeps, builtNames...)) + pkgInfo, err := buildPkgMetadata(vars, dirs, pkgFormat, append(repoDeps, builtNames...)) // Собираем метаданные пакета if err != nil { return nil, nil, err } - packager, err := nfpm.Get(pkgFormat) + packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета if err != nil { return nil, nil, err } - pkgName := packager.ConventionalFileName(pkgInfo) - pkgPath := filepath.Join(dirs.BaseDir, pkgName) + pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета + pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету - pkgFile, err := os.Create(pkgPath) + pkgFile, err := os.Create(pkgPath) // Создаём файл пакета if err != nil { return nil, nil, err } - log.Info("Compressing package").Str("name", pkgName).Send() + log.Info("Compressing package").Str("name", pkgName).Send() // Логгируем сжатие пакета - err = packager.Package(pkgInfo, pkgFile) + err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет if err != nil { return nil, nil, err } - err = removeBuildDeps(ctx, buildDeps, opts) + err = removeBuildDeps(ctx, buildDeps, opts) // Удаляем зависимости для сборки if err != nil { return nil, nil, err } - // Add the path and name of the package we just built to the - // appropriate slices + // Добавляем путь и имя только что собранного пакета в + // соответствующие срезы pkgPaths := append(builtPaths, pkgPath) pkgNames := append(builtNames, vars.Name) - // Remove any duplicates from the pkgPaths and pkgNames. - // Duplicates can be introduced if several of the dependencies - // depend on the same packages. + // Удаляем дубликаты из pkgPaths и pkgNames. + // Дубликаты могут появиться, если несколько зависимостей + // зависят от одних и тех же пакетов. pkgPaths = removeDuplicates(pkgPaths) pkgNames = removeDuplicates(pkgNames) - return pkgPaths, pkgNames, nil + return pkgPaths, pkgNames, nil // Возвращаем пути и имена пакетов } -// parseScript parses the build script using the built-in bash implementation +// Функция parseScript анализирует скрипт сборки с использованием встроенной реализации bash func parseScript(info *distro.OSRelease, script string) (*syntax.File, error) { - fl, err := os.Open(script) + fl, err := os.Open(script) // Открываем файл скрипта if err != nil { return nil, err } - defer fl.Close() + defer fl.Close() // Закрываем файл после выполнения - file, err := syntax.NewParser().Parse(fl, "alr.sh") + file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора if err != nil { return nil, err } - return file, nil + return file, nil // Возвращаем синтаксическое дерево } -// executeFirstPass executes the parsed script in a restricted environment -// to extract the build variables without executing any actual code. +// Функция executeFirstPass выполняет парсированный скрипт в ограниченной среде, +// чтобы извлечь переменные сборки без выполнения реального кода. func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, script string) (*types.BuildVars, error) { - scriptDir := filepath.Dir(script) - env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir}) + scriptDir := filepath.Dir(script) // Получаем директорию скрипта + env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir}) // Создаём переменные окружения для сборки runner, err := interp.New( - interp.Env(expand.ListEnviron(env...)), - interp.StdIO(os.Stdin, os.Stdout, os.Stderr), - interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), - interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)), - interp.StatHandler(handlers.RestrictedStat(scriptDir)), - interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), + interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение + interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод + interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение + interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий + interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов + interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов ) if err != nil { return nil, err } - err = runner.Run(ctx, fl) + err = runner.Run(ctx, fl) // Запускаем скрипт if err != nil { return nil, err } - dec := decoder.New(info, runner) + dec := decoder.New(info, runner) // Создаём новый декодер var vars types.BuildVars - err = dec.DecodeVars(&vars) + err = dec.DecodeVars(&vars) // Декодируем переменные if err != nil { return nil, err } - return &vars, nil + return &vars, nil // Возвращаем переменные сборки } -// getDirs returns the appropriate directories for the script +// Функция getDirs возвращает соответствующие директории для скрипта func getDirs(ctx context.Context, vars *types.BuildVars, script string) types.Directories { - baseDir := filepath.Join(config.GetPaths(ctx).PkgsDir, vars.Name) + baseDir := filepath.Join(config.GetPaths(ctx).PkgsDir, vars.Name) // Определяем базовую директорию return types.Directories{ BaseDir: baseDir, SrcDir: filepath.Join(baseDir, "src"), @@ -266,46 +276,46 @@ func getDirs(ctx context.Context, vars *types.BuildVars, script string) types.Di } } -// executeSecondPass executes the build script for the second time, this time without any restrictions. -// It returns a decoder that can be used to retrieve functions and variables from the script. +// Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер, +// который может быть использован для получения функций и переменных из скрипта. func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) { - env := createBuildEnvVars(info, dirs) + env := createBuildEnvVars(info, dirs) // Создаём переменные окружения для сборки - fakeroot := handlers.FakerootExecHandler(2 * time.Second) + fakeroot := handlers.FakerootExecHandler(2 * time.Second) // Настраиваем "fakeroot" для выполнения runner, err := interp.New( - interp.Env(expand.ListEnviron(env...)), - interp.StdIO(os.Stdin, os.Stdout, os.Stderr), - interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)), + interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение + interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод + interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)), // Обрабатываем выполнение через fakeroot ) if err != nil { return nil, err } - err = runner.Run(ctx, fl) + err = runner.Run(ctx, fl) // Запускаем скрипт if err != nil { return nil, err } - return decoder.New(info, runner), nil + return decoder.New(info, runner), nil // Возвращаем новый декодер } -// prepareDirs prepares the directories for building. +// Функция prepareDirs подготавливает директории для сборки. func prepareDirs(dirs types.Directories) error { - err := os.RemoveAll(dirs.BaseDir) + err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует if err != nil { return err } - err = os.MkdirAll(dirs.SrcDir, 0o755) + err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников if err != nil { return err } - return os.MkdirAll(dirs.PkgDir, 0o755) + return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов } -// performChecks checks various things on the system to ensure that the package can be installed. +// Функция performChecks проверяет различные аспекты в системе, чтобы убедиться, что пакет может быть установлен. func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, installed map[string]string) (bool, error) { log := loggerctx.From(ctx) - if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { + if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры cont, err := cliutils.YesNoPrompt(ctx, "Your system's CPU architecture doesn't match this package. Do you want to build anyway?", interactive, true) if err != nil { return false, err @@ -316,7 +326,7 @@ func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, } } - if instVer, ok := installed[vars.Name]; ok { + if instVer, ok := installed[vars.Name]; ok { // Если пакет уже установлен, выводим предупреждение log.Warn("This package is already installed"). Str("name", vars.Name). Str("version", instVer). @@ -326,33 +336,33 @@ func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, return true, nil } -// installBuildDeps installs any build dependencies that aren't already installed and returns -// a slice containing the names of all the packages it installed. +// Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает +// срез, содержащий имена всех установленных пакетов. func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]string, error) { log := loggerctx.From(ctx) var buildDeps []string if len(vars.BuildDepends) > 0 { - found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends) + found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends) // Находим пакеты-зависимости if err != nil { return nil, err } - found = removeAlreadyInstalled(found, installed) + found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости - log.Info("Installing build dependencies").Send() + log.Info("Installing build dependencies").Send() // Логгируем установку зависимостей - flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) + flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) // Уплощаем список зависимостей buildDeps = packageNames(flattened) - InstallPkgs(ctx, flattened, notFound, opts) + InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем пакеты } return buildDeps, nil } -// installOptDeps asks the user which, if any, optional dependencies they want to install. -// If the user chooses to install any optional dependencies, it performs the installation. +// Функция 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) + optDeps, err := cliutils.ChooseOptDepends(ctx, vars.OptDepends, "install", opts.Interactive) // Пользователя просят выбрать опциональные зависимости if err != nil { return err } @@ -361,63 +371,63 @@ func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.Build return nil } - found, notFound, err := repos.FindPkgs(ctx, optDeps) + found, notFound, err := repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости if err != nil { return err } - found = removeAlreadyInstalled(found, installed) + found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) - InstallPkgs(ctx, flattened, notFound, opts) + InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем выбранные пакеты } return nil } -// buildALRDeps builds all the ALR dependencies of the package. It returns the paths and names -// of the packages it built, as well as all the dependencies it didn't find in the ALR repo so -// they can be installed from the system repos. +// Функция buildALRDeps собирает все ALR зависимости пакета. Возвращает пути и имена +// пакетов, которые она собрала, а также все зависимости, которые не были найдены в ALR репозитории, +// чтобы они могли быть установлены из системных репозиториев. func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) { log := loggerctx.From(ctx) if len(vars.Depends) > 0 { log.Info("Installing dependencies").Send() - found, notFound, err := repos.FindPkgs(ctx, vars.Depends) + found, notFound, err := repos.FindPkgs(ctx, vars.Depends) // Поиск зависимостей if err != nil { return nil, nil, nil, err } repoDeps = notFound - // If there are multiple options for some packages, flatten them all into a single slice + // Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез pkgs := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) scripts := GetScriptPaths(ctx, pkgs) for _, script := range scripts { newOpts := opts newOpts.Script = script - // Build the dependency + // Собираем зависимости pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts) if err != nil { return nil, nil, nil, err } - // Append the paths of all the built packages to builtPaths + // Добавляем пути всех собранных пакетов в builtPaths builtPaths = append(builtPaths, pkgPaths...) - // Append the names of all the built packages to builtNames + // Добавляем пути всех собранных пакетов в builtPaths builtNames = append(builtNames, pkgNames...) - // Append the name of the current package to builtNames + // Добавляем имя текущего пакета в builtNames builtNames = append(builtNames, filepath.Base(filepath.Dir(script))) } } - // Remove any potential duplicates, which can be introduced if - // several of the dependencies depend on the same packages. + // Удаляем возможные дубликаты, которые могут быть введены, если + // несколько зависимостей зависят от одних и тех же пакетов. repoDeps = removeDuplicates(repoDeps) builtPaths = removeDuplicates(builtPaths) builtNames = removeDuplicates(builtNames) return builtPaths, builtNames, repoDeps, nil } -// executeFunctions executes the special ALR functions, such as version(), prepare(), etc. +// Функция executeFunctions выполняет специальные функции ALR, такие как version(), prepare() и т.д. func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (err error) { log := loggerctx.From(ctx) version, ok := dec.GetFunc("version") @@ -465,22 +475,34 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire } } - packageFn, ok := dec.GetFunc("package") - if ok { - log.Info("Executing package()").Send() + // Выполнение всех функций, начинающихся с package_ + for { + packageFn, ok := dec.GetFunc("package") + if ok { + log.Info("Executing package()").Send() + err = packageFn(ctx, interp.Dir(dirs.SrcDir)) + if err != nil { + return err + } + } - err = packageFn(ctx, interp.Dir(dirs.SrcDir)) - if err != nil { - return err - } - } else { - log.Fatal("The package() function is required").Send() - } + // Проверка на наличие дополнительных функций package_* + packageFuncName := "package_" + if packageFunc, ok := dec.GetFunc(packageFuncName); ok { + log.Info("Executing " + packageFuncName).Send() + err = packageFunc(ctx, interp.Dir(dirs.SrcDir)) + if err != nil { + return err + } + } else { + break // Если больше нет функций package_*, выходим из цикла + } + } return nil } -// buildPkgMetadata builds the metadata for the package that's going to be built. +// Функция buildPkgMetadata создает метаданные для пакета, который будет собран. func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat string, deps []string) (*nfpm.Info, error) { pkgInfo := &nfpm.Info{ Name: vars.Name, @@ -501,7 +523,7 @@ func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat s } if pkgFormat == "apk" { - // Alpine refuses to install packages that provide themselves, so remove any such provides + // Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool { return s == pkgInfo.Name }) @@ -526,8 +548,8 @@ func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat s return pkgInfo, nil } -// buildContents builds the contents section of the package, which contains the files -// that will be placed into the final package. +// Функция buildContents создает секцию содержимого пакета, которая содержит файлы, +// которые будут включены в конечный пакет. func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Content, error) { contents := []*files.Content{} err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error { @@ -539,7 +561,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont return err } - // If the directory is empty, skip it + // Если директория пустая, пропускаем её _, err = f.Readdirnames(1) if err != io.EOF { return nil @@ -556,13 +578,13 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont return f.Close() } - + // Если файл является символической ссылкой, прорабатываем это if fi.Mode()&os.ModeSymlink != 0 { link, err := os.Readlink(path) if err != nil { return err } - // Remove pkgdir from the symlink's path + // Удаляем pkgdir из пути символической ссылки link = strings.TrimPrefix(link, dirs.PkgDir) contents = append(contents, &files.Content{ @@ -577,7 +599,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont return nil } - + // Обрабатываем обычные файлы fileContent := &files.Content{ Source: path, Destination: trimmed, @@ -588,7 +610,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont }, } - // If the file is supposed to be backed up, set its type to config|noreplace + // Если файл должен быть сохранен, установите его тип как config|noreplace if slices.Contains(vars.Backup, trimmed) { fileContent.Type = "config|noreplace" } @@ -600,8 +622,8 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont return contents, err } -// removeBuildDeps asks the user if they'd like to remove the build dependencies that were -// installed by installBuildDeps. If so, it uses the package manager to do that. +// Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости, +// установленные для сборки. Если да, использует менеджер пакетов для их удаления. func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOpts) error { if len(buildDeps) > 0 { remove, err := cliutils.YesNoPrompt(ctx, "Would you like to remove the build dependencies?", opts.Interactive, false) @@ -625,8 +647,8 @@ func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOp return nil } -// checkForBuiltPackage tries to detect a previously-built package and returns its path -// and true if it finds one. If it doesn't find it, it returns "", false, nil. +// Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь +// и true, если нашла. Если нет, возвратит "", false, nil. func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, baseDir string) (string, bool, error) { filename, err := pkgFileName(vars, pkgFormat) if err != nil { @@ -643,8 +665,8 @@ func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, return pkgPath, true, nil } -// pkgFileName returns the filename of the package if it were to be built. -// This is used to check if the package has already been built. +// Функция pkgFileName возвращает имя файла пакета, если оно было бы создано. +// Это используется для проверки, был ли пакет уже собран. func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) { pkgInfo := &nfpm.Info{ Name: vars.Name, @@ -662,8 +684,8 @@ func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) { return packager.ConventionalFileName(pkgInfo), nil } -// getPkgFormat returns the package format of the package manager, -// or ALR_PKG_FORMAT if that's set. +// Функция getPkgFormat возвращает формат пакета из менеджера пакетов, +// или ALR_PKG_FORMAT, если он установлен. func getPkgFormat(mgr manager.Manager) string { pkgFormat := mgr.Format() if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok { @@ -672,8 +694,8 @@ func getPkgFormat(mgr manager.Manager) string { return pkgFormat } -// createBuildEnvVars creates the environment variables that will be set in the -// build script when it's executed. +// Функция createBuildEnvVars создает переменные окружения, которые будут установлены +// в скрипте сборки при его выполнении. func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string { env := os.Environ() @@ -703,7 +725,7 @@ func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string return env } -// getSources downloads the sources from the script. +// Функция getSources загружает исходники скрипта. func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error { log := loggerctx.From(ctx) if len(bv.Sources) != len(bv.Checksums) { @@ -720,9 +742,9 @@ func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars } if !strings.EqualFold(bv.Checksums[i], "SKIP") { - // If the checksum contains a colon, use the part before the colon - // as the algorithm and the part after as the actual checksum. - // Otherwise, use the default sha256 with the whole string as the checksum. + // Если контрольная сумма содержит двоеточие, используйте часть до двоеточия + // как алгоритм, а часть после как фактическую контрольную сумму. + // В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой. algo, hashData, ok := strings.Cut(bv.Checksums[i], ":") if ok { checksum, err := hex.DecodeString(hashData) @@ -749,7 +771,7 @@ func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars return nil } -// setScripts adds any hook scripts to the package metadata. +// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета. func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) { if vars.Scripts.PreInstall != "" { info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall) @@ -786,8 +808,8 @@ func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) { } } -// setVersion changes the version variable in the script runner. -// It's used to set the version to the output of the version() function. +// Функция setVersion изменяет переменную версии в скрипте runner. +// Она используется для установки версии на вывод функции version(). func setVersion(ctx context.Context, r *interp.Runner, to string) error { fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "") if err != nil { @@ -796,7 +818,7 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error { return r.Run(ctx, fl) } -// removeAlreadyInstalled returns a map without any dependencies that are already installed +// Функция removeAlreadyInstalled возвращает карту без каких-либо зависимостей, которые уже установлены. func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package { filteredPackages := make(map[string][]db.Package) @@ -813,7 +835,7 @@ func removeAlreadyInstalled(found map[string][]db.Package, installed map[string] return filteredPackages } -// packageNames returns the names of all the given packages +// Функция packageNames возвращает имена всех предоставленных пакетов. func packageNames(pkgs []db.Package) []string { names := make([]string, len(pkgs)) for i, p := range pkgs { @@ -822,7 +844,7 @@ func packageNames(pkgs []db.Package) []string { return names } -// removeDuplicates removes any duplicates from the given slice +// Функция removeDuplicates убирает любые дубликаты из предоставленного среза. func removeDuplicates(slice []string) []string { seen := map[string]struct{}{} result := []string{} diff --git a/pkg/build/install.go b/pkg/build/install.go index 9d99cc1..19ee98f 100644 --- a/pkg/build/install.go +++ b/pkg/build/install.go @@ -1,19 +1,30 @@ /* * ALR - Any Linux Repository + * ALR - Любой Linux Репозиторий * Copyright (C) 2024 Евгений Храмов * * 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 + * на условиях GNU General Public License, опубликованной * the Free Software Foundation, either version 3 of the License, or + * Free Software Foundation, либо версии 3 лицензии, либо * (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. + * Подробности смотрите в GNU General Public License. * * You should have received a copy of the GNU General Public License + * Вы должны были получить копию GNU General Public License * along with this program. If not, see . + * вместе с этой программой. Если нет, посмотрите . */ package build @@ -28,45 +39,53 @@ import ( "plemya-x.ru/alr/pkg/loggerctx" ) -// InstallPkgs installs native packages via the package manager, -// then builds and installs the ALR packages +// InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов, +// затем строит и устанавливает пакеты ALR func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string, opts types.BuildOpts) { - log := loggerctx.From(ctx) + log := loggerctx.From(ctx) // Инициализируем логгер из контекста if len(nativePkgs) > 0 { err := opts.Manager.Install(nil, nativePkgs...) + // Если есть нативные пакеты, выполняем их установку if err != nil { log.Fatal("Error installing native packages").Err(err).Send() + // Логируем и завершаем выполнение при ошибке } } InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts) + // Устанавливаем скрипты сборки через функцию InstallScripts } -// GetScriptPaths returns a slice of script paths corresponding to the -// given packages +// GetScriptPaths возвращает срез путей к скриптам, соответствующий +// данным пакетам func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string { var scripts []string for _, pkg := range pkgs { + // Для каждого пакета создаем путь к скрипту сборки scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh") scripts = append(scripts, scriptPath) } return scripts } -// InstallScripts builds and installs the given alr build scripts +// InstallScripts строит и устанавливает переданные alr скрипты сборки func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) { - log := loggerctx.From(ctx) + log := loggerctx.From(ctx) // Получаем логгер из контекста for _, script := range scripts { - opts.Script = script + opts.Script = script // Устанавливаем текущий скрипт в опции builtPkgs, _, err := BuildPackage(ctx, opts) + // Выполняем сборку пакета if err != nil { log.Fatal("Error building package").Err(err).Send() + // Логируем и завершаем выполнение при ошибке сборки } err = opts.Manager.InstallLocal(nil, builtPkgs...) + // Устанавливаем локально собранные пакеты if err != nil { log.Fatal("Error installing package").Err(err).Send() + // Логируем и завершаем выполнение при ошибке установки } } } diff --git a/pkg/gen/funcs.go b/pkg/gen/funcs.go index c0b6c51..46022e3 100644 --- a/pkg/gen/funcs.go +++ b/pkg/gen/funcs.go @@ -1,13 +1,20 @@ package gen import ( - "strings" - "text/template" + "strings" + "text/template" ) +// Определяем переменную funcs типа template.FuncMap, которая будет использоваться для +// предоставления пользовательских функций в шаблонах var funcs = template.FuncMap{ - "tolower": strings.ToLower, - "firstchar": func(s string) string { - return s[:1] - }, + // Функция "tolower" использует strings.ToLower + // для преобразования строки в нижний регистр + "tolower": strings.ToLower, + + // Функция "firstchar" — это лямбда-функция, которая берет строку + // и возвращает её первый символ + "firstchar": func(s string) string { + return s[:1] + }, } diff --git a/pkg/gen/pip.go b/pkg/gen/pip.go index 6f8f984..c9a7384 100644 --- a/pkg/gen/pip.go +++ b/pkg/gen/pip.go @@ -1,84 +1,98 @@ package gen import ( - _ "embed" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "text/template" + _ "embed" // Пакет для встраивания содержимого файлов в бинарники Go, использовав откладку //go:embed + "encoding/json" // Пакет для работы с JSON: декодирование и кодирование + "errors" // Пакет для создания и обработки ошибок + "fmt" // Пакет для форматированного ввода и вывода + "io" // Пакет для интерфейсов ввода и вывода + "net/http" // Пакет для HTTP-клиентов и серверов + "text/template" // Пакет для обработки текстовых шаблонов ) +// Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl +// Встраивание файла tmpls/pip.tmpl.sh //go:embed tmpls/pip.tmpl.sh var pipTmpl string +// PipOptions содержит параметры, которые будут переданы в шаблон type PipOptions struct { - Name string - Version string - Description string + Name string // Имя пакета + Version string // Версия пакета + Description string // Описание пакета } +// pypiAPIResponse представляет структуру ответа от API PyPI type pypiAPIResponse struct { - Info pypiInfo `json:"info"` - URLs []pypiURL `json:"urls"` + Info pypiInfo `json:"info"` // Информация о пакете + URLs []pypiURL `json:"urls"` // Список URL-адресов для загрузки пакета } +// Метод SourceURL ищет и возвращает URL исходного distribution для пакета, если он существует func (res pypiAPIResponse) SourceURL() (pypiURL, error) { - for _, url := range res.URLs { - if url.PackageType == "sdist" { - return url, nil - } - } - return pypiURL{}, errors.New("package doesn't have a source distribution") + for _, url := range res.URLs { + if url.PackageType == "sdist" { + return url, nil + } + } + return pypiURL{}, errors.New("package doesn't have a source distribution") } +// pypiInfo содержит основную информацию о пакете, такую как имя, версия и пр. type pypiInfo struct { - Name string `json:"name"` - Version string `json:"version"` - Summary string `json:"summary"` - Homepage string `json:"home_page"` - License string `json:"license"` + Name string `json:"name"` + Version string `json:"version"` + Summary string `json:"summary"` + Homepage string `json:"home_page"` + License string `json:"license"` } +// pypiURL представляет информацию об одном из доступных для загрузки URL type pypiURL struct { - Digests map[string]string `json:"digests"` - Filename string `json:"filename"` - PackageType string `json:"packagetype"` + Digests map[string]string `json:"digests"` // Контрольные суммы для файлов + Filename string `json:"filename"` // Имя файла + PackageType string `json:"packagetype"` // Тип пакета (например sdist) } +// Функция Pip загружает информацию о пакете из PyPI и использует шаблон для вывода информации func Pip(w io.Writer, opts PipOptions) error { - tmpl, err := template.New("pip"). - Funcs(funcs). - Parse(pipTmpl) - if err != nil { - return err - } + // Создаем новый шаблон с добавлением функций из FuncMap + tmpl, err := template.New("pip"). + Funcs(funcs). + Parse(pipTmpl) + if err != nil { + return err + } - url := fmt.Sprintf( - "https://pypi.org/pypi/%s/%s/json", - opts.Name, - opts.Version, - ) + // Формируем URL для запроса к PyPI на основании имени и версии пакета + url := fmt.Sprintf( + "https://pypi.org/pypi/%s/%s/json", + opts.Name, + opts.Version, + ) - res, err := http.Get(url) - if err != nil { - return err - } - defer res.Body.Close() - if res.StatusCode != 200 { - return fmt.Errorf("pypi: %s", res.Status) - } + // Выполняем HTTP GET запрос к PyPI + res, err := http.Get(url) + if err != nil { + return err + } + defer res.Body.Close() // Закрываем тело ответа после завершения работы + if res.StatusCode != 200 { + return fmt.Errorf("pypi: %s", res.Status) + } - var resp pypiAPIResponse - err = json.NewDecoder(res.Body).Decode(&resp) - if err != nil { - return err - } + // Раскодируем ответ JSON от PyPI в структуру pypiAPIResponse + var resp pypiAPIResponse + err = json.NewDecoder(res.Body).Decode(&resp) + if err != nil { + return err + } - if opts.Description != "" { - resp.Info.Summary = opts.Description - } + // Если в opts указано описание, используем его вместо описания из PyPI + if opts.Description != "" { + resp.Info.Summary = opts.Description + } - return tmpl.Execute(w, resp) + // Выполняем шаблон с использованием данных из resp и записываем результат в w + return tmpl.Execute(w, resp) } diff --git a/pkg/manager/dnf.go b/pkg/manager/dnf.go index 7f9fc6f..c97c573 100644 --- a/pkg/manager/dnf.go +++ b/pkg/manager/dnf.go @@ -1,160 +1,172 @@ /* * ALR - Any Linux Repository + * ALR - Любой Linux Репозиторий * Copyright (C) 2024 Евгений Храмов * - * 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 является свободным: вы можете распространять его и/или изменять + * на условиях GNU General Public License, опубликованной Free Software Foundation, + * либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии. * - * 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. + * Это программное обеспечение распространяется в надежде, что оно будет полезным, + * но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; без подразумеваемой гарантии + * КОММЕРЧЕСКОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. + * Подробности см. в GNU General Public License. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Вы должны были получить копию GNU General Public License + * вместе с этой программой. Если нет, см. . */ package manager import ( - "bufio" - "fmt" - "os/exec" - "strings" + "bufio" + "fmt" + "os/exec" + "strings" ) -// DNF represents the DNF package manager +// DNF представляет менеджер пакетов DNF type DNF struct { - rootCmd string + rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root } +// Exists проверяет, доступен ли DNF в системе, возвращает true если да func (*DNF) Exists() bool { - _, err := exec.LookPath("dnf") - return err == nil + _, err := exec.LookPath("dnf") + return err == nil } +// Name возвращает имя менеджера пакетов, в данном случае "dnf" func (*DNF) Name() string { - return "dnf" + return "dnf" } +// Format возвращает формат пакетов "rpm", используемый DNF func (*DNF) Format() string { - return "rpm" + return "rpm" } +// SetRootCmd устанавливает команду, используемую для выполнения операций с правами root func (d *DNF) SetRootCmd(s string) { - d.rootCmd = s + d.rootCmd = s } +// Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий func (d *DNF) Sync(opts *Opts) error { - opts = ensureOpts(opts) - cmd := d.getCmd(opts, "dnf", "upgrade") - setCmdEnv(cmd) - err := cmd.Run() - if err != nil { - return fmt.Errorf("dnf: sync: %w", err) - } - return nil + opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения + cmd := d.getCmd(opts, "dnf", "upgrade") + setCmdEnv(cmd) // Устанавливает переменные окружения для команды + err := cmd.Run() // Выполняет команду + if err != nil { + return fmt.Errorf("dnf: sync: %w", err) + } + return nil } +// Install устанавливает указанные пакеты с помощью DNF func (d *DNF) Install(opts *Opts, pkgs ...string) error { - opts = ensureOpts(opts) - cmd := d.getCmd(opts, "dnf", "install", "--allowerasing") - cmd.Args = append(cmd.Args, pkgs...) - setCmdEnv(cmd) - err := cmd.Run() - if err != nil { - return fmt.Errorf("dnf: install: %w", err) - } - return nil + opts = ensureOpts(opts) + cmd := d.getCmd(opts, "dnf", "install", "--allowerasing") + cmd.Args = append(cmd.Args, pkgs...) // Добавляем названия пакетов к команде + setCmdEnv(cmd) + err := cmd.Run() + if err != nil { + return fmt.Errorf("dnf: install: %w", err) + } + return nil } +// InstallLocal расширяет метод Install для установки пакетов, расположенных локально func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error { - opts = ensureOpts(opts) - return d.Install(opts, pkgs...) + opts = ensureOpts(opts) + return d.Install(opts, pkgs...) } +// Remove удаляет указанные пакеты с помощью DNF func (d *DNF) Remove(opts *Opts, pkgs ...string) error { - opts = ensureOpts(opts) - cmd := d.getCmd(opts, "dnf", "remove") - cmd.Args = append(cmd.Args, pkgs...) - setCmdEnv(cmd) - err := cmd.Run() - if err != nil { - return fmt.Errorf("dnf: remove: %w", err) - } - return nil + opts = ensureOpts(opts) + cmd := d.getCmd(opts, "dnf", "remove") + cmd.Args = append(cmd.Args, pkgs...) + setCmdEnv(cmd) + err := cmd.Run() + if err != nil { + return fmt.Errorf("dnf: remove: %w", err) + } + return nil } +// Upgrade обновляет указанные пакеты до более новых версий func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error { - opts = ensureOpts(opts) - cmd := d.getCmd(opts, "dnf", "upgrade") - cmd.Args = append(cmd.Args, pkgs...) - setCmdEnv(cmd) - err := cmd.Run() - if err != nil { - return fmt.Errorf("dnf: upgrade: %w", err) - } - return nil + opts = ensureOpts(opts) + cmd := d.getCmd(opts, "dnf", "upgrade") + cmd.Args = append(cmd.Args, pkgs...) + setCmdEnv(cmd) + err := cmd.Run() + if err != nil { + return fmt.Errorf("dnf: upgrade: %w", err) + } + return nil } +// UpgradeAll обновляет все установленные пакеты func (d *DNF) UpgradeAll(opts *Opts) error { - opts = ensureOpts(opts) - cmd := d.getCmd(opts, "dnf", "upgrade") - setCmdEnv(cmd) - err := cmd.Run() - if err != nil { - return fmt.Errorf("dnf: upgradeall: %w", err) - } - return nil + opts = ensureOpts(opts) + cmd := d.getCmd(opts, "dnf", "upgrade") + setCmdEnv(cmd) + err := cmd.Run() + if err != nil { + return fmt.Errorf("dnf: upgradeall: %w", err) + } + 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") + 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 - } + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } - err = cmd.Start() - 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 - } + 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 - } + err = scanner.Err() + if err != nil { + return nil, err + } - return out, nil + return out, nil } +// getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { - var cmd *exec.Cmd - if opts.AsRoot { - cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd) - cmd.Args = append(cmd.Args, opts.Args...) - cmd.Args = append(cmd.Args, args...) - } else { - cmd = exec.Command(mgrCmd, args...) - } + var cmd *exec.Cmd + if opts.AsRoot { + cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd) + cmd.Args = append(cmd.Args, opts.Args...) + cmd.Args = append(cmd.Args, args...) + } else { + cmd = exec.Command(mgrCmd, args...) + } - if opts.NoConfirm { - cmd.Args = append(cmd.Args, "-y") - } + if opts.NoConfirm { + cmd.Args = append(cmd.Args, "-y") // Добавляет параметр автоматического подтверждения (-y) + } - return cmd + return cmd }