// 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 ( "fmt" "io" "os" "path/filepath" "regexp" "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" "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/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) // Создаем директорию для пакетов } // Функция 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 } var RegexpALRPackageName = regexp.MustCompile(`^(?P[^+]+)\+alr-(?P.+)$`) func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease, opts *types.BuildOpts) *nfpm.Info { return &nfpm.Info{ Name: fmt.Sprintf("%s+alr-%s", vars.Name, opts.Repository), Arch: cpu.Arch(), Version: vars.Version, Release: overrides.ReleasePlatformSpecific(vars.Release, info), Epoch: strconv.FormatUint(uint64(vars.Epoch), 10), } } // Функция 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 }