From 7cb1bc9548675de1bca23318c95d8ed1df0f1fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=28=D0=A5?= =?UTF-8?q?=D1=80=D0=B0=D0=BC=D1=8B=D1=87=D0=AA=29=20=D0=A5=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Thu, 21 Aug 2025 18:44:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B2=D0=B8=D1=87=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B8=D1=82=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B8=D0=B7=20aur=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen.go | 23 ++ internal/gen/aur.go | 668 +++++++++++++++++++++++++++++++++ internal/gen/tmpls/aur.tmpl.sh | 133 +++++++ pkg/alrsh/package_gen.go | 16 - 4 files changed, 824 insertions(+), 16 deletions(-) create mode 100644 internal/gen/aur.go create mode 100644 internal/gen/tmpls/aur.tmpl.sh diff --git a/gen.go b/gen.go index ac00a8a..ccbde5d 100644 --- a/gen.go +++ b/gen.go @@ -61,6 +61,29 @@ func GenCmd() *cli.Command { }) }, }, + { + Name: "aur", + Usage: gotext.Get("Generate a ALR script for an AUR package"), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Required: true, + Usage: gotext.Get("Name of the AUR package"), + }, + &cli.StringFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: gotext.Get("Version of the package (optional, uses latest if not specified)"), + }, + }, + Action: func(c *cli.Context) error { + return gen.AUR(os.Stdout, gen.AUROptions{ + Name: c.String("name"), + Version: c.String("version"), + }) + }, + }, }, } } diff --git a/internal/gen/aur.go b/internal/gen/aur.go new file mode 100644 index 0000000..99c1236 --- /dev/null +++ b/internal/gen/aur.go @@ -0,0 +1,668 @@ +// 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 the ALR Authors. +// +// 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 . + +package gen + +import ( + _ "embed" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "regexp" + "strings" + "text/template" +) + +// Встраиваем шаблон для AUR пакетов +// +//go:embed tmpls/aur.tmpl.sh +var aurTmpl string + +// AUROptions содержит параметры для генерации шаблона AUR +type AUROptions struct { + Name string // Имя пакета в AUR + Version string // Версия пакета (опционально, если не указана - берется последняя) + CreateDir bool // Создавать ли директорию для пакета и дополнительные файлы +} + +// aurAPIResponse представляет структуру ответа от API AUR +type aurAPIResponse struct { + Version int `json:"version"` // Версия API + Type string `json:"type"` // Тип ответа + ResultCount int `json:"resultcount"` // Количество результатов + Results []aurResult `json:"results"` // Массив результатов + Error string `json:"error"` // Сообщение об ошибке (если есть) +} + +// aurResult содержит информацию о пакете из AUR +type aurResult struct { + ID int `json:"ID"` + Name string `json:"Name"` + PackageBaseID int `json:"PackageBaseID"` + PackageBase string `json:"PackageBase"` + Version string `json:"Version"` + Description string `json:"Description"` + URL string `json:"URL"` + NumVotes int `json:"NumVotes"` + Popularity float64 `json:"Popularity"` + OutOfDate *int `json:"OutOfDate"` + Maintainer string `json:"Maintainer"` + FirstSubmitted int `json:"FirstSubmitted"` + LastModified int `json:"LastModified"` + URLPath string `json:"URLPath"` + License []string `json:"License"` + Keywords []string `json:"Keywords"` + Depends []string `json:"Depends"` + MakeDepends []string `json:"MakeDepends"` + OptDepends []string `json:"OptDepends"` + CheckDepends []string `json:"CheckDepends"` + Conflicts []string `json:"Conflicts"` + Provides []string `json:"Provides"` + Replaces []string `json:"Replaces"` + // Дополнительные поля для данных из PKGBUILD + Sources []string `json:"-"` + Checksums []string `json:"-"` + BuildFunc string `json:"-"` + PackageFunc string `json:"-"` + PrepareFunc string `json:"-"` + PackageType string `json:"-"` // python, go, rust, cpp, nodejs, bin, git + HasDesktop bool `json:"-"` // Есть ли desktop файлы + HasSystemd bool `json:"-"` // Есть ли systemd сервисы + HasVersion bool `json:"-"` // Есть ли функция version() + HasScripts []string `json:"-"` // Дополнительные скрипты (postinstall, postremove, etc) + HasPatches bool `json:"-"` // Есть ли патчи + Architectures []string `json:"-"` // Поддерживаемые архитектуры + + // Автоматически определяемые файлы для install-* команд + BinaryFiles []string `json:"-"` // Исполняемые файлы для install-binary + LicenseFiles []string `json:"-"` // Лицензионные файлы для install-license + ManualFiles []string `json:"-"` // Man страницы для install-manual + DesktopFiles []string `json:"-"` // Desktop файлы для install-desktop + ServiceFiles []string `json:"-"` // Systemd сервисы для install-systemd + CompletionFiles map[string]string `json:"-"` // Файлы автодополнения по типу (bash, zsh, fish) +} + +// Вспомогательные методы для шаблона +func (r aurResult) LicenseString() string { + if len(r.License) == 0 { + return "custom:Unknown" + } + // Форматируем лицензии для alr.sh + licenses := make([]string, len(r.License)) + for i, l := range r.License { + licenses[i] = fmt.Sprintf("'%s'", l) + } + return strings.Join(licenses, " ") +} + +func (r aurResult) DependsString() string { + if len(r.Depends) == 0 { + return "" + } + deps := make([]string, len(r.Depends)) + for i, d := range r.Depends { + // Убираем версионные ограничения для простоты + dep := strings.Split(d, ">=")[0] + dep = strings.Split(dep, "<=")[0] + dep = strings.Split(dep, "=")[0] + dep = strings.Split(dep, ">")[0] + dep = strings.Split(dep, "<")[0] + deps[i] = fmt.Sprintf("'%s'", dep) + } + return strings.Join(deps, " ") +} + +func (r aurResult) MakeDependsString() string { + if len(r.MakeDepends) == 0 { + return "" + } + deps := make([]string, len(r.MakeDepends)) + for i, d := range r.MakeDepends { + // Убираем версионные ограничения для простоты + dep := strings.Split(d, ">=")[0] + dep = strings.Split(dep, "<=")[0] + dep = strings.Split(dep, "=")[0] + dep = strings.Split(dep, ">")[0] + dep = strings.Split(dep, "<")[0] + deps[i] = fmt.Sprintf("'%s'", dep) + } + return strings.Join(deps, " ") +} + +func (r aurResult) GitURL() string { + // Формируем URL для клонирования из AUR + return fmt.Sprintf("https://aur.archlinux.org/%s.git", r.PackageBase) +} + +func (r aurResult) ArchitecturesString() string { + if len(r.Architectures) == 0 { + return "'all'" + } + archs := make([]string, len(r.Architectures)) + for i, arch := range r.Architectures { + archs[i] = fmt.Sprintf("'%s'", arch) + } + return strings.Join(archs, " ") +} + +func (r aurResult) OptDependsString() string { + if len(r.OptDepends) == 0 { + return "" + } + optDeps := make([]string, 0, len(r.OptDepends)) + for _, dep := range r.OptDepends { + // Форматируем опциональные зависимости для alr.sh + parts := strings.SplitN(dep, ": ", 2) + if len(parts) == 2 { + optDeps = append(optDeps, fmt.Sprintf("'%s: %s'", parts[0], parts[1])) + } else { + optDeps = append(optDeps, fmt.Sprintf("'%s'", dep)) + } + } + return strings.Join(optDeps, "\n\t") +} + +func (r aurResult) ScriptsString() string { + if len(r.HasScripts) == 0 { + return "" + } + scripts := make([]string, len(r.HasScripts)) + for i, script := range r.HasScripts { + scripts[i] = fmt.Sprintf("['%s']='%s.sh'", script, script) + } + return strings.Join(scripts, "\n\t") +} + +// GenerateInstallCommands генерирует команды install-* для шаблона +func (r aurResult) GenerateInstallCommands() string { + var commands []string + + // install-binary команды + for _, binary := range r.BinaryFiles { + if binary == "./"+r.Name { + commands = append(commands, fmt.Sprintf("\tinstall-binary %s", binary)) + } else { + commands = append(commands, fmt.Sprintf("\tinstall-binary %s %s", binary, r.Name)) + } + } + + // install-license команды + for _, license := range r.LicenseFiles { + if license == "LICENSE" || license == "./LICENSE" { + commands = append(commands, fmt.Sprintf("\tinstall-license %s %s/LICENSE", license, r.Name)) + } else { + commands = append(commands, fmt.Sprintf("\tinstall-license %s %s/LICENSE", license, r.Name)) + } + } + + // install-manual команды + for _, manual := range r.ManualFiles { + commands = append(commands, fmt.Sprintf("\tinstall-manual %s", manual)) + } + + // install-desktop команды + for _, desktop := range r.DesktopFiles { + commands = append(commands, fmt.Sprintf("\tinstall-desktop %s", desktop)) + } + + // install-systemd команды + for _, service := range r.ServiceFiles { + if strings.Contains(service, "user") { + commands = append(commands, fmt.Sprintf("\tinstall-systemd-user %s", service)) + } else { + commands = append(commands, fmt.Sprintf("\tinstall-systemd %s", service)) + } + } + + // install-completion команды + for shell, file := range r.CompletionFiles { + switch shell { + case "bash": + commands = append(commands, fmt.Sprintf("\tinstall-completion bash %s < %s", r.Name, file)) + case "zsh": + commands = append(commands, fmt.Sprintf("\tinstall-completion zsh %s < %s", r.Name, file)) + case "fish": + commands = append(commands, fmt.Sprintf("\t%s completion fish | install-completion fish %s", r.Name, r.Name)) + } + } + + if len(commands) == 0 { + return "\t# TODO: Добавьте команды установки файлов" + } + + return strings.Join(commands, "\n") +} + +// fetchPKGBUILD загружает PKGBUILD файл для пакета +func fetchPKGBUILD(packageBase string) (string, error) { + // URL для raw PKGBUILD + pkgbuildURL := fmt.Sprintf("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=%s", packageBase) + + res, err := http.Get(pkgbuildURL) + if err != nil { + return "", fmt.Errorf("failed to fetch PKGBUILD: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return "", fmt.Errorf("failed to fetch PKGBUILD: status %s", res.Status) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return "", fmt.Errorf("failed to read PKGBUILD: %w", err) + } + + return string(data), nil +} + +// parseSources извлекает источники из PKGBUILD +func parseSources(pkgbuild string) []string { + var sources []string + + // Регулярное выражение для поиска массива source + // Поддерживает как однострочные, так и многострочные определения + sourceRegex := regexp.MustCompile(`(?ms)source=\((.*?)\)`) + matches := sourceRegex.FindStringSubmatch(pkgbuild) + + if len(matches) > 1 { + // Извлекаем содержимое массива source + sourceContent := matches[1] + + // Разбираем элементы массива + // Учитываем кавычки и переносы строк + elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`) + elements := elemRegex.FindAllStringSubmatch(sourceContent, -1) + + for _, elem := range elements { + if len(elem) > 1 { + source := elem[1] + // Заменяем переменные версии + source = strings.ReplaceAll(source, "$pkgver", "${version}") + source = strings.ReplaceAll(source, "${pkgver}", "${version}") + source = strings.ReplaceAll(source, "$pkgname", "${name}") + source = strings.ReplaceAll(source, "${pkgname}", "${name}") + // Обрабатываем другие переменные (упрощенно) + source = strings.ReplaceAll(source, "$_commit", "${_commit}") + source = strings.ReplaceAll(source, "${_commit}", "${_commit}") + sources = append(sources, source) + } + } + } + + // Если источники не найдены в source=(), проверяем source_x86_64 и другие архитектуры + if len(sources) == 0 { + archSourceRegex := regexp.MustCompile(`(?ms)source_(?:x86_64|aarch64)=\((.*?)\)`) + matches = archSourceRegex.FindStringSubmatch(pkgbuild) + if len(matches) > 1 { + sourceContent := matches[1] + elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`) + elements := elemRegex.FindAllStringSubmatch(sourceContent, -1) + + for _, elem := range elements { + if len(elem) > 1 { + source := elem[1] + source = strings.ReplaceAll(source, "$pkgver", "${version}") + source = strings.ReplaceAll(source, "${pkgver}", "${version}") + source = strings.ReplaceAll(source, "$pkgname", "${name}") + source = strings.ReplaceAll(source, "${pkgname}", "${name}") + sources = append(sources, source) + } + } + } + } + + return sources +} + +// parseChecksums извлекает контрольные суммы из PKGBUILD +func parseChecksums(pkgbuild string) []string { + var checksums []string + + // Пробуем разные типы контрольных сумм + for _, hashType := range []string{"sha256sums", "sha512sums", "sha1sums", "md5sums", "b2sums"} { + regex := regexp.MustCompile(fmt.Sprintf(`(?ms)%s=\((.*?)\)`, hashType)) + matches := regex.FindStringSubmatch(pkgbuild) + + if len(matches) > 1 { + content := matches[1] + elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`) + elements := elemRegex.FindAllStringSubmatch(content, -1) + + for _, elem := range elements { + if len(elem) > 1 { + checksums = append(checksums, elem[1]) + } + } + + if len(checksums) > 0 { + break // Используем первый найденный тип хешей + } + } + } + + return checksums +} + +// parseFunctions извлекает функции build(), package() и prepare() из PKGBUILD +func parseFunctions(pkgbuild string) (buildFunc, packageFunc, prepareFunc string) { + // Извлекаем функцию build() + buildRegex := regexp.MustCompile(`(?ms)^build\(\)\s*\{(.*?)^\}`) + if matches := buildRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 { + buildFunc = strings.TrimSpace(matches[1]) + } + + // Извлекаем функцию package() + packageRegex := regexp.MustCompile(`(?ms)^package\(\)\s*\{(.*?)^\}`) + if matches := packageRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 { + packageFunc = strings.TrimSpace(matches[1]) + } + + // Извлекаем функцию prepare() + prepareRegex := regexp.MustCompile(`(?ms)^prepare\(\)\s*\{(.*?)^\}`) + if matches := prepareRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 { + prepareFunc = strings.TrimSpace(matches[1]) + } + + return buildFunc, packageFunc, prepareFunc +} + +// detectInstallableFiles анализирует PKGBUILD и определяет файлы для install-* команд +func detectInstallableFiles(pkg *aurResult, pkgbuild string) { + // Инициализируем карту для файлов автодополнения + pkg.CompletionFiles = make(map[string]string) + + // Для простоты, добавляем стандартные файлы для типа пакета + switch pkg.PackageType { + case "go": + pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) + case "rust": + pkg.BinaryFiles = append(pkg.BinaryFiles, "./target/release/"+pkg.Name) + case "cpp", "meson": + pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) // обычно в корне после сборки + case "bin": + pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) + default: + if pkg.PackageType != "python" && pkg.PackageType != "nodejs" { + pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) + } + } + + // Ищем лицензионные файлы для install-license с более точными паттернами + licenseRegex := regexp.MustCompile(`(?i)\b(LICENSE|COPYING|COPYRIGHT|LICENCE)(?:\.[a-zA-Z0-9]+)?\b`) + licenseMatches := licenseRegex.FindAllString(pkgbuild, -1) + for _, match := range licenseMatches { + // Фильтруем только реальные файлы лицензий + if strings.Contains(strings.ToLower(match), "license") || + strings.Contains(strings.ToLower(match), "copying") || + strings.Contains(strings.ToLower(match), "copyright") { + if !contains(pkg.LicenseFiles, "./"+match) { + pkg.LicenseFiles = append(pkg.LicenseFiles, "./"+match) + } + } + } + + // Если не найдены лицензионные файлы, добавляем стандартные + if len(pkg.LicenseFiles) == 0 { + pkg.LicenseFiles = append(pkg.LicenseFiles, "LICENSE") + } + + // Ищем man страницы для install-manual с более точными паттернами + manRegex := regexp.MustCompile(`\b\w+\.(?:1|2|3|4|5|6|7|8)(?:\.gz)?\b`) + manMatches := manRegex.FindAllString(pkgbuild, -1) + for _, match := range manMatches { + // Проверяем, что это не переменная или часть кода + if !strings.Contains(match, "$") && !strings.Contains(match, "{") { + if !contains(pkg.ManualFiles, "./"+match) { + pkg.ManualFiles = append(pkg.ManualFiles, "./"+match) + } + } + } + + // Ищем desktop файлы для install-desktop + desktopRegex := regexp.MustCompile(`[^/\s]*\.desktop`) + desktopMatches := desktopRegex.FindAllString(pkgbuild, -1) + for _, match := range desktopMatches { + if !contains(pkg.DesktopFiles, "./"+match) { + pkg.DesktopFiles = append(pkg.DesktopFiles, "./"+match) + } + } + + // Ищем systemd сервисы для install-systemd + serviceRegex := regexp.MustCompile(`[^/\s]*\.service`) + serviceMatches := serviceRegex.FindAllString(pkgbuild, -1) + for _, match := range serviceMatches { + if !contains(pkg.ServiceFiles, "./"+match) { + pkg.ServiceFiles = append(pkg.ServiceFiles, "./"+match) + } + } + + // Ищем файлы автодополнения + completionPatterns := map[string]string{ + "bash": `completions?/.*\.bash|bash-completion`, + "zsh": `completions?/.*\.zsh|zsh.*completion`, + "fish": `completions?/.*\.fish|fish.*completion`, + } + + for shell, pattern := range completionPatterns { + regex := regexp.MustCompile(fmt.Sprintf(`(?i)%s`, pattern)) + matches := regex.FindAllString(pkgbuild, -1) + if len(matches) > 0 { + pkg.CompletionFiles[shell] = matches[0] + } + } +} + +// contains проверяет, содержит ли слайс строк указанную строку +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + +// detectPackageType определяет тип пакета на основе имени, зависимостей и источников +func detectPackageType(pkg *aurResult, pkgbuild string) { + name := strings.ToLower(pkg.Name) + + // Определяем тип на основе имени пакета + switch { + case strings.HasPrefix(name, "python") || strings.HasPrefix(name, "python3-"): + pkg.PackageType = "python" + case strings.Contains(name, "nodejs") || strings.Contains(name, "node-"): + pkg.PackageType = "nodejs" + case strings.HasSuffix(name, "-bin"): + pkg.PackageType = "bin" + case strings.HasSuffix(name, "-git"): + pkg.PackageType = "git" + pkg.HasVersion = true // Git пакеты обычно имеют функцию version() + case strings.Contains(name, "rust") || hasRustSources(pkg.Sources): + pkg.PackageType = "rust" + case strings.Contains(name, "go-") || hasGoSources(pkg.Sources): + pkg.PackageType = "go" + case strings.Contains(name, "-rust") || strings.Contains(name, "paru") || strings.Contains(name, "cargo-"): + pkg.PackageType = "rust" + default: + // Определяем по зависимостям сборки + for _, dep := range pkg.MakeDepends { + depLower := strings.ToLower(dep) + switch { + case strings.Contains(depLower, "meson") || strings.Contains(depLower, "ninja"): + pkg.PackageType = "meson" + case strings.Contains(depLower, "cmake") || strings.Contains(depLower, "gcc") || strings.Contains(depLower, "clang"): + pkg.PackageType = "cpp" + case strings.Contains(depLower, "python"): + pkg.PackageType = "python" + case strings.Contains(depLower, "go"): + pkg.PackageType = "go" + case strings.Contains(depLower, "rust") || strings.Contains(depLower, "cargo"): + pkg.PackageType = "rust" + case strings.Contains(depLower, "npm") || strings.Contains(depLower, "nodejs"): + pkg.PackageType = "nodejs" + } + if pkg.PackageType != "" { + break + } + } + } + + // Определяем архитектуры на основе типа пакета + if pkg.PackageType == "bin" { + pkg.Architectures = []string{"amd64"} // Бинарные пакеты обычно специфичны для архитектуры + } else { + pkg.Architectures = []string{"all"} // Исходный код собирается для любой архитектуры + } + + // Определяем наличие desktop файлов + pkg.HasDesktop = strings.Contains(pkgbuild, ".desktop") || + strings.Contains(pkgbuild, "install-desktop") || + strings.Contains(pkgbuild, "xdg-desktop") + + // Определяем наличие systemd сервисов + pkg.HasSystemd = strings.Contains(pkgbuild, ".service") || + strings.Contains(pkgbuild, "systemctl") || + strings.Contains(pkgbuild, "install-systemd") + + // Определяем наличие функции version() для -git пакетов + pkg.HasVersion = strings.Contains(pkgbuild, "pkgver()") || + (strings.HasSuffix(name, "-git") && strings.Contains(pkgbuild, "git describe")) + + // Определяем наличие патчей + pkg.HasPatches = strings.Contains(pkgbuild, "patch ") || + strings.Contains(pkgbuild, ".patch") || + strings.Contains(pkgbuild, ".diff") + + // Определяем дополнительные скрипты + if strings.Contains(pkgbuild, "post_install") { + pkg.HasScripts = append(pkg.HasScripts, "postinstall") + } + if strings.Contains(pkgbuild, "pre_remove") || strings.Contains(pkgbuild, "post_remove") { + pkg.HasScripts = append(pkg.HasScripts, "postremove") + } +} + +// hasRustSources проверяет, содержат ли источники Rust проекты +func hasRustSources(sources []string) bool { + for _, src := range sources { + if strings.Contains(src, "crates.io") || strings.Contains(src, "Cargo.toml") { + return true + } + } + return false +} + +// hasGoSources проверяет, содержат ли источники Go проекты +func hasGoSources(sources []string) bool { + for _, src := range sources { + if strings.Contains(src, "github.com") && strings.Contains(src, "/go") { + return true + } + } + return false +} + +// AUR генерирует шаблон alr.sh на основе пакета из AUR +func AUR(w io.Writer, opts AUROptions) error { + // Создаем шаблон с функциями + tmpl, err := template.New("aur"). + Funcs(funcs). + Parse(aurTmpl) + if err != nil { + return err + } + + // Формируем URL запроса к AUR API + apiURL := "https://aur.archlinux.org/rpc/v5/info" + params := url.Values{} + params.Add("arg[]", opts.Name) + fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode()) + + // Выполняем запрос к AUR API + res, err := http.Get(fullURL) + if err != nil { + return fmt.Errorf("failed to fetch AUR package info: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return fmt.Errorf("AUR API returned status: %s", res.Status) + } + + // Декодируем ответ + var resp aurAPIResponse + err = json.NewDecoder(res.Body).Decode(&resp) + if err != nil { + return fmt.Errorf("failed to decode AUR response: %w", err) + } + + // Проверяем наличие ошибки в ответе + if resp.Error != "" { + return fmt.Errorf("AUR API error: %s", resp.Error) + } + + // Проверяем, что пакет найден + if resp.ResultCount == 0 { + return fmt.Errorf("package '%s' not found in AUR", opts.Name) + } + + // Берем первый результат + pkg := resp.Results[0] + + // Если указана версия, проверяем соответствие + if opts.Version != "" && pkg.Version != opts.Version { + // Предупреждаем, но продолжаем с актуальной версией из AUR + fmt.Fprintf(w, "# WARNING: Requested version %s, but AUR has %s\n", opts.Version, pkg.Version) + } + + // Загружаем PKGBUILD для получения источников + pkgbuild, err := fetchPKGBUILD(pkg.PackageBase) + if err != nil { + // Если не удалось загрузить PKGBUILD, используем fallback на AUR репозиторий + fmt.Fprintf(w, "# WARNING: Could not fetch PKGBUILD: %v\n", err) + fmt.Fprintf(w, "# Using AUR repository as source\n") + pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())} + pkg.Checksums = []string{"SKIP"} + } else { + // Извлекаем источники из PKGBUILD + pkg.Sources = parseSources(pkgbuild) + pkg.Checksums = parseChecksums(pkgbuild) + pkg.BuildFunc, pkg.PackageFunc, pkg.PrepareFunc = parseFunctions(pkgbuild) + + // Определяем тип пакета + detectPackageType(&pkg, pkgbuild) + + // Определяем файлы для install-* команд + detectInstallableFiles(&pkg, pkgbuild) + + // Если источники не найдены, используем fallback + if len(pkg.Sources) == 0 { + fmt.Fprintf(w, "# WARNING: No sources found in PKGBUILD\n") + fmt.Fprintf(w, "# Using AUR repository as source\n") + pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())} + pkg.Checksums = []string{"SKIP"} + } + } + + // Выполняем шаблон + return tmpl.Execute(w, pkg) +} \ No newline at end of file diff --git a/internal/gen/tmpls/aur.tmpl.sh b/internal/gen/tmpls/aur.tmpl.sh new file mode 100644 index 0000000..97dfb38 --- /dev/null +++ b/internal/gen/tmpls/aur.tmpl.sh @@ -0,0 +1,133 @@ +# 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 the ALR Authors. +# +# 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 . + +# Generated from AUR package: {{.Name}} +# Package type: {{.PackageType}} +# AUR votes: {{.NumVotes}} | Popularity: {{printf "%.2f" .Popularity}} +# Original maintainer: {{.Maintainer}} +# Adapted for ALR by automation + +name='{{.Name}}' +version='{{.Version}}' +release='1' +desc='{{.Description}}' +{{if ne .Description ""}}desc_ru='{{.Description}}'{{end}} +homepage='{{.URL}}' +maintainer="Евгений Храмов (imported from AUR)" +{{if ne .Description ""}}maintainer_ru="Евгений Храмов (импортирован из AUR)"{{end}} +architectures=({{.ArchitecturesString}}) +license=({{.LicenseString}}) +{{if .Provides}}provides=({{range .Provides}}'{{.}}' {{end}}){{end}} +{{if .Conflicts}}conflicts=({{range .Conflicts}}'{{.}}' {{end}}){{end}} +{{if .Replaces}}replaces=({{range .Replaces}}'{{.}}' {{end}}){{end}} + +# Базовые зависимости +{{if .DependsString}}deps=({{.DependsString}}){{else}}deps=(){{end}} +{{if .MakeDependsString}}build_deps=({{.MakeDependsString}}){{else}}build_deps=(){{end}} + +# Зависимости для конкретных дистрибутивов (адаптируйте под нужды пакета) +{{if .DependsString}}deps_arch=({{.DependsString}}) +deps_debian=({{.DependsString}}) +deps_altlinux=({{.DependsString}}) +deps_alpine=({{.DependsString}}){{end}} + +{{if and .MakeDependsString (ne .PackageType "bin")}}# Зависимости сборки для конкретных дистрибутивов +build_deps_arch=({{.MakeDependsString}}) +build_deps_debian=({{.MakeDependsString}}) +build_deps_altlinux=({{.MakeDependsString}}) +build_deps_alpine=({{.MakeDependsString}}){{end}} + +{{if .OptDependsString}}# Опциональные зависимости +opt_deps=( + {{.OptDependsString}} +){{end}} + +# Источники из PKGBUILD +sources=({{range .Sources}}"{{.}}" {{end}}) +checksums=({{range .Checksums}}'{{.}}' {{end}}) + +{{if .HasVersion}}# Функция версии для Git-пакетов +version() { + cd "$srcdir/{{.Name}}" + git-version +} +{{end}} + +{{if .ScriptsString}}# Дополнительные скрипты +scripts=( + {{.ScriptsString}} +){{end}} + +{{if or .PrepareFunc .HasPatches}}prepare() { + cd "$srcdir"{{if .PrepareFunc}} + # Из PKGBUILD: + {{.PrepareFunc}}{{else}} + # Применение патчей и подготовка исходников + # Раскомментируйте и адаптируйте при необходимости: + # patch -p1 < "${scriptdir}/fix.patch"{{end}} +}{{else}}# prepare() { +# cd "$srcdir" +# # Применение патчей и подготовка исходников при необходимости +# # patch -p1 < "${scriptdir}/fix.patch" +# }{{end}} + +{{if ne .PackageType "bin"}}build() { + cd "$srcdir"{{if .BuildFunc}} + # Из PKGBUILD: + {{.BuildFunc}}{{else}} + + # TODO: Адаптируйте команды сборки под конкретный проект ({{.PackageType}}) + {{if eq .PackageType "meson"}}# Для Meson проектов: + meson setup build --prefix=/usr --buildtype=release + ninja -C build -j $(nproc){{else if eq .PackageType "cpp"}}# Для C/C++ проектов: + mkdir -p build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr + make -j$(nproc){{else if eq .PackageType "go"}}# Для Go проектов: + go build -buildmode=pie -trimpath -ldflags "-s -w" -o {{.Name}}{{else if eq .PackageType "python"}}# Для Python проектов: + python -m build --wheel --no-isolation{{else if eq .PackageType "nodejs"}}# Для Node.js проектов: + npm ci --production + npm run build{{else if eq .PackageType "rust"}}# Для Rust проектов: + cargo build --release --locked{{else if eq .PackageType "git"}}# Для Git проектов (обычно исходный код): + make -j$(nproc){{else}}# Стандартная сборка: + make -j$(nproc){{end}}{{end}} +}{{else}}# Бинарный пакет - сборка не требуется{{end}} + +package() { + cd "$srcdir"{{if .PackageFunc}} + # Из PKGBUILD (адаптировано для ALR): + {{.PackageFunc}} + + # Автоматически сгенерированные команды установки: +{{.GenerateInstallCommands}}{{else}} + + # TODO: Адаптируйте установку файлов под конкретный проект {{.Name}} + {{if eq .PackageType "meson"}}# Для Meson проектов: + meson install -C build --destdir="$pkgdir"{{else if eq .PackageType "cpp"}}# Для C/C++ проектов: + cd build + make DESTDIR="$pkgdir" install{{else if eq .PackageType "go"}}# Для Go проектов: + # Исполняемый файл уже собран в корне{{else if eq .PackageType "python"}}# Для Python проектов: + pip install --root="$pkgdir/" . --no-deps --disable-pip-version-check{{else if eq .PackageType "nodejs"}}# Для Node.js проектов: + npm install -g --prefix="$pkgdir/usr" .{{else if eq .PackageType "rust"}}# Для Rust проектов: + # Исполняемый файл в target/release/{{else if eq .PackageType "bin"}}# Бинарный пакет: + # Файлы уже распакованы{{else}}# Стандартная установка: + make DESTDIR="$pkgdir" install{{end}} + + # Автоматически сгенерированные команды установки: +{{.GenerateInstallCommands}}{{end}} +} \ No newline at end of file diff --git a/pkg/alrsh/package_gen.go b/pkg/alrsh/package_gen.go index 834deb8..0fca143 100644 --- a/pkg/alrsh/package_gen.go +++ b/pkg/alrsh/package_gen.go @@ -1,19 +1,3 @@ -// 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 . - // DO NOT EDIT MANUALLY. This file is generated. package alrsh