forked from Plemya-x/ALR
refactor: keep only one struct for package
This commit is contained in:
@ -38,7 +38,7 @@ import (
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
)
|
||||
|
||||
type ALRSh struct {
|
||||
type ScriptFile struct {
|
||||
file *syntax.File
|
||||
path string
|
||||
}
|
||||
@ -72,97 +72,134 @@ func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string
|
||||
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),
|
||||
)
|
||||
func (s *ScriptFile) ParseBuildVars(ctx context.Context, info *distro.OSRelease, packages []string) (string, []*Package, error) {
|
||||
runner, err := s.createRunner(info)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, s.file) // Запускаем скрипт
|
||||
if err := runScript(ctx, runner, s.file); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
dec := newDecoder(info, runner)
|
||||
|
||||
pkgNames, err := ParseNames(dec)
|
||||
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 {
|
||||
if len(pkgNames.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
|
||||
targetPackages := packages
|
||||
if len(targetPackages) == 0 {
|
||||
targetPackages = pkgNames.Names
|
||||
}
|
||||
|
||||
var pkgNames []string
|
||||
|
||||
if len(packages) != 0 {
|
||||
pkgNames = packages
|
||||
} else {
|
||||
pkgNames = pkgs.Names
|
||||
varsOfPackages, err := s.createPackagesForBuildVars(ctx, dec, pkgNames, targetPackages)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
baseName := pkgNames.BasePkgName
|
||||
if len(pkgNames.Names) == 1 {
|
||||
baseName = pkgNames.Names[0]
|
||||
}
|
||||
|
||||
return pkgs.BasePkgName, varsOfPackages, nil
|
||||
return baseName, varsOfPackages, nil
|
||||
}
|
||||
|
||||
func (a *ALRSh) Path() string {
|
||||
func (s *ScriptFile) createRunner(info *distro.OSRelease) (*interp.Runner, error) {
|
||||
scriptDir := filepath.Dir(s.path)
|
||||
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir})
|
||||
|
||||
return 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),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *ScriptFile) createPackagesForBuildVars(
|
||||
ctx context.Context,
|
||||
dec *decoder.Decoder,
|
||||
pkgNames *PackageNames,
|
||||
targetPackages []string,
|
||||
) ([]*Package, error) {
|
||||
var varsOfPackages []*Package
|
||||
|
||||
if len(pkgNames.Names) == 1 {
|
||||
var pkg Package
|
||||
pkg.Name = pkgNames.Names[0]
|
||||
if err := dec.DecodeVars(&pkg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
varsOfPackages = append(varsOfPackages, &pkg)
|
||||
return varsOfPackages, nil
|
||||
}
|
||||
|
||||
for _, pkgName := range targetPackages {
|
||||
pkg, err := s.createPackageFromMeta(ctx, dec, pkgName, pkgNames.BasePkgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
varsOfPackages = append(varsOfPackages, pkg)
|
||||
}
|
||||
|
||||
return varsOfPackages, nil
|
||||
}
|
||||
|
||||
func (s *ScriptFile) createPackageFromMeta(
|
||||
ctx context.Context,
|
||||
dec *decoder.Decoder,
|
||||
pkgName, basePkgName string,
|
||||
) (*Package, error) {
|
||||
funcName := fmt.Sprintf("meta_%s", pkgName)
|
||||
meta, ok := dec.GetFuncWithSubshell(funcName)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("func %s is missing", funcName)
|
||||
}
|
||||
|
||||
metaRunner, err := meta(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metaDecoder := decoder.New(&distro.OSRelease{}, metaRunner)
|
||||
|
||||
var vars Package
|
||||
if err := metaDecoder.DecodeVars(&vars); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vars.Name = pkgName
|
||||
vars.BasePkgName = basePkgName
|
||||
|
||||
return &vars, nil
|
||||
}
|
||||
|
||||
func runScript(ctx context.Context, runner *interp.Runner, fl *syntax.File) error {
|
||||
runner.Reset()
|
||||
return runner.Run(ctx, fl)
|
||||
}
|
||||
|
||||
func newDecoder(info *distro.OSRelease, runner *interp.Runner) *decoder.Decoder {
|
||||
d := decoder.New(info, runner)
|
||||
// d.Overrides = false
|
||||
// d.LikeDistros = false
|
||||
return d
|
||||
}
|
||||
|
||||
func (a *ScriptFile) Path() string {
|
||||
return a.path
|
||||
}
|
||||
|
||||
func (a *ALRSh) File() *syntax.File {
|
||||
func (a *ScriptFile) File() *syntax.File {
|
||||
return a.file
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
"mvdan.cc/sh/v3/syntax/typedjson"
|
||||
)
|
||||
|
||||
func (s *ALRSh) GobEncode() ([]byte, error) {
|
||||
func (s *ScriptFile) GobEncode() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
if err := enc.Encode(s.path); err != nil {
|
||||
@ -41,7 +41,7 @@ func (s *ALRSh) GobEncode() ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *ALRSh) GobDecode(data []byte) error {
|
||||
func (s *ScriptFile) GobDecode(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := gob.NewDecoder(buf)
|
||||
if err := dec.Decode(&s.path); err != nil {
|
||||
|
146
pkg/alrsh/overridable.go
Normal file
146
pkg/alrsh/overridable.go
Normal file
@ -0,0 +1,146 @@
|
||||
// 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"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type OverridableField[T any] struct {
|
||||
data map[string]T
|
||||
// It can't be a pointer
|
||||
//
|
||||
// See https://gitea.com/xorm/xorm/issues/2431
|
||||
resolved T
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) Set(key string, value T) {
|
||||
if f.data == nil {
|
||||
f.data = make(map[string]T)
|
||||
}
|
||||
f.data[key] = value
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) Get(key string) T {
|
||||
if f.data == nil {
|
||||
f.data = make(map[string]T)
|
||||
}
|
||||
return f.data[key]
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) Has(key string) (T, bool) {
|
||||
if f.data == nil {
|
||||
f.data = make(map[string]T)
|
||||
}
|
||||
val, ok := f.data[key]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) SetResolved(value T) {
|
||||
f.resolved = value
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) Resolved() T {
|
||||
return f.resolved
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) All() map[string]T {
|
||||
return f.data
|
||||
}
|
||||
|
||||
func (o *OverridableField[T]) Resolve(overrides []string) {
|
||||
for _, override := range overrides {
|
||||
if v, ok := o.Has(override); ok {
|
||||
o.SetResolved(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) ToDB() ([]byte, error) {
|
||||
var data map[string]T
|
||||
|
||||
if f.data == nil {
|
||||
data = make(map[string]T)
|
||||
} else {
|
||||
data = f.data
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) FromDB(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
*f = OverridableField[T]{data: make(map[string]T)}
|
||||
return nil
|
||||
}
|
||||
|
||||
var temp map[string]T
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if temp == nil {
|
||||
temp = make(map[string]T)
|
||||
}
|
||||
|
||||
*f = OverridableField[T]{data: temp}
|
||||
return nil
|
||||
}
|
||||
|
||||
type overridableFieldGobPayload[T any] struct {
|
||||
Data map[string]T
|
||||
Resolved T
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) GobEncode() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
|
||||
payload := overridableFieldGobPayload[T]{
|
||||
Data: f.data,
|
||||
Resolved: f.resolved,
|
||||
}
|
||||
|
||||
if err := enc.Encode(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) GobDecode(data []byte) error {
|
||||
dec := gob.NewDecoder(bytes.NewBuffer(data))
|
||||
|
||||
var payload overridableFieldGobPayload[T]
|
||||
if err := dec.Decode(&payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.data = payload.Data
|
||||
f.resolved = payload.Resolved
|
||||
return nil
|
||||
}
|
||||
|
||||
func OverridableFromMap[T any](data map[string]T) OverridableField[T] {
|
||||
if data == nil {
|
||||
data = make(map[string]T)
|
||||
}
|
||||
return OverridableField[T]{
|
||||
data: data,
|
||||
}
|
||||
}
|
105
pkg/alrsh/package.go
Normal file
105
pkg/alrsh/package.go
Normal file
@ -0,0 +1,105 @@
|
||||
// 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"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||
)
|
||||
|
||||
type PackageNames struct {
|
||||
BasePkgName string `sh:"basepkg_name"`
|
||||
Names []string `sh:"name"`
|
||||
}
|
||||
|
||||
func ParseNames(dec *decoder.Decoder) (*PackageNames, error) {
|
||||
var pkgs PackageNames
|
||||
err := dec.DecodeVars(&pkgs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail parse names: %w", err)
|
||||
}
|
||||
return &pkgs, nil
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Repository string `xorm:"pk 'repository'"`
|
||||
Name string `xorm:"pk 'name'"`
|
||||
BasePkgName string `xorm:"notnull 'basepkg_name'"`
|
||||
|
||||
Version string `sh:"version" xorm:"notnull 'version'"`
|
||||
Release int `sh:"release" xorm:"notnull 'release'"`
|
||||
Epoch uint `sh:"epoch" xorm:"'epoch'"`
|
||||
Architectures []string `sh:"architectures" xorm:"json 'architectures'"`
|
||||
Licenses []string `sh:"license" xorm:"json 'licenses'"`
|
||||
Provides []string `sh:"provides" xorm:"json 'provides'"`
|
||||
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'"`
|
||||
Replaces []string `sh:"replaces" xorm:"json 'replaces'"`
|
||||
|
||||
Summary OverridableField[string] `sh:"summary" xorm:"'summary'"`
|
||||
Description OverridableField[string] `sh:"desc" xorm:"'description'"`
|
||||
Group OverridableField[string] `sh:"group" xorm:"'group_name'"`
|
||||
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'"`
|
||||
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'"`
|
||||
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'"`
|
||||
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'"`
|
||||
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'"`
|
||||
Sources OverridableField[[]string] `sh:"sources" xorm:"-"`
|
||||
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-"`
|
||||
Backup OverridableField[[]string] `sh:"backup" xorm:"-"`
|
||||
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-"`
|
||||
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-"`
|
||||
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
|
||||
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
|
||||
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
|
||||
}
|
||||
|
||||
type Scripts struct {
|
||||
PreInstall string `sh:"preinstall"`
|
||||
PostInstall string `sh:"postinstall"`
|
||||
PreRemove string `sh:"preremove"`
|
||||
PostRemove string `sh:"postremove"`
|
||||
PreUpgrade string `sh:"preupgrade"`
|
||||
PostUpgrade string `sh:"postupgrade"`
|
||||
PreTrans string `sh:"pretrans"`
|
||||
PostTrans string `sh:"posttrans"`
|
||||
}
|
||||
|
||||
func ResolvePackage(p *Package, overrides []string) {
|
||||
val := reflect.ValueOf(p).Elem()
|
||||
typ := val.Type()
|
||||
|
||||
for i := range val.NumField() {
|
||||
field := val.Field(i)
|
||||
fieldType := typ.Field(i)
|
||||
|
||||
if !field.CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Struct && strings.HasPrefix(fieldType.Type.String(), "alrsh.OverridableField") {
|
||||
of := field.Addr().Interface()
|
||||
if res, ok := of.(interface {
|
||||
Resolve([]string)
|
||||
}); ok {
|
||||
res.Resolve(overrides)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package alrsh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
@ -30,23 +31,27 @@ func (fs *localFs) Open(name string) (fs.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
func ReadFromFS(fsys fs.FS, script string) (*ALRSh, error) {
|
||||
func ReadFromIOReader(r io.Reader, script string) (*ScriptFile, error) {
|
||||
file, err := syntax.NewParser().Parse(r, "alr.sh")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ScriptFile{
|
||||
file: file,
|
||||
path: script,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadFromFS(fsys fs.FS, script string) (*ScriptFile, 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
|
||||
return ReadFromIOReader(fl, script)
|
||||
}
|
||||
|
||||
func ReadFromLocal(script string) (*ALRSh, error) {
|
||||
func ReadFromLocal(script string) (*ScriptFile, error) {
|
||||
return ReadFromFS(&localFs{}, script)
|
||||
}
|
||||
|
@ -24,49 +24,6 @@ type BuildOpts struct {
|
||||
Interactive bool
|
||||
}
|
||||
|
||||
type BuildVarsPre struct {
|
||||
Version string `sh:"version,required"`
|
||||
Release int `sh:"release,required"`
|
||||
Epoch uint `sh:"epoch"`
|
||||
Summary string `sh:"summary"`
|
||||
Description string `sh:"desc"`
|
||||
Group string `sh:"group"`
|
||||
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"`
|
||||
AutoReqSkipList []string `sh:"auto_req_skiplist"`
|
||||
AutoProvSkipList []string `sh:"auto_prov_skiplist"`
|
||||
}
|
||||
|
||||
func (bv *BuildVarsPre) ToBuildVars() BuildVars {
|
||||
return BuildVars{
|
||||
Name: "",
|
||||
Base: "",
|
||||
BuildVarsPre: *bv,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildVars represents the script variables required
|
||||
// to build a package
|
||||
type BuildVars struct {
|
||||
Name string `sh:"name,required"`
|
||||
Base string
|
||||
BuildVarsPre
|
||||
}
|
||||
|
||||
type Scripts struct {
|
||||
PreInstall string `sh:"preinstall"`
|
||||
PostInstall string `sh:"postinstall"`
|
||||
|
Reference in New Issue
Block a user