2025-01-18 16:30:02 +00:00
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2024-01-22 10:36:06 +00:00
package build
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
2025-01-22 13:37:16 +00:00
"log/slog"
2024-01-22 10:36:06 +00:00
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"time"
2024-12-19 16:21:41 +00:00
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
2025-01-25 06:39:33 +00:00
2024-01-22 10:36:06 +00:00
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
2025-01-22 13:37:16 +00:00
"github.com/leonelquinteros/gotext"
2024-11-15 13:25:09 +00:00
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
2025-01-25 06:39:33 +00:00
"mvdan.cc/sh/v3/shell"
2024-11-15 13:25:09 +00:00
"mvdan.cc/sh/v3/syntax"
2024-01-22 10:36:06 +00:00
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
2025-01-18 16:30:02 +00:00
2025-01-20 16:58:24 +00:00
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
2025-01-25 08:16:33 +00:00
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
2025-01-20 16:58:24 +00:00
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
2024-01-22 10:36:06 +00:00
)
2024-11-16 08:32:47 +00:00
// Функция BuildPackage выполняет сборку скрипта по указанному пути. Возвращает два среза.
// Один содержит пути к собранным пакетам, другой - имена собранных пакетов.
2024-01-22 10:36:06 +00:00
func BuildPackage ( ctx context . Context , opts types . BuildOpts ) ( [ ] string , [ ] string , error ) {
2025-01-19 08:49:00 +00:00
reposInstance := repos . GetInstance ( ctx )
2024-01-22 10:36:06 +00:00
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
}
2024-12-19 16:21:41 +00:00
// Первый проход предназначен для получения значений переменных и выполняется
// до отображения скрипта, чтобы предотвратить выполнение вредоносного кода.
2024-01-22 10:36:06 +00:00
vars , err := executeFirstPass ( ctx , info , fl , opts . Script )
if err != nil {
return nil , nil , err
}
dirs := getDirs ( ctx , vars , opts . Script )
2024-12-19 16:21:41 +00:00
// Если флаг opts.Clean не установлен, и пакет уже собран,
// возвращаем е г о , а не собираем заново.
2024-01-22 10:36:06 +00:00
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
}
}
2024-12-19 16:21:41 +00:00
// Спрашиваем у пользователя, хочет ли он увидеть скрипт сборки.
2024-01-22 10:36:06 +00:00
err = cliutils . PromptViewScript ( ctx , opts . Script , vars . Name , config . Config ( ctx ) . PagerStyle , opts . Interactive )
if err != nil {
2025-01-22 13:37:16 +00:00
slog . Error ( gotext . Get ( "Failed to prompt user to view build script" ) , "err" , err )
os . Exit ( 1 )
2024-01-22 10:36:06 +00:00
}
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Building package" ) , "name" , vars . Name , "version" , vars . Version )
2024-01-22 10:36:06 +00:00
2024-12-19 16:21:41 +00:00
// Второй проход будет использоваться для выполнения реального кода,
// поэтому он не ограничен. Скрипт уже был показан
// пользователю к этому моменту, так что это должно быть безопасно.
2024-01-22 10:36:06 +00:00
dec , err := executeSecondPass ( ctx , info , fl , dirs )
if err != nil {
return nil , nil , err
}
2024-11-16 08:32:47 +00:00
// Получаем список установленных пакетов в системе
2024-01-22 10:36:06 +00:00
installed , err := opts . Manager . ListInstalled ( nil )
if err != nil {
return nil , nil , err
}
2024-11-16 08:32:47 +00:00
cont , err := performChecks ( ctx , vars , opts . Interactive , installed ) // Выполняем различные проверки
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
} else if ! cont {
2024-11-16 08:32:47 +00:00
os . Exit ( 1 ) // Если проверки не пройдены, выходим из программы
2024-01-22 10:36:06 +00:00
}
2024-12-19 16:21:41 +00:00
// Подготавливаем директории для сборки
2024-01-22 10:36:06 +00:00
err = prepareDirs ( dirs )
if err != nil {
return nil , nil , err
}
2025-01-19 08:49:00 +00:00
buildDeps , err := installBuildDeps ( ctx , reposInstance , vars , opts ) // Устанавливаем зависимости для сборки
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2025-01-19 08:49:00 +00:00
err = installOptDeps ( ctx , reposInstance , vars , opts ) // Устанавливаем опциональные зависимости
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2024-11-16 08:32:47 +00:00
builtPaths , builtNames , repoDeps , err := buildALRDeps ( ctx , opts , vars ) // Собираем зависимости
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Downloading sources" ) ) // Записываем в лог загрузку источников
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
err = getSources ( ctx , dirs , vars ) // Загружаем исходники
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2025-01-24 16:23:41 +00:00
funcOut , err := executeFunctions ( ctx , dec , dirs , vars ) // Выполняем специальные функции
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Building package metadata" ) , "name" , vars . Name )
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
pkgFormat := getPkgFormat ( opts . Manager ) // Получаем формат пакета
2024-01-22 10:36:06 +00:00
2025-01-24 16:23:41 +00:00
pkgInfo , err := buildPkgMetadata ( ctx , vars , dirs , pkgFormat , info , append ( repoDeps , builtNames ... ) , funcOut . Contents ) // Собираем метаданные пакета
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2024-11-16 08:32:47 +00:00
packager , err := nfpm . Get ( pkgFormat ) // Получаем упаковщик для формата пакета
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2024-11-16 08:32:47 +00:00
pkgName := packager . ConventionalFileName ( pkgInfo ) // Получаем имя файла пакета
2024-12-19 16:21:41 +00:00
pkgPath := filepath . Join ( dirs . BaseDir , pkgName ) // Определяем путь к пакету
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
pkgFile , err := os . Create ( pkgPath ) // Создаём файл пакета
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Compressing package" ) , "name" , pkgName ) // Логгируем сжатие пакета
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
err = packager . Package ( pkgInfo , pkgFile ) // Упаковываем пакет
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2024-11-16 08:32:47 +00:00
err = removeBuildDeps ( ctx , buildDeps , opts ) // Удаляем зависимости для сборки
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , err
}
2024-12-19 16:21:41 +00:00
// Добавляем путь и имя только что собранного пакета в
// соответствующие срезы
2024-01-22 10:36:06 +00:00
pkgPaths := append ( builtPaths , pkgPath )
pkgNames := append ( builtNames , vars . Name )
2024-12-19 16:21:41 +00:00
// Удаляем дубликаты из pkgPaths и pkgNames.
// Дубликаты могут появиться, если несколько зависимостей
// зависят от одних и тех же пакетов.
2024-01-22 10:36:06 +00:00
pkgPaths = removeDuplicates ( pkgPaths )
pkgNames = removeDuplicates ( pkgNames )
2024-11-16 08:32:47 +00:00
return pkgPaths , pkgNames , nil // Возвращаем пути и имена пакетов
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция parseScript анализирует скрипт сборки с использованием встроенной реализации bash
2024-01-22 10:36:06 +00:00
func parseScript ( info * distro . OSRelease , script string ) ( * syntax . File , error ) {
2024-11-16 08:32:47 +00:00
fl , err := os . Open ( script ) // Открываем файл скрипта
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
defer fl . Close ( ) // Закрываем файл после выполнения
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
file , err := syntax . NewParser ( ) . Parse ( fl , "alr.sh" ) // Парсим скрипт с помощью синтаксического анализатора
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
return file , nil // Возвращаем синтаксическое дерево
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция executeFirstPass выполняет парсированный скрипт в ограниченной среде,
// чтобы извлечь переменные сборки без выполнения реального кода.
2024-01-22 10:36:06 +00:00
func executeFirstPass ( ctx context . Context , info * distro . OSRelease , fl * syntax . File , script string ) ( * types . BuildVars , error ) {
2024-12-19 16:21:41 +00:00
scriptDir := filepath . Dir ( script ) // Получаем директорию скрипта
2024-11-16 08:32:47 +00:00
env := createBuildEnvVars ( info , types . Directories { ScriptDir : scriptDir } ) // Создаём переменные окружения для сборки
2024-01-22 10:36:06 +00:00
runner , err := interp . New (
2024-12-19 16:21:41 +00:00
interp . Env ( expand . ListEnviron ( env ... ) ) , // Устанавливаем окружение
interp . StdIO ( os . Stdin , os . Stdout , os . Stderr ) , // Устанавливаем стандартный ввод-вывод
2024-11-16 08:32:47 +00:00
interp . ExecHandler ( helpers . Restricted . ExecHandler ( handlers . NopExec ) ) , // Ограничиваем выполнение
2024-12-19 16:21:41 +00:00
interp . ReadDirHandler ( handlers . RestrictedReadDir ( scriptDir ) ) , // Ограничиваем чтение директорий
interp . StatHandler ( handlers . RestrictedStat ( scriptDir ) ) , // Ограничиваем доступ к статистике файлов
interp . OpenHandler ( handlers . RestrictedOpen ( scriptDir ) ) , // Ограничиваем открытие файлов
2024-01-22 10:36:06 +00:00
)
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
err = runner . Run ( ctx , fl ) // Запускаем скрипт
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
dec := decoder . New ( info , runner ) // Создаём новый декодер
2024-01-22 10:36:06 +00:00
var vars types . BuildVars
2024-11-16 08:32:47 +00:00
err = dec . DecodeVars ( & vars ) // Декодируем переменные
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
return & vars , nil // Возвращаем переменные сборки
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция getDirs возвращает соответствующие директории для скрипта
2024-01-22 10:36:06 +00:00
func getDirs ( ctx context . Context , vars * types . BuildVars , script string ) types . Directories {
2024-11-16 08:32:47 +00:00
baseDir := filepath . Join ( config . GetPaths ( ctx ) . PkgsDir , vars . Name ) // Определяем базовую директорию
2024-01-22 10:36:06 +00:00
return types . Directories {
BaseDir : baseDir ,
SrcDir : filepath . Join ( baseDir , "src" ) ,
PkgDir : filepath . Join ( baseDir , "pkg" ) ,
ScriptDir : filepath . Dir ( script ) ,
}
}
2024-11-16 08:32:47 +00:00
// Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер,
// который может быть использован для получения функций и переменных из скрипта.
2024-01-22 10:36:06 +00:00
func executeSecondPass ( ctx context . Context , info * distro . OSRelease , fl * syntax . File , dirs types . Directories ) ( * decoder . Decoder , error ) {
2024-11-16 08:32:47 +00:00
env := createBuildEnvVars ( info , dirs ) // Создаём переменные окружения для сборки
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
fakeroot := handlers . FakerootExecHandler ( 2 * time . Second ) // Настраиваем "fakeroot" для выполнения
2024-01-22 10:36:06 +00:00
runner , err := interp . New (
2024-12-19 16:21:41 +00:00
interp . Env ( expand . ListEnviron ( env ... ) ) , // Устанавливаем окружение
interp . StdIO ( os . Stdin , os . Stdout , os . Stderr ) , // Устанавливаем стандартный ввод-вывод
2024-11-16 08:32:47 +00:00
interp . ExecHandler ( helpers . Helpers . ExecHandler ( fakeroot ) ) , // Обрабатываем выполнение через fakeroot
2024-01-22 10:36:06 +00:00
)
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
err = runner . Run ( ctx , fl ) // Запускаем скрипт
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
2024-11-16 08:32:47 +00:00
return decoder . New ( info , runner ) , nil // Возвращаем новый декодер
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция prepareDirs подготавливает директории для сборки.
2024-01-22 10:36:06 +00:00
func prepareDirs ( dirs types . Directories ) error {
2024-11-16 08:32:47 +00:00
err := os . RemoveAll ( dirs . BaseDir ) // Удаляем базовую директорию, если она существует
2024-01-22 10:36:06 +00:00
if err != nil {
return err
}
2024-11-16 08:32:47 +00:00
err = os . MkdirAll ( dirs . SrcDir , 0 o755 ) // Создаем директорию для источников
2024-01-22 10:36:06 +00:00
if err != nil {
return err
}
2024-11-16 08:32:47 +00:00
return os . MkdirAll ( dirs . PkgDir , 0 o755 ) // Создаем директорию для пакетов
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция performChecks проверяет различные аспекты в системе, чтобы убедиться, что пакет может быть установлен.
2024-01-22 10:36:06 +00:00
func performChecks ( ctx context . Context , vars * types . BuildVars , interactive bool , installed map [ string ] string ) ( bool , error ) {
2024-11-16 08:32:47 +00:00
if ! cpu . IsCompatibleWith ( cpu . Arch ( ) , vars . Architectures ) { // Проверяем совместимость архитектуры
2025-01-22 13:53:30 +00:00
cont , err := cliutils . YesNoPrompt ( ctx , gotext . Get ( "Your system's CPU architecture doesn't match this package. Do you want to build anyway?" ) , interactive , true )
2024-01-22 10:36:06 +00:00
if err != nil {
return false , err
}
if ! cont {
return false , nil
}
}
2024-11-16 08:32:47 +00:00
if instVer , ok := installed [ vars . Name ] ; ok { // Если пакет уже установлен, выводим предупреждение
2025-01-22 13:37:16 +00:00
slog . Warn ( gotext . Get ( "This package is already installed" ) ,
"name" , vars . Name ,
"version" , instVer ,
)
2024-01-22 10:36:06 +00:00
}
return true , nil
}
2025-01-19 08:49:00 +00:00
type PackageFinder interface {
FindPkgs ( ctx context . Context , pkgs [ ] string ) ( map [ string ] [ ] db . Package , [ ] string , error )
}
2024-11-16 08:32:47 +00:00
// Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает
// срез, содержащий имена всех установленных пакетов.
2025-01-19 08:49:00 +00:00
func installBuildDeps ( ctx context . Context , repos PackageFinder , vars * types . BuildVars , opts types . BuildOpts ) ( [ ] string , error ) {
2024-01-22 10:36:06 +00:00
var buildDeps [ ] string
if len ( vars . BuildDepends ) > 0 {
2025-01-19 08:49:00 +00:00
deps , err := removeAlreadyInstalled ( opts , vars . BuildDepends )
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
2025-01-19 08:49:00 +00:00
found , notFound , err := repos . FindPkgs ( ctx , deps ) // Находим пакеты-зависимости
if err != nil {
return nil , err
}
2024-01-22 10:36:06 +00:00
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Installing build dependencies" ) ) // Логгируем установку зависимостей
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
flattened := cliutils . FlattenPkgs ( ctx , found , "install" , opts . Interactive ) // Уплощаем список зависимостей
2024-01-22 10:36:06 +00:00
buildDeps = packageNames ( flattened )
2024-11-16 08:32:47 +00:00
InstallPkgs ( ctx , flattened , notFound , opts ) // Устанавливаем пакеты
2024-01-22 10:36:06 +00:00
}
return buildDeps , nil
}
2024-11-16 08:32:47 +00:00
// Функция installOptDeps спрашивает у пользователя, какие, если таковые имеются, опциональные зависимости он хочет установить.
// Если пользователь решает установить какие-либо опциональные зависимости, выполняется их установка.
2025-01-19 08:49:00 +00:00
func installOptDeps ( ctx context . Context , repos PackageFinder , vars * types . BuildVars , opts types . BuildOpts ) error {
optDeps , err := removeAlreadyInstalled ( opts , vars . OptDepends )
if err != nil {
return err
}
if len ( optDeps ) > 0 {
optDeps , err := cliutils . ChooseOptDepends ( ctx , optDeps , "install" , opts . Interactive ) // Пользователя просят выбрать опциональные зависимости
2024-01-22 10:36:06 +00:00
if err != nil {
return err
}
if len ( optDeps ) == 0 {
return nil
}
2024-11-16 08:32:47 +00:00
found , notFound , err := repos . FindPkgs ( ctx , optDeps ) // Находим опциональные зависимости
2024-01-22 10:36:06 +00:00
if err != nil {
return err
}
flattened := cliutils . FlattenPkgs ( ctx , found , "install" , opts . Interactive )
2024-11-16 08:32:47 +00:00
InstallPkgs ( ctx , flattened , notFound , opts ) // Устанавливаем выбранные пакеты
2024-01-22 10:36:06 +00:00
}
return nil
}
2024-11-16 08:32:47 +00:00
// Функция buildALRDeps собирает все ALR зависимости пакета. Возвращает пути и имена
// пакетов, которые она собрала, а также все зависимости, которые не были найдены в ALR репозитории,
// чтобы они могли быть установлены из системных репозиториев.
2024-05-05 10:32:08 +00:00
func buildALRDeps ( ctx context . Context , opts types . BuildOpts , vars * types . BuildVars ) ( builtPaths , builtNames , repoDeps [ ] string , err error ) {
2024-01-22 10:36:06 +00:00
if len ( vars . Depends ) > 0 {
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Installing dependencies" ) )
2024-01-22 10:36:06 +00:00
2024-11-16 08:32:47 +00:00
found , notFound , err := repos . FindPkgs ( ctx , vars . Depends ) // Поиск зависимостей
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , nil , nil , err
}
repoDeps = notFound
2024-12-19 16:21:41 +00:00
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
2024-01-22 10:36:06 +00:00
pkgs := cliutils . FlattenPkgs ( ctx , found , "install" , opts . Interactive )
scripts := GetScriptPaths ( ctx , pkgs )
for _ , script := range scripts {
newOpts := opts
newOpts . Script = script
2024-12-19 16:21:41 +00:00
// Собираем зависимости
2024-01-22 10:36:06 +00:00
pkgPaths , pkgNames , err := BuildPackage ( ctx , newOpts )
if err != nil {
return nil , nil , nil , err
}
2024-12-19 16:21:41 +00:00
// Добавляем пути всех собранных пакетов в builtPaths
2024-01-22 10:36:06 +00:00
builtPaths = append ( builtPaths , pkgPaths ... )
2024-12-19 16:21:41 +00:00
// Добавляем пути всех собранных пакетов в builtPaths
2024-01-22 10:36:06 +00:00
builtNames = append ( builtNames , pkgNames ... )
2024-12-19 16:21:41 +00:00
// Добавляем имя текущего пакета в builtNames
2024-01-22 10:36:06 +00:00
builtNames = append ( builtNames , filepath . Base ( filepath . Dir ( script ) ) )
}
}
2024-12-19 16:21:41 +00:00
// Удаляем возможные дубликаты, которые могут быть введены, если
// несколько зависимостей зависят от одних и тех же пакетов.
2024-01-22 10:36:06 +00:00
repoDeps = removeDuplicates ( repoDeps )
builtPaths = removeDuplicates ( builtPaths )
builtNames = removeDuplicates ( builtNames )
return builtPaths , builtNames , repoDeps , nil
}
2025-01-24 16:23:41 +00:00
type FunctionsOutput struct {
Contents * [ ] string
}
2024-11-16 08:32:47 +00:00
// Функция executeFunctions выполняет специальные функции ALR, такие как version(), prepare() и т.д.
2025-01-24 16:23:41 +00:00
func executeFunctions ( ctx context . Context , dec * decoder . Decoder , dirs types . Directories , vars * types . BuildVars ) ( * FunctionsOutput , error ) {
2024-01-22 10:36:06 +00:00
version , ok := dec . GetFunc ( "version" )
if ok {
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Executing version()" ) )
2024-01-22 10:36:06 +00:00
buf := & bytes . Buffer { }
2025-01-24 16:23:41 +00:00
err := version (
2024-01-22 10:36:06 +00:00
ctx ,
interp . Dir ( dirs . SrcDir ) ,
interp . StdIO ( os . Stdin , buf , os . Stderr ) ,
)
if err != nil {
2025-01-24 16:23:41 +00:00
return nil , err
2024-01-22 10:36:06 +00:00
}
newVer := strings . TrimSpace ( buf . String ( ) )
err = setVersion ( ctx , dec . Runner , newVer )
if err != nil {
2025-01-24 16:23:41 +00:00
return nil , err
2024-01-22 10:36:06 +00:00
}
vars . Version = newVer
2025-01-24 16:23:41 +00:00
slog . Info ( gotext . Get ( "Updating version" ) , "new" , newVer )
2024-01-22 10:36:06 +00:00
}
prepare , ok := dec . GetFunc ( "prepare" )
if ok {
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Executing prepare()" ) )
2024-01-22 10:36:06 +00:00
2025-01-24 16:23:41 +00:00
err := prepare ( ctx , interp . Dir ( dirs . SrcDir ) )
2024-01-22 10:36:06 +00:00
if err != nil {
2025-01-24 16:23:41 +00:00
return nil , err
2024-01-22 10:36:06 +00:00
}
}
build , ok := dec . GetFunc ( "build" )
if ok {
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Executing build()" ) )
2024-01-22 10:36:06 +00:00
2025-01-24 16:23:41 +00:00
err := build ( ctx , interp . Dir ( dirs . SrcDir ) )
2024-01-22 10:36:06 +00:00
if err != nil {
2025-01-24 16:23:41 +00:00
return nil , err
2024-01-22 10:36:06 +00:00
}
}
2024-12-19 16:21:41 +00:00
// Выполнение всех функций, начинающихся с package_
for {
packageFn , ok := dec . GetFunc ( "package" )
if ok {
2025-01-22 13:37:16 +00:00
slog . Info ( gotext . Get ( "Executing package()" ) )
2025-01-24 16:23:41 +00:00
err := packageFn ( ctx , interp . Dir ( dirs . SrcDir ) )
2024-12-19 16:21:41 +00:00
if err != nil {
2025-01-24 16:23:41 +00:00
return nil , err
2024-12-19 16:21:41 +00:00
}
}
2025-01-22 13:37:16 +00:00
/ *
// Проверка на наличие дополнительных функций package_*
packageFuncName := "package_"
if packageFunc , ok := dec . GetFunc ( packageFuncName ) ; ok {
slog . Info ( "Executing " + packageFuncName )
err = packageFunc ( ctx , interp . Dir ( dirs . SrcDir ) )
if err != nil {
return err
}
} else {
break // Если больше нет функций package_*, выходим из цикла
2024-12-19 16:21:41 +00:00
}
2025-01-22 13:37:16 +00:00
* /
break
2024-12-19 16:21:41 +00:00
}
2024-01-22 10:36:06 +00:00
2025-01-24 16:23:41 +00:00
output := & FunctionsOutput { }
files , ok := dec . GetFuncP ( "files" , func ( ctx context . Context , s * interp . Runner ) error {
// It should be done via interp.RunnerOption,
// but due to the issues below, it cannot be done.
// - https://github.com/mvdan/sh/issues/962
// - https://github.com/mvdan/sh/issues/1125
script , err := syntax . NewParser ( ) . Parse ( strings . NewReader ( "cd $pkgdir && shopt -s globstar" ) , "" )
if err != nil {
return err
}
return s . Run ( ctx , script )
} )
if ok {
slog . Info ( gotext . Get ( "Executing files()" ) )
buf := & bytes . Buffer { }
err := files (
ctx ,
interp . Dir ( dirs . PkgDir ) ,
interp . StdIO ( os . Stdin , buf , os . Stderr ) ,
)
if err != nil {
return nil , err
}
2025-01-25 06:39:33 +00:00
contents , err := shell . Fields ( buf . String ( ) , func ( s string ) string { return "" } )
if err != nil {
return nil , err
}
2025-01-24 16:23:41 +00:00
output . Contents = & contents
}
return output , nil
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
2025-01-24 16:23:41 +00:00
func buildPkgMetadata (
ctx context . Context ,
vars * types . BuildVars ,
dirs types . Directories ,
pkgFormat string ,
info * distro . OSRelease ,
deps [ ] string ,
preferedContents * [ ] string ,
) ( * nfpm . Info , error ) {
2024-11-15 13:25:09 +00:00
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 ,
2024-01-22 10:36:06 +00:00
}
if pkgFormat == "apk" {
2024-12-19 16:21:41 +00:00
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
2024-01-22 10:36:06 +00:00
pkgInfo . Overridables . Provides = slices . DeleteFunc ( pkgInfo . Overridables . Provides , func ( s string ) bool {
return s == pkgInfo . Name
} )
}
2025-01-25 08:16:33 +00:00
pkgInfo . Release = overrides . ReleasePlatformSpecific ( vars . Release , info )
2024-11-15 13:25:09 +00:00
2024-01-22 10:36:06 +00:00
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"
}
2025-01-24 16:23:41 +00:00
contents , err := buildContents ( vars , dirs , preferedContents )
2024-01-22 10:36:06 +00:00
if err != nil {
return nil , err
}
pkgInfo . Overridables . Contents = contents
2024-12-27 18:15:37 +00:00
if len ( vars . AutoProv ) == 1 && decoder . IsTruthy ( vars . AutoProv [ 0 ] ) {
if pkgFormat == "rpm" {
err = rpmFindProvides ( ctx , pkgInfo , dirs )
if err != nil {
return nil , err
}
} else {
2025-01-24 18:20:45 +00:00
slog . Info ( gotext . Get ( "AutoProv is not implemented for this package format, so it's skipped" ) )
2024-12-19 16:21:41 +00:00
}
2024-12-27 18:15:37 +00:00
}
if len ( vars . AutoReq ) == 1 && decoder . IsTruthy ( vars . AutoReq [ 0 ] ) {
if pkgFormat == "rpm" {
err = rpmFindRequires ( ctx , pkgInfo , dirs )
if err != nil {
return nil , err
}
} else {
2025-01-24 18:20:45 +00:00
slog . Info ( gotext . Get ( "AutoReq is not implemented for this package format, so it's skipped" ) )
2024-12-19 16:21:41 +00:00
}
}
2024-01-22 10:36:06 +00:00
return pkgInfo , nil
}
2024-11-16 08:32:47 +00:00
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет.
2025-01-24 16:23:41 +00:00
func buildContents ( vars * types . BuildVars , dirs types . Directories , preferedContents * [ ] string ) ( [ ] * files . Content , error ) {
2024-01-22 10:36:06 +00:00
contents := [ ] * files . Content { }
2025-01-24 16:23:41 +00:00
processPath := func ( path , trimmed string , prefered bool ) error {
fi , err := os . Lstat ( path )
if err != nil {
return err
}
2024-01-22 10:36:06 +00:00
if fi . IsDir ( ) {
f , err := os . Open ( path )
if err != nil {
return err
}
2025-01-24 16:23:41 +00:00
defer f . Close ( )
2024-01-22 10:36:06 +00:00
2025-01-24 16:23:41 +00:00
if ! prefered {
_ , err = f . Readdirnames ( 1 )
if err != io . EOF {
return nil
}
2024-01-22 10:36:06 +00:00
}
contents = append ( contents , & files . Content {
Source : path ,
Destination : trimmed ,
Type : "dir" ,
FileInfo : & files . ContentFileInfo {
MTime : fi . ModTime ( ) ,
} ,
} )
2025-01-24 16:23:41 +00:00
return nil
2024-01-22 10:36:06 +00:00
}
2025-01-24 16:23:41 +00:00
2024-01-22 10:36:06 +00:00
if fi . Mode ( ) & os . ModeSymlink != 0 {
link , err := os . Readlink ( path )
if err != nil {
return err
}
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
}
2025-01-24 16:23:41 +00:00
2024-01-22 10:36:06 +00:00
fileContent := & files . Content {
Source : path ,
Destination : trimmed ,
FileInfo : & files . ContentFileInfo {
MTime : fi . ModTime ( ) ,
Mode : fi . Mode ( ) ,
Size : fi . Size ( ) ,
} ,
}
if slices . Contains ( vars . Backup , trimmed ) {
fileContent . Type = "config|noreplace"
}
contents = append ( contents , fileContent )
return nil
2025-01-24 16:23:41 +00:00
}
if preferedContents != nil {
for _ , trimmed := range * preferedContents {
path := filepath . Join ( dirs . PkgDir , trimmed )
if err := processPath ( path , trimmed , true ) ; err != nil {
return nil , err
}
}
} else {
err := filepath . Walk ( dirs . PkgDir , func ( path string , fi os . FileInfo , err error ) error {
if err != nil {
return err
}
trimmed := strings . TrimPrefix ( path , dirs . PkgDir )
return processPath ( path , trimmed , false )
} )
if err != nil {
return nil , err
}
}
return contents , nil
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости,
// установленные для сборки. Если да, использует менеджер пакетов для их удаления.
2024-01-22 10:36:06 +00:00
func removeBuildDeps ( ctx context . Context , buildDeps [ ] string , opts types . BuildOpts ) error {
if len ( buildDeps ) > 0 {
2025-01-22 13:53:30 +00:00
remove , err := cliutils . YesNoPrompt ( ctx , gotext . Get ( "Would you like to remove the build dependencies?" ) , opts . Interactive , false )
2024-01-22 10:36:06 +00:00
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
}
2024-11-16 08:32:47 +00:00
// Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть е г о путь
// и true, если нашла. Если нет, возвратит "", false, nil.
2024-01-22 10:36:06 +00:00
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
}
2024-11-15 13:25:09 +00:00
func getBasePkgInfo ( vars * types . BuildVars ) * nfpm . Info {
return & nfpm . Info {
2024-01-22 10:36:06 +00:00
Name : vars . Name ,
Arch : cpu . Arch ( ) ,
Version : vars . Version ,
Release : strconv . Itoa ( vars . Release ) ,
Epoch : strconv . FormatUint ( uint64 ( vars . Epoch ) , 10 ) ,
}
2024-11-15 13:25:09 +00:00
}
// 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 )
2024-01-22 10:36:06 +00:00
packager , err := nfpm . Get ( pkgFormat )
if err != nil {
return "" , err
}
return packager . ConventionalFileName ( pkgInfo ) , nil
}
2024-11-16 08:32:47 +00:00
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
2024-01-22 10:36:06 +00:00
func getPkgFormat ( mgr manager . Manager ) string {
pkgFormat := mgr . Format ( )
2024-05-05 10:32:08 +00:00
if format , ok := os . LookupEnv ( "ALR_PKG_FORMAT" ) ; ok {
2024-01-22 10:36:06 +00:00
pkgFormat = format
}
return pkgFormat
}
2024-11-16 08:32:47 +00:00
// Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// в скрипте сборки при е г о выполнении.
2024-01-22 10:36:06 +00:00
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
}
2024-11-16 08:32:47 +00:00
// Функция getSources загружает исходники скрипта.
2024-01-22 10:36:06 +00:00
func getSources ( ctx context . Context , dirs types . Directories , bv * types . BuildVars ) error {
if len ( bv . Sources ) != len ( bv . Checksums ) {
2025-01-22 13:37:16 +00:00
slog . Error ( gotext . Get ( "The checksums array must be the same length as sources" ) )
os . Exit ( 1 )
2024-01-22 10:36:06 +00:00
}
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" ) {
2024-12-19 16:21:41 +00:00
// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия
// как алгоритм, а часть после как фактическую контрольную сумму.
// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой.
2024-01-22 10:36:06 +00:00
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
}
2024-11-16 08:32:47 +00:00
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
2024-01-22 10:36:06 +00:00
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 )
}
}
2024-11-16 08:32:47 +00:00
// Функция setVersion изменяет переменную версии в скрипте runner.
// Она используется для установки версии на вывод функции version().
2024-01-22 10:36:06 +00:00
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 )
}
2025-01-19 08:49:00 +00:00
// Returns not installed dependencies
func removeAlreadyInstalled ( opts types . BuildOpts , dependencies [ ] string ) ( [ ] string , error ) {
filteredPackages := [ ] string { }
2024-01-22 10:36:06 +00:00
2025-01-19 08:49:00 +00:00
for _ , dep := range dependencies {
installed , err := opts . Manager . IsInstalled ( dep )
if err != nil {
return nil , err
}
if installed {
continue
2024-01-22 10:36:06 +00:00
}
2025-01-19 08:49:00 +00:00
filteredPackages = append ( filteredPackages , dep )
2024-01-22 10:36:06 +00:00
}
2025-01-19 08:49:00 +00:00
return filteredPackages , nil
2024-01-22 10:36:06 +00:00
}
2024-11-16 08:32:47 +00:00
// Функция packageNames возвращает имена всех предоставленных пакетов.
2024-01-22 10:36:06 +00:00
func packageNames ( pkgs [ ] db . Package ) [ ] string {
names := make ( [ ] string , len ( pkgs ) )
for i , p := range pkgs {
names [ i ] = p . Name
}
return names
}
2024-11-16 08:32:47 +00:00
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
2024-01-22 10:36:06 +00:00
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
}