0
0
Fork 0
mirror of https://gitea.plemya-x.ru/Plemya-x/ALR.git synced 2025-01-10 17:26:45 +00:00
ALR/pkg/build/build.go

873 lines
32 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* ALR - Any Linux Repository
* 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 <http://www.gnu.org/licenses/>.
* Вы должны были получить копию GNU General Public License
* вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>.
*/
package build
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"time"
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"plemya-x.ru/alr/internal/cliutils"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/cpu"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/dl"
"plemya-x.ru/alr/internal/shutils/decoder"
"plemya-x.ru/alr/internal/shutils/handlers"
"plemya-x.ru/alr/internal/shutils/helpers"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/distro"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/manager"
"plemya-x.ru/alr/pkg/repos"
)
// Функция BuildPackage выполняет сборку скрипта по указанному пути. Возвращает два среза.
// Один содержит пути к собранным пакетам, другой - имена собранных пакетов.
func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) {
log := loggerctx.From(ctx)
info, err := distro.ParseOSRelease(ctx)
if err != nil {
return nil, nil, err
}
fl, err := parseScript(info, opts.Script)
if err != nil {
return nil, nil, err
}
// Первый проход предназначен для получения значений переменных и выполняется
// до отображения скрипта, чтобы предотвратить выполнение вредоносного кода.
vars, err := executeFirstPass(ctx, info, fl, opts.Script)
if err != nil {
return nil, nil, err
}
dirs := getDirs(ctx, vars, opts.Script)
// Если флаг opts.Clean не установлен, и пакет уже собран,
// возвращаем его, а не собираем заново.
if !opts.Clean {
builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir)
if err != nil {
return nil, nil, err
}
if ok {
return []string{builtPkgPath}, nil, err
}
}
// Спрашиваем у пользователя, хочет ли он увидеть скрипт сборки.
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()
}
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
// Второй проход будет использоваться для выполнения реального кода,
// поэтому он не ограничен. Скрипт уже был показан
// пользователю к этому моменту, так что это должно быть безопасно.
dec, err := executeSecondPass(ctx, info, fl, dirs)
if err != nil {
return nil, nil, err
}
// Получаем список установленных пакетов в системе
installed, err := opts.Manager.ListInstalled(nil)
if err != nil {
return nil, nil, err
}
cont, err := performChecks(ctx, vars, opts.Interactive, installed) // Выполняем различные проверки
if err != nil {
return nil, nil, err
} else if !cont {
os.Exit(1) // Если проверки не пройдены, выходим из программы
}
// Подготавливаем директории для сборки
err = prepareDirs(dirs)
if err != nil {
return nil, nil, err
}
buildDeps, err := installBuildDeps(ctx, vars, opts, installed) // Устанавливаем зависимости для сборки
if err != nil {
return nil, nil, err
}
err = installOptDeps(ctx, vars, opts, installed) // Устанавливаем опциональные зависимости
if err != nil {
return nil, nil, err
}
builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) // Собираем зависимости
if err != nil {
return nil, nil, err
}
log.Info("Downloading sources").Send() // Записываем в лог загрузку источников
err = getSources(ctx, dirs, vars) // Загружаем исходники
if err != nil {
return nil, nil, err
}
err = executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции
if err != nil {
return nil, nil, err
}
log.Info("Building package metadata").Str("name", vars.Name).Send() // Логгируем сборку метаданных пакета
pkgFormat := getPkgFormat(opts.Manager) // Получаем формат пакета
pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, info, append(repoDeps, builtNames...)) // Собираем метаданные пакета
if err != nil {
return nil, nil, err
}
packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
if err != nil {
return nil, nil, err
}
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
pkgFile, err := os.Create(pkgPath) // Создаём файл пакета
if err != nil {
return nil, nil, err
}
log.Info("Compressing package").Str("name", pkgName).Send() // Логгируем сжатие пакета
err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет
if err != nil {
return nil, nil, err
}
err = removeBuildDeps(ctx, buildDeps, opts) // Удаляем зависимости для сборки
if err != nil {
return nil, nil, err
}
// Добавляем путь и имя только что собранного пакета в
// соответствующие срезы
pkgPaths := append(builtPaths, pkgPath)
pkgNames := append(builtNames, vars.Name)
// Удаляем дубликаты из pkgPaths и pkgNames.
// Дубликаты могут появиться, если несколько зависимостей
// зависят от одних и тех же пакетов.
pkgPaths = removeDuplicates(pkgPaths)
pkgNames = removeDuplicates(pkgNames)
return pkgPaths, pkgNames, nil // Возвращаем пути и имена пакетов
}
// Функция parseScript анализирует скрипт сборки с использованием встроенной реализации bash
func parseScript(info *distro.OSRelease, script string) (*syntax.File, error) {
fl, err := os.Open(script) // Открываем файл скрипта
if err != nil {
return nil, err
}
defer fl.Close() // Закрываем файл после выполнения
file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора
if err != nil {
return nil, err
}
return file, nil // Возвращаем синтаксическое дерево
}
// Функция 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}) // Создаём переменные окружения для сборки
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)), // Ограничиваем открытие файлов
)
if err != nil {
return nil, err
}
err = runner.Run(ctx, fl) // Запускаем скрипт
if err != nil {
return nil, err
}
dec := decoder.New(info, runner) // Создаём новый декодер
var vars types.BuildVars
err = dec.DecodeVars(&vars) // Декодируем переменные
if err != nil {
return nil, err
}
return &vars, nil // Возвращаем переменные сборки
}
// Функция getDirs возвращает соответствующие директории для скрипта
func getDirs(ctx context.Context, vars *types.BuildVars, script string) types.Directories {
baseDir := filepath.Join(config.GetPaths(ctx).PkgsDir, vars.Name) // Определяем базовую директорию
return types.Directories{
BaseDir: baseDir,
SrcDir: filepath.Join(baseDir, "src"),
PkgDir: filepath.Join(baseDir, "pkg"),
ScriptDir: filepath.Dir(script),
}
}
// Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер,
// который может быть использован для получения функций и переменных из скрипта.
func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) {
env := createBuildEnvVars(info, dirs) // Создаём переменные окружения для сборки
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)), // Обрабатываем выполнение через fakeroot
)
if err != nil {
return nil, err
}
err = runner.Run(ctx, fl) // Запускаем скрипт
if err != nil {
return nil, err
}
return decoder.New(info, runner), nil // Возвращаем новый декодер
}
// Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
if err != nil {
return err
}
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
if err != nil {
return err
}
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
}
// Функция 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) { // Проверяем совместимость архитектуры
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
}
if !cont {
return false, nil
}
}
if instVer, ok := installed[vars.Name]; ok { // Если пакет уже установлен, выводим предупреждение
log.Warn("This package is already installed").
Str("name", vars.Name).
Str("version", instVer).
Send()
}
return true, nil
}
// Функция 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) // Находим пакеты-зависимости
if err != nil {
return nil, err
}
found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости
log.Info("Installing build dependencies").Send() // Логгируем установку зависимостей
flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) // Уплощаем список зависимостей
buildDeps = packageNames(flattened)
InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем пакеты
}
return buildDeps, nil
}
// Функция 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) // Пользователя просят выбрать опциональные зависимости
if err != nil {
return err
}
if len(optDeps) == 0 {
return nil
}
found, notFound, err := repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости
if err != nil {
return err
}
found = removeAlreadyInstalled(found, installed) // Убираем уже установленные зависимости
flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем выбранные пакеты
}
return nil
}
// Функция 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) // Поиск зависимостей
if err != nil {
return nil, nil, nil, err
}
repoDeps = notFound
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
pkgs := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
scripts := GetScriptPaths(ctx, pkgs)
for _, script := range scripts {
newOpts := opts
newOpts.Script = script
// Собираем зависимости
pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts)
if err != nil {
return nil, nil, nil, err
}
// Добавляем пути всех собранных пакетов в builtPaths
builtPaths = append(builtPaths, pkgPaths...)
// Добавляем пути всех собранных пакетов в builtPaths
builtNames = append(builtNames, pkgNames...)
// Добавляем имя текущего пакета в builtNames
builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
}
}
// Удаляем возможные дубликаты, которые могут быть введены, если
// несколько зависимостей зависят от одних и тех же пакетов.
repoDeps = removeDuplicates(repoDeps)
builtPaths = removeDuplicates(builtPaths)
builtNames = removeDuplicates(builtNames)
return builtPaths, builtNames, repoDeps, nil
}
// Функция 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")
if ok {
log.Info("Executing version()").Send()
buf := &bytes.Buffer{}
err = version(
ctx,
interp.Dir(dirs.SrcDir),
interp.StdIO(os.Stdin, buf, os.Stderr),
)
if err != nil {
return err
}
newVer := strings.TrimSpace(buf.String())
err = setVersion(ctx, dec.Runner, newVer)
if err != nil {
return err
}
vars.Version = newVer
log.Info("Updating version").Str("new", newVer).Send()
}
prepare, ok := dec.GetFunc("prepare")
if ok {
log.Info("Executing prepare()").Send()
err = prepare(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
build, ok := dec.GetFunc("build")
if ok {
log.Info("Executing build()").Send()
err = build(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
// Выполнение всех функций, начинающихся с 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
}
}
// Проверка на наличие дополнительных функций 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 создает метаданные для пакета, который будет собран.
func buildPkgMetadata(ctx context.Context, vars *types.BuildVars, dirs types.Directories, pkgFormat string, info *distro.OSRelease, deps []string) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars)
pkgInfo.Description = vars.Description
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: vars.Conflicts,
Replaces: vars.Replaces,
Provides: vars.Provides,
Depends: deps,
}
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if pkgFormat == "rpm" && info.ID == "altlinux" {
pkgInfo.Release = "alt" + pkgInfo.Release
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs)
if err != nil {
return nil, err
}
pkgInfo.Overridables.Contents = contents
if pkgFormat == "rpm" {
err = rpmFindProvides(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
err = rpmFindRequires(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
}
return pkgInfo, nil
}
// Функция 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 {
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
if fi.IsDir() {
f, err := os.Open(path)
if err != nil {
return err
}
// Если директория пустая, пропускаем её
_, err = f.Readdirnames(1)
if err != io.EOF {
return nil
}
contents = append(contents, &files.Content{
Source: path,
Destination: trimmed,
Type: "dir",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
},
})
return f.Close()
}
// Если файл является символической ссылкой, прорабатываем это
if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path)
if err != nil {
return err
}
// Удаляем pkgdir из пути символической ссылки
link = strings.TrimPrefix(link, dirs.PkgDir)
contents = append(contents, &files.Content{
Source: link,
Destination: trimmed,
Type: "symlink",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
},
})
return nil
}
// Обрабатываем обычные файлы
fileContent := &files.Content{
Source: path,
Destination: trimmed,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}
// Если файл должен быть сохранен, установите его тип как config|noreplace
if slices.Contains(vars.Backup, trimmed) {
fileContent.Type = "config|noreplace"
}
contents = append(contents, fileContent)
return nil
})
return contents, err
}
// Функция 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)
if err != nil {
return err
}
if remove {
err = opts.Manager.Remove(
&manager.Opts{
AsRoot: true,
NoConfirm: true,
},
buildDeps...,
)
if err != nil {
return err
}
}
}
return 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 {
return "", false, err
}
pkgPath := filepath.Join(baseDir, filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func getBasePkgInfo(vars *types.BuildVars) *nfpm.Info {
return &nfpm.Info{
Name: vars.Name,
Arch: cpu.Arch(),
Version: vars.Version,
Release: strconv.Itoa(vars.Release),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// 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.
func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) {
pkgInfo := getBasePkgInfo(vars)
packager, err := nfpm.Get(pkgFormat)
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
func getPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format
}
return pkgFormat
}
// Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// в скрипте сборки при его выполнении.
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
env := os.Environ()
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+cpu.Arch(),
"NCPU="+strconv.Itoa(runtime.NumCPU()),
)
if dirs.ScriptDir != "" {
env = append(env, "scriptdir="+dirs.ScriptDir)
}
if dirs.PkgDir != "" {
env = append(env, "pkgdir="+dirs.PkgDir)
}
if dirs.SrcDir != "" {
env = append(env, "srcdir="+dirs.SrcDir)
}
return env
}
// Функция getSources загружает исходники скрипта.
func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
log := loggerctx.From(ctx)
if len(bv.Sources) != len(bv.Checksums) {
log.Fatal("The checksums array must be the same length as sources").Send()
}
for i, src := range bv.Sources {
opts := dl.Options{
Name: fmt.Sprintf("%s[%d]", bv.Name, i),
URL: src,
Destination: dirs.SrcDir,
Progress: os.Stderr,
LocalDir: dirs.ScriptDir,
}
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия
// как алгоритм, а часть после как фактическую контрольную сумму.
// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой.
algo, hashData, ok := strings.Cut(bv.Checksums[i], ":")
if ok {
checksum, err := hex.DecodeString(hashData)
if err != nil {
return err
}
opts.Hash = checksum
opts.HashAlgorithm = algo
} else {
checksum, err := hex.DecodeString(bv.Checksums[i])
if err != nil {
return err
}
opts.Hash = checksum
}
}
err := dl.Download(ctx, opts)
if err != nil {
return err
}
}
return nil
}
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
if vars.Scripts.PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
}
if vars.Scripts.PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
}
if vars.Scripts.PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
}
if vars.Scripts.PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
}
if vars.Scripts.PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
}
if vars.Scripts.PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
}
if vars.Scripts.PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
}
if vars.Scripts.PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
}
}
// Функция 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 {
return err
}
return r.Run(ctx, fl)
}
// Функция removeAlreadyInstalled возвращает карту без каких-либо зависимостей, которые уже установлены.
func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
filteredPackages := make(map[string][]db.Package)
for name, pkgList := range found {
filteredPkgList := []db.Package{}
for _, pkg := range pkgList {
if _, isInstalled := installed[pkg.Name]; !isInstalled {
filteredPkgList = append(filteredPkgList, pkg)
}
}
filteredPackages[name] = filteredPkgList
}
return filteredPackages
}
// Функция packageNames возвращает имена всех предоставленных пакетов.
func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs))
for i, p := range pkgs {
names[i] = p.Name
}
return names
}
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string {
seen := map[string]struct{}{}
result := []string{}
for _, s := range slice {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
}
}
return result
}