Оптимизация сборки зависимостей и исправление кеширования
All checks were successful
Create Release / changelog (push) Successful in 2m26s
All checks were successful
Create Release / changelog (push) Successful in 2m26s
- Добавлено полное разрешение дерева зависимостей перед сборкой - Общие зависимости теперь собираются только один раз - Исправлена работа кеша для подпакетов - Исправлена обработка системных зависимостей
This commit is contained in:
@@ -565,79 +565,173 @@ func (b *Builder) BuildALRDeps(
|
||||
},
|
||||
depends []string,
|
||||
) (buildDeps []*BuiltDep, 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, fmt.Errorf("failed FindPkgs: %w", err)
|
||||
}
|
||||
repoDeps = notFound
|
||||
|
||||
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
||||
// Для зависимостей указываем isDependency = true
|
||||
pkgs := cliutils.FlattenPkgsWithContext(
|
||||
ctx,
|
||||
found,
|
||||
"install",
|
||||
input.BuildOpts().Interactive,
|
||||
true,
|
||||
)
|
||||
|
||||
pkgs, err = b.installerExecutor.FilterPackagesByVersion(ctx, pkgs, input.OSRelease())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to filter packages by version: %w", err)
|
||||
}
|
||||
|
||||
type item struct {
|
||||
pkg *alrsh.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, fmt.Errorf("failed build package from db: %w", err)
|
||||
}
|
||||
|
||||
buildDeps = append(buildDeps, res...)
|
||||
}
|
||||
if len(depends) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
slog.Info(gotext.Get("Installing dependencies"))
|
||||
|
||||
// Шаг 1: Рекурсивно разрешаем ВСЕ зависимости
|
||||
depTree, systemDeps, err := b.ResolveDependencyTree(ctx, input, depends)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to resolve dependency tree: %w", err)
|
||||
}
|
||||
|
||||
// Системные зависимости возвращаем как repoDeps
|
||||
repoDeps = systemDeps
|
||||
|
||||
// Шаг 2: Собираем список всех пакетов из дерева для топологической сортировки
|
||||
allFound := make(map[string][]alrsh.Package)
|
||||
for baseName, node := range depTree {
|
||||
allFound[baseName] = []alrsh.Package{*node.Package}
|
||||
}
|
||||
|
||||
// Шаг 3: Топологическая сортировка (от корней к листьям)
|
||||
sortedPkgs, err := TopologicalSort(depTree, allFound)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
|
||||
for _, basePkgName := range sortedPkgs {
|
||||
node := depTree[basePkgName]
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pkg := node.Package
|
||||
|
||||
// Находим ВСЕ подпакеты с этим BasePkgName
|
||||
allSubpkgs, err := b.findAllSubpackages(ctx, basePkgName, pkg.Repository)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find subpackages for %s: %w", basePkgName, err)
|
||||
}
|
||||
|
||||
// Проверяем кеш для ВСЕХ подпакетов
|
||||
scriptInfo := b.scriptResolver.ResolveScript(ctx, pkg)
|
||||
buildInput := &BuildInput{
|
||||
script: scriptInfo.Script,
|
||||
repository: scriptInfo.Repository,
|
||||
packages: allSubpkgs,
|
||||
pkgFormat: input.PkgFormat(),
|
||||
opts: input.BuildOpts(),
|
||||
info: input.OSRelease(),
|
||||
}
|
||||
|
||||
cachedDeps, allInCache, err := b.checkCacheForAllSubpackages(ctx, buildInput, basePkgName, allSubpkgs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if allInCache {
|
||||
// Все подпакеты в кеше, используем их
|
||||
buildDeps = append(buildDeps, cachedDeps...)
|
||||
continue
|
||||
}
|
||||
|
||||
// Собираем пакет (без рекурсивной сборки зависимостей, так как они уже собраны)
|
||||
res, err := b.BuildPackageFromDb(
|
||||
ctx,
|
||||
&BuildPackageFromDbArgs{
|
||||
Package: pkg,
|
||||
Packages: allSubpkgs,
|
||||
BuildArgs: BuildArgs{
|
||||
Opts: input.BuildOpts(),
|
||||
Info: input.OSRelease(),
|
||||
PkgFormat_: input.PkgFormat(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed build package from db: %w", err)
|
||||
}
|
||||
|
||||
buildDeps = append(buildDeps, res...)
|
||||
}
|
||||
|
||||
repoDeps = removeDuplicates(repoDeps)
|
||||
buildDeps = removeDuplicates(buildDeps)
|
||||
|
||||
return buildDeps, repoDeps, nil
|
||||
}
|
||||
|
||||
// findAllSubpackages находит все подпакеты для базового пакета
|
||||
func (b *Builder) findAllSubpackages(ctx context.Context, basePkgName, repository string) ([]string, error) {
|
||||
// Запрашиваем все пакеты с этим basepkg_name
|
||||
pkgs, _, err := b.repos.FindPkgs(ctx, []string{basePkgName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subpkgs []string
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, pkgList := range pkgs {
|
||||
for _, pkg := range pkgList {
|
||||
// Проверяем, что это пакет из нужного репозитория
|
||||
if pkg.Repository == repository {
|
||||
pkgBase := pkg.BasePkgName
|
||||
if pkgBase == "" {
|
||||
pkgBase = pkg.Name
|
||||
}
|
||||
|
||||
// Добавляем только если это пакет с нужным BasePkgName
|
||||
if pkgBase == basePkgName && !seen[pkg.Name] {
|
||||
subpkgs = append(subpkgs, pkg.Name)
|
||||
seen[pkg.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return subpkgs, nil
|
||||
}
|
||||
|
||||
// checkCacheForAllSubpackages проверяет кеш для всех подпакетов
|
||||
func (b *Builder) checkCacheForAllSubpackages(
|
||||
ctx context.Context,
|
||||
buildInput *BuildInput,
|
||||
basePkgName string,
|
||||
subpkgs []string,
|
||||
) ([]*BuiltDep, bool, error) {
|
||||
var cachedDeps []*BuiltDep
|
||||
allInCache := true
|
||||
|
||||
// Получаем информацию обо всех подпакетах
|
||||
pkgsInfo, _, err := b.repos.FindPkgs(ctx, subpkgs)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to find subpackages info: %w", err)
|
||||
}
|
||||
|
||||
for _, pkgName := range subpkgs {
|
||||
var pkgForCheck *alrsh.Package
|
||||
|
||||
// Находим Package для подпакета
|
||||
if pkgList, ok := pkgsInfo[pkgName]; ok && len(pkgList) > 0 {
|
||||
pkgForCheck = &pkgList[0]
|
||||
}
|
||||
|
||||
if pkgForCheck != nil {
|
||||
pkgPath, found, err := b.cacheExecutor.CheckForBuiltPackage(ctx, buildInput, pkgForCheck)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to check cache: %w", err)
|
||||
}
|
||||
|
||||
if found {
|
||||
slog.Info(gotext.Get("Using cached package"), "name", pkgName, "path", pkgPath)
|
||||
cachedDeps = append(cachedDeps, &BuiltDep{
|
||||
Name: pkgName,
|
||||
Path: pkgPath,
|
||||
})
|
||||
} else {
|
||||
allInCache = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cachedDeps, allInCache && len(cachedDeps) > 0, nil
|
||||
}
|
||||
|
||||
func (i *Builder) installBuildDeps(
|
||||
ctx context.Context,
|
||||
input interface {
|
||||
|
||||
@@ -40,7 +40,12 @@ func (c *Cache) CheckForBuiltPackage(
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename)
|
||||
// Для подпакетов используем BasePkgName, чтобы искать в правильной директории
|
||||
baseName := vars.BasePkgName
|
||||
if baseName == "" {
|
||||
baseName = vars.Name
|
||||
}
|
||||
pkgPath := filepath.Join(getBaseDir(c.cfg, baseName), filename)
|
||||
|
||||
_, err = os.Stat(pkgPath)
|
||||
if err != nil {
|
||||
|
||||
193
internal/build/dependency_tree.go
Normal file
193
internal/build/dependency_tree.go
Normal file
@@ -0,0 +1,193 @@
|
||||
// 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 build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
// DependencyNode представляет узел в дереве зависимостей
|
||||
type DependencyNode struct {
|
||||
Package *alrsh.Package
|
||||
BasePkgName string
|
||||
Dependencies []string // Имена зависимостей
|
||||
}
|
||||
|
||||
// ResolveDependencyTree рекурсивно разрешает все зависимости и возвращает
|
||||
// плоский список всех уникальных пакетов, необходимых для сборки
|
||||
// и список системных зависимостей (не найденных в ALR-репозиториях)
|
||||
func (b *Builder) ResolveDependencyTree(
|
||||
ctx context.Context,
|
||||
input interface {
|
||||
OsInfoProvider
|
||||
PkgFormatProvider
|
||||
},
|
||||
initialPkgs []string,
|
||||
) (map[string]*DependencyNode, []string, error) {
|
||||
resolved := make(map[string]*DependencyNode)
|
||||
visited := make(map[string]bool)
|
||||
systemDeps := make(map[string]bool) // Для дедупликации системных зависимостей
|
||||
|
||||
var resolve func(pkgNames []string) error
|
||||
resolve = func(pkgNames []string) error {
|
||||
if len(pkgNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Находим пакеты
|
||||
found, notFound, err := b.repos.FindPkgs(ctx, pkgNames)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find packages: %w", err)
|
||||
}
|
||||
|
||||
// Собираем системные зависимости (не найденные в ALR)
|
||||
for _, pkgName := range notFound {
|
||||
systemDeps[pkgName] = true
|
||||
}
|
||||
|
||||
// Обрабатываем найденные пакеты
|
||||
for pkgName, pkgList := range found {
|
||||
if visited[pkgName] {
|
||||
continue
|
||||
}
|
||||
visited[pkgName] = true
|
||||
|
||||
// Берем первый пакет из списка (или можно добавить выбор пользователя)
|
||||
if len(pkgList) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pkg := pkgList[0]
|
||||
|
||||
// Определяем базовое имя пакета
|
||||
baseName := pkg.BasePkgName
|
||||
if baseName == "" {
|
||||
baseName = pkg.Name
|
||||
}
|
||||
|
||||
// Если уже обработали этот базовый пакет, пропускаем
|
||||
if resolved[baseName] != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Получаем зависимости для этого дистрибутива
|
||||
// Пакет из БД уже содержит разрешенные значения для текущего дистрибутива
|
||||
deps := pkg.Depends.Resolved()
|
||||
buildDeps := pkg.BuildDepends.Resolved()
|
||||
|
||||
// Объединяем зависимости
|
||||
allDeps := append([]string{}, deps...)
|
||||
allDeps = append(allDeps, buildDeps...)
|
||||
|
||||
// Добавляем узел в resolved
|
||||
resolved[baseName] = &DependencyNode{
|
||||
Package: &pkg,
|
||||
BasePkgName: baseName,
|
||||
Dependencies: allDeps,
|
||||
}
|
||||
|
||||
// Рекурсивно разрешаем зависимости
|
||||
if len(allDeps) > 0 {
|
||||
if err := resolve(allDeps); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Начинаем разрешение с начальных пакетов
|
||||
if err := resolve(initialPkgs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Преобразуем map в слайс для системных зависимостей
|
||||
var systemDepsList []string
|
||||
for dep := range systemDeps {
|
||||
systemDepsList = append(systemDepsList, dep)
|
||||
}
|
||||
|
||||
return resolved, systemDepsList, nil
|
||||
}
|
||||
|
||||
// TopologicalSort выполняет топологическую сортировку пакетов по зависимостям
|
||||
// Возвращает список базовых имен пакетов в порядке сборки (от корней к листьям)
|
||||
func TopologicalSort(nodes map[string]*DependencyNode, allPkgs map[string][]alrsh.Package) ([]string, error) {
|
||||
// Список для результата
|
||||
var result []string
|
||||
|
||||
// Множество посещенных узлов
|
||||
visited := make(map[string]bool)
|
||||
|
||||
// Множество узлов в текущем пути (для обнаружения циклов)
|
||||
inStack := make(map[string]bool)
|
||||
|
||||
var visit func(basePkgName string) error
|
||||
visit = func(basePkgName string) error {
|
||||
if visited[basePkgName] {
|
||||
return nil
|
||||
}
|
||||
|
||||
if inStack[basePkgName] {
|
||||
return fmt.Errorf("circular dependency detected: %s", basePkgName)
|
||||
}
|
||||
|
||||
node := nodes[basePkgName]
|
||||
if node == nil {
|
||||
// Это системный пакет, игнорируем
|
||||
return nil
|
||||
}
|
||||
|
||||
inStack[basePkgName] = true
|
||||
|
||||
// Посещаем все зависимости
|
||||
for _, dep := range node.Dependencies {
|
||||
// Находим базовое имя для зависимости
|
||||
depBaseName := dep
|
||||
|
||||
// Проверяем, есть ли этот пакет в 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
|
||||
}
|
||||
}
|
||||
|
||||
inStack[basePkgName] = false
|
||||
visited[basePkgName] = true
|
||||
result = append(result, basePkgName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Посещаем все узлы
|
||||
for basePkgName := range nodes {
|
||||
if err := visit(basePkgName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -42,6 +42,15 @@ func getDirs(
|
||||
cfg Config,
|
||||
scriptPath string,
|
||||
basePkg string,
|
||||
) (types.Directories, error) {
|
||||
return getDirsForPackage(cfg, scriptPath, basePkg, "")
|
||||
}
|
||||
|
||||
func getDirsForPackage(
|
||||
cfg Config,
|
||||
scriptPath string,
|
||||
basePkg string,
|
||||
packageName string,
|
||||
) (types.Directories, error) {
|
||||
pkgsDir := cfg.GetPaths().PkgsDir
|
||||
|
||||
@@ -50,10 +59,18 @@ func getDirs(
|
||||
return types.Directories{}, err
|
||||
}
|
||||
baseDir := filepath.Join(pkgsDir, basePkg)
|
||||
|
||||
// Для подпакетов используем отдельную директорию pkg_<имя_подпакета>
|
||||
// Для обычных пакетов используем просто pkg
|
||||
pkgDirName := "pkg"
|
||||
if packageName != "" {
|
||||
pkgDirName = "pkg_" + packageName
|
||||
}
|
||||
|
||||
return types.Directories{
|
||||
BaseDir: getBaseDir(cfg, basePkg),
|
||||
SrcDir: getSrcDir(cfg, basePkg),
|
||||
PkgDir: filepath.Join(baseDir, "pkg"),
|
||||
PkgDir: filepath.Join(baseDir, pkgDirName),
|
||||
ScriptDir: getScriptDir(scriptPath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -130,12 +130,34 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
||||
packageName = vars.Name
|
||||
}
|
||||
|
||||
// Для каждого подпакета создаём отдельную директорию
|
||||
pkgDirs, err := getDirsForPackage(e.cfg, sf.Path(), basePkg, packageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Создаём директорию для подпакета
|
||||
if err := os.MkdirAll(pkgDirs.PkgDir, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Обновляем переменную окружения $pkgdir для текущего подпакета
|
||||
setPkgdirCmd := fmt.Sprintf("pkgdir='%s'", pkgDirs.PkgDir)
|
||||
setPkgdirScript, err := syntax.NewParser().Parse(strings.NewReader(setPkgdirCmd), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = runner.Run(ctx, setPkgdirScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkgFormat := input.pkgFormat
|
||||
|
||||
funcOut, err := e.ExecutePackageFunctions(
|
||||
ctx,
|
||||
dec,
|
||||
dirs,
|
||||
pkgDirs,
|
||||
packageName,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -148,7 +170,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
||||
ctx,
|
||||
input,
|
||||
vars,
|
||||
dirs,
|
||||
pkgDirs,
|
||||
append(
|
||||
repoDeps,
|
||||
GetBuiltName(builtDeps)...,
|
||||
@@ -165,7 +187,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
||||
}
|
||||
|
||||
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
|
||||
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
|
||||
pkgPath := filepath.Join(pkgDirs.BaseDir, pkgName) // Определяем путь к пакету
|
||||
|
||||
slog.Info(gotext.Get("Creating package file"), "path", pkgPath, "name", pkgName)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user