// 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 ( "bytes" "context" "encoding/gob" "errors" "log/slog" "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/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" ) type BuildInput struct { opts *types.BuildOpts info *distro.OSRelease pkgFormat string script string repository string packages []string } func (bi *BuildInput) GobEncode() ([]byte, error) { w := new(bytes.Buffer) encoder := gob.NewEncoder(w) if err := encoder.Encode(bi.opts); err != nil { return nil, err } if err := encoder.Encode(bi.info); err != nil { return nil, err } if err := encoder.Encode(bi.pkgFormat); err != nil { return nil, err } if err := encoder.Encode(bi.script); err != nil { return nil, err } if err := encoder.Encode(bi.repository); err != nil { return nil, err } if err := encoder.Encode(bi.packages); err != nil { return nil, err } return w.Bytes(), nil } func (bi *BuildInput) GobDecode(data []byte) error { r := bytes.NewBuffer(data) decoder := gob.NewDecoder(r) if err := decoder.Decode(&bi.opts); err != nil { return err } if err := decoder.Decode(&bi.info); err != nil { return err } if err := decoder.Decode(&bi.pkgFormat); err != nil { return err } if err := decoder.Decode(&bi.script); err != nil { return err } if err := decoder.Decode(&bi.repository); err != nil { return err } if err := decoder.Decode(&bi.packages); err != nil { return err } return nil } func (b *BuildInput) Repository() string { return b.repository } func (b *BuildInput) BuildOpts() *types.BuildOpts { return b.opts } func (b *BuildInput) OSRelease() *distro.OSRelease { return b.info } func (b *BuildInput) PkgFormat() string { return b.pkgFormat } type BuildOptsProvider interface { BuildOpts() *types.BuildOpts } type OsInfoProvider interface { OSRelease() *distro.OSRelease } type PkgFormatProvider interface { PkgFormat() string } type RepositoryProvider interface { Repository() string } // ================================================ 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 BuildResult struct { PackagePaths []string PackageNames []string } type PackageFinder interface { FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) } type Config interface { GetPaths() *config.Paths PagerStyle() string } type FunctionsOutput struct { Contents *[]string } // EXECUTORS type ScriptResolverExecutor interface { ResolveScript(ctx context.Context, pkg *db.Package) *ScriptInfo } type ScriptExecutor interface { ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) PrepareDirs( ctx context.Context, input *BuildInput, basePkg string, ) error ExecuteSecondPass( ctx context.Context, input *BuildInput, sf *ScriptFile, varsOfPackages []*types.BuildVars, repoDeps []string, builtNames []string, basePkg string, ) (*SecondPassResult, error) } type CacheExecutor interface { CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *types.BuildVars) (string, bool, error) } type ScriptViewerExecutor interface { ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error } type CheckerExecutor interface { PerformChecks( ctx context.Context, input *BuildInput, vars *types.BuildVars, ) (bool, error) } type InstallerExecutor interface { InstallLocal(paths []string, opts *manager.Opts) error Install(pkgs []string, opts *manager.Opts) error RemoveAlreadyInstalled(pkgs []string) ([]string, error) } type SourcesInput struct { Sources []string Checksums []string } type SourceDownloaderExecutor interface { DownloadSources( ctx context.Context, input *BuildInput, basePkg string, si SourcesInput, ) error } // func NewBuilder( scriptResolver ScriptResolverExecutor, scriptExecutor ScriptExecutor, cacheExecutor CacheExecutor, scriptViewerExecutor ScriptViewerExecutor, checkerExecutor CheckerExecutor, installerExecutor InstallerExecutor, sourceExecutor SourceDownloaderExecutor, ) *Builder { return &Builder{ scriptResolver: scriptResolver, scriptExecutor: scriptExecutor, cacheExecutor: cacheExecutor, scriptViewerExecutor: scriptViewerExecutor, checkerExecutor: checkerExecutor, installerExecutor: installerExecutor, sourceExecutor: sourceExecutor, } } type Builder struct { scriptResolver ScriptResolverExecutor scriptExecutor ScriptExecutor cacheExecutor CacheExecutor scriptViewerExecutor ScriptViewerExecutor checkerExecutor CheckerExecutor installerExecutor InstallerExecutor sourceExecutor SourceDownloaderExecutor repos PackageFinder // mgr manager.Manager } type BuildArgs struct { Opts *types.BuildOpts Info *distro.OSRelease PkgFormat_ string } func (b *BuildArgs) BuildOpts() *types.BuildOpts { return b.Opts } func (b *BuildArgs) OSRelease() *distro.OSRelease { return b.Info } func (b *BuildArgs) PkgFormat() string { return b.PkgFormat_ } type BuildPackageFromDbArgs struct { BuildArgs Package *db.Package Packages []string } type BuildPackageFromScriptArgs struct { BuildArgs Script string Packages []string } func (b *Builder) BuildPackageFromDb( ctx context.Context, args *BuildPackageFromDbArgs, ) (*BuildResult, error) { scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package) return b.BuildPackage(ctx, &BuildInput{ script: scriptInfo.Script, repository: scriptInfo.Repository, packages: args.Packages, pkgFormat: args.PkgFormat(), opts: args.Opts, info: args.Info, }) } func (b *Builder) BuildPackageFromScript( ctx context.Context, args *BuildPackageFromScriptArgs, ) (*BuildResult, error) { return b.BuildPackage(ctx, &BuildInput{ script: args.Script, repository: "default", packages: args.Packages, pkgFormat: args.PkgFormat(), opts: args.Opts, info: args.Info, }) } func (b *Builder) BuildPackage( ctx context.Context, input *BuildInput, ) (*BuildResult, error) { scriptPath := input.script slog.Debug("ReadScript") sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath) if err != nil { return nil, err } slog.Debug("ExecuteFirstPass") basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf) if err != nil { return nil, err } builtPaths := make([]string, 0) if !input.opts.Clean { var remainingVars []*types.BuildVars for _, vars := range varsOfPackages { builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars) if err != nil { return nil, err } if ok { builtPaths = append(builtPaths, builtPkgPath) } else { remainingVars = append(remainingVars, vars) } } if len(remainingVars) == 0 { return &BuildResult{builtPaths, nil}, nil } } slog.Debug("ViewScript") err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg) if err != nil { return nil, err } slog.Info(gotext.Get("Building package"), "name", basePkg) for _, vars := range varsOfPackages { cont, err := b.checkerExecutor.PerformChecks(ctx, input, vars) if err != nil { return nil, err } if !cont { return nil, errors.New("exit...") } } 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) if len(sources) != len(checksums) { slog.Error(gotext.Get("The checksums array must be the same length as sources")) return nil, errors.New("exit...") } sources, checksums = removeDuplicatesSources(sources, checksums) slog.Debug("installBuildDeps") err = b.installBuildDeps(ctx, input, buildDepends) if err != nil { return nil, err } slog.Debug("installOptDeps") err = b.installOptDeps(ctx, input, optDepends) if err != nil { return nil, err } slog.Debug("BuildALRDeps") _, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends) if err != nil { return nil, err } slog.Debug("PrepareDirs") err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg) if err != nil { return nil, err } // builtPaths = append(builtPaths, newBuildPaths...) slog.Info(gotext.Get("Downloading sources")) slog.Debug("DownloadSources") err = b.sourceExecutor.DownloadSources( ctx, input, basePkg, SourcesInput{ Sources: sources, Checksums: checksums, }, ) if err != nil { return nil, err } slog.Debug("ExecuteSecondPass") res, err := b.scriptExecutor.ExecuteSecondPass( ctx, input, sf, varsOfPackages, repoDeps, builtNames, basePkg, ) if err != nil { return nil, err } pkgPaths := removeDuplicates(res.BuiltPaths) pkgNames := removeDuplicates(res.BuiltNames) return &BuildResult{ PackagePaths: pkgPaths, PackageNames: pkgNames, }, nil } type InstallPkgsArgs struct { BuildArgs AlrPkgs []db.Package NativePkgs []string } func (b *Builder) InstallALRPackages( ctx context.Context, input interface { OsInfoProvider BuildOptsProvider PkgFormatProvider }, alrPkgs []db.Package, ) error { for _, pkg := range alrPkgs { res, err := b.BuildPackageFromDb( ctx, &BuildPackageFromDbArgs{ Package: &pkg, Packages: []string{}, BuildArgs: BuildArgs{ Opts: input.BuildOpts(), Info: input.OSRelease(), PkgFormat_: input.PkgFormat(), }, }, ) if err != nil { return err } err = b.installerExecutor.InstallLocal( res.PackagePaths, &manager.Opts{ NoConfirm: !input.BuildOpts().Interactive, }, ) if err != nil { return err } } return nil } func (b *Builder) BuildALRDeps( ctx context.Context, input interface { OsInfoProvider BuildOptsProvider PkgFormatProvider }, depends []string, ) (builtPaths, builtNames, repoDeps []string, err error) { if len(depends) > 0 { slog.Info(gotext.Get("Installing dependencies")) found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей if err != nil { return nil, nil, nil, err } repoDeps = notFound // Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез pkgs := cliutils.FlattenPkgs( ctx, found, "install", input.BuildOpts().Interactive, ) type item struct { pkg *db.Package packages []string } pkgsMap := make(map[string]*item) for _, pkg := range pkgs { name := pkg.BasePkgName if name == "" { name = pkg.Name } if pkgsMap[name] == nil { pkgsMap[name] = &item{ pkg: &pkg, } } pkgsMap[name].packages = append( pkgsMap[name].packages, pkg.Name, ) } for basePkgName := range pkgsMap { pkg := pkgsMap[basePkgName].pkg res, err := b.BuildPackageFromDb( ctx, &BuildPackageFromDbArgs{ Package: pkg, Packages: pkgsMap[basePkgName].packages, BuildArgs: BuildArgs{ Opts: input.BuildOpts(), Info: input.OSRelease(), PkgFormat_: input.PkgFormat(), }, }, ) if err != nil { return nil, nil, nil, err } builtPaths = append(builtPaths, res.PackagePaths...) builtNames = append(builtNames, res.PackageNames...) } } // Удаляем возможные дубликаты, которые могут быть введены, если // несколько зависимостей зависят от одних и тех же пакетов. repoDeps = removeDuplicates(repoDeps) builtPaths = removeDuplicates(builtPaths) builtNames = removeDuplicates(builtNames) return builtPaths, builtNames, repoDeps, nil } func (i *Builder) installBuildDeps( ctx context.Context, input interface { OsInfoProvider BuildOptsProvider PkgFormatProvider }, pkgs []string, ) error { if len(pkgs) > 0 { deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs) if err != nil { return err } err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты if err != nil { return err } } return nil } func (i *Builder) installOptDeps( ctx context.Context, input interface { OsInfoProvider BuildOptsProvider PkgFormatProvider }, pkgs []string, ) error { optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs) if err != nil { return err } if len(optDeps) > 0 { optDeps, err := cliutils.ChooseOptDepends( ctx, optDeps, "install", input.BuildOpts().Interactive, ) // Пользователя просят выбрать опциональные зависимости if err != nil { return err } if len(optDeps) == 0 { return nil } err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты if err != nil { return err } } return nil } func (i *Builder) InstallPkgs( ctx context.Context, input interface { OsInfoProvider BuildOptsProvider PkgFormatProvider }, pkgs []string, ) error { builtPaths, _, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs) if err != nil { return err } if len(builtPaths) > 0 { err = i.installerExecutor.InstallLocal(builtPaths, &manager.Opts{ NoConfirm: !input.BuildOpts().Interactive, }) if err != nil { return err } } if len(repoDeps) > 0 { err = i.installerExecutor.Install(repoDeps, &manager.Opts{ NoConfirm: !input.BuildOpts().Interactive, }) if err != nil { return err } } return nil }