diff --git a/build.go b/build.go
index d259986..2c63799 100644
--- a/build.go
+++ b/build.go
@@ -28,9 +28,11 @@ import (
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
+ database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
+ "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"
)
@@ -59,30 +61,43 @@ func BuildCmd() *cli.Command {
},
Action: func(c *cli.Context) error {
ctx := c.Context
+ cfg := config.New()
+ db := database.New(cfg)
+ rs := repos.New(cfg, db)
+ err := db.Init(ctx)
+ if err != nil {
+ slog.Error(gotext.Get("Error db init"), "err", err)
+ os.Exit(1)
+ }
- var script string
+ var script, packageName string
// Проверяем, установлен ли флаг script (-s)
+ repoDir := cfg.GetPaths(ctx).RepoDir
+
switch {
case c.IsSet("script"):
script = c.String("script")
case c.IsSet("package"):
+ // TODO: refactor
packageInput := c.String("package")
- if filepath.Dir(packageInput) == "." {
- // Не указана директория репозитория, используем 'default' как префикс
- script = filepath.Join(config.GetPaths(ctx).RepoDir, "default", packageInput, "alr.sh")
+ pkgs, _, _ := rs.FindPkgs(ctx, []string{packageInput})
+ pkg := pkgs[packageInput]
+ if pkg[0].BasePkgName != "" {
+ script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].BasePkgName, "alr.sh")
+ packageName = pkg[0].Name
} else {
- // Используем путь с указанным репозиторием
- script = filepath.Join(config.GetPaths(ctx).RepoDir, packageInput, "alr.sh")
+ script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].Name, "alr.sh")
}
+
default:
- script = filepath.Join(config.GetPaths(ctx).RepoDir, "alr.sh")
+ script = filepath.Join(repoDir, "alr.sh")
}
// Проверка автоматического пулла репозиториев
- if config.GetInstance(ctx).AutoPull(ctx) {
- err := repos.Pull(ctx, config.Config(ctx).Repos)
+ if cfg.AutoPull(ctx) {
+ err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
@@ -96,13 +111,28 @@ func BuildCmd() *cli.Command {
os.Exit(1)
}
+ info, err := distro.ParseOSRelease(ctx)
+ if err != nil {
+ slog.Error(gotext.Get("Error parsing os release"), "err", err)
+ os.Exit(1)
+ }
+
+ builder := build.New(
+ ctx,
+ types.BuildOpts{
+ Package: packageName,
+ Script: script,
+ Manager: mgr,
+ Clean: c.Bool("clean"),
+ Interactive: c.Bool("interactive"),
+ },
+ rs,
+ info,
+ cfg,
+ )
+
// Сборка пакета
- pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
- Script: script,
- Manager: mgr,
- Clean: c.Bool("clean"),
- Interactive: c.Bool("interactive"),
- })
+ pkgPaths, _, err := builder.BuildPackage(ctx)
if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
diff --git a/coverage-badge.svg b/coverage-badge.svg
index 2a6facc..b7b7768 100644
--- a/coverage-badge.svg
+++ b/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 19.2%
- 19.2%
+ 19.7%
+ 19.7%
diff --git a/internal/config/config.go b/internal/config/config.go
index f8457c6..62df450 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -170,3 +170,10 @@ func (c *ALRConfig) AutoPull(ctx context.Context) bool {
})
return c.cfg.AutoPull
}
+
+func (c *ALRConfig) PagerStyle(ctx context.Context) string {
+ c.cfgOnce.Do(func() {
+ c.Load(ctx)
+ })
+ return c.cfg.PagerStyle
+}
diff --git a/internal/db/db.go b/internal/db/db.go
index 7a8ee6d..b76b748 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -31,10 +31,11 @@ import (
// CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this.
-const CurrentVersion = 2
+const CurrentVersion = 3
// Package is a ALR package's database representation
type Package struct {
+ BasePkgName string `sh:"base" db:"basepkg_name"`
Name string `sh:"name,required" db:"name"`
Version string `sh:"version,required" db:"version"`
Release int `sh:"release,required" db:"release"`
@@ -99,6 +100,7 @@ func (d *Database) initDB(ctx context.Context) error {
conn := d.conn
_, err := conn.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS pkgs (
+ basepkg_name TEXT NOT NULL,
name TEXT NOT NULL,
repository TEXT NOT NULL,
version TEXT NOT NULL,
@@ -196,6 +198,7 @@ func (d *Database) IsEmpty(ctx context.Context) bool {
func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
_, err := d.conn.NamedExecContext(ctx, `
INSERT OR REPLACE INTO pkgs (
+ basepkg_name,
name,
repository,
version,
@@ -213,6 +216,7 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
builddepends,
optdepends
) VALUES (
+ :basepkg_name,
:name,
:repository,
:version,
diff --git a/internal/dl/file.go b/internal/dl/file.go
index 2e63b39..96ce294 100644
--- a/internal/dl/file.go
+++ b/internal/dl/file.go
@@ -123,6 +123,7 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
} else {
out = fl
}
+ defer out.Close()
h, err := opts.NewHash()
if err != nil {
diff --git a/internal/shutils/decoder/decoder.go b/internal/shutils/decoder/decoder.go
index 629e2a7..d59e8ea 100644
--- a/internal/shutils/decoder/decoder.go
+++ b/internal/shutils/decoder/decoder.go
@@ -197,6 +197,27 @@ func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool)
}, true
}
+// TODO: replace
+func (d *Decoder) GetFuncSub(name string) (
+ func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error), bool,
+) {
+ fn := d.getFunc(name)
+ if fn == nil {
+ return nil, false
+ }
+
+ return func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error) {
+ sub := d.Runner.Subshell()
+ for _, opt := range opts {
+ err := opt(sub)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return sub, sub.Run(ctx, fn)
+ }, true
+}
+
func (d *Decoder) getFunc(name string) *syntax.Stmt {
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
if err != nil {
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index b0cc59f..f2e3d0a 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -9,40 +9,48 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: build.go:41
+#: build.go:43
msgid "Build a local package"
msgstr ""
-#: build.go:47
+#: build.go:49
msgid "Path to the build script"
msgstr ""
-#: build.go:52
+#: build.go:54
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr ""
-#: build.go:57
+#: build.go:59
msgid ""
"Build package from scratch even if there's an already built package available"
msgstr ""
-#: build.go:87
+#: build.go:69
+msgid "Error db init"
+msgstr ""
+
+#: build.go:102
msgid "Error pulling repositories"
msgstr ""
-#: build.go:95
+#: build.go:110
msgid "Unable to detect a supported package manager on the system"
msgstr ""
-#: build.go:107
+#: build.go:116
+msgid "Error parsing os release"
+msgstr ""
+
+#: build.go:137
msgid "Error building package"
msgstr ""
-#: build.go:114
+#: build.go:144
msgid "Error getting working directory"
msgstr ""
-#: build.go:123
+#: build.go:153
msgid "Error moving the package"
msgstr ""
@@ -222,11 +230,11 @@ msgstr ""
msgid "Error parsing system language"
msgstr ""
-#: internal/db/db.go:131
+#: internal/db/db.go:133
msgid "Database version mismatch; resetting"
msgstr ""
-#: internal/db/db.go:138
+#: internal/db/db.go:140
msgid ""
"Database version does not exist. Run alr fix if something isn't working."
msgstr ""
@@ -293,82 +301,78 @@ msgstr ""
msgid "Error while running app"
msgstr ""
-#: pkg/build/build.go:108
+#: pkg/build/build.go:116
msgid "Failed to prompt user to view build script"
msgstr ""
-#: pkg/build/build.go:112
+#: pkg/build/build.go:120
msgid "Building package"
msgstr ""
-#: pkg/build/build.go:156
+#: pkg/build/build.go:164
msgid "Downloading sources"
msgstr ""
-#: pkg/build/build.go:168
+#: pkg/build/build.go:176
msgid "Building package metadata"
msgstr ""
-#: pkg/build/build.go:190
+#: pkg/build/build.go:198
msgid "Compressing package"
msgstr ""
-#: pkg/build/build.go:316
+#: pkg/build/build.go:322
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
-#: pkg/build/build.go:327
+#: pkg/build/build.go:336
msgid "This package is already installed"
msgstr ""
-#: pkg/build/build.go:355
+#: pkg/build/build.go:360
msgid "Installing build dependencies"
msgstr ""
-#: pkg/build/build.go:397
+#: pkg/build/build.go:371
msgid "Installing dependencies"
msgstr ""
-#: pkg/build/build.go:443
-msgid "Executing version()"
+#: pkg/build/build.go:419
+msgid "The checksums array must be the same length as sources"
msgstr ""
-#: pkg/build/build.go:463
-msgid "Updating version"
-msgstr ""
-
-#: pkg/build/build.go:468
-msgid "Executing prepare()"
-msgstr ""
-
-#: pkg/build/build.go:478
-msgid "Executing build()"
-msgstr ""
-
-#: pkg/build/build.go:488
-msgid "Executing package()"
-msgstr ""
-
-#: pkg/build/build.go:510
-msgid "Executing files()"
-msgstr ""
-
-#: pkg/build/build.go:588
-msgid "AutoProv is not implemented for this package format, so it's skipped"
-msgstr ""
-
-#: pkg/build/build.go:599
-msgid "AutoReq is not implemented for this package format, so it's skipped"
-msgstr ""
-
-#: pkg/build/build.go:706
+#: pkg/build/build.go:470
msgid "Would you like to remove the build dependencies?"
msgstr ""
-#: pkg/build/build.go:812
-msgid "The checksums array must be the same length as sources"
+#: pkg/build/build.go:507
+msgid "Executing version()"
+msgstr ""
+
+#: pkg/build/build.go:527
+msgid "Updating version"
+msgstr ""
+
+#: pkg/build/build.go:532
+msgid "Executing prepare()"
+msgstr ""
+
+#: pkg/build/build.go:542
+msgid "Executing build()"
+msgstr ""
+
+#: pkg/build/build.go:558 pkg/build/build.go:586
+msgid "Executing %s()"
+msgstr ""
+
+#: pkg/build/build_legacy.go:196
+msgid "AutoProv is not implemented for this package format, so it's skipped"
+msgstr ""
+
+#: pkg/build/build_legacy.go:207
+msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/findDeps.go:35
@@ -383,27 +387,27 @@ msgstr ""
msgid "Required dependency found"
msgstr ""
-#: pkg/build/install.go:42
+#: pkg/build/install.go:44
msgid "Error installing native packages"
msgstr ""
-#: pkg/build/install.go:79
+#: pkg/build/install.go:94
msgid "Error installing package"
msgstr ""
-#: pkg/repos/pull.go:75
+#: pkg/repos/pull.go:79
msgid "Pulling repository"
msgstr ""
-#: pkg/repos/pull.go:99
+#: pkg/repos/pull.go:103
msgid "Repository up to date"
msgstr ""
-#: pkg/repos/pull.go:156
+#: pkg/repos/pull.go:160
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
-#: pkg/repos/pull.go:172
+#: pkg/repos/pull.go:176
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index 3d5e548..de06d3b 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -16,40 +16,49 @@ msgstr ""
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 47.1\n"
-#: build.go:41
+#: build.go:43
msgid "Build a local package"
msgstr "Сборка локального пакета"
-#: build.go:47
+#: build.go:49
msgid "Path to the build script"
msgstr "Путь к скрипту сборки"
-#: build.go:52
+#: build.go:54
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
-#: build.go:57
+#: build.go:59
msgid ""
"Build package from scratch even if there's an already built package available"
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
-#: build.go:87
+#: build.go:69
+msgid "Error db init"
+msgstr ""
+
+#: build.go:102
msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев"
-#: build.go:95
+#: build.go:110
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
-#: build.go:107
+#: build.go:116
+#, fuzzy
+msgid "Error parsing os release"
+msgstr "Ошибка при разборе файла выпуска операционной системы"
+
+#: build.go:137
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
-#: build.go:114
+#: build.go:144
msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога"
-#: build.go:123
+#: build.go:153
msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета"
@@ -233,11 +242,11 @@ msgstr "Не удалось создать каталог кэша пакето
msgid "Error parsing system language"
msgstr "Ошибка при парсинге языка системы"
-#: internal/db/db.go:131
+#: internal/db/db.go:133
msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек"
-#: internal/db/db.go:138
+#: internal/db/db.go:140
msgid ""
"Database version does not exist. Run alr fix if something isn't working."
msgstr ""
@@ -307,27 +316,27 @@ msgstr ""
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
-#: pkg/build/build.go:108
+#: pkg/build/build.go:116
msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
-#: pkg/build/build.go:112
+#: pkg/build/build.go:120
msgid "Building package"
msgstr "Сборка пакета"
-#: pkg/build/build.go:156
+#: pkg/build/build.go:164
msgid "Downloading sources"
msgstr "Скачивание источников"
-#: pkg/build/build.go:168
+#: pkg/build/build.go:176
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
-#: pkg/build/build.go:190
+#: pkg/build/build.go:198
msgid "Compressing package"
msgstr "Сжатие пакета"
-#: pkg/build/build.go:316
+#: pkg/build/build.go:322
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
@@ -335,60 +344,57 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?"
-#: pkg/build/build.go:327
+#: pkg/build/build.go:336
msgid "This package is already installed"
msgstr "Этот пакет уже установлен"
-#: pkg/build/build.go:355
+#: pkg/build/build.go:360
msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки"
-#: pkg/build/build.go:397
+#: pkg/build/build.go:371
msgid "Installing dependencies"
msgstr "Установка зависимостей"
-#: pkg/build/build.go:443
+#: pkg/build/build.go:419
+msgid "The checksums array must be the same length as sources"
+msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
+
+#: pkg/build/build.go:470
+msgid "Would you like to remove the build dependencies?"
+msgstr "Хотели бы вы удалить зависимости сборки?"
+
+#: pkg/build/build.go:507
msgid "Executing version()"
msgstr "Исполнение версия()"
-#: pkg/build/build.go:463
+#: pkg/build/build.go:527
msgid "Updating version"
msgstr "Обновление версии"
-#: pkg/build/build.go:468
+#: pkg/build/build.go:532
msgid "Executing prepare()"
msgstr "Исполнение prepare()"
-#: pkg/build/build.go:478
+#: pkg/build/build.go:542
msgid "Executing build()"
msgstr "Исполнение build()"
-#: pkg/build/build.go:488
-msgid "Executing package()"
-msgstr "Исполнение package()"
-
-#: pkg/build/build.go:510
-msgid "Executing files()"
+#: pkg/build/build.go:558 pkg/build/build.go:586
+#, fuzzy
+msgid "Executing %s()"
msgstr "Исполнение files()"
-#: pkg/build/build.go:588
+#: pkg/build/build_legacy.go:196
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
-#: pkg/build/build.go:599
+#: pkg/build/build_legacy.go:207
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
-#: pkg/build/build.go:706
-msgid "Would you like to remove the build dependencies?"
-msgstr "Хотели бы вы удалить зависимости сборки?"
-
-#: pkg/build/build.go:812
-msgid "The checksums array must be the same length as sources"
-msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
-
#: pkg/build/findDeps.go:35
msgid "Command not found on the system"
msgstr "Команда не найдена в системе"
@@ -401,27 +407,27 @@ msgstr "Найденная предоставленная зависимость
msgid "Required dependency found"
msgstr "Найдена требуемая зависимость"
-#: pkg/build/install.go:42
+#: pkg/build/install.go:44
msgid "Error installing native packages"
msgstr "Ошибка при установке нативных пакетов"
-#: pkg/build/install.go:79
+#: pkg/build/install.go:94
msgid "Error installing package"
msgstr "Ошибка при установке пакета"
-#: pkg/repos/pull.go:75
+#: pkg/repos/pull.go:79
msgid "Pulling repository"
msgstr "Скачивание репозитория"
-#: pkg/repos/pull.go:99
+#: pkg/repos/pull.go:103
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
-#: pkg/repos/pull.go:156
+#: pkg/repos/pull.go:160
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
-#: pkg/repos/pull.go:172
+#: pkg/repos/pull.go:176
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
@@ -484,3 +490,6 @@ msgstr "Ошибка при проверке обновлений"
#: upgrade.go:94
msgid "There is nothing to do."
msgstr "Здесь нечего делать."
+
+#~ msgid "Executing package()"
+#~ msgstr "Исполнение package()"
diff --git a/internal/types/build.go b/internal/types/build.go
index cef93a0..7682115 100644
--- a/internal/types/build.go
+++ b/internal/types/build.go
@@ -23,11 +23,61 @@ import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct {
Script string
+ Package string
Manager manager.Manager
Clean bool
Interactive bool
}
+type BuildVarsPre struct {
+ Version string `sh:"version,required"`
+ Release int `sh:"release,required"`
+ Epoch uint `sh:"epoch"`
+ Description string `sh:"desc"`
+ Homepage string `sh:"homepage"`
+ Maintainer string `sh:"maintainer"`
+ Architectures []string `sh:"architectures"`
+ Licenses []string `sh:"license"`
+ Provides []string `sh:"provides"`
+ Conflicts []string `sh:"conflicts"`
+ Depends []string `sh:"deps"`
+ BuildDepends []string `sh:"build_deps"`
+ OptDepends []string `sh:"opt_deps"`
+ Replaces []string `sh:"replaces"`
+ Sources []string `sh:"sources"`
+ Checksums []string `sh:"checksums"`
+ Backup []string `sh:"backup"`
+ Scripts Scripts `sh:"scripts"`
+ AutoReq []string `sh:"auto_req"`
+ AutoProv []string `sh:"auto_prov"`
+}
+
+func (bv *BuildVarsPre) ToBuildVars() BuildVars {
+ return BuildVars{
+ Name: "",
+ Version: bv.Version,
+ Release: bv.Release,
+ Epoch: bv.Epoch,
+ Description: bv.Description,
+ Homepage: bv.Homepage,
+ Maintainer: bv.Maintainer,
+ Architectures: bv.Architectures,
+ Licenses: bv.Licenses,
+ Provides: bv.Provides,
+ Conflicts: bv.Conflicts,
+ Depends: bv.Depends,
+ BuildDepends: bv.BuildDepends,
+ OptDepends: bv.OptDepends,
+ Replaces: bv.Replaces,
+ Sources: bv.Sources,
+ Checksums: bv.Checksums,
+ Backup: bv.Backup,
+ Scripts: bv.Scripts,
+ AutoReq: bv.AutoReq,
+ AutoProv: bv.AutoProv,
+ }
+}
+
// BuildVars represents the script variables required
// to build a package
type BuildVars struct {
diff --git a/pkg/build/build.go b/pkg/build/build.go
index 62be0a0..9125530 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -23,39 +23,26 @@ import (
"bytes"
"context"
"encoding/hex"
+ "errors"
"fmt"
- "io"
"log/slog"
"os"
"path/filepath"
- "runtime"
- "slices"
- "strconv"
"strings"
"time"
- // Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
-
"github.com/google/shlex"
- _ "github.com/goreleaser/nfpm/v2/apk"
- _ "github.com/goreleaser/nfpm/v2/arch"
- _ "github.com/goreleaser/nfpm/v2/deb"
- _ "github.com/goreleaser/nfpm/v2/rpm"
+ "github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"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"
-
"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"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"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"
@@ -65,34 +52,49 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
)
-// Функция BuildPackage выполняет сборку скрипта по указанному пути. Возвращает два среза.
-// Один содержит пути к собранным пакетам, другой - имена собранных пакетов.
-func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) {
- reposInstance := repos.GetInstance(ctx)
+type Builder struct {
+ ctx context.Context
+ opts types.BuildOpts
+ info *distro.OSRelease
+ repos *repos.Repos
+ config *config.ALRConfig
+}
- info, err := distro.ParseOSRelease(ctx)
- if err != nil {
- return nil, nil, err
+func New(
+ ctx context.Context,
+ opts types.BuildOpts,
+ repos *repos.Repos,
+ info *distro.OSRelease,
+ config *config.ALRConfig,
+) *Builder {
+ return &Builder{
+ ctx: ctx,
+ opts: opts,
+ info: info,
+ repos: repos,
+ config: config,
}
+}
- fl, err := parseScript(info, opts.Script)
+func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error) {
+ fl, err := readScript(b.opts.Script)
if err != nil {
return nil, nil, err
}
// Первый проход предназначен для получения значений переменных и выполняется
// до отображения скрипта, чтобы предотвратить выполнение вредоносного кода.
- vars, err := executeFirstPass(ctx, info, fl, opts.Script)
+ vars, err := b.executeFirstPass(fl)
if err != nil {
return nil, nil, err
}
- dirs := getDirs(ctx, vars, opts.Script)
+ dirs := b.getDirs(vars)
// Если флаг opts.Clean не установлен, и пакет уже собран,
// возвращаем его, а не собираем заново.
- if !opts.Clean {
- builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir)
+ if !b.opts.Clean {
+ builtPkgPath, ok, err := checkForBuiltPackage(b.opts.Manager, vars, getPkgFormat(b.opts.Manager), dirs.BaseDir)
if err != nil {
return nil, nil, err
}
@@ -103,7 +105,13 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
}
// Спрашиваем у пользователя, хочет ли он увидеть скрипт сборки.
- err = cliutils.PromptViewScript(ctx, opts.Script, vars.Name, config.Config(ctx).PagerStyle, opts.Interactive)
+ err = cliutils.PromptViewScript(
+ ctx,
+ b.opts.Script,
+ vars.Name,
+ b.config.PagerStyle(ctx),
+ b.opts.Interactive,
+ )
if err != nil {
slog.Error(gotext.Get("Failed to prompt user to view build script"), "err", err)
os.Exit(1)
@@ -114,18 +122,18 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
// Второй проход будет использоваться для выполнения реального кода,
// поэтому он не ограничен. Скрипт уже был показан
// пользователю к этому моменту, так что это должно быть безопасно.
- dec, err := executeSecondPass(ctx, info, fl, dirs)
+ dec, err := b.executeSecondPass(ctx, fl, dirs)
if err != nil {
return nil, nil, err
}
// Получаем список установленных пакетов в системе
- installed, err := opts.Manager.ListInstalled(nil)
+ installed, err := b.opts.Manager.ListInstalled(nil)
if err != nil {
return nil, nil, err
}
- cont, err := performChecks(ctx, vars, opts.Interactive, installed) // Выполняем различные проверки
+ cont, err := b.performChecks(ctx, vars, installed) // Выполняем различные проверки
if err != nil {
return nil, nil, err
} else if !cont {
@@ -138,38 +146,38 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
return nil, nil, err
}
- buildDeps, err := installBuildDeps(ctx, reposInstance, vars, opts) // Устанавливаем зависимости для сборки
+ buildDeps, err := b.installBuildDeps(ctx, vars) // Устанавливаем зависимости для сборки
if err != nil {
return nil, nil, err
}
- err = installOptDeps(ctx, reposInstance, vars, opts) // Устанавливаем опциональные зависимости
+ err = installOptDeps(ctx, b.repos, vars, b.opts) // Устанавливаем опциональные зависимости
if err != nil {
return nil, nil, err
}
- builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) // Собираем зависимости
+ builtPaths, builtNames, repoDeps, err := b.buildALRDeps(ctx, vars) // Собираем зависимости
if err != nil {
return nil, nil, err
}
slog.Info(gotext.Get("Downloading sources")) // Записываем в лог загрузку источников
- err = getSources(ctx, dirs, vars) // Загружаем исходники
+ err = b.getSources(ctx, dirs, vars) // Загружаем исходники
if err != nil {
return nil, nil, err
}
- funcOut, err := executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции
+ funcOut, err := b.executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции
if err != nil {
return nil, nil, err
}
slog.Info(gotext.Get("Building package metadata"), "name", vars.Name)
- pkgFormat := getPkgFormat(opts.Manager) // Получаем формат пакета
+ pkgFormat := getPkgFormat(b.opts.Manager) // Получаем формат пакета
- pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, info, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета
+ pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, b.info, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета
if err != nil {
return nil, nil, err
}
@@ -194,7 +202,7 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
return nil, nil, err
}
- err = removeBuildDeps(ctx, buildDeps, opts) // Удаляем зависимости для сборки
+ err = b.removeBuildDeps(ctx, buildDeps) // Удаляем зависимости для сборки
if err != nil {
return nil, nil, err
}
@@ -213,27 +221,13 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
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}) // Создаём переменные окружения для сборки
+func (b *Builder) executeFirstPass(
+ fl *syntax.File,
+) (*types.BuildVars, error) {
+ scriptDir := filepath.Dir(b.opts.Script) // Получаем директорию скрипта
+ env := createBuildEnvVars(b.info, types.Directories{ScriptDir: scriptDir}) // Создаём переменные окружения для сборки
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
@@ -247,37 +241,60 @@ func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.Fi
return nil, err
}
- err = runner.Run(ctx, fl) // Запускаем скрипт
+ err = runner.Run(b.ctx, fl) // Запускаем скрипт
if err != nil {
return nil, err
}
- dec := decoder.New(info, runner) // Создаём новый декодер
-
+ dec := decoder.New(b.info, runner) // Создаём новый декодер
var vars types.BuildVars
- err = dec.DecodeVars(&vars) // Декодируем переменные
+ if b.opts.Package == "" {
+ err = dec.DecodeVars(&vars) // Декодируем переменные
+ if err != nil {
+ return nil, err
+ }
+ return &vars, nil
+ }
+ var preVars types.BuildVarsPre
+ funcName := fmt.Sprintf("meta_%s", b.opts.Package)
+ meta, ok := dec.GetFuncSub(funcName)
+ if !ok {
+ return nil, errors.New("func is missing")
+ }
+ r, err := meta(b.ctx)
if err != nil {
return nil, err
}
+ d := decoder.New(&distro.OSRelease{}, r)
+ err = d.DecodeVars(&preVars)
+ if err != nil {
+ return nil, err
+ }
+ vars = preVars.ToBuildVars()
+ vars.Name = b.opts.Package
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) // Определяем базовую директорию
+func (b *Builder) getDirs(vars *types.BuildVars) types.Directories {
+ baseDir := filepath.Join(b.config.GetPaths(b.ctx).PkgsDir, vars.Name) // Определяем базовую директорию
return types.Directories{
BaseDir: baseDir,
SrcDir: filepath.Join(baseDir, "src"),
PkgDir: filepath.Join(baseDir, "pkg"),
- ScriptDir: filepath.Dir(script),
+ ScriptDir: filepath.Dir(b.opts.Script),
}
}
// Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер,
// который может быть использован для получения функций и переменных из скрипта.
-func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) {
- env := createBuildEnvVars(info, dirs) // Создаём переменные окружения для сборки
+func (b *Builder) executeSecondPass(
+ ctx context.Context,
+ fl *syntax.File,
+ dirs types.Directories,
+) (*decoder.Decoder, error) {
+ env := createBuildEnvVars(b.info, dirs) // Создаём переменные окружения для сборки
fakeroot := handlers.FakerootExecHandler(2 * time.Second) // Настраиваем "fakeroot" для выполнения
runner, err := interp.New(
@@ -294,26 +311,18 @@ func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.F
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) // Создаем директорию для пакетов
+ return decoder.New(b.info, runner), nil // Возвращаем новый декодер
}
// Функция performChecks проверяет различные аспекты в системе, чтобы убедиться, что пакет может быть установлен.
-func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, installed map[string]string) (bool, error) {
+func (b *Builder) performChecks(ctx context.Context, vars *types.BuildVars, installed map[string]string) (bool, error) {
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
- 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)
+ cont, err := cliutils.YesNoPrompt(
+ ctx,
+ gotext.Get("Your system's CPU architecture doesn't match this package. Do you want to build anyway?"),
+ b.opts.Interactive,
+ true,
+ )
if err != nil {
return false, err
}
@@ -333,16 +342,12 @@ func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool,
return true, nil
}
-type PackageFinder interface {
- FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
-}
-
// Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает
// срез, содержащий имена всех установленных пакетов.
-func installBuildDeps(ctx context.Context, repos PackageFinder, vars *types.BuildVars, opts types.BuildOpts) ([]string, error) {
+func (b *Builder) installBuildDeps(ctx context.Context, vars *types.BuildVars) ([]string, error) {
var buildDeps []string
if len(vars.BuildDepends) > 0 {
- deps, err := removeAlreadyInstalled(opts, vars.BuildDepends)
+ deps, err := removeAlreadyInstalled(b.opts, vars.BuildDepends)
if err != nil {
return nil, err
}
@@ -354,45 +359,14 @@ func installBuildDeps(ctx context.Context, repos PackageFinder, vars *types.Buil
slog.Info(gotext.Get("Installing build dependencies")) // Логгируем установку зависимостей
- flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive) // Уплощаем список зависимостей
+ flattened := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive) // Уплощаем список зависимостей
buildDeps = packageNames(flattened)
- InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем пакеты
+ InstallPkgs(ctx, flattened, notFound, b.opts) // Устанавливаем пакеты
}
return buildDeps, nil
}
-// Функция installOptDeps спрашивает у пользователя, какие, если таковые имеются, опциональные зависимости он хочет установить.
-// Если пользователь решает установить какие-либо опциональные зависимости, выполняется их установка.
-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) // Пользователя просят выбрать опциональные зависимости
- if err != nil {
- return err
- }
-
- if len(optDeps) == 0 {
- return nil
- }
-
- found, notFound, err := repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости
- if err != nil {
- return err
- }
-
- 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) {
+func (b *Builder) buildALRDeps(ctx context.Context, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
if len(vars.Depends) > 0 {
slog.Info(gotext.Get("Installing dependencies"))
@@ -403,14 +377,22 @@ func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVa
repoDeps = notFound
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
- pkgs := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
- scripts := GetScriptPaths(ctx, pkgs)
- for _, script := range scripts {
- newOpts := opts
- newOpts.Script = script
+ pkgs := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive)
+
+ for _, pkg := range pkgs {
+ newOpts := b.opts
+ UpdateOpts(ctx, &newOpts, &pkg)
+
+ newB := New(
+ ctx,
+ newOpts,
+ b.repos,
+ b.info,
+ b.config,
+ )
// Собираем зависимости
- pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts)
+ pkgPaths, pkgNames, err := newB.BuildPackage(ctx)
if err != nil {
return nil, nil, nil, err
}
@@ -420,7 +402,7 @@ func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVa
// Добавляем пути всех собранных пакетов в builtPaths
builtNames = append(builtNames, pkgNames...)
// Добавляем имя текущего пакета в builtNames
- builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
+ builtNames = append(builtNames, filepath.Base(filepath.Dir(newOpts.Script)))
}
}
@@ -432,12 +414,94 @@ func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVa
return builtPaths, builtNames, repoDeps, nil
}
+func (b *Builder) getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
+ if len(bv.Sources) != len(bv.Checksums) {
+ slog.Error(gotext.Get("The checksums array must be the same length as sources"))
+ os.Exit(1)
+ }
+
+ 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
+ }
+ }
+
+ opts.DlCache = dlcache.New(b.config)
+
+ err := dl.Download(ctx, opts)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости,
+// установленные для сборки. Если да, использует менеджер пакетов для их удаления.
+func (b *Builder) removeBuildDeps(ctx context.Context, buildDeps []string) error {
+ if len(buildDeps) > 0 {
+ remove, err := cliutils.YesNoPrompt(
+ ctx,
+ gotext.Get("Would you like to remove the build dependencies?"),
+ b.opts.Interactive,
+ false,
+ )
+ if err != nil {
+ return err
+ }
+
+ if remove {
+ err = b.opts.Manager.Remove(
+ &manager.Opts{
+ AsRoot: true,
+ NoConfirm: true,
+ },
+ buildDeps...,
+ )
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
type FunctionsOutput struct {
Contents *[]string
}
// Функция executeFunctions выполняет специальные функции ALR, такие как version(), prepare() и т.д.
-func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (*FunctionsOutput, error) {
+func (b *Builder) executeFunctions(
+ ctx context.Context,
+ dec *decoder.Decoder,
+ dirs types.Directories,
+ vars *types.BuildVars,
+) (*FunctionsOutput, error) {
version, ok := dec.GetFunc("version")
if ok {
slog.Info(gotext.Get("Executing version()"))
@@ -483,9 +547,15 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire
}
}
- packageFn, ok := dec.GetFunc("package")
+ var packageFuncName string
+ if b.opts.Package == "" {
+ packageFuncName = "package"
+ } else {
+ packageFuncName = fmt.Sprintf("package_%s", b.opts.Package)
+ }
+ packageFn, ok := dec.GetFunc(packageFuncName)
if ok {
- slog.Info(gotext.Get("Executing package()"))
+ slog.Info(gotext.Get("Executing %s()", packageFuncName))
err := packageFn(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return nil, err
@@ -494,7 +564,13 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire
output := &FunctionsOutput{}
- files, ok := dec.GetFuncP("files", func(ctx context.Context, s *interp.Runner) error {
+ var filesFuncName string
+ if b.opts.Package == "" {
+ filesFuncName = "files"
+ } else {
+ filesFuncName = fmt.Sprintf("files_%s", b.opts.Package)
+ }
+ files, ok := dec.GetFuncP(filesFuncName, 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
@@ -507,7 +583,7 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire
})
if ok {
- slog.Info(gotext.Get("Executing files()"))
+ slog.Info(gotext.Get("Executing %s()", filesFuncName))
buf := &bytes.Buffer{}
@@ -529,417 +605,3 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire
return output, nil
}
-
-// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
-func buildPkgMetadata(
- ctx context.Context,
- vars *types.BuildVars,
- dirs types.Directories,
- pkgFormat string,
- info *distro.OSRelease,
- deps []string,
- preferedContents *[]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
- })
- }
-
- pkgInfo.Release = overrides.ReleasePlatformSpecific(vars.Release, info)
-
- 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, preferedContents)
- if err != nil {
- return nil, err
- }
- pkgInfo.Overridables.Contents = contents
-
- 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 {
- slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
- }
- }
-
- 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 {
- slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
- }
- }
-
- return pkgInfo, nil
-}
-
-// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
-// которые будут включены в конечный пакет.
-func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
- contents := []*files.Content{}
-
- processPath := func(path, trimmed string, prefered bool) error {
- fi, err := os.Lstat(path)
- if err != nil {
- return err
- }
-
- if fi.IsDir() {
- f, err := os.Open(path)
- if err != nil {
- return err
- }
- defer f.Close()
-
- if !prefered {
- _, 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 nil
- }
-
- 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
- }
-
- 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
- }
-
- 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
-}
-
-// Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости,
-// установленные для сборки. Если да, использует менеджер пакетов для их удаления.
-func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOpts) error {
- if len(buildDeps) > 0 {
- remove, err := cliutils.YesNoPrompt(ctx, gotext.Get("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 {
- if len(bv.Sources) != len(bv.Checksums) {
- slog.Error(gotext.Get("The checksums array must be the same length as sources"))
- os.Exit(1)
- }
-
- 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
- }
- }
-
- cfg := config.GetInstance(ctx)
- opts.DlCache = dlcache.New(cfg)
-
- 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)
-}
-
-// Returns not installed dependencies
-func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
- filteredPackages := []string{}
-
- for _, dep := range dependencies {
- installed, err := opts.Manager.IsInstalled(dep)
- if err != nil {
- return nil, err
- }
- if installed {
- continue
- }
- filteredPackages = append(filteredPackages, dep)
- }
-
- return filteredPackages, nil
-}
-
-// Функция 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
-}
diff --git a/pkg/build/build_legacy.go b/pkg/build/build_legacy.go
new file mode 100644
index 0000000..aee4286
--- /dev/null
+++ b/pkg/build/build_legacy.go
@@ -0,0 +1,479 @@
+// 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 .
+
+package build
+
+import (
+ "context"
+ "io"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "runtime"
+ "slices"
+ "strconv"
+ "strings"
+
+ // Импортируем пакеты для поддержки различных форматов пакетов (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"
+ "github.com/leonelquinteros/gotext"
+ "mvdan.cc/sh/v3/interp"
+ "mvdan.cc/sh/v3/syntax"
+
+ "github.com/goreleaser/nfpm/v2"
+ "github.com/goreleaser/nfpm/v2/files"
+
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
+ "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/overrides"
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
+ "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"
+)
+
+// Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash
+func readScript(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 // Возвращаем синтаксическое дерево
+}
+
+// Функция 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) // Создаем директорию для пакетов
+}
+
+type PackageFinder interface {
+ FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
+}
+
+// Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает
+// срез, содержащий имена всех установленных пакетов.
+func installBuildDeps(ctx context.Context, repos PackageFinder, vars *types.BuildVars, opts types.BuildOpts) ([]string, error) {
+ var buildDeps []string
+ if len(vars.BuildDepends) > 0 {
+ deps, err := removeAlreadyInstalled(opts, vars.BuildDepends)
+ if err != nil {
+ return nil, err
+ }
+
+ found, notFound, err := repos.FindPkgs(ctx, deps) // Находим пакеты-зависимости
+ if err != nil {
+ return nil, err
+ }
+
+ slog.Info(gotext.Get("Installing build dependencies")) // Логгируем установку зависимостей
+
+ 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, 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) // Пользователя просят выбрать опциональные зависимости
+ if err != nil {
+ return err
+ }
+
+ if len(optDeps) == 0 {
+ return nil
+ }
+
+ found, notFound, err := repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости
+ if err != nil {
+ return err
+ }
+
+ flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
+ InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем выбранные пакеты
+ }
+ return nil
+}
+
+// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
+func buildPkgMetadata(
+ ctx context.Context,
+ vars *types.BuildVars,
+ dirs types.Directories,
+ pkgFormat string,
+ info *distro.OSRelease,
+ deps []string,
+ preferedContents *[]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
+ })
+ }
+
+ pkgInfo.Release = overrides.ReleasePlatformSpecific(vars.Release, info)
+
+ 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, preferedContents)
+ if err != nil {
+ return nil, err
+ }
+ pkgInfo.Overridables.Contents = contents
+
+ 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 {
+ slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
+ }
+ }
+
+ 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 {
+ slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
+ }
+ }
+
+ return pkgInfo, nil
+}
+
+// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
+// которые будут включены в конечный пакет.
+func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
+ contents := []*files.Content{}
+
+ processPath := func(path, trimmed string, prefered bool) error {
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return err
+ }
+
+ if fi.IsDir() {
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ if !prefered {
+ _, 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 nil
+ }
+
+ 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
+ }
+
+ 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
+ }
+
+ 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
+}
+
+// Функция 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
+}
+
+// Функция 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)
+}
+
+// Returns not installed dependencies
+func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
+ filteredPackages := []string{}
+
+ for _, dep := range dependencies {
+ installed, err := opts.Manager.IsInstalled(dep)
+ if err != nil {
+ return nil, err
+ }
+ if installed {
+ continue
+ }
+ filteredPackages = append(filteredPackages, dep)
+ }
+
+ return filteredPackages, nil
+}
+
+// Функция 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
+}
diff --git a/pkg/build/install.go b/pkg/build/install.go
index 902fb93..bdb8c2e 100644
--- a/pkg/build/install.go
+++ b/pkg/build/install.go
@@ -30,6 +30,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"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/repos"
)
// InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов,
@@ -45,27 +47,40 @@ func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string,
}
}
- InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts)
+ InstallALRPackages(ctx, alrPkgs, opts)
// Устанавливаем скрипты сборки через функцию InstallScripts
}
-// GetScriptPaths возвращает срез путей к скриптам, соответствующий
-// данным пакетам
-func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string {
- var scripts []string
- for _, pkg := range pkgs {
- // Для каждого пакета создаем путь к скрипту сборки
- scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh")
- scripts = append(scripts, scriptPath)
+func UpdateOpts(ctx context.Context, opts *types.BuildOpts, pkg *db.Package) {
+ repodir := config.GetPaths(ctx).RepoDir
+ if pkg.BasePkgName != "" {
+ opts.Script = filepath.Join(repodir, pkg.Repository, pkg.BasePkgName, "alr.sh")
+ opts.Package = pkg.Name
+ } else {
+ opts.Script = filepath.Join(repodir, pkg.Repository, pkg.Name, "alr.sh")
}
- return scripts
}
-// InstallScripts строит и устанавливает переданные alr скрипты сборки
-func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
- for _, script := range scripts {
- opts.Script = script // Устанавливаем текущий скрипт в опции
- builtPkgs, _, err := BuildPackage(ctx, opts)
+// InstallALRPackages строит и устанавливает переданные alr скрипты сборки
+func InstallALRPackages(ctx context.Context, pkgs []db.Package, opts types.BuildOpts) {
+ info, err := distro.ParseOSRelease(ctx)
+ if err != nil {
+ slog.Error(gotext.Get("Error parsing os release"), "err", err)
+ os.Exit(1)
+ }
+
+ for _, pkg := range pkgs {
+ UpdateOpts(ctx, &opts, &pkg)
+
+ builder := New(
+ ctx,
+ opts,
+ repos.GetInstance(ctx),
+ info,
+ config.GetInstance(ctx),
+ )
+
+ builtPkgs, _, err := builder.BuildPackage(ctx)
// Выполняем сборку пакета
if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err)
diff --git a/pkg/repos/pull.go b/pkg/repos/pull.go
index 1d41a52..bc66824 100644
--- a/pkg/repos/pull.go
+++ b/pkg/repos/pull.go
@@ -22,6 +22,8 @@ package repos
import (
"context"
"errors"
+ "fmt"
+ "io"
"log/slog"
"net/url"
"os"
@@ -41,8 +43,10 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
+ "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/types"
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type actionType uint8
@@ -177,6 +181,96 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
return nil
}
+func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error {
+ parser := syntax.NewParser()
+
+ defer scriptFl.Close()
+ fl, err := parser.Parse(scriptFl, "alr.sh")
+ if err != nil {
+ return err
+ }
+
+ runner.Reset()
+ err = runner.Run(ctx, fl)
+ if err != nil {
+ return err
+ }
+
+ type packages struct {
+ BasePkgName string `sh:"basepkg_name"`
+ Names []string `sh:"name"`
+ }
+
+ var pkgs packages
+
+ d := decoder.New(&distro.OSRelease{}, runner)
+ d.Overrides = false
+ d.LikeDistros = false
+ err = d.DecodeVars(&pkgs)
+ if err != nil {
+ return err
+ }
+
+ if len(pkgs.Names) > 1 {
+ if pkgs.BasePkgName == "" {
+ pkgs.BasePkgName = pkgs.Names[0]
+ }
+ for _, pkgName := range pkgs.Names {
+ pkgInfo := PackageInfo{}
+ funcName := fmt.Sprintf("meta_%s", pkgName)
+ runner.Reset()
+ err = runner.Run(ctx, fl)
+ if err != nil {
+ return err
+ }
+ meta, ok := d.GetFuncSub(funcName)
+ if !ok {
+ return errors.New("func is missing")
+ }
+ r, err := meta(ctx)
+ if err != nil {
+ return err
+ }
+ d := decoder.New(&distro.OSRelease{}, r)
+ d.Overrides = false
+ d.LikeDistros = false
+ err = d.DecodeVars(&pkgInfo)
+ if err != nil {
+ return err
+ }
+ pkg := pkgInfo.ToPackage(repo.Name)
+ resolveOverrides(r, pkg)
+ pkg.Name = pkgName
+ pkg.BasePkgName = pkgs.BasePkgName
+ err = rs.db.InsertPackage(ctx, *pkg)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ pkg := EmptyPackage(repo.Name)
+ err = d.DecodeVars(pkg)
+ if err != nil {
+ return err
+ }
+ resolveOverrides(runner, pkg)
+ return rs.db.InsertPackage(ctx, *pkg)
+}
+
+func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) {
+ env := append(os.Environ(), "scriptdir="+scriptDir)
+ return interp.New(
+ interp.Env(expand.ListEnviron(env...)),
+ interp.ExecHandler(handlers.NopExec),
+ interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
+ interp.StatHandler(handlers.RestrictedStat(repoDir)),
+ interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
+ interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
+ )
+}
+
func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error {
oldCommit, err := r.CommitObject(old.Hash())
if err != nil {
@@ -235,15 +329,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
parser := syntax.NewParser()
for _, action := range actions {
- env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File)))
- runner, err := interp.New(
- interp.Env(expand.ListEnviron(env...)),
- interp.ExecHandler(handlers.NopExec),
- interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
- interp.StatHandler(handlers.RestrictedStat(repoDir)),
- interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
- interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
- )
+ runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File)))
if err != nil {
return err
}
@@ -289,23 +375,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
return nil
}
- pkg := db.Package{
- Description: db.NewJSON(map[string]string{}),
- Homepage: db.NewJSON(map[string]string{}),
- Maintainer: db.NewJSON(map[string]string{}),
- Depends: db.NewJSON(map[string][]string{}),
- BuildDepends: db.NewJSON(map[string][]string{}),
- Repository: repo.Name,
- }
-
- err = parseScript(ctx, parser, runner, r, &pkg)
- if err != nil {
- return err
- }
-
- resolveOverrides(runner, &pkg)
-
- err = rs.db.InsertPackage(ctx, pkg)
+ err = rs.updatePkg(ctx, repo, runner, r)
if err != nil {
return err
}
@@ -322,18 +392,8 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s
return err
}
- parser := syntax.NewParser()
-
for _, match := range matches {
- env := append(os.Environ(), "scriptdir="+filepath.Dir(match))
- runner, err := interp.New(
- interp.Env(expand.ListEnviron(env...)),
- interp.ExecHandler(handlers.NopExec),
- interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
- interp.StatHandler(handlers.RestrictedStat(repoDir)),
- interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
- interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
- )
+ runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match))
if err != nil {
return err
}
@@ -343,23 +403,7 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s
return err
}
- pkg := db.Package{
- Description: db.NewJSON(map[string]string{}),
- Homepage: db.NewJSON(map[string]string{}),
- Maintainer: db.NewJSON(map[string]string{}),
- Depends: db.NewJSON(map[string][]string{}),
- BuildDepends: db.NewJSON(map[string][]string{}),
- Repository: repo.Name,
- }
-
- err = parseScript(ctx, parser, runner, scriptFl, &pkg)
- if err != nil {
- return err
- }
-
- resolveOverrides(runner, &pkg)
-
- err = rs.db.InsertPackage(ctx, pkg)
+ err = rs.updatePkg(ctx, repo, runner, scriptFl)
if err != nil {
return err
}
diff --git a/pkg/repos/pull_internal_test.go b/pkg/repos/pull_internal_test.go
new file mode 100644
index 0000000..b209e9c
--- /dev/null
+++ b/pkg/repos/pull_internal_test.go
@@ -0,0 +1,173 @@
+// 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 .
+
+package repos
+
+import (
+ "context"
+ "io"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
+)
+
+type TestALRConfig struct{}
+
+func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
+ return &config.Paths{
+ DBPath: ":memory:",
+ }
+}
+
+func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
+ return []types.Repo{
+ {
+ Name: "test",
+ URL: "https://test",
+ },
+ }
+}
+
+func createReadCloserFromString(input string) io.ReadCloser {
+ reader := strings.NewReader(input)
+ return struct {
+ io.Reader
+ io.Closer
+ }{
+ Reader: reader,
+ Closer: io.NopCloser(reader),
+ }
+}
+
+func TestUpdatePkg(t *testing.T) {
+ type testCase struct {
+ name string
+ file string
+ verify func(context.Context, *db.Database)
+ }
+
+ repo := types.Repo{
+ Name: "test",
+ URL: "https://test",
+ }
+
+ for _, tc := range []testCase{
+ {
+ name: "single package",
+ file: `name=foo
+version='0.0.1'
+release=1
+desc="main desc"
+deps=('sudo')
+build_deps=('golang')
+`,
+ verify: func(ctx context.Context, database *db.Database) {
+ result, err := database.GetPkgs(ctx, "1 = 1")
+ assert.NoError(t, err)
+ pkgCount := 0
+ for result.Next() {
+ var dbPkg db.Package
+ err = result.StructScan(&dbPkg)
+ if err != nil {
+ t.Errorf("Expected no error, got %s", err)
+ }
+
+ assert.Equal(t, "foo", dbPkg.Name)
+ assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
+ assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
+ pkgCount++
+ }
+ assert.Equal(t, 1, pkgCount)
+ },
+ },
+ {
+ name: "multiple package",
+ file: `basepkg_name=foo
+name=(
+ bar
+ buz
+)
+version='0.0.1'
+release=1
+desc="main desc"
+deps=('sudo')
+build_deps=('golang')
+
+meta_bar() {
+ desc="foo desc"
+}
+
+meta_buz() {
+ deps+=('doas')
+}
+`,
+ verify: func(ctx context.Context, database *db.Database) {
+ result, err := database.GetPkgs(ctx, "1 = 1")
+ assert.NoError(t, err)
+
+ pkgCount := 0
+ for result.Next() {
+ var dbPkg db.Package
+ err = result.StructScan(&dbPkg)
+ if err != nil {
+ t.Errorf("Expected no error, got %s", err)
+ }
+ if dbPkg.Name == "bar" {
+ assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description)
+ assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
+ }
+
+ if dbPkg.Name == "buz" {
+ assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
+ assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends)
+ }
+ pkgCount++
+ }
+ assert.Equal(t, 2, pkgCount)
+ },
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ cfg := &TestALRConfig{}
+ ctx := context.Background()
+
+ database := db.New(&TestALRConfig{})
+ database.Init(ctx)
+
+ rs := New(cfg, database)
+
+ path, err := os.MkdirTemp("", "test-update-pkg")
+ assert.NoError(t, err)
+ defer os.RemoveAll(path)
+
+ runner, err := rs.processRepoChangesRunner(path, path)
+ assert.NoError(t, err)
+
+ err = rs.updatePkg(ctx, repo, runner, createReadCloserFromString(
+ tc.file,
+ ))
+ assert.NoError(t, err)
+
+ tc.verify(ctx, database)
+ })
+ }
+}
diff --git a/pkg/repos/repos_legacy.go b/pkg/repos/repos_legacy.go
index bdf66ef..046b6fb 100644
--- a/pkg/repos/repos_legacy.go
+++ b/pkg/repos/repos_legacy.go
@@ -21,7 +21,6 @@ import (
"sync"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
@@ -41,7 +40,7 @@ func Pull(ctx context.Context, repos []types.Repo) error {
// It also returns a slice that contains the names of all packages that were not found.
//
// Deprecated: use struct method
-func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
+func FindPkgs(ctx context.Context, pkgs []string) (map[string][]database.Package, []string, error) {
return GetInstance(ctx).FindPkgs(ctx, pkgs)
}
diff --git a/pkg/repos/utils.go b/pkg/repos/utils.go
index fc6b615..58b1ed3 100644
--- a/pkg/repos/utils.go
+++ b/pkg/repos/utils.go
@@ -67,6 +67,47 @@ func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runn
return d.DecodeVars(pkg)
}
+type PackageInfo struct {
+ Version string `sh:"version,required"`
+ Release int `sh:"release,required"`
+ Epoch uint `sh:"epoch"`
+ Architectures db.JSON[[]string] `sh:"architectures"`
+ Licenses db.JSON[[]string] `sh:"license"`
+ Provides db.JSON[[]string] `sh:"provides"`
+ Conflicts db.JSON[[]string] `sh:"conflicts"`
+ Replaces db.JSON[[]string] `sh:"replaces"`
+}
+
+func (inf *PackageInfo) ToPackage(repoName string) *db.Package {
+ return &db.Package{
+ Version: inf.Version,
+ Release: inf.Release,
+ Epoch: inf.Epoch,
+ Architectures: inf.Architectures,
+ Licenses: inf.Licenses,
+ Provides: inf.Provides,
+ Conflicts: inf.Conflicts,
+ Replaces: inf.Replaces,
+ Description: db.NewJSON(map[string]string{}),
+ Homepage: db.NewJSON(map[string]string{}),
+ Maintainer: db.NewJSON(map[string]string{}),
+ Depends: db.NewJSON(map[string][]string{}),
+ BuildDepends: db.NewJSON(map[string][]string{}),
+ Repository: repoName,
+ }
+}
+
+func EmptyPackage(repoName string) *db.Package {
+ return &db.Package{
+ Description: db.NewJSON(map[string]string{}),
+ Homepage: db.NewJSON(map[string]string{}),
+ Maintainer: db.NewJSON(map[string]string{}),
+ Depends: db.NewJSON(map[string][]string{}),
+ BuildDepends: db.NewJSON(map[string][]string{}),
+ Repository: repoName,
+ }
+}
+
var overridable = map[string]string{
"deps": "Depends",
"build_deps": "BuildDepends",