Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 97c40e32fb | |||
| 2e28c139ba | |||
| b649a459b8 | |||
| e8c20bad25 | |||
| df69f3dcab | |||
| a44da806d4 | |||
| 6567f8a71f | |||
| 7448d91817 | |||
| f775641cb7 | |||
| e05396f214 | |||
| 5e094fa69f |
@@ -18,8 +18,6 @@
|
|||||||
name: Pre-commit
|
name: Pre-commit
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ commit_msg.txt
|
|||||||
/scripts/.claude/settings.local.json
|
/scripts/.claude/settings.local.json
|
||||||
/ALR
|
/ALR
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
.directory
|
||||||
|
|||||||
@@ -202,6 +202,23 @@ func main() {
|
|||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
buf.WriteString(`// 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/>.
|
||||||
|
|
||||||
|
`)
|
||||||
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
|
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
|
||||||
buf.WriteString("package alrsh")
|
buf.WriteString("package alrsh")
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ type BuildInput struct {
|
|||||||
script string
|
script string
|
||||||
repository string
|
repository string
|
||||||
packages []string
|
packages []string
|
||||||
|
skipDepsBuilding bool // Пропустить сборку зависимостей (используется при вызове из BuildALRDeps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bi *BuildInput) GobEncode() ([]byte, error) {
|
func (bi *BuildInput) GobEncode() ([]byte, error) {
|
||||||
@@ -245,6 +246,7 @@ type BuildArgs struct {
|
|||||||
Opts *types.BuildOpts
|
Opts *types.BuildOpts
|
||||||
Info *distro.OSRelease
|
Info *distro.OSRelease
|
||||||
PkgFormat_ string
|
PkgFormat_ string
|
||||||
|
SkipDepsBuilding bool // Пропустить сборку зависимостей (используется при вызове из BuildALRDeps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuildArgs) BuildOpts() *types.BuildOpts {
|
func (b *BuildArgs) BuildOpts() *types.BuildOpts {
|
||||||
@@ -284,6 +286,7 @@ func (b *Builder) BuildPackageFromDb(
|
|||||||
pkgFormat: args.PkgFormat(),
|
pkgFormat: args.PkgFormat(),
|
||||||
opts: args.Opts,
|
opts: args.Opts,
|
||||||
info: args.Info,
|
info: args.Info,
|
||||||
|
skipDepsBuilding: args.SkipDepsBuilding,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +296,7 @@ func (b *Builder) BuildPackageFromScript(
|
|||||||
) ([]*BuiltDep, error) {
|
) ([]*BuiltDep, error) {
|
||||||
return b.BuildPackage(ctx, &BuildInput{
|
return b.BuildPackage(ctx, &BuildInput{
|
||||||
script: args.Script,
|
script: args.Script,
|
||||||
repository: "default",
|
repository: ExtractRepoNameFromPath(args.Script),
|
||||||
packages: args.Packages,
|
packages: args.Packages,
|
||||||
pkgFormat: args.PkgFormat(),
|
pkgFormat: args.PkgFormat(),
|
||||||
opts: args.Opts,
|
opts: args.Opts,
|
||||||
@@ -409,9 +412,10 @@ func (b *Builder) BuildPackage(
|
|||||||
var filteredDepends []string
|
var filteredDepends []string
|
||||||
|
|
||||||
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
|
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
|
||||||
|
// Используем имена из varsOfPackages, так как input.packages может быть пустым
|
||||||
currentPackageNames := make(map[string]struct{})
|
currentPackageNames := make(map[string]struct{})
|
||||||
for _, pkg := range input.packages {
|
for _, vars := range varsOfPackages {
|
||||||
currentPackageNames[pkg] = struct{}{}
|
currentPackageNames[vars.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range depends {
|
for _, d := range depends {
|
||||||
@@ -423,11 +427,17 @@ func (b *Builder) BuildPackage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newBuiltDeps []*BuiltDep
|
||||||
|
var repoDeps []string
|
||||||
|
|
||||||
|
// Пропускаем сборку зависимостей если флаг установлен (вызов из BuildALRDeps)
|
||||||
|
if !input.skipDepsBuilding {
|
||||||
slog.Debug("BuildALRDeps")
|
slog.Debug("BuildALRDeps")
|
||||||
newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends)
|
newBuiltDeps, repoDeps, err = b.BuildALRDeps(ctx, input, filteredDepends)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
slog.Debug("PrepareDirs")
|
slog.Debug("PrepareDirs")
|
||||||
err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg)
|
err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg)
|
||||||
@@ -580,65 +590,68 @@ func (b *Builder) BuildALRDeps(
|
|||||||
// Системные зависимости возвращаем как repoDeps
|
// Системные зависимости возвращаем как repoDeps
|
||||||
repoDeps = systemDeps
|
repoDeps = systemDeps
|
||||||
|
|
||||||
// Шаг 2: Собираем список всех пакетов из дерева для топологической сортировки
|
// Шаг 2: Топологическая сортировка (от корней к листьям)
|
||||||
allFound := make(map[string][]alrsh.Package)
|
sortedPkgs, err := TopologicalSort(depTree)
|
||||||
for baseName, node := range depTree {
|
|
||||||
allFound[baseName] = []alrsh.Package{*node.Package}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Шаг 3: Топологическая сортировка (от корней к листьям)
|
|
||||||
sortedPkgs, err := TopologicalSort(depTree, allFound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
|
return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Шаг 3: Группируем подпакеты по basePkgName для оптимизации сборки
|
||||||
|
// Если несколько подпакетов из одного мультипакета, собираем их вместе
|
||||||
|
builtBasePkgs := make(map[string]bool) // Уже собранные базовые пакеты
|
||||||
|
|
||||||
// Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
|
// Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
|
||||||
for _, basePkgName := range sortedPkgs {
|
for _, pkgName := range sortedPkgs {
|
||||||
node := depTree[basePkgName]
|
node := depTree[pkgName]
|
||||||
if node == nil {
|
if node == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg := node.Package
|
pkg := node.Package
|
||||||
|
basePkgName := node.BasePkgName
|
||||||
|
|
||||||
// Находим ВСЕ подпакеты с этим BasePkgName
|
// Если базовый пакет уже собран, пропускаем
|
||||||
allSubpkgs, err := b.findAllSubpackages(ctx, basePkgName, pkg.Repository)
|
if builtBasePkgs[basePkgName] {
|
||||||
if err != nil {
|
continue
|
||||||
return nil, nil, fmt.Errorf("failed to find subpackages for %s: %w", basePkgName, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем кеш для ВСЕХ подпакетов
|
// Собираем только запрошенный подпакет (или все, если запрошен basePkgName)
|
||||||
|
packagesToBuilt := []string{pkgName}
|
||||||
|
|
||||||
|
// Проверяем кеш для запрошенного подпакета
|
||||||
scriptInfo := b.scriptResolver.ResolveScript(ctx, pkg)
|
scriptInfo := b.scriptResolver.ResolveScript(ctx, pkg)
|
||||||
buildInput := &BuildInput{
|
buildInput := &BuildInput{
|
||||||
script: scriptInfo.Script,
|
script: scriptInfo.Script,
|
||||||
repository: scriptInfo.Repository,
|
repository: scriptInfo.Repository,
|
||||||
packages: allSubpkgs,
|
packages: packagesToBuilt,
|
||||||
pkgFormat: input.PkgFormat(),
|
pkgFormat: input.PkgFormat(),
|
||||||
opts: input.BuildOpts(),
|
opts: input.BuildOpts(),
|
||||||
info: input.OSRelease(),
|
info: input.OSRelease(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedDeps, allInCache, err := b.checkCacheForAllSubpackages(ctx, buildInput, basePkgName, allSubpkgs)
|
cachedDeps, allInCache, err := b.checkCacheForAllSubpackages(ctx, buildInput, basePkgName, packagesToBuilt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if allInCache {
|
if allInCache {
|
||||||
// Все подпакеты в кеше, используем их
|
// Подпакет в кеше, используем его
|
||||||
buildDeps = append(buildDeps, cachedDeps...)
|
buildDeps = append(buildDeps, cachedDeps...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Собираем пакет (без рекурсивной сборки зависимостей, так как они уже собраны)
|
// Собираем только запрошенный подпакет
|
||||||
|
// SkipDepsBuilding: true предотвращает рекурсивный вызов BuildALRDeps
|
||||||
res, err := b.BuildPackageFromDb(
|
res, err := b.BuildPackageFromDb(
|
||||||
ctx,
|
ctx,
|
||||||
&BuildPackageFromDbArgs{
|
&BuildPackageFromDbArgs{
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
Packages: allSubpkgs,
|
Packages: packagesToBuilt,
|
||||||
BuildArgs: BuildArgs{
|
BuildArgs: BuildArgs{
|
||||||
Opts: input.BuildOpts(),
|
Opts: input.BuildOpts(),
|
||||||
Info: input.OSRelease(),
|
Info: input.OSRelease(),
|
||||||
PkgFormat_: input.PkgFormat(),
|
PkgFormat_: input.PkgFormat(),
|
||||||
|
SkipDepsBuilding: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -647,6 +660,7 @@ func (b *Builder) BuildALRDeps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildDeps = append(buildDeps, res...)
|
buildDeps = append(buildDeps, res...)
|
||||||
|
builtBasePkgs[basePkgName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDeps = removeDuplicates(buildDeps)
|
buildDeps = removeDuplicates(buildDeps)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
type DependencyNode struct {
|
type DependencyNode struct {
|
||||||
Package *alrsh.Package
|
Package *alrsh.Package
|
||||||
BasePkgName string
|
BasePkgName string
|
||||||
|
PkgName string // Имя конкретного подпакета (может отличаться от BasePkgName)
|
||||||
Dependencies []string // Имена зависимостей
|
Dependencies []string // Имена зависимостей
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +83,9 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
baseName = pkg.Name
|
baseName = pkg.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если уже обработали этот базовый пакет, пропускаем
|
// Используем имя конкретного подпакета как ключ (не basePkgName)
|
||||||
if resolved[baseName] != nil {
|
// Это позволяет собирать только запрошенный подпакет, а не весь мультипакет
|
||||||
|
if resolved[pkgName] != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,10 +98,11 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
allDeps := append([]string{}, deps...)
|
allDeps := append([]string{}, deps...)
|
||||||
allDeps = append(allDeps, buildDeps...)
|
allDeps = append(allDeps, buildDeps...)
|
||||||
|
|
||||||
// Добавляем узел в resolved
|
// Добавляем узел в resolved с ключом = имя подпакета
|
||||||
resolved[baseName] = &DependencyNode{
|
resolved[pkgName] = &DependencyNode{
|
||||||
Package: &pkg,
|
Package: &pkg,
|
||||||
BasePkgName: baseName,
|
BasePkgName: baseName,
|
||||||
|
PkgName: pkgName,
|
||||||
Dependencies: allDeps,
|
Dependencies: allDeps,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +132,8 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TopologicalSort выполняет топологическую сортировку пакетов по зависимостям
|
// TopologicalSort выполняет топологическую сортировку пакетов по зависимостям
|
||||||
// Возвращает список базовых имен пакетов в порядке сборки (от корней к листьям)
|
// Возвращает список имен подпакетов в порядке сборки (от корней к листьям)
|
||||||
func TopologicalSort(nodes map[string]*DependencyNode, allPkgs map[string][]alrsh.Package) ([]string, error) {
|
func TopologicalSort(nodes map[string]*DependencyNode) ([]string, error) {
|
||||||
// Список для результата
|
// Список для результата
|
||||||
var result []string
|
var result []string
|
||||||
|
|
||||||
@@ -140,51 +143,42 @@ func TopologicalSort(nodes map[string]*DependencyNode, allPkgs map[string][]alrs
|
|||||||
// Множество узлов в текущем пути (для обнаружения циклов)
|
// Множество узлов в текущем пути (для обнаружения циклов)
|
||||||
inStack := make(map[string]bool)
|
inStack := make(map[string]bool)
|
||||||
|
|
||||||
var visit func(basePkgName string) error
|
var visit func(pkgName string) error
|
||||||
visit = func(basePkgName string) error {
|
visit = func(pkgName string) error {
|
||||||
if visited[basePkgName] {
|
if visited[pkgName] {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if inStack[basePkgName] {
|
if inStack[pkgName] {
|
||||||
return fmt.Errorf("circular dependency detected: %s", basePkgName)
|
return fmt.Errorf("circular dependency detected: %s", pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
node := nodes[basePkgName]
|
node := nodes[pkgName]
|
||||||
if node == nil {
|
if node == nil {
|
||||||
// Это системный пакет, игнорируем
|
// Это системный пакет или пакет не в дереве, игнорируем
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
inStack[basePkgName] = true
|
inStack[pkgName] = true
|
||||||
|
|
||||||
// Посещаем все зависимости
|
// Посещаем все зависимости
|
||||||
for _, dep := range node.Dependencies {
|
for _, dep := range node.Dependencies {
|
||||||
// Находим базовое имя для зависимости
|
// Используем имя зависимости напрямую (это имя подпакета)
|
||||||
depBaseName := dep
|
if err := visit(dep); err != nil {
|
||||||
|
|
||||||
// Проверяем, есть ли этот пакет в allPkgs
|
|
||||||
if pkgs, ok := allPkgs[dep]; ok && len(pkgs) > 0 {
|
|
||||||
if pkgs[0].BasePkgName != "" {
|
|
||||||
depBaseName = pkgs[0].BasePkgName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := visit(depBaseName); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inStack[basePkgName] = false
|
inStack[pkgName] = false
|
||||||
visited[basePkgName] = true
|
visited[pkgName] = true
|
||||||
result = append(result, basePkgName)
|
result = append(result, pkgName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Посещаем все узлы
|
// Посещаем все узлы
|
||||||
for basePkgName := range nodes {
|
for pkgName := range nodes {
|
||||||
if err := visit(basePkgName); err != nil {
|
if err := visit(pkgName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,3 +331,29 @@ func removeDuplicatesSources(sources, checksums []string) ([]string, []string) {
|
|||||||
}
|
}
|
||||||
return newSources, newChecksums
|
return newSources, newChecksums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractRepoNameFromPath извлекает имя репозитория из пути к скрипту.
|
||||||
|
// Ожидаемый формат: repo-name/package-name/alr.sh или /abs/path/repo-name/package-name/alr.sh
|
||||||
|
// Возвращает "default", если не удалось извлечь имя.
|
||||||
|
func ExtractRepoNameFromPath(scriptPath string) string {
|
||||||
|
// Нормализуем путь
|
||||||
|
cleanPath := filepath.Clean(scriptPath)
|
||||||
|
|
||||||
|
// Разбиваем путь на компоненты
|
||||||
|
dir := filepath.Dir(cleanPath) // package-name
|
||||||
|
if dir == "." || dir == "/" {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
repoDir := filepath.Dir(dir) // repo-name
|
||||||
|
if repoDir == "." || repoDir == "/" {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
repoName := filepath.Base(repoDir)
|
||||||
|
if repoName == "." || repoName == "/" || repoName == "" {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
return repoName
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,3 +156,56 @@ func TestRegexpALRPackageName(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractRepoNameFromPath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
scriptPath string
|
||||||
|
expectedRepo string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "относительный путь - стандартная структура",
|
||||||
|
scriptPath: "alr-default/alr-bin/alr.sh",
|
||||||
|
expectedRepo: "alr-default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "абсолютный путь",
|
||||||
|
scriptPath: "/home/user/repos/alr-default/alr-bin/alr.sh",
|
||||||
|
expectedRepo: "alr-default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "репозиторий без префикса alr-",
|
||||||
|
scriptPath: "my-repo/my-package/alr.sh",
|
||||||
|
expectedRepo: "my-repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "только имя файла",
|
||||||
|
scriptPath: "alr.sh",
|
||||||
|
expectedRepo: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "один уровень директории",
|
||||||
|
scriptPath: "package/alr.sh",
|
||||||
|
expectedRepo: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "путь с точками",
|
||||||
|
scriptPath: "./alr-default/alr-bin/alr.sh",
|
||||||
|
expectedRepo: "alr-default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "путь с двойными точками",
|
||||||
|
scriptPath: "../alr-default/alr-bin/alr.sh",
|
||||||
|
expectedRepo: "alr-default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := ExtractRepoNameFromPath(tt.scriptPath)
|
||||||
|
if result != tt.expectedRepo {
|
||||||
|
t.Errorf("ExtractRepoNameFromPath(%q) = %q, ожидается %q", tt.scriptPath, result, tt.expectedRepo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ func defaultConfigKoanf() *koanf.Koanf {
|
|||||||
"logLevel": "info",
|
"logLevel": "info",
|
||||||
"autoPull": true,
|
"autoPull": true,
|
||||||
"updateSystemOnUpgrade": false,
|
"updateSystemOnUpgrade": false,
|
||||||
"repos": []types.Repo{
|
"repo": []types.Repo{
|
||||||
{
|
{
|
||||||
Name: "alr-default",
|
Name: "alr-default",
|
||||||
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
|
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
// DO NOT EDIT MANUALLY. This file is generated.
|
// DO NOT EDIT MANUALLY. This file is generated.
|
||||||
package alrsh
|
package alrsh
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user