diff --git a/build.go b/build.go
index d259986..dc09827 100644
--- a/build.go
+++ b/build.go
@@ -23,14 +23,17 @@ import (
"log/slog"
"os"
"path/filepath"
+ "strings"
"github.com/leonelquinteros/gotext"
"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"
)
@@ -46,6 +49,11 @@ func BuildCmd() *cli.Command {
Value: "alr.sh",
Usage: gotext.Get("Path to the build script"),
},
+ &cli.StringFlag{
+ Name: "script-package",
+ Aliases: []string{"sp"},
+ Usage: gotext.Get("Specify package in script (for multi package script only)"),
+ },
&cli.StringFlag{
Name: "package",
Aliases: []string{"p"},
@@ -59,30 +67,58 @@ 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 packages []string
// Проверяем, установлен ли флаг script (-s)
+ repoDir := cfg.GetPaths(ctx).RepoDir
+
switch {
case c.IsSet("script"):
script = c.String("script")
+ packages = append(packages, c.String("script-package"))
case c.IsSet("package"):
+ // TODO: handle multiple packages
packageInput := c.String("package")
- if filepath.Dir(packageInput) == "." {
- // Не указана директория репозитория, используем 'default' как префикс
- script = filepath.Join(config.GetPaths(ctx).RepoDir, "default", packageInput, "alr.sh")
+
+ arr := strings.Split(packageInput, "/")
+ var packageSearch string
+ if len(arr) == 2 {
+ packageSearch = arr[1]
} else {
- // Используем путь с указанным репозиторием
- script = filepath.Join(config.GetPaths(ctx).RepoDir, packageInput, "alr.sh")
+ packageSearch = arr[0]
+ }
+
+ pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch})
+ pkg, ok := pkgs[packageSearch]
+ if len(pkg) < 1 || !ok {
+ slog.Error(gotext.Get("Package not found"))
+ os.Exit(1)
+ }
+
+ if pkg[0].BasePkgName != "" {
+ script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].BasePkgName, "alr.sh")
+ packages = append(packages, pkg[0].Name)
+ } else {
+ 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 +132,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.NewBuilder(
+ ctx,
+ types.BuildOpts{
+ Packages: packages,
+ 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..b686468 100644
--- a/coverage-badge.svg
+++ b/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 19.2%
- 19.2%
+ 20.8%
+ 20.8%
diff --git a/install.go b/install.go
index 54982b4..2706ada 100644
--- a/install.go
+++ b/install.go
@@ -29,9 +29,10 @@ import (
"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/db"
+ database "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/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"
)
@@ -63,22 +64,52 @@ func InstallCmd() *cli.Command {
os.Exit(1)
}
- if config.GetInstance(ctx).AutoPull(ctx) {
- err := repos.Pull(ctx, config.Config(ctx).Repos)
+ 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)
+ }
+
+ 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)
}
}
- found, notFound, err := repos.FindPkgs(ctx, args.Slice())
+ found, notFound, err := rs.FindPkgs(ctx, args.Slice())
if err != nil {
slog.Error(gotext.Get("Error finding packages"), "err", err)
os.Exit(1)
}
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
- build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
+
+ opts := types.BuildOpts{
+ Manager: mgr,
+ Clean: c.Bool("clean"),
+ Interactive: c.Bool("interactive"),
+ }
+
+ info, err := distro.ParseOSRelease(ctx)
+ if err != nil {
+ slog.Error(gotext.Get("Error parsing os release"), "err", err)
+ os.Exit(1)
+ }
+
+ builder := build.NewBuilder(
+ ctx,
+ opts,
+ rs,
+ info,
+ cfg,
+ )
+
+ builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
@@ -86,6 +117,8 @@ func InstallCmd() *cli.Command {
return nil
},
BashComplete: func(c *cli.Context) {
+ cfg := config.New()
+ db := database.New(cfg)
result, err := db.GetPkgs(c.Context, "true")
if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err)
@@ -94,7 +127,7 @@ func InstallCmd() *cli.Command {
defer result.Close()
for result.Next() {
- var pkg db.Package
+ var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
slog.Error(gotext.Get("Error iterating over packages"), "err", err)
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..c89bfb4 100644
--- a/internal/shutils/decoder/decoder.go
+++ b/internal/shutils/decoder/decoder.go
@@ -164,7 +164,10 @@ func (d *Decoder) DecodeVars(val any) error {
return nil
}
-type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
+type (
+ ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
+ ScriptFuncWithSubshell func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error)
+)
// GetFunc returns a function corresponding to a bash function
// with the given name
@@ -197,6 +200,24 @@ func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool)
}, true
}
+func (d *Decoder) GetFuncWithSubshell(name string) (ScriptFuncWithSubshell, 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/shutils/handlers/fakeroot.go b/internal/shutils/handlers/fakeroot.go
index b8d02c5..bc0090b 100644
--- a/internal/shutils/handlers/fakeroot.go
+++ b/internal/shutils/handlers/fakeroot.go
@@ -29,9 +29,9 @@ import (
"syscall"
"time"
+ "gitea.plemya-x.ru/Plemya-x/fakeroot"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
- "gitea.plemya-x.ru/Plemya-x/fakeroot"
)
// FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index b0cc59f..5c6c28b 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -9,40 +9,56 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: build.go:41
+#: build.go:44
msgid "Build a local package"
msgstr ""
-#: build.go:47
+#: build.go:50
msgid "Path to the build script"
msgstr ""
-#: build.go:52
+#: build.go:55
+msgid "Specify package in script (for multi package script only)"
+msgstr ""
+
+#: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr ""
-#: build.go:57
+#: build.go:65
msgid ""
"Build package from scratch even if there's an already built package available"
msgstr ""
-#: build.go:87
-msgid "Error pulling repositories"
+#: build.go:75
+msgid "Error db init"
msgstr ""
-#: build.go:95
-msgid "Unable to detect a supported package manager on the system"
-msgstr ""
-
-#: build.go:107
-msgid "Error building package"
-msgstr ""
-
-#: build.go:114
-msgid "Error getting working directory"
+#: build.go:105
+msgid "Package not found"
msgstr ""
#: build.go:123
+msgid "Error pulling repositories"
+msgstr ""
+
+#: build.go:131
+msgid "Unable to detect a supported package manager on the system"
+msgstr ""
+
+#: build.go:137
+msgid "Error parsing os release"
+msgstr ""
+
+#: build.go:158
+msgid "Error building package"
+msgstr ""
+
+#: build.go:165
+msgid "Error getting working directory"
+msgstr ""
+
+#: build.go:174
msgid "Error moving the package"
msgstr ""
@@ -130,31 +146,31 @@ msgstr ""
msgid "Error encoding script variables"
msgstr ""
-#: install.go:42
+#: install.go:43
msgid "Install a new package"
msgstr ""
-#: install.go:56
+#: install.go:57
msgid "Command install expected at least 1 argument, got %d"
msgstr ""
-#: install.go:91
+#: install.go:124
msgid "Error getting packages"
msgstr ""
-#: install.go:100
+#: install.go:133
msgid "Error iterating over packages"
msgstr ""
-#: install.go:113
+#: install.go:146
msgid "Remove an installed package"
msgstr ""
-#: install.go:118
+#: install.go:151
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
-#: install.go:130
+#: install.go:163
msgid "Error removing packages"
msgstr ""
@@ -222,11 +238,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 +309,70 @@ msgstr ""
msgid "Error while running app"
msgstr ""
-#: pkg/build/build.go:108
+#: pkg/build/build.go:153
msgid "Failed to prompt user to view build script"
msgstr ""
-#: pkg/build/build.go:112
+#: pkg/build/build.go:157
msgid "Building package"
msgstr ""
-#: pkg/build/build.go:156
+#: pkg/build/build.go:228
msgid "Downloading sources"
msgstr ""
-#: pkg/build/build.go:168
+#: pkg/build/build.go:246
msgid "Building package metadata"
msgstr ""
-#: pkg/build/build.go:190
+#: pkg/build/build.go:268
msgid "Compressing package"
msgstr ""
-#: pkg/build/build.go:316
+#: pkg/build/build.go:419
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:433
msgid "This package is already installed"
msgstr ""
-#: pkg/build/build.go:355
+#: pkg/build/build.go:457
msgid "Installing build dependencies"
msgstr ""
-#: pkg/build/build.go:397
+#: pkg/build/build.go:498
msgid "Installing dependencies"
msgstr ""
-#: pkg/build/build.go:443
-msgid "Executing version()"
+#: pkg/build/build.go:533
+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:584
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:647
+msgid "Executing prepare()"
+msgstr ""
+
+#: pkg/build/build.go:657
+msgid "Executing build()"
+msgstr ""
+
+#: pkg/build/build.go:687 pkg/build/build.go:707
+msgid "Executing %s()"
+msgstr ""
+
+#: pkg/build/build.go:766
+msgid "Error installing native packages"
+msgstr ""
+
+#: pkg/build/build.go:790
+msgid "Error installing package"
msgstr ""
#: pkg/build/findDeps.go:35
@@ -383,27 +387,27 @@ msgstr ""
msgid "Required dependency found"
msgstr ""
-#: pkg/build/install.go:42
-msgid "Error installing native packages"
+#: pkg/build/utils.go:133
+msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
-#: pkg/build/install.go:79
-msgid "Error installing package"
+#: pkg/build/utils.go:144
+msgid "AutoReq is not implemented for this package format, so it's skipped"
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."
@@ -457,10 +461,10 @@ msgstr ""
msgid "Upgrade all installed packages"
msgstr ""
-#: upgrade.go:83
+#: upgrade.go:90
msgid "Error checking for updates"
msgstr ""
-#: upgrade.go:94
+#: upgrade.go:112
msgid "There is nothing to do."
msgstr ""
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index 3d5e548..7f6d85d 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -16,40 +16,57 @@ msgstr ""
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 47.1\n"
-#: build.go:41
+#: build.go:44
msgid "Build a local package"
msgstr "Сборка локального пакета"
-#: build.go:47
+#: build.go:50
msgid "Path to the build script"
msgstr "Путь к скрипту сборки"
-#: build.go:52
+#: build.go:55
+msgid "Specify package in script (for multi package script only)"
+msgstr ""
+
+#: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
-#: build.go:57
+#: build.go:65
msgid ""
"Build package from scratch even if there's an already built package available"
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
-#: build.go:87
+#: build.go:75
+msgid "Error db init"
+msgstr ""
+
+#: build.go:105
+msgid "Package not found"
+msgstr ""
+
+#: build.go:123
msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев"
-#: build.go:95
+#: build.go:131
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
-#: build.go:107
+#: build.go:137
+#, fuzzy
+msgid "Error parsing os release"
+msgstr "Ошибка при разборе файла выпуска операционной системы"
+
+#: build.go:158
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
-#: build.go:114
+#: build.go:165
msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога"
-#: build.go:123
+#: build.go:174
msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета"
@@ -137,31 +154,31 @@ msgstr "Ошибка устранения переорпеделений"
msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита"
-#: install.go:42
+#: install.go:43
msgid "Install a new package"
msgstr "Установить новый пакет"
-#: install.go:56
+#: install.go:57
msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
-#: install.go:91
+#: install.go:124
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
-#: install.go:100
+#: install.go:133
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
-#: install.go:113
+#: install.go:146
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
-#: install.go:118
+#: install.go:151
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
-#: install.go:130
+#: install.go:163
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
@@ -233,11 +250,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 +324,27 @@ msgstr ""
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
-#: pkg/build/build.go:108
+#: pkg/build/build.go:153
msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
-#: pkg/build/build.go:112
+#: pkg/build/build.go:157
msgid "Building package"
msgstr "Сборка пакета"
-#: pkg/build/build.go:156
+#: pkg/build/build.go:228
msgid "Downloading sources"
msgstr "Скачивание источников"
-#: pkg/build/build.go:168
+#: pkg/build/build.go:246
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
-#: pkg/build/build.go:190
+#: pkg/build/build.go:268
msgid "Compressing package"
msgstr "Сжатие пакета"
-#: pkg/build/build.go:316
+#: pkg/build/build.go:419
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
@@ -335,59 +352,46 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?"
-#: pkg/build/build.go:327
+#: pkg/build/build.go:433
msgid "This package is already installed"
msgstr "Этот пакет уже установлен"
-#: pkg/build/build.go:355
+#: pkg/build/build.go:457
msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки"
-#: pkg/build/build.go:397
+#: pkg/build/build.go:498
msgid "Installing dependencies"
msgstr "Установка зависимостей"
-#: pkg/build/build.go:443
-msgid "Executing version()"
-msgstr "Исполнение версия()"
+#: pkg/build/build.go:533
+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 "Исполнение prepare()"
-
-#: pkg/build/build.go:478
-msgid "Executing build()"
-msgstr "Исполнение build()"
-
-#: pkg/build/build.go:488
-msgid "Executing package()"
-msgstr "Исполнение package()"
-
-#: pkg/build/build.go:510
-msgid "Executing files()"
-msgstr "Исполнение files()"
-
-#: pkg/build/build.go:588
-msgid "AutoProv is not implemented for this package format, so it's skipped"
-msgstr ""
-"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
-
-#: pkg/build/build.go:599
-msgid "AutoReq is not implemented for this package format, so it's skipped"
-msgstr ""
-"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
-
-#: pkg/build/build.go:706
+#: pkg/build/build.go:584
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/build.go:647
+msgid "Executing prepare()"
+msgstr "Исполнение prepare()"
+
+#: pkg/build/build.go:657
+msgid "Executing build()"
+msgstr "Исполнение build()"
+
+#: pkg/build/build.go:687 pkg/build/build.go:707
+#, fuzzy
+msgid "Executing %s()"
+msgstr "Исполнение files()"
+
+#: pkg/build/build.go:766
+msgid "Error installing native packages"
+msgstr "Ошибка при установке нативных пакетов"
+
+#: pkg/build/build.go:790
+msgid "Error installing package"
+msgstr "Ошибка при установке пакета"
#: pkg/build/findDeps.go:35
msgid "Command not found on the system"
@@ -401,27 +405,29 @@ msgstr "Найденная предоставленная зависимость
msgid "Required dependency found"
msgstr "Найдена требуемая зависимость"
-#: pkg/build/install.go:42
-msgid "Error installing native packages"
-msgstr "Ошибка при установке нативных пакетов"
+#: pkg/build/utils.go:133
+msgid "AutoProv is not implemented for this package format, so it's skipped"
+msgstr ""
+"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
-#: pkg/build/install.go:79
-msgid "Error installing package"
-msgstr "Ошибка при установке пакета"
+#: pkg/build/utils.go:144
+msgid "AutoReq is not implemented for this package format, so it's skipped"
+msgstr ""
+"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
-#: 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."
@@ -477,10 +483,19 @@ msgstr "Скачать все изменённые репозитории"
msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты"
-#: upgrade.go:83
+#: upgrade.go:90
msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений"
-#: upgrade.go:94
+#: upgrade.go:112
msgid "There is nothing to do."
msgstr "Здесь нечего делать."
+
+#~ msgid "Executing version()"
+#~ msgstr "Исполнение версия()"
+
+#~ msgid "Updating version"
+#~ msgstr "Обновление версии"
+
+#~ msgid "Executing package()"
+#~ msgstr "Исполнение package()"
diff --git a/internal/types/build.go b/internal/types/build.go
index cef93a0..a975e92 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
+ Packages []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..0ecd9b3 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -23,113 +23,160 @@ 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"
"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"
)
-// Функция BuildPackage выполняет сборку скрипта по указанному пути. Возвращает два среза.
-// Один содержит пути к собранным пакетам, другой - имена собранных пакетов.
-func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) {
- reposInstance := repos.GetInstance(ctx)
+type PackageFinder interface {
+ FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
+}
- info, err := distro.ParseOSRelease(ctx)
- if err != nil {
- return nil, nil, err
+type Config interface {
+ GetPaths(ctx context.Context) *config.Paths
+ PagerStyle(ctx context.Context) string
+}
+
+type Builder struct {
+ ctx context.Context
+ opts types.BuildOpts
+ info *distro.OSRelease
+ repos PackageFinder
+ config Config
+}
+
+func NewBuilder(
+ ctx context.Context,
+ opts types.BuildOpts,
+ repos PackageFinder,
+ info *distro.OSRelease,
+ config Config,
+) *Builder {
+ return &Builder{
+ ctx: ctx,
+ opts: opts,
+ info: info,
+ repos: repos,
+ config: config,
}
+}
- fl, err := parseScript(info, opts.Script)
+func (b *Builder) UpdateOptsFromPkg(pkg *db.Package, packages []string) {
+ repodir := b.config.GetPaths(b.ctx).RepoDir
+ if pkg.BasePkgName != "" {
+ b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.BasePkgName, "alr.sh")
+ b.opts.Packages = packages
+ } else {
+ b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.Name, "alr.sh")
+ }
+}
+
+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)
+ basePkg, varsOfPackages, err := b.executeFirstPass(fl)
if err != nil {
return nil, nil, err
}
- dirs := getDirs(ctx, vars, opts.Script)
+ dirs := b.getDirs(basePkg)
+
+ builtPaths := make([]string, 0)
// Если флаг opts.Clean не установлен, и пакет уже собран,
// возвращаем его, а не собираем заново.
- if !opts.Clean {
- builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir)
- if err != nil {
- return nil, nil, err
+ if !b.opts.Clean {
+ var remainingVars []*types.BuildVars
+
+ for _, vars := range varsOfPackages {
+ builtPkgPath, ok, err := checkForBuiltPackage(
+ b.opts.Manager,
+ vars,
+ getPkgFormat(b.opts.Manager),
+ dirs.BaseDir,
+ b.info,
+ )
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if ok {
+ builtPaths = append(builtPaths, builtPkgPath)
+ } else {
+ remainingVars = append(remainingVars, vars)
+ }
}
- if ok {
- return []string{builtPkgPath}, nil, err
+ if len(remainingVars) == 0 {
+ return builtPaths, nil, nil
}
}
// Спрашиваем у пользователя, хочет ли он увидеть скрипт сборки.
- err = cliutils.PromptViewScript(ctx, opts.Script, vars.Name, config.Config(ctx).PagerStyle, opts.Interactive)
+ err = cliutils.PromptViewScript(
+ ctx,
+ b.opts.Script,
+ basePkg,
+ 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)
}
- slog.Info(gotext.Get("Building package"), "name", vars.Name, "version", vars.Version)
+ slog.Info(gotext.Get("Building package"), "name", basePkg)
// Второй проход будет использоваться для выполнения реального кода,
// поэтому он не ограничен. Скрипт уже был показан
// пользователю к этому моменту, так что это должно быть безопасно.
- 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) // Выполняем различные проверки
- if err != nil {
- return nil, nil, err
- } else if !cont {
- os.Exit(1) // Если проверки не пройдены, выходим из программы
+ for _, vars := range varsOfPackages {
+ cont, err := b.performChecks(ctx, vars, installed) // Выполняем различные проверки
+ if err != nil {
+ return nil, nil, err
+ } else if !cont {
+ os.Exit(1) // Если проверки не пройдены, выходим из программы
+ }
}
// Подготавливаем директории для сборки
@@ -138,72 +185,104 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string
return nil, nil, err
}
- buildDeps, err := installBuildDeps(ctx, reposInstance, vars, opts) // Устанавливаем зависимости для сборки
+ buildDepends := []string{}
+ optDepends := []string{}
+ depends := []string{}
+ sources := []string{}
+ checksums := []string{}
+ for _, vars := range varsOfPackages {
+ buildDepends = append(buildDepends, vars.BuildDepends...)
+ optDepends = append(optDepends, vars.OptDepends...)
+ depends = append(depends, vars.Depends...)
+ sources = append(sources, vars.Sources...)
+ checksums = append(checksums, vars.Checksums...)
+ }
+ buildDepends = removeDuplicates(buildDepends)
+ optDepends = removeDuplicates(optDepends)
+ depends = removeDuplicates(depends)
+ sources = removeDuplicates(sources)
+ checksums = removeDuplicates(checksums)
+
+ mergedVars := types.BuildVars{
+ Sources: sources,
+ Checksums: checksums,
+ }
+
+ buildDeps, err := b.installBuildDeps(ctx, buildDepends) // Устанавливаем зависимости для сборки
if err != nil {
return nil, nil, err
}
- err = installOptDeps(ctx, reposInstance, vars, opts) // Устанавливаем опциональные зависимости
+ err = b.installOptDeps(ctx, optDepends) // Устанавливаем опциональные зависимости
if err != nil {
return nil, nil, err
}
- builtPaths, builtNames, repoDeps, err := buildALRDeps(ctx, opts, vars) // Собираем зависимости
+ newBuildPaths, builtNames, repoDeps, err := b.buildALRDeps(ctx, depends) // Собираем зависимости
if err != nil {
return nil, nil, err
}
+ builtPaths = append(builtPaths, newBuildPaths...)
+
slog.Info(gotext.Get("Downloading sources")) // Записываем в лог загрузку источников
- err = getSources(ctx, dirs, vars) // Загружаем исходники
+ err = b.getSources(ctx, dirs, &mergedVars) // Загружаем исходники
if err != nil {
return nil, nil, err
}
- funcOut, err := executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции
+ err = b.executeFunctions(ctx, dec, dirs) // Выполняем специальные функции
if err != nil {
return nil, nil, err
}
- slog.Info(gotext.Get("Building package metadata"), "name", vars.Name)
+ for _, vars := range varsOfPackages {
+ funcOut, err := b.executePackageFunctions(ctx, dec, dirs, vars.Name)
+ if err != nil {
+ return nil, nil, err
+ }
- pkgFormat := getPkgFormat(opts.Manager) // Получаем формат пакета
+ slog.Info(gotext.Get("Building package metadata"), "name", basePkg)
- pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, info, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета
+ pkgFormat := getPkgFormat(b.opts.Manager) // Получаем формат пакета
+
+ pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, b.info, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета
+ if err != nil {
+ return nil, nil, err
+ }
+
+ packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
+ pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
+
+ pkgFile, err := os.Create(pkgPath) // Создаём файл пакета
+ if err != nil {
+ return nil, nil, err
+ }
+
+ slog.Info(gotext.Get("Compressing package"), "name", pkgName) // Логгируем сжатие пакета
+
+ err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Добавляем путь и имя только что собранного пакета в
+ // соответствующие срезы
+ builtPaths = append(builtPaths, pkgPath)
+ builtNames = append(builtNames, vars.Name)
+ }
+
+ err = b.removeBuildDeps(ctx, buildDeps) // Удаляем зависимости для сборки
if err != nil {
return nil, nil, err
}
- packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
- if err != nil {
- return nil, nil, err
- }
-
- pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
- pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
-
- pkgFile, err := os.Create(pkgPath) // Создаём файл пакета
- if err != nil {
- return nil, nil, err
- }
-
- slog.Info(gotext.Get("Compressing package"), "name", pkgName) // Логгируем сжатие пакета
-
- err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет
- if err != nil {
- return nil, nil, err
- }
-
- err = removeBuildDeps(ctx, buildDeps, opts) // Удаляем зависимости для сборки
- if err != nil {
- return nil, nil, err
- }
-
- // Добавляем путь и имя только что собранного пакета в
- // соответствующие срезы
- builtPaths = append(builtPaths, pkgPath)
- builtNames = append(builtNames, vars.Name)
-
// Удаляем дубликаты из pkgPaths и pkgNames.
// Дубликаты могут появиться, если несколько зависимостей
// зависят от одних и тех же пакетов.
@@ -213,27 +292,15 @@ 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,
+) (string, []*types.BuildVars, error) {
+ varsOfPackages := []*types.BuildVars{}
+
+ scriptDir := filepath.Dir(b.opts.Script) // Получаем директорию скрипта
+ env := createBuildEnvVars(b.info, types.Directories{ScriptDir: scriptDir}) // Создаём переменные окружения для сборки
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
@@ -244,40 +311,87 @@ func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.Fi
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
)
if err != nil {
- return nil, err
+ return "", nil, err
}
- err = runner.Run(ctx, fl) // Запускаем скрипт
+ err = runner.Run(b.ctx, fl) // Запускаем скрипт
if err != nil {
- return nil, err
+ return "", nil, err
}
- dec := decoder.New(info, runner) // Создаём новый декодер
+ dec := decoder.New(b.info, runner) // Создаём новый декодер
+ type packages struct {
+ BasePkgName string `sh:"basepkg_name"`
+ Names []string `sh:"name"`
+ }
+
+ var pkgs packages
+ err = dec.DecodeVars(&pkgs)
+ if err != nil {
+ return "", nil, err
+ }
+ if len(pkgs.Names) == 0 {
+ return "", nil, errors.New("package name is missing")
+ }
var vars types.BuildVars
- err = dec.DecodeVars(&vars) // Декодируем переменные
- if err != nil {
- return nil, err
+ if len(pkgs.Names) == 1 {
+ err = dec.DecodeVars(&vars) // Декодируем переменные
+ if err != nil {
+ return "", nil, err
+ }
+ varsOfPackages = append(varsOfPackages, &vars)
+
+ return vars.Name, varsOfPackages, nil
+ }
+ if len(b.opts.Packages) == 0 {
+ return "", nil, errors.New("script has multiple packages but package is not specified")
}
- return &vars, nil // Возвращаем переменные сборки
+ for _, pkgName := range b.opts.Packages {
+ var preVars types.BuildVarsPre
+ funcName := fmt.Sprintf("meta_%s", pkgName)
+ meta, ok := dec.GetFuncWithSubshell(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 = pkgName
+
+ varsOfPackages = append(varsOfPackages, &vars)
+ }
+
+ return pkgs.BasePkgName, varsOfPackages, 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(basePkg string) types.Directories {
+ baseDir := filepath.Join(b.config.GetPaths(b.ctx).PkgsDir, basePkg) // Определяем базовую директорию
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 +408,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,84 +439,76 @@ 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, buildDepends []string) ([]string, error) {
var buildDeps []string
- if len(vars.BuildDepends) > 0 {
- deps, err := removeAlreadyInstalled(opts, vars.BuildDepends)
+ if len(buildDepends) > 0 {
+ deps, err := removeAlreadyInstalled(b.opts, buildDepends)
if err != nil {
return nil, err
}
- found, notFound, err := repos.FindPkgs(ctx, deps) // Находим пакеты-зависимости
+ found, notFound, err := b.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) // Уплощаем список зависимостей
+ flattened := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive) // Уплощаем список зависимостей
buildDeps = packageNames(flattened)
- InstallPkgs(ctx, flattened, notFound, opts) // Устанавливаем пакеты
+ b.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
+func (b *Builder) getBuildersForPackages(pkgs []db.Package) []*Builder {
+ type item struct {
+ pkg *db.Package
+ packages []string
}
- if len(optDeps) > 0 {
- optDeps, err := cliutils.ChooseOptDepends(ctx, optDeps, "install", opts.Interactive) // Пользователя просят выбрать опциональные зависимости
- if err != nil {
- return err
+ pkgsMap := make(map[string]*item)
+ for _, pkg := range pkgs {
+ if pkgsMap[pkg.BasePkgName] == nil {
+ pkgsMap[pkg.BasePkgName] = &item{
+ pkg: &pkg,
+ }
}
-
- 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) // Устанавливаем выбранные пакеты
+ pkgsMap[pkg.BasePkgName].packages = append(
+ pkgsMap[pkg.BasePkgName].packages,
+ pkg.Name,
+ )
}
- return nil
+
+ builders := []*Builder{}
+
+ for basePkgName := range pkgsMap {
+ pkg := pkgsMap[basePkgName].pkg
+ builder := *b
+ builder.UpdateOptsFromPkg(pkg, pkgsMap[basePkgName].packages)
+ builders = append(builders, &builder)
+ }
+
+ return builders
}
-// Функция buildALRDeps собирает все ALR зависимости пакета. Возвращает пути и имена
-// пакетов, которые она собрала, а также все зависимости, которые не были найдены в ALR репозитории,
-// чтобы они могли быть установлены из системных репозиториев.
-func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
- if len(vars.Depends) > 0 {
+func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPaths, builtNames, repoDeps []string, err error) {
+ if len(depends) > 0 {
slog.Info(gotext.Get("Installing dependencies"))
- found, notFound, err := repos.FindPkgs(ctx, vars.Depends) // Поиск зависимостей
+ found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
if err != nil {
return nil, nil, nil, err
}
repoDeps = notFound
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
- pkgs := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
- scripts := GetScriptPaths(ctx, pkgs)
- for _, script := range scripts {
- newOpts := opts
- newOpts.Script = script
-
+ pkgs := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive)
+ builders := b.getBuildersForPackages(pkgs)
+ for _, builder := range builders {
// Собираем зависимости
- pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts)
+ pkgPaths, pkgNames, err := builder.BuildPackage(ctx)
if err != nil {
return nil, nil, nil, err
}
@@ -419,8 +517,6 @@ func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVa
builtPaths = append(builtPaths, pkgPaths...)
// Добавляем пути всех собранных пакетов в builtPaths
builtNames = append(builtNames, pkgNames...)
- // Добавляем имя текущего пакета в builtNames
- builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
}
}
@@ -432,382 +528,7 @@ func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVa
return builtPaths, builtNames, repoDeps, 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) {
- version, ok := dec.GetFunc("version")
- if ok {
- slog.Info(gotext.Get("Executing version()"))
-
- buf := &bytes.Buffer{}
-
- err := version(
- ctx,
- interp.Dir(dirs.SrcDir),
- interp.StdIO(os.Stdin, buf, os.Stderr),
- )
- if err != nil {
- return nil, err
- }
-
- newVer := strings.TrimSpace(buf.String())
- err = setVersion(ctx, dec.Runner, newVer)
- if err != nil {
- return nil, err
- }
- vars.Version = newVer
-
- slog.Info(gotext.Get("Updating version"), "new", newVer)
- }
-
- prepare, ok := dec.GetFunc("prepare")
- if ok {
- slog.Info(gotext.Get("Executing prepare()"))
-
- err := prepare(ctx, interp.Dir(dirs.SrcDir))
- if err != nil {
- return nil, err
- }
- }
-
- build, ok := dec.GetFunc("build")
- if ok {
- slog.Info(gotext.Get("Executing build()"))
-
- err := build(ctx, interp.Dir(dirs.SrcDir))
- if err != nil {
- return nil, err
- }
- }
-
- packageFn, ok := dec.GetFunc("package")
- if ok {
- slog.Info(gotext.Get("Executing package()"))
- err := packageFn(ctx, interp.Dir(dirs.SrcDir))
- if err != nil {
- return nil, err
- }
- }
-
- 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
- }
-
- contents, err := shlex.Split(buf.String())
- if err != nil {
- return nil, err
- }
- output.Contents = &contents
- }
-
- 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 {
+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)
@@ -843,8 +564,7 @@ func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars
}
}
- cfg := config.GetInstance(ctx)
- opts.DlCache = dlcache.New(cfg)
+ opts.DlCache = dlcache.New(b.config)
err := dl.Download(ctx, opts)
if err != nil {
@@ -855,91 +575,221 @@ func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars
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)
- }
+// Функция 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 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)
+ if remove {
+ err = b.opts.Manager.Remove(
+ &manager.Opts{
+ AsRoot: true,
+ NoConfirm: true,
+ },
+ buildDeps...,
+ )
+ if err != nil {
+ return err
+ }
+ }
}
+ return nil
}
-// Функция 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)
+type FunctionsOutput struct {
+ Contents *[]string
}
-// Returns not installed dependencies
-func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
- filteredPackages := []string{}
+// Функция executeFunctions выполняет специальные функции ALR, такие как version(), prepare() и т.д.
+func (b *Builder) executeFunctions(
+ ctx context.Context,
+ dec *decoder.Decoder,
+ dirs types.Directories,
+) error {
+ /*
+ version, ok := dec.GetFunc("version")
+ if ok {
+ slog.Info(gotext.Get("Executing version()"))
- for _, dep := range dependencies {
- installed, err := opts.Manager.IsInstalled(dep)
+ buf := &bytes.Buffer{}
+
+ err := version(
+ ctx,
+ interp.Dir(dirs.SrcDir),
+ interp.StdIO(os.Stdin, buf, os.Stderr),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ newVer := strings.TrimSpace(buf.String())
+ err = setVersion(ctx, dec.Runner, newVer)
+ if err != nil {
+ return nil, err
+ }
+ vars.Version = newVer
+
+ slog.Info(gotext.Get("Updating version"), "new", newVer)
+ }
+ */
+
+ prepare, ok := dec.GetFunc("prepare")
+ if ok {
+ slog.Info(gotext.Get("Executing prepare()"))
+
+ err := prepare(ctx, interp.Dir(dirs.SrcDir))
+ if err != nil {
+ return err
+ }
+ }
+
+ build, ok := dec.GetFunc("build")
+ if ok {
+ slog.Info(gotext.Get("Executing build()"))
+
+ err := build(ctx, interp.Dir(dirs.SrcDir))
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (b *Builder) executePackageFunctions(
+ ctx context.Context,
+ dec *decoder.Decoder,
+ dirs types.Directories,
+ packageName string,
+) (*FunctionsOutput, error) {
+ output := &FunctionsOutput{}
+ var packageFuncName string
+ var filesFuncName string
+
+ if packageName == "" {
+ filesFuncName = "files"
+ packageFuncName = "package"
+ } else {
+ packageFuncName = fmt.Sprintf("package_%s", packageName)
+ filesFuncName = fmt.Sprintf("files_%s", packageName)
+ }
+ packageFn, ok := dec.GetFunc(packageFuncName)
+ if ok {
+ slog.Info(gotext.Get("Executing %s()", packageFuncName))
+ err := packageFn(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return nil, err
}
- if installed {
- continue
+ }
+
+ 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
+ // - https://github.com/mvdan/sh/issues/1125
+ script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "")
+ if err != nil {
+ return err
}
- filteredPackages = append(filteredPackages, dep)
+ return s.Run(ctx, script)
+ })
+
+ if ok {
+ slog.Info(gotext.Get("Executing %s()", filesFuncName))
+
+ buf := &bytes.Buffer{}
+
+ err := files(
+ ctx,
+ interp.Dir(dirs.PkgDir),
+ interp.StdIO(os.Stdin, buf, os.Stderr),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ contents, err := shlex.Split(buf.String())
+ if err != nil {
+ return nil, err
+ }
+ output.Contents = &contents
}
- return filteredPackages, nil
+ return output, nil
}
-// Функция packageNames возвращает имена всех предоставленных пакетов.
-func packageNames(pkgs []db.Package) []string {
- names := make([]string, len(pkgs))
- for i, p := range pkgs {
- names[i] = p.Name
+func (b *Builder) installOptDeps(ctx context.Context, optDepends []string) error {
+ optDeps, err := removeAlreadyInstalled(b.opts, optDepends)
+ if err != nil {
+ return err
}
- return names
+ if len(optDeps) > 0 {
+ optDeps, err := cliutils.ChooseOptDepends(ctx, optDeps, "install", b.opts.Interactive) // Пользователя просят выбрать опциональные зависимости
+ if err != nil {
+ return err
+ }
+
+ if len(optDeps) == 0 {
+ return nil
+ }
+
+ found, notFound, err := b.repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости
+ if err != nil {
+ return err
+ }
+
+ flattened := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive)
+ b.InstallPkgs(ctx, flattened, notFound, b.opts) // Устанавливаем выбранные пакеты
+ }
+ return nil
}
-// Функция 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)
+func (b *Builder) InstallPkgs(
+ ctx context.Context,
+ alrPkgs []db.Package,
+ nativePkgs []string,
+ opts types.BuildOpts,
+) {
+ if len(nativePkgs) > 0 {
+ err := opts.Manager.Install(nil, nativePkgs...)
+ // Если есть нативные пакеты, выполняем их установку
+ if err != nil {
+ slog.Error(gotext.Get("Error installing native packages"), "err", err)
+ os.Exit(1)
+ // Логируем и завершаем выполнение при ошибке
}
}
- return result
+ b.InstallALRPackages(ctx, alrPkgs, opts)
+ // Устанавливаем скрипты сборки через функцию InstallScripts
+}
+
+func (b *Builder) InstallALRPackages(ctx context.Context, pkgs []db.Package, opts types.BuildOpts) {
+ builders := b.getBuildersForPackages(pkgs)
+ for _, builder := range builders {
+ builtPkgs, _, err := builder.BuildPackage(ctx)
+ // Выполняем сборку пакета
+ if err != nil {
+ slog.Error(gotext.Get("Error building package"), "err", err)
+ os.Exit(1)
+ // Логируем и завершаем выполнение при ошибке сборки
+ }
+
+ err = opts.Manager.InstallLocal(nil, builtPkgs...)
+ // Устанавливаем локально собранные пакеты
+ if err != nil {
+ slog.Error(gotext.Get("Error installing package"), "err", err)
+ os.Exit(1)
+ // Логируем и завершаем выполнение при ошибке установки
+ }
+ }
}
diff --git a/pkg/build/build_internal_test.go b/pkg/build/build_internal_test.go
index e09fac0..10fd80c 100644
--- a/pkg/build/build_internal_test.go
+++ b/pkg/build/build_internal_test.go
@@ -18,10 +18,18 @@ package build
import (
"context"
+ "fmt"
+ "os"
+ "strings"
"testing"
+ "github.com/stretchr/testify/assert"
+ "mvdan.cc/sh/v3/syntax"
+
+ "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/manager"
)
@@ -134,93 +142,145 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) {
return true, nil
}
-// TODO: fix test
-func TestInstallBuildDeps(t *testing.T) {
- type testEnv struct {
- pf PackageFinder
- vars *types.BuildVars
- opts types.BuildOpts
+type TestConfig struct{}
- // Contains pkgs captured by FindPkgsFunc
- // capturedPkgs []string
+func (c *TestConfig) PagerStyle(ctx context.Context) string {
+ return "native"
+}
+
+func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths {
+ return &config.Paths{
+ CacheDir: "/tmp",
+ }
+}
+
+func TestExecuteFirstPassIsSecure(t *testing.T) {
+ cfg := &TestConfig{}
+ pf := &TestPackageFinder{}
+ info := &distro.OSRelease{}
+ m := &TestManager{}
+
+ opts := types.BuildOpts{
+ Manager: m,
+ Interactive: false,
}
+ ctx := context.Background()
+
+ b := NewBuilder(
+ ctx,
+ opts,
+ pf,
+ info,
+ cfg,
+ )
+
+ tmpFile, err := os.CreateTemp("", "testfile-")
+ assert.NoError(t, err)
+ tmpFilePath := tmpFile.Name()
+ defer os.Remove(tmpFilePath)
+
+ _, err = os.Stat(tmpFilePath)
+ assert.NoError(t, err)
+
+ testScript := fmt.Sprintf(`name='test'
+version=1.0.0
+release=1
+rm -f %s`, tmpFilePath)
+
+ fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh")
+ assert.NoError(t, err)
+
+ _, _, err = b.executeFirstPass(fl)
+ assert.NoError(t, err)
+
+ _, err = os.Stat(tmpFilePath)
+ assert.NoError(t, err)
+}
+
+func TestExecuteFirstPassIsCorrect(t *testing.T) {
type testCase struct {
Name string
- Prepare func() *testEnv
- Expected func(t *testing.T, e *testEnv, res []string, err error)
+ Script string
+ Opts types.BuildOpts
+ Expected func(t *testing.T, vars []*types.BuildVars)
}
- for _, tc := range []testCase{
- /*
- {
- Name: "install only needed deps",
- Prepare: func() *testEnv {
- pf := TestPackageFinder{}
- vars := types.BuildVars{}
- m := TestManager{}
- opts := types.BuildOpts{
- Manager: &m,
- Interactive: false,
- }
+ for _, tc := range []testCase{{
+ Name: "single package",
+ Script: `name='test'
+version='1.0.0'
+release=1
+epoch=2
+desc="Test package"
+homepage='https://example.com'
+maintainer='Ivan Ivanov'
+`,
+ Opts: types.BuildOpts{
+ Manager: &TestManager{},
+ Interactive: false,
+ },
+ Expected: func(t *testing.T, vars []*types.BuildVars) {
+ assert.Equal(t, 1, len(vars))
+ assert.Equal(t, vars[0].Name, "test")
+ assert.Equal(t, vars[0].Version, "1.0.0")
+ assert.Equal(t, vars[0].Release, int(1))
+ assert.Equal(t, vars[0].Epoch, uint(2))
+ assert.Equal(t, vars[0].Description, "Test package")
+ },
+ }, {
+ Name: "multiple packages",
+ Script: `name=(
+ foo
+ bar
+)
- env := &testEnv{
- pf: &pf,
- vars: &vars,
- opts: opts,
- capturedPkgs: []string{},
- }
+version='0.0.1'
+release=1
+epoch=2
+desc="Test package"
- pf.FindPkgsFunc = func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
- env.capturedPkgs = append(env.capturedPkgs, pkgs...)
- result := make(map[string][]db.Package)
- result["bar"] = []db.Package{{
- Name: "bar-pkg",
- }}
- result["buz"] = []db.Package{{
- Name: "buz-pkg",
- }}
+meta_foo() {
+ desc="Foo package"
+}
- return result, []string{}, nil
- }
+meta_bar() {
- vars.BuildDepends = []string{
- "foo",
- "bar",
- "buz",
- }
- m.IsInstalledFunc = func(pkg string) (bool, error) {
- if pkg == "foo" {
- return true, nil
- } else {
- return false, nil
- }
- }
+}
+`,
+ Opts: types.BuildOpts{
+ Packages: []string{"foo"},
+ Manager: &TestManager{},
+ Interactive: false,
+ },
+ Expected: func(t *testing.T, vars []*types.BuildVars) {
+ assert.Equal(t, 1, len(vars))
+ assert.Equal(t, vars[0].Name, "foo")
+ assert.Equal(t, vars[0].Description, "Foo package")
+ },
+ }} {
+ t.Run(tc.Name, func(t *testing.T) {
+ cfg := &TestConfig{}
+ pf := &TestPackageFinder{}
+ info := &distro.OSRelease{}
- return env
- },
- Expected: func(t *testing.T, e *testEnv, res []string, err error) {
- assert.NoError(t, err)
- assert.Len(t, res, 2)
- assert.ElementsMatch(t, res, []string{"bar-pkg", "buz-pkg"})
-
- assert.ElementsMatch(t, e.capturedPkgs, []string{"bar", "buz"})
- },
- },
- */
- } {
- t.Run(tc.Name, func(tt *testing.T) {
ctx := context.Background()
- env := tc.Prepare()
- result, err := installBuildDeps(
+ b := NewBuilder(
ctx,
- env.pf,
- env.vars,
- env.opts,
+ tc.Opts,
+ pf,
+ info,
+ cfg,
)
- tc.Expected(tt, env, result, err)
+ fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
+ assert.NoError(t, err)
+
+ _, allVars, err := b.executeFirstPass(fl)
+ assert.NoError(t, err)
+
+ tc.Expected(t, allVars)
})
}
}
diff --git a/pkg/build/install.go b/pkg/build/install.go
deleted file mode 100644
index 902fb93..0000000
--- a/pkg/build/install.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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"
- "log/slog"
- "os"
- "path/filepath"
-
- "github.com/leonelquinteros/gotext"
-
- "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"
-)
-
-// InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов,
-// затем строит и устанавливает пакеты ALR
-func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string, opts types.BuildOpts) {
- if len(nativePkgs) > 0 {
- err := opts.Manager.Install(nil, nativePkgs...)
- // Если есть нативные пакеты, выполняем их установку
- if err != nil {
- slog.Error(gotext.Get("Error installing native packages"), "err", err)
- os.Exit(1)
- // Логируем и завершаем выполнение при ошибке
- }
- }
-
- InstallScripts(ctx, GetScriptPaths(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)
- }
- 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)
- // Выполняем сборку пакета
- if err != nil {
- slog.Error(gotext.Get("Error building package"), "err", err)
- os.Exit(1)
- // Логируем и завершаем выполнение при ошибке сборки
- }
-
- err = opts.Manager.InstallLocal(nil, builtPkgs...)
- // Устанавливаем локально собранные пакеты
- if err != nil {
- slog.Error(gotext.Get("Error installing package"), "err", err)
- os.Exit(1)
- // Логируем и завершаем выполнение при ошибке установки
- }
- }
-}
diff --git a/pkg/build/utils.go b/pkg/build/utils.go
new file mode 100644
index 0000000..3b3ba49
--- /dev/null
+++ b/pkg/build/utils.go
@@ -0,0 +1,423 @@
+// 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/syntax"
+
+ "github.com/goreleaser/nfpm/v2"
+ "github.com/goreleaser/nfpm/v2/files"
+
+ "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) // Создаем директорию для пакетов
+}
+
+// Функция 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, info)
+ pkgInfo.Description = vars.Description
+ pkgInfo.Platform = "linux"
+ pkgInfo.Homepage = vars.Homepage
+ pkgInfo.License = strings.Join(vars.Licenses, ", ")
+ pkgInfo.Maintainer = vars.Maintainer
+ pkgInfo.Overridables = nfpm.Overridables{
+ Conflicts: vars.Conflicts,
+ Replaces: vars.Replaces,
+ Provides: vars.Provides,
+ Depends: deps,
+ }
+
+ if pkgFormat == "apk" {
+ // Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
+ pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
+ return s == pkgInfo.Name
+ })
+ }
+
+ if 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,
+ info *distro.OSRelease,
+) (string, bool, error) {
+ filename, err := pkgFileName(vars, pkgFormat, info)
+ 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, info *distro.OSRelease) *nfpm.Info {
+ return &nfpm.Info{
+ Name: vars.Name,
+ Arch: cpu.Arch(),
+ Version: vars.Version,
+ Release: overrides.ReleasePlatformSpecific(vars.Release, info),
+ 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, info *distro.OSRelease) (string, error) {
+ pkgInfo := getBasePkgInfo(vars, info)
+
+ 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/repos/pull.go b/pkg/repos/pull.go
index 1d41a52..98a86fd 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.GetFuncWithSubshell(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..81b3bb6 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"
)
@@ -36,15 +35,6 @@ func Pull(ctx context.Context, repos []types.Repo) error {
return GetInstance(ctx).Pull(ctx, repos)
}
-// FindPkgs looks for packages matching the inputs inside the database.
-// It returns a map that maps the package name input to any packages found for it.
-// 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) {
- return GetInstance(ctx).FindPkgs(ctx, pkgs)
-}
-
// =======================
// FOR LEGACY ONLY
// =======================
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",
diff --git a/scripts/install.sh b/scripts/install.sh
index 1fd7a72..c642b4a 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,3 +1,22 @@
+# 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 .
+
info() {
echo $'\x1b[32m[ИНФО]\x1b[0m' $@
}
diff --git a/upgrade.go b/upgrade.go
index e7b152a..553cf41 100644
--- a/upgrade.go
+++ b/upgrade.go
@@ -32,7 +32,7 @@ import (
"golang.org/x/exp/slices"
"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/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
@@ -56,7 +56,14 @@ func UpgradeCmd() *cli.Command {
Action: func(c *cli.Context) error {
ctx := c.Context
- cfg := config.GetInstance(ctx)
+ 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)
+ }
info, err := distro.ParseOSRelease(ctx)
if err != nil {
@@ -71,21 +78,32 @@ func UpgradeCmd() *cli.Command {
}
if cfg.AutoPull(ctx) {
- err = repos.Pull(ctx, config.Config(ctx).Repos)
+ err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
}
}
- updates, err := checkForUpdates(ctx, mgr, info)
+ updates, err := checkForUpdates(ctx, mgr, cfg, rs, info)
if err != nil {
slog.Error(gotext.Get("Error checking for updates"), "err", err)
os.Exit(1)
}
if len(updates) > 0 {
- build.InstallPkgs(ctx, updates, nil, types.BuildOpts{
+ builder := build.NewBuilder(
+ ctx,
+ types.BuildOpts{
+ Manager: mgr,
+ Clean: c.Bool("clean"),
+ Interactive: c.Bool("interactive"),
+ },
+ rs,
+ info,
+ cfg,
+ )
+ builder.InstallPkgs(ctx, updates, nil, types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
@@ -99,27 +117,33 @@ func UpgradeCmd() *cli.Command {
}
}
-func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
+func checkForUpdates(
+ ctx context.Context,
+ mgr manager.Manager,
+ cfg *config.ALRConfig,
+ rs *repos.Repos,
+ info *distro.OSRelease,
+) ([]database.Package, error) {
installed, err := mgr.ListInstalled(nil)
if err != nil {
return nil, err
}
pkgNames := maps.Keys(installed)
- found, _, err := repos.FindPkgs(ctx, pkgNames)
+ found, _, err := rs.FindPkgs(ctx, pkgNames)
if err != nil {
return nil, err
}
- var out []db.Package
+ var out []database.Package
for pkgName, pkgs := range found {
- if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkgName) {
+ if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkgName) {
continue
}
if len(pkgs) > 1 {
// Puts the element with the highest version first
- slices.SortFunc(pkgs, func(a, b db.Package) int {
+ slices.SortFunc(pkgs, func(a, b database.Package) int {
return vercmp.Compare(a.Version, b.Version)
})
}