0
0
Fork 0
mirror of https://gitea.plemya-x.ru/Plemya-x/ALR.git synced 2025-01-10 09:16:45 +00:00

Комментирование кода, добавление возможности сборки нескольких пакетов package_* из одного alr.sh

This commit is contained in:
Евгений Храмов 2024-11-16 11:32:47 +03:00
parent 6238765a7d
commit 49785d4dc8
6 changed files with 384 additions and 309 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@
/internal/config/version.txt /internal/config/version.txt
.fleet .fleet
.idea .idea
.gigaide

View file

@ -1,19 +1,29 @@
/* /*
* ALR - Any Linux Repository * ALR - Any Linux Repository
* Любая Linux репозитория
* Copyright (C) 2024 Евгений Храмов * Copyright (C) 2024 Евгений Храмов
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (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, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* Это программное обеспечение распространяется в надежде, что оно будет полезным,
* но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии
* КОММЕРЧЕСКОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. См.
* GNU General Public License для более подробной информации.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* Вы должны были получить копию GNU General Public License
* вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>.
*/ */
package build package build
@ -32,6 +42,7 @@ import (
"strings" "strings"
"time" "time"
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
_ "github.com/goreleaser/nfpm/v2/apk" _ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb" _ "github.com/goreleaser/nfpm/v2/deb"
@ -57,8 +68,8 @@ import (
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
) )
// BuildPackage builds the script at the given path. It returns two slices. One contains the paths // Функция BuildPackage выполняет сборку скрипта по указанному пути. Возвращает два среза.
// to the built package(s), the other contains the names of the built package(s). // Один содержит пути к собранным пакетам, другой - имена собранных пакетов.
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)
@ -72,9 +83,8 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
return nil, nil, err 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) vars, err := executeFirstPass(ctx, info, fl, opts.Script)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -82,8 +92,8 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
dirs := getDirs(ctx, vars, opts.Script) dirs := getDirs(ctx, vars, opts.Script)
// If opts.Clean isn't set and we find the package already built, // Если флаг opts.Clean не установлен, и пакет уже собран,
// just return it rather than rebuilding // возвращаем его, а не собираем заново.
if !opts.Clean { if !opts.Clean {
builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir) builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir)
if err != nil { 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) err = cliutils.PromptViewScript(ctx, opts.Script, vars.Name, config.Config(ctx).PagerStyle, opts.Interactive)
if err != nil { if err != nil {
log.Fatal("Failed to prompt user to view build script").Err(err).Send() 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() 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) dec, err := executeSecondPass(ctx, info, fl, dirs)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Get the installed packages on the system // Получаем список установленных пакетов в системе
installed, err := opts.Manager.ListInstalled(nil) installed, err := opts.Manager.ListInstalled(nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
cont, err := performChecks(ctx, vars, opts.Interactive, installed) cont, err := performChecks(ctx, vars, opts.Interactive, installed) // Выполняем различные проверки
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} else if !cont { } else if !cont {
os.Exit(1) os.Exit(1) // Если проверки не пройдены, выходим из программы
} }
// Prepare the directories for building // Подготавливаем директории для сборки
err = prepareDirs(dirs) err = prepareDirs(dirs)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
buildDeps, err := installBuildDeps(ctx, vars, opts, installed) buildDeps, err := installBuildDeps(ctx, vars, opts, installed) // Устанавливаем зависимости для сборки
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = installOptDeps(ctx, vars, opts, installed) err = installOptDeps(ctx, vars, opts, installed) // Устанавливаем опциональные зависимости
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) // Собираем зависимости
if err != nil { if err != nil {
return nil, nil, err 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = executeFunctions(ctx, dec, dirs, vars) err = executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции
if err != nil { if err != nil {
return nil, nil, err 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
packager, err := nfpm.Get(pkgFormat) packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pkgName := packager.ConventionalFileName(pkgInfo) pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
pkgFile, err := os.Create(pkgPath) pkgFile, err := os.Create(pkgPath) // Создаём файл пакета
if err != nil { if err != nil {
return nil, nil, err 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = removeBuildDeps(ctx, buildDeps, opts) err = removeBuildDeps(ctx, buildDeps, opts) // Удаляем зависимости для сборки
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Add the path and name of the package we just built to the // Добавляем путь и имя только что собранного пакета в
// appropriate slices // соответствующие срезы
pkgPaths := append(builtPaths, pkgPath) pkgPaths := append(builtPaths, pkgPath)
pkgNames := append(builtNames, vars.Name) pkgNames := append(builtNames, vars.Name)
// Remove any duplicates from the pkgPaths and pkgNames. // Удаляем дубликаты из pkgPaths и pkgNames.
// Duplicates can be introduced if several of the dependencies // Дубликаты могут появиться, если несколько зависимостей
// depend on the same packages. // зависят от одних и тех же пакетов.
pkgPaths = removeDuplicates(pkgPaths) pkgPaths = removeDuplicates(pkgPaths)
pkgNames = removeDuplicates(pkgNames) 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) { func parseScript(info *distro.OSRelease, script string) (*syntax.File, error) {
fl, err := os.Open(script) fl, err := os.Open(script) // Открываем файл скрипта
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
return file, nil return file, nil // Возвращаем синтаксическое дерево
} }
// executeFirstPass executes the parsed script in a restricted environment // Функция executeFirstPass выполняет парсированный скрипт в ограниченной среде,
// to extract the build variables without executing any actual code. // чтобы извлечь переменные сборки без выполнения реального кода.
func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, script string) (*types.BuildVars, error) { func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, script string) (*types.BuildVars, error) {
scriptDir := filepath.Dir(script) scriptDir := filepath.Dir(script) // Получаем директорию скрипта
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir}) env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir}) // Создаём переменные окружения для сборки
runner, err := interp.New( runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stdout, os.Stderr), interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)), interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
interp.StatHandler(handlers.RestrictedStat(scriptDir)), interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = runner.Run(ctx, fl) err = runner.Run(ctx, fl) // Запускаем скрипт
if err != nil { if err != nil {
return nil, err return nil, err
} }
dec := decoder.New(info, runner) dec := decoder.New(info, runner) // Создаём новый декодер
var vars types.BuildVars var vars types.BuildVars
err = dec.DecodeVars(&vars) err = dec.DecodeVars(&vars) // Декодируем переменные
if err != nil { if err != nil {
return nil, err 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 { 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{ return types.Directories{
BaseDir: baseDir, BaseDir: baseDir,
SrcDir: filepath.Join(baseDir, "src"), 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. // Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер,
// It returns a decoder that can be used to retrieve functions and variables from the script. // который может быть использован для получения функций и переменных из скрипта.
func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) { 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( runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stdout, os.Stderr), interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)), interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)), // Обрабатываем выполнение через fakeroot
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = runner.Run(ctx, fl) err = runner.Run(ctx, fl) // Запускаем скрипт
if err != nil { if err != nil {
return nil, err 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 { func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
if err != nil { if err != nil {
return err return err
} }
err = os.MkdirAll(dirs.SrcDir, 0o755) err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
if err != nil { if err != nil {
return err 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) { func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, installed map[string]string) (bool, error) {
log := loggerctx.From(ctx) 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) 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 { if err != nil {
return false, err 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"). log.Warn("This package is already installed").
Str("name", vars.Name). Str("name", vars.Name).
Str("version", instVer). Str("version", instVer).
@ -326,33 +336,33 @@ func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool,
return true, nil return true, nil
} }
// installBuildDeps installs any build dependencies that aren't already installed and returns // Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает
// a slice containing the names of all the packages it installed. // срез, содержащий имена всех установленных пакетов.
func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]string, error) { func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]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) found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends) // Находим пакеты-зависимости
if err != nil { if err != nil {
return nil, err 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) buildDeps = packageNames(flattened)
InstallPkgs(ctx, flattened, notFound, opts) InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем пакеты
} }
return buildDeps, nil return buildDeps, nil
} }
// installOptDeps asks the user which, if any, optional dependencies they want to install. // Функция installOptDeps спрашивает у пользователя, какие, если таковые имеются, опциональные зависимости он хочет установить.
// If the user chooses to install any optional dependencies, it performs the installation. // Если пользователь решает установить какие-либо опциональные зависимости, выполняется их установка.
func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) error { func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) error {
if len(vars.OptDepends) > 0 { 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 { if err != nil {
return err return err
} }
@ -361,63 +371,63 @@ func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.Build
return nil return nil
} }
found, notFound, err := repos.FindPkgs(ctx, optDeps) found, notFound, err := repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости
if err != nil { if err != nil {
return err return err
} }
found = removeAlreadyInstalled(found, installed) 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) // Устанавливаем выбранные пакеты
} }
return nil return nil
} }
// buildALRDeps builds all the ALR dependencies of the package. It returns the paths and names // Функция buildALRDeps собирает все ALR зависимости пакета. Возвращает пути и имена
// of the packages it built, as well as all the dependencies it didn't find in the ALR repo so // пакетов, которые она собрала, а также все зависимости, которые не были найдены в ALR репозитории,
// they can be installed from the system repos. // чтобы они могли быть установлены из системных репозиториев.
func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) { func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
log := loggerctx.From(ctx) log := loggerctx.From(ctx)
if len(vars.Depends) > 0 { if len(vars.Depends) > 0 {
log.Info("Installing dependencies").Send() log.Info("Installing dependencies").Send()
found, notFound, err := repos.FindPkgs(ctx, vars.Depends) found, notFound, err := repos.FindPkgs(ctx, vars.Depends) // Поиск зависимостей
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
repoDeps = notFound 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) pkgs := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
scripts := GetScriptPaths(ctx, pkgs) scripts := GetScriptPaths(ctx, pkgs)
for _, script := range scripts { for _, script := range scripts {
newOpts := opts newOpts := opts
newOpts.Script = script newOpts.Script = script
// Build the dependency // Собираем зависимости
pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts) pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
// Append the paths of all the built packages to builtPaths // Добавляем пути всех собранных пакетов в builtPaths
builtPaths = append(builtPaths, pkgPaths...) builtPaths = append(builtPaths, pkgPaths...)
// Append the names of all the built packages to builtNames // Добавляем пути всех собранных пакетов в builtPaths
builtNames = append(builtNames, pkgNames...) builtNames = append(builtNames, pkgNames...)
// Append the name of the current package to builtNames // Добавляем имя текущего пакета в builtNames
builtNames = append(builtNames, filepath.Base(filepath.Dir(script))) 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) repoDeps = removeDuplicates(repoDeps)
builtPaths = removeDuplicates(builtPaths) builtPaths = removeDuplicates(builtPaths)
builtNames = removeDuplicates(builtNames) builtNames = removeDuplicates(builtNames)
return builtPaths, builtNames, repoDeps, nil 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) { func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (err error) {
log := loggerctx.From(ctx) log := loggerctx.From(ctx)
version, ok := dec.GetFunc("version") version, ok := dec.GetFunc("version")
@ -465,22 +475,34 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire
} }
} }
// Выполнение всех функций, начинающихся с package_
for {
packageFn, ok := dec.GetFunc("package") packageFn, ok := dec.GetFunc("package")
if ok { if ok {
log.Info("Executing package()").Send() log.Info("Executing package()").Send()
err = packageFn(ctx, interp.Dir(dirs.SrcDir)) err = packageFn(ctx, interp.Dir(dirs.SrcDir))
if err != nil { if err != nil {
return err return err
} }
}
// Проверка на наличие дополнительных функций 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 { } else {
log.Fatal("The package() function is required").Send() break // Если больше нет функций package_*, выходим из цикла
}
} }
return nil 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) { func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat string, deps []string) (*nfpm.Info, error) {
pkgInfo := &nfpm.Info{ pkgInfo := &nfpm.Info{
Name: vars.Name, Name: vars.Name,
@ -501,7 +523,7 @@ func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat s
} }
if pkgFormat == "apk" { 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 { pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name return s == pkgInfo.Name
}) })
@ -526,8 +548,8 @@ func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat s
return pkgInfo, nil return pkgInfo, nil
} }
// buildContents builds the contents section of the package, which contains the files // Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// that will be placed into the final package. // которые будут включены в конечный пакет.
func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Content, error) { func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Content, error) {
contents := []*files.Content{} contents := []*files.Content{}
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error { 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 return err
} }
// If the directory is empty, skip it // Если директория пустая, пропускаем её
_, err = f.Readdirnames(1) _, err = f.Readdirnames(1)
if err != io.EOF { if err != io.EOF {
return nil return nil
@ -556,13 +578,13 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont
return f.Close() return f.Close()
} }
// Если файл является символической ссылкой, прорабатываем это
if fi.Mode()&os.ModeSymlink != 0 { if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path) link, err := os.Readlink(path)
if err != nil { if err != nil {
return err return err
} }
// Remove pkgdir from the symlink's path // Удаляем pkgdir из пути символической ссылки
link = strings.TrimPrefix(link, dirs.PkgDir) link = strings.TrimPrefix(link, dirs.PkgDir)
contents = append(contents, &files.Content{ contents = append(contents, &files.Content{
@ -577,7 +599,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont
return nil return nil
} }
// Обрабатываем обычные файлы
fileContent := &files.Content{ fileContent := &files.Content{
Source: path, Source: path,
Destination: trimmed, 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) { if slices.Contains(vars.Backup, trimmed) {
fileContent.Type = "config|noreplace" fileContent.Type = "config|noreplace"
} }
@ -600,8 +622,8 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont
return contents, err return contents, err
} }
// removeBuildDeps asks the user if they'd like to remove the build dependencies that were // Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости,
// installed by installBuildDeps. If so, it uses the package manager to do that. // установленные для сборки. Если да, использует менеджер пакетов для их удаления.
func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOpts) error { func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOpts) error {
if len(buildDeps) > 0 { if len(buildDeps) > 0 {
remove, err := cliutils.YesNoPrompt(ctx, "Would you like to remove the build dependencies?", opts.Interactive, false) 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 return nil
} }
// checkForBuiltPackage tries to detect a previously-built package and returns its path // Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
// and true if it finds one. If it doesn't find it, it returns "", false, nil. // и true, если нашла. Если нет, возвратит "", false, nil.
func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, baseDir string) (string, bool, error) { func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, baseDir string) (string, bool, error) {
filename, err := pkgFileName(vars, pkgFormat) filename, err := pkgFileName(vars, pkgFormat)
if err != nil { if err != nil {
@ -643,8 +665,8 @@ func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat,
return pkgPath, true, nil return pkgPath, true, nil
} }
// pkgFileName returns the filename of the package if it were to be built. // Функция pkgFileName возвращает имя файла пакета, если оно было бы создано.
// This is used to check if the package has already been built. // Это используется для проверки, был ли пакет уже собран.
func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) { func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) {
pkgInfo := &nfpm.Info{ pkgInfo := &nfpm.Info{
Name: vars.Name, Name: vars.Name,
@ -662,8 +684,8 @@ func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) {
return packager.ConventionalFileName(pkgInfo), nil return packager.ConventionalFileName(pkgInfo), nil
} }
// getPkgFormat returns the package format of the package manager, // Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// or ALR_PKG_FORMAT if that's set. // или ALR_PKG_FORMAT, если он установлен.
func getPkgFormat(mgr manager.Manager) string { func getPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format() pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok { if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
@ -672,8 +694,8 @@ func getPkgFormat(mgr manager.Manager) string {
return pkgFormat return pkgFormat
} }
// createBuildEnvVars creates the environment variables that will be set in the // Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// build script when it's executed. // в скрипте сборки при его выполнении.
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string { func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
env := os.Environ() env := os.Environ()
@ -703,7 +725,7 @@ func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string
return env return env
} }
// getSources downloads the sources from the script. // Функция getSources загружает исходники скрипта.
func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error { func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
log := loggerctx.From(ctx) log := loggerctx.From(ctx)
if len(bv.Sources) != len(bv.Checksums) { 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 !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], ":") algo, hashData, ok := strings.Cut(bv.Checksums[i], ":")
if ok { if ok {
checksum, err := hex.DecodeString(hashData) checksum, err := hex.DecodeString(hashData)
@ -749,7 +771,7 @@ func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars
return nil return nil
} }
// setScripts adds any hook scripts to the package metadata. // Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) { func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
if vars.Scripts.PreInstall != "" { if vars.Scripts.PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, 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. // Функция setVersion изменяет переменную версии в скрипте runner.
// It's used to set the version to the output of the version() function. // Она используется для установки версии на вывод функции version().
func setVersion(ctx context.Context, r *interp.Runner, to string) error { func setVersion(ctx context.Context, r *interp.Runner, to string) error {
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "") fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
if err != nil { if err != nil {
@ -796,7 +818,7 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error {
return r.Run(ctx, fl) 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 { func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
filteredPackages := make(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 return filteredPackages
} }
// packageNames returns the names of all the given packages // Функция packageNames возвращает имена всех предоставленных пакетов.
func packageNames(pkgs []db.Package) []string { func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs)) names := make([]string, len(pkgs))
for i, p := range pkgs { for i, p := range pkgs {
@ -822,7 +844,7 @@ func packageNames(pkgs []db.Package) []string {
return names return names
} }
// removeDuplicates removes any duplicates from the given slice // Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string { func removeDuplicates(slice []string) []string {
seen := map[string]struct{}{} seen := map[string]struct{}{}
result := []string{} result := []string{}

View file

@ -1,19 +1,30 @@
/* /*
* ALR - Any Linux Repository * ALR - Any Linux Repository
* ALR - Любой Linux Репозиторий
* Copyright (C) 2024 Евгений Храмов * Copyright (C) 2024 Евгений Храмов
* *
* This program is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* Free Software Foundation, либо версии 3 лицензии, либо
* (at your option) any later version. * (at your option) any later version.
* (по вашему усмотрению) любой более поздней версии.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* Это программное обеспечение распространяется в надежде, что оно будет полезным,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* но БЕЗ КАКОЙ-ЛИБО ГАРАНТИИ; даже без подразумеваемой гарантии
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* КОММЕРЧЕСКОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ.
* GNU General Public License for more details. * GNU General Public License for more details.
* Подробности смотрите в GNU General Public License.
* *
* You should have received a copy of the 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 <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* вместе с этой программой. Если нет, посмотрите <http://www.gnu.org/licenses/>.
*/ */
package build package build
@ -28,45 +39,53 @@ import (
"plemya-x.ru/alr/pkg/loggerctx" "plemya-x.ru/alr/pkg/loggerctx"
) )
// InstallPkgs installs native packages via the package manager, // InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов,
// then builds and installs the ALR packages // затем строит и устанавливает пакеты ALR
func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string, opts types.BuildOpts) { 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 { if len(nativePkgs) > 0 {
err := opts.Manager.Install(nil, nativePkgs...) err := opts.Manager.Install(nil, nativePkgs...)
// Если есть нативные пакеты, выполняем их установку
if err != nil { if err != nil {
log.Fatal("Error installing native packages").Err(err).Send() log.Fatal("Error installing native packages").Err(err).Send()
// Логируем и завершаем выполнение при ошибке
} }
} }
InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts) InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts)
// Устанавливаем скрипты сборки через функцию InstallScripts
} }
// GetScriptPaths returns a slice of script paths corresponding to the // GetScriptPaths возвращает срез путей к скриптам, соответствующий
// given packages // данным пакетам
func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string { func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string {
var scripts []string var scripts []string
for _, pkg := range pkgs { for _, pkg := range pkgs {
// Для каждого пакета создаем путь к скрипту сборки
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh") scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh")
scripts = append(scripts, scriptPath) scripts = append(scripts, scriptPath)
} }
return scripts return scripts
} }
// InstallScripts builds and installs the given alr build scripts // InstallScripts строит и устанавливает переданные alr скрипты сборки
func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) { func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
log := loggerctx.From(ctx) log := loggerctx.From(ctx) // Получаем логгер из контекста
for _, script := range scripts { for _, script := range scripts {
opts.Script = script opts.Script = script // Устанавливаем текущий скрипт в опции
builtPkgs, _, err := BuildPackage(ctx, opts) builtPkgs, _, err := BuildPackage(ctx, opts)
// Выполняем сборку пакета
if err != nil { if err != nil {
log.Fatal("Error building package").Err(err).Send() log.Fatal("Error building package").Err(err).Send()
// Логируем и завершаем выполнение при ошибке сборки
} }
err = opts.Manager.InstallLocal(nil, builtPkgs...) err = opts.Manager.InstallLocal(nil, builtPkgs...)
// Устанавливаем локально собранные пакеты
if err != nil { if err != nil {
log.Fatal("Error installing package").Err(err).Send() log.Fatal("Error installing package").Err(err).Send()
// Логируем и завершаем выполнение при ошибке установки
} }
} }
} }

View file

@ -5,8 +5,15 @@ import (
"text/template" "text/template"
) )
// Определяем переменную funcs типа template.FuncMap, которая будет использоваться для
// предоставления пользовательских функций в шаблонах
var funcs = template.FuncMap{ var funcs = template.FuncMap{
// Функция "tolower" использует strings.ToLower
// для преобразования строки в нижний регистр
"tolower": strings.ToLower, "tolower": strings.ToLower,
// Функция "firstchar" — это лямбда-функция, которая берет строку
// и возвращает её первый символ
"firstchar": func(s string) string { "firstchar": func(s string) string {
return s[:1] return s[:1]
}, },

View file

@ -1,29 +1,34 @@
package gen package gen
import ( import (
_ "embed" _ "embed" // Пакет для встраивания содержимого файлов в бинарники Go, использовав откладку //go:embed
"encoding/json" "encoding/json" // Пакет для работы с JSON: декодирование и кодирование
"errors" "errors" // Пакет для создания и обработки ошибок
"fmt" "fmt" // Пакет для форматированного ввода и вывода
"io" "io" // Пакет для интерфейсов ввода и вывода
"net/http" "net/http" // Пакет для HTTP-клиентов и серверов
"text/template" "text/template" // Пакет для обработки текстовых шаблонов
) )
// Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl
// Встраивание файла tmpls/pip.tmpl.sh
//go:embed tmpls/pip.tmpl.sh //go:embed tmpls/pip.tmpl.sh
var pipTmpl string var pipTmpl string
// PipOptions содержит параметры, которые будут переданы в шаблон
type PipOptions struct { type PipOptions struct {
Name string Name string // Имя пакета
Version string Version string // Версия пакета
Description string Description string // Описание пакета
} }
// pypiAPIResponse представляет структуру ответа от API PyPI
type pypiAPIResponse struct { type pypiAPIResponse struct {
Info pypiInfo `json:"info"` Info pypiInfo `json:"info"` // Информация о пакете
URLs []pypiURL `json:"urls"` URLs []pypiURL `json:"urls"` // Список URL-адресов для загрузки пакета
} }
// Метод SourceURL ищет и возвращает URL исходного distribution для пакета, если он существует
func (res pypiAPIResponse) SourceURL() (pypiURL, error) { func (res pypiAPIResponse) SourceURL() (pypiURL, error) {
for _, url := range res.URLs { for _, url := range res.URLs {
if url.PackageType == "sdist" { if url.PackageType == "sdist" {
@ -33,6 +38,7 @@ func (res pypiAPIResponse) SourceURL() (pypiURL, error) {
return pypiURL{}, errors.New("package doesn't have a source distribution") return pypiURL{}, errors.New("package doesn't have a source distribution")
} }
// pypiInfo содержит основную информацию о пакете, такую как имя, версия и пр.
type pypiInfo struct { type pypiInfo struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
@ -41,13 +47,16 @@ type pypiInfo struct {
License string `json:"license"` License string `json:"license"`
} }
// pypiURL представляет информацию об одном из доступных для загрузки URL
type pypiURL struct { type pypiURL struct {
Digests map[string]string `json:"digests"` Digests map[string]string `json:"digests"` // Контрольные суммы для файлов
Filename string `json:"filename"` Filename string `json:"filename"` // Имя файла
PackageType string `json:"packagetype"` PackageType string `json:"packagetype"` // Тип пакета (например sdist)
} }
// Функция Pip загружает информацию о пакете из PyPI и использует шаблон для вывода информации
func Pip(w io.Writer, opts PipOptions) error { func Pip(w io.Writer, opts PipOptions) error {
// Создаем новый шаблон с добавлением функций из FuncMap
tmpl, err := template.New("pip"). tmpl, err := template.New("pip").
Funcs(funcs). Funcs(funcs).
Parse(pipTmpl) Parse(pipTmpl)
@ -55,30 +64,35 @@ func Pip(w io.Writer, opts PipOptions) error {
return err return err
} }
// Формируем URL для запроса к PyPI на основании имени и версии пакета
url := fmt.Sprintf( url := fmt.Sprintf(
"https://pypi.org/pypi/%s/%s/json", "https://pypi.org/pypi/%s/%s/json",
opts.Name, opts.Name,
opts.Version, opts.Version,
) )
// Выполняем HTTP GET запрос к PyPI
res, err := http.Get(url) res, err := http.Get(url)
if err != nil { if err != nil {
return err return err
} }
defer res.Body.Close() defer res.Body.Close() // Закрываем тело ответа после завершения работы
if res.StatusCode != 200 { if res.StatusCode != 200 {
return fmt.Errorf("pypi: %s", res.Status) return fmt.Errorf("pypi: %s", res.Status)
} }
// Раскодируем ответ JSON от PyPI в структуру pypiAPIResponse
var resp pypiAPIResponse var resp pypiAPIResponse
err = json.NewDecoder(res.Body).Decode(&resp) err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil { if err != nil {
return err return err
} }
// Если в opts указано описание, используем его вместо описания из PyPI
if opts.Description != "" { if opts.Description != "" {
resp.Info.Summary = opts.Description resp.Info.Summary = opts.Description
} }
// Выполняем шаблон с использованием данных из resp и записываем результат в w
return tmpl.Execute(w, resp) return tmpl.Execute(w, resp)
} }

View file

@ -1,19 +1,19 @@
/* /*
* ALR - Any Linux Repository * ALR - Any Linux Repository
* ALR - Любой Linux Репозиторий
* Copyright (C) 2024 Евгений Храмов * Copyright (C) 2024 Евгений Храмов
* *
* This program is free software: you can redistribute it and/or modify * This program является свободным: вы можете распространять его и/или изменять
* it under the terms of the GNU General Public License as published by * на условиях GNU General Public License, опубликованной Free Software Foundation,
* the Free Software Foundation, either version 3 of the License, or * либо версии 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 <http://www.gnu.org/licenses/>. * вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>.
*/ */
package manager package manager
@ -25,43 +25,49 @@ import (
"strings" "strings"
) )
// DNF represents the DNF package manager // DNF представляет менеджер пакетов DNF
type DNF struct { type DNF struct {
rootCmd string rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root
} }
// Exists проверяет, доступен ли DNF в системе, возвращает true если да
func (*DNF) Exists() bool { func (*DNF) Exists() bool {
_, err := exec.LookPath("dnf") _, err := exec.LookPath("dnf")
return err == nil return err == nil
} }
// Name возвращает имя менеджера пакетов, в данном случае "dnf"
func (*DNF) Name() string { func (*DNF) Name() string {
return "dnf" return "dnf"
} }
// Format возвращает формат пакетов "rpm", используемый DNF
func (*DNF) Format() string { func (*DNF) Format() string {
return "rpm" return "rpm"
} }
// SetRootCmd устанавливает команду, используемую для выполнения операций с правами root
func (d *DNF) SetRootCmd(s string) { func (d *DNF) SetRootCmd(s string) {
d.rootCmd = s d.rootCmd = s
} }
// Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий
func (d *DNF) Sync(opts *Opts) error { func (d *DNF) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения
cmd := d.getCmd(opts, "dnf", "upgrade") cmd := d.getCmd(opts, "dnf", "upgrade")
setCmdEnv(cmd) setCmdEnv(cmd) // Устанавливает переменные окружения для команды
err := cmd.Run() err := cmd.Run() // Выполняет команду
if err != nil { if err != nil {
return fmt.Errorf("dnf: sync: %w", err) return fmt.Errorf("dnf: sync: %w", err)
} }
return nil return nil
} }
// Install устанавливает указанные пакеты с помощью DNF
func (d *DNF) Install(opts *Opts, pkgs ...string) error { func (d *DNF) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "install", "--allowerasing") cmd := d.getCmd(opts, "dnf", "install", "--allowerasing")
cmd.Args = append(cmd.Args, pkgs...) cmd.Args = append(cmd.Args, pkgs...) // Добавляем названия пакетов к команде
setCmdEnv(cmd) setCmdEnv(cmd)
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
@ -70,11 +76,13 @@ func (d *DNF) Install(opts *Opts, pkgs ...string) error {
return nil return nil
} }
// InstallLocal расширяет метод Install для установки пакетов, расположенных локально
func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error { func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
return d.Install(opts, pkgs...) return d.Install(opts, pkgs...)
} }
// Remove удаляет указанные пакеты с помощью DNF
func (d *DNF) Remove(opts *Opts, pkgs ...string) error { func (d *DNF) Remove(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "remove") cmd := d.getCmd(opts, "dnf", "remove")
@ -87,6 +95,7 @@ func (d *DNF) Remove(opts *Opts, pkgs ...string) error {
return nil return nil
} }
// Upgrade обновляет указанные пакеты до более новых версий
func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error { func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "upgrade") cmd := d.getCmd(opts, "dnf", "upgrade")
@ -99,6 +108,7 @@ func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error {
return nil return nil
} }
// UpgradeAll обновляет все установленные пакеты
func (d *DNF) UpgradeAll(opts *Opts) error { func (d *DNF) UpgradeAll(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "upgrade") cmd := d.getCmd(opts, "dnf", "upgrade")
@ -110,6 +120,7 @@ func (d *DNF) UpgradeAll(opts *Opts) error {
return nil return nil
} }
// ListInstalled возвращает список установленных пакетов и их версий
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) { func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{} out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n") cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
@ -142,6 +153,7 @@ func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil return out, nil
} }
// 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
if opts.AsRoot { if opts.AsRoot {
@ -153,7 +165,7 @@ func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
} }
if opts.NoConfirm { if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y") cmd.Args = append(cmd.Args, "-y") // Добавляет параметр автоматического подтверждения (-y)
} }
return cmd return cmd