refactor: move alr.sh parsing to pkg

This commit is contained in:
2025-06-09 17:56:46 +03:00
parent 237e2c338d
commit 1cdab8dfed
12 changed files with 389 additions and 190 deletions

View File

@ -11,7 +11,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text> <text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">16.9%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">16.7%</text>
<text x="86" y="14">16.9%</text> <text x="86" y="14">16.7%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

@ -27,14 +27,13 @@ import (
"log/slog" "log/slog"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/syntax/typedjson"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "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/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/distro" "gitea.plemya-x.ru/Plemya-x/ALR/internal/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" "gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
@ -133,47 +132,6 @@ type RepositoryProvider interface {
// ================================================ // ================================================
type ScriptFile struct {
File *syntax.File
Path string
}
func (s *ScriptFile) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s.Path); err != nil {
return nil, err
}
var fileBuf bytes.Buffer
if err := typedjson.Encode(&fileBuf, s.File); err != nil {
return nil, err
}
fileData := fileBuf.Bytes()
if err := enc.Encode(fileData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *ScriptFile) GobDecode(data []byte) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&s.Path); err != nil {
return err
}
var fileData []byte
if err := dec.Decode(&fileData); err != nil {
return err
}
fileReader := bytes.NewReader(fileData)
file, err := typedjson.Decode(fileReader)
if err != nil {
return err
}
s.File = file.(*syntax.File)
return nil
}
type BuiltDep struct { type BuiltDep struct {
Name string Name string
Path string Path string
@ -219,8 +177,8 @@ type ScriptResolverExecutor interface {
} }
type ScriptExecutor interface { type ScriptExecutor interface {
ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ALRSh, error)
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh) (string, []*types.BuildVars, error)
PrepareDirs( PrepareDirs(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
@ -229,7 +187,7 @@ type ScriptExecutor interface {
ExecuteSecondPass( ExecuteSecondPass(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
sf *ScriptFile, sf *alrsh.ALRSh,
varsOfPackages []*types.BuildVars, varsOfPackages []*types.BuildVars,
repoDeps []string, repoDeps []string,
builtDeps []*BuiltDep, builtDeps []*BuiltDep,
@ -242,7 +200,7 @@ type CacheExecutor interface {
} }
type ScriptViewerExecutor interface { type ScriptViewerExecutor interface {
ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh, basePkg string) error
} }
type CheckerExecutor interface { type CheckerExecutor interface {

View File

@ -28,6 +28,7 @@ import (
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
@ -50,13 +51,13 @@ type ScriptExecutorRPCServer struct {
// ReadScript // ReadScript
// //
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ALRSh, error) {
var resp *ScriptFile var resp *alrsh.ALRSh
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp) err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
return resp, err return resp, err
} }
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error { func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ALRSh) error {
file, err := s.Impl.ReadScript(context.Background(), scriptPath) file, err := s.Impl.ReadScript(context.Background(), scriptPath)
if err != nil { if err != nil {
return err return err
@ -72,7 +73,7 @@ func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile
type ExecuteFirstPassArgs struct { type ExecuteFirstPassArgs struct {
Input *BuildInput Input *BuildInput
Sf *ScriptFile Sf *alrsh.ALRSh
} }
type ExecuteFirstPassResp struct { type ExecuteFirstPassResp struct {
@ -80,7 +81,7 @@ type ExecuteFirstPassResp struct {
VarsOfPackages []*types.BuildVars VarsOfPackages []*types.BuildVars
} }
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh) (string, []*types.BuildVars, error) {
var resp *ExecuteFirstPassResp var resp *ExecuteFirstPassResp
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{ err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
Input: input, Input: input,
@ -148,7 +149,7 @@ func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *stru
type ExecuteSecondPassArgs struct { type ExecuteSecondPassArgs struct {
Input *BuildInput Input *BuildInput
Sf *ScriptFile Sf *alrsh.ALRSh
VarsOfPackages []*types.BuildVars VarsOfPackages []*types.BuildVars
RepoDeps []string RepoDeps []string
BuiltDeps []*BuiltDep BuiltDeps []*BuiltDep
@ -158,7 +159,7 @@ type ExecuteSecondPassArgs struct {
func (s *ScriptExecutorRPC) ExecuteSecondPass( func (s *ScriptExecutorRPC) ExecuteSecondPass(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
sf *ScriptFile, sf *alrsh.ALRSh,
varsOfPackages []*types.BuildVars, varsOfPackages []*types.BuildVars,
repoDeps []string, repoDeps []string,
builtDeps []*BuiltDep, builtDeps []*BuiltDep,

View File

@ -19,7 +19,6 @@ package build
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
@ -37,10 +36,10 @@ import (
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps" finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" "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/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
@ -54,102 +53,12 @@ func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
} }
} }
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ALRSh, error) {
fl, err := readScript(scriptPath) return alrsh.ReadFromLocal(scriptPath)
if err != nil {
return nil, err
}
return &ScriptFile{
Path: scriptPath,
File: fl,
}, nil
} }
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh) (string, []*types.BuildVars, error) {
varsOfPackages := []*types.BuildVars{} return sf.ParseBuildVars(ctx, input.info, input.packages)
scriptDir := filepath.Dir(sf.Path)
env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir})
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
interp.Dir(scriptDir),
)
if err != nil {
return "", nil, err
}
err = runner.Run(ctx, sf.File) // Запускаем скрипт
if err != nil {
return "", nil, err
}
dec := decoder.New(input.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
if len(pkgs.Names) == 1 {
err = dec.DecodeVars(&vars) // Декодируем переменные
if err != nil {
return "", nil, err
}
varsOfPackages = append(varsOfPackages, &vars)
return vars.Name, varsOfPackages, nil
}
var pkgNames []string
if len(input.packages) != 0 {
pkgNames = input.packages
} else {
pkgNames = pkgs.Names
}
for _, pkgName := range pkgNames {
var preVars types.BuildVarsPre
funcName := fmt.Sprintf("meta_%s", pkgName)
meta, ok := dec.GetFuncWithSubshell(funcName)
if !ok {
return "", nil, fmt.Errorf("func %s is missing", funcName)
}
r, err := meta(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
vars.Base = pkgs.BasePkgName
varsOfPackages = append(varsOfPackages, &vars)
}
return pkgs.BasePkgName, varsOfPackages, nil
} }
func (e *LocalScriptExecutor) PrepareDirs( func (e *LocalScriptExecutor) PrepareDirs(
@ -177,13 +86,13 @@ func (e *LocalScriptExecutor) PrepareDirs(
func (e *LocalScriptExecutor) ExecuteSecondPass( func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
sf *ScriptFile, sf *alrsh.ALRSh,
varsOfPackages []*types.BuildVars, varsOfPackages []*types.BuildVars,
repoDeps []string, repoDeps []string,
builtDeps []*BuiltDep, builtDeps []*BuiltDep,
basePkg string, basePkg string,
) ([]*BuiltDep, error) { ) ([]*BuiltDep, error) {
dirs, err := getDirs(e.cfg, sf.Path, basePkg) dirs, err := getDirs(e.cfg, sf.Path(), basePkg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -201,7 +110,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
return nil, err return nil, err
} }
err = runner.Run(ctx, sf.File) err = runner.Run(ctx, sf.File())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -20,6 +20,7 @@ import (
"context" "context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
) )
type ScriptViewerConfig interface { type ScriptViewerConfig interface {
@ -33,12 +34,12 @@ type ScriptViewer struct {
func (s *ScriptViewer) ViewScript( func (s *ScriptViewer) ViewScript(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
sf *ScriptFile, a *alrsh.ALRSh,
basePkg string, basePkg string,
) error { ) error {
return cliutils.PromptViewScript( return cliutils.PromptViewScript(
ctx, ctx,
sf.Path, a.Path(),
basePkg, basePkg,
s.config.PagerStyle(), s.config.PagerStyle(),
input.opts.Interactive, input.opts.Interactive,

View File

@ -33,7 +33,6 @@ import (
_ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb" _ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm" _ "github.com/goreleaser/nfpm/v2/rpm"
"mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files" "github.com/goreleaser/nfpm/v2/files"
@ -45,22 +44,6 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
// Функция 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 подготавливает директории для сборки. // Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error { func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует

View File

@ -178,19 +178,19 @@ msgstr ""
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
#: internal/build/build.go:417 #: internal/build/build.go:375
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: internal/build/build.go:446 #: internal/build/build.go:404
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: internal/build/build.go:488 #: internal/build/build.go:446
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: internal/build/build.go:580 #: internal/build/build.go:538
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "" msgstr ""
@ -224,19 +224,19 @@ msgstr ""
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
#: internal/build/script_executor.go:236 #: internal/build/script_executor.go:145
msgid "Building package metadata" msgid "Building package metadata"
msgstr "" msgstr ""
#: internal/build/script_executor.go:366 #: internal/build/script_executor.go:275
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: internal/build/script_executor.go:375 #: internal/build/script_executor.go:284
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: internal/build/script_executor.go:404 internal/build/script_executor.go:424 #: internal/build/script_executor.go:313 internal/build/script_executor.go:333
msgid "Executing %s()" msgid "Executing %s()"
msgstr "" msgstr ""

View File

@ -185,19 +185,19 @@ msgstr "Для команды remove ожидался хотя бы 1 аргум
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr "Ошибка при удалении пакетов"
#: internal/build/build.go:417 #: internal/build/build.go:375
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: internal/build/build.go:446 #: internal/build/build.go:404
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники" msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: internal/build/build.go:488 #: internal/build/build.go:446
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: internal/build/build.go:580 #: internal/build/build.go:538
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr "Установка зависимостей"
@ -235,19 +235,19 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: internal/build/script_executor.go:236 #: internal/build/script_executor.go:145
msgid "Building package metadata" msgid "Building package metadata"
msgstr "Сборка метаданных пакета" msgstr "Сборка метаданных пакета"
#: internal/build/script_executor.go:366 #: internal/build/script_executor.go:275
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "Выполнение prepare()" msgstr "Выполнение prepare()"
#: internal/build/script_executor.go:375 #: internal/build/script_executor.go:284
msgid "Executing build()" msgid "Executing build()"
msgstr "Выполнение build()" msgstr "Выполнение build()"
#: internal/build/script_executor.go:404 internal/build/script_executor.go:424 #: internal/build/script_executor.go:313 internal/build/script_executor.go:333
msgid "Executing %s()" msgid "Executing %s()"
msgstr "Выполнение %s()" msgstr "Выполнение %s()"

168
pkg/alrsh/alrsh.go Normal file
View File

@ -0,0 +1,168 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package alrsh
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/distro"
"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/pkg/types"
)
type ALRSh struct {
file *syntax.File
path string
}
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
}
func (s *ALRSh) ParseBuildVars(ctx context.Context, info *distro.OSRelease, packages []string) (string, []*types.BuildVars, error) {
varsOfPackages := []*types.BuildVars{}
scriptDir := filepath.Dir(s.path)
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir})
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
interp.Dir(scriptDir),
)
if err != nil {
return "", nil, err
}
err = runner.Run(ctx, s.file) // Запускаем скрипт
if err != nil {
return "", nil, err
}
dec := decoder.New(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
if len(pkgs.Names) == 1 {
err = dec.DecodeVars(&vars)
if err != nil {
return "", nil, err
}
varsOfPackages = append(varsOfPackages, &vars)
return vars.Name, varsOfPackages, nil
}
var pkgNames []string
if len(packages) != 0 {
pkgNames = packages
} else {
pkgNames = pkgs.Names
}
for _, pkgName := range pkgNames {
var preVars types.BuildVarsPre
funcName := fmt.Sprintf("meta_%s", pkgName)
meta, ok := dec.GetFuncWithSubshell(funcName)
if !ok {
return "", nil, fmt.Errorf("func %s is missing", funcName)
}
r, err := meta(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
vars.Base = pkgs.BasePkgName
varsOfPackages = append(varsOfPackages, &vars)
}
return pkgs.BasePkgName, varsOfPackages, nil
}
func (a *ALRSh) Path() string {
return a.path
}
func (a *ALRSh) File() *syntax.File {
return a.file
}

61
pkg/alrsh/gob.go Normal file
View File

@ -0,0 +1,61 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package alrsh
import (
"bytes"
"encoding/gob"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/syntax/typedjson"
)
func (s *ALRSh) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s.path); err != nil {
return nil, err
}
var fileBuf bytes.Buffer
if err := typedjson.Encode(&fileBuf, s.file); err != nil {
return nil, err
}
fileData := fileBuf.Bytes()
if err := enc.Encode(fileData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *ALRSh) GobDecode(data []byte) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&s.path); err != nil {
return err
}
var fileData []byte
if err := dec.Decode(&fileData); err != nil {
return err
}
fileReader := bytes.NewReader(fileData)
file, err := typedjson.Decode(fileReader)
if err != nil {
return err
}
s.file = file.(*syntax.File)
return nil
}

52
pkg/alrsh/read.go Normal file
View File

@ -0,0 +1,52 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package alrsh
import (
"fmt"
"io/fs"
"os"
"mvdan.cc/sh/v3/syntax"
)
type localFs struct{}
func (fs *localFs) Open(name string) (fs.File, error) {
return os.Open(name)
}
func ReadFromFS(fsys fs.FS, script string) (*ALRSh, error) {
fl, err := fsys.Open(script)
if err != nil {
return nil, fmt.Errorf("failed to open alr.sh: %w", err)
}
defer fl.Close()
file, err := syntax.NewParser().Parse(fl, "alr.sh")
if err != nil {
return nil, err
}
return &ALRSh{
file: file,
path: script,
}, nil
}
func ReadFromLocal(script string) (*ALRSh, error) {
return ReadFromFS(&localFs{}, script)
}

66
pkg/types/script.go Normal file
View File

@ -0,0 +1,66 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"encoding/gob"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/syntax/typedjson"
)
type ScriptFile struct {
File *syntax.File
Path string
}
func (s *ScriptFile) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s.Path); err != nil {
return nil, err
}
var fileBuf bytes.Buffer
if err := typedjson.Encode(&fileBuf, s.File); err != nil {
return nil, err
}
fileData := fileBuf.Bytes()
if err := enc.Encode(fileData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *ScriptFile) GobDecode(data []byte) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&s.Path); err != nil {
return err
}
var fileData []byte
if err := dec.Decode(&fileData); err != nil {
return err
}
fileReader := bytes.NewReader(fileData)
file, err := typedjson.Decode(fileReader)
if err != nil {
return err
}
s.File = file.(*syntax.File)
return nil
}