zsvo/pkg/resolver/fast_resolver.go
2026-03-15 16:14:13 +03:00

269 lines
6.6 KiB
Go

package resolver
import (
"fmt"
"strings"
"sync"
"time"
"zsvo/pkg/cache"
"zsvo/pkg/deps"
)
// FastResolver provides high-performance dependency resolution using cached indices
type FastResolver struct {
indexCache *cache.IndexCache
packageIndex *PackageIndex
mu sync.RWMutex
}
// PackageIndex wraps the cache PackageIndex with additional resolver functionality
type PackageIndex struct {
*cache.PackageIndex
}
// NewFastResolver creates a new fast dependency resolver
func NewFastResolver(cacheDir string) *FastResolver {
return &FastResolver{
indexCache: cache.NewIndexCache(cacheDir),
}
}
// LoadIndex loads the package index for the given repository configuration
func (r *FastResolver) LoadIndex(mirror, suite, component string) error {
r.mu.Lock()
defer r.mu.Unlock()
idx, err := r.indexCache.GetIndex(mirror, suite, component)
if err != nil {
return err
}
r.packageIndex = &PackageIndex{PackageIndex: idx}
return nil
}
// ResolvePackage resolves a package name to its source information in O(1) time
func (r *FastResolver) ResolvePackage(name string) (*SourcePackage, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if r.packageIndex == nil {
return nil, fmt.Errorf("no package index loaded")
}
// Try direct package name lookup first
entry, found := r.packageIndex.LookupPackage(name)
if found {
return r.entryToSourcePackage(entry), nil
}
// Try binary package lookup
source, found := r.packageIndex.LookupBinary(name)
if found {
entry, found = r.packageIndex.LookupPackage(source)
if found {
return r.entryToSourcePackage(entry), nil
}
}
return nil, fmt.Errorf("package %s not found", name)
}
// ResolveDependencies recursively resolves all dependencies for a package
func (r *FastResolver) ResolveDependencies(rootPackage string) (*DependencyGraph, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if r.packageIndex == nil {
return nil, fmt.Errorf("no package index loaded")
}
graph := NewDependencyGraph()
visited := make(map[string]bool)
// Start recursive resolution
if err := r.resolveDependenciesRecursive(rootPackage, graph, visited, nil); err != nil {
return nil, err
}
// Calculate build order
if err := graph.CalculateBuildOrder(); err != nil {
return nil, err
}
return graph, nil
}
// resolveDependenciesRecursive builds the dependency graph recursively
func (r *FastResolver) resolveDependenciesRecursive(pkgName string, graph *DependencyGraph, visited map[string]bool, path []string) error {
// Check for cycles
for _, p := range path {
if p == pkgName {
return fmt.Errorf("dependency cycle detected: %s -> %s", strings.Join(path, " -> "), pkgName)
}
}
// Skip if already visited
if visited[pkgName] {
return nil
}
visited[pkgName] = true
// Resolve the package
pkg, err := r.ResolvePackage(pkgName)
if err != nil {
return err
}
// Add to graph
graph.AddPackage(pkg)
// Recursively resolve dependencies
for _, dep := range pkg.BuildDepends {
depName := extractPackageName(dep)
if depName == "" {
continue
}
// Skip if it's a system package that doesn't need building
if isSystemPackage(depName) {
continue
}
// Add dependency relationship
graph.AddDependency(pkgName, depName)
// Recursively resolve
newPath := append(path, pkgName)
if err := r.resolveDependenciesRecursive(depName, graph, visited, newPath); err != nil {
return err
}
}
return nil
}
// GetBuildOrder returns the packages in build order (dependencies first)
func (r *FastResolver) GetBuildOrder(rootPackage string) ([]*SourcePackage, error) {
graph, err := r.ResolveDependencies(rootPackage)
if err != nil {
return nil, err
}
return graph.GetBuildOrder(), nil
}
// entryToSourcePackage converts a cache entry to a SourcePackage
func (r *FastResolver) entryToSourcePackage(entry *cache.PackageEntry) *SourcePackage {
return &SourcePackage{
Name: entry.Package,
Version: entry.Version,
Directory: entry.Directory,
DSCName: entry.DSCName,
DSCSHA256: entry.DSCSHA256,
Binaries: entry.Binaries,
BuildDepends: entry.BuildDepends,
}
}
// AddPackage adds a package to the current index (for updating cache)
func (r *FastResolver) AddPackage(entry *cache.PackageEntry) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.packageIndex == nil {
return fmt.Errorf("no package index loaded")
}
r.packageIndex.AddPackage(entry)
return nil
}
// GetStats returns resolver statistics
func (r *FastResolver) GetStats() (int, int, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
if r.packageIndex == nil {
return 0, 0, false
}
pkgCount, binCount := r.packageIndex.GetStats()
expired := r.packageIndex.IsExpired(24 * time.Hour) // Consider expired after 24 hours
return pkgCount, binCount, expired
}
// SaveIndex persists the current index to disk
func (r *FastResolver) SaveIndex(mirror, suite, component string) error {
r.mu.RLock()
defer r.mu.RUnlock()
if r.packageIndex == nil {
return fmt.Errorf("no package index loaded")
}
cacheFile := r.indexCache.CacheFilePath(mirror, suite, component)
return r.packageIndex.Save(cacheFile)
}
// SourcePackage represents a Debian source package
type SourcePackage struct {
Name string
Version string
Directory string
DSCName string
DSCSHA256 string
Binaries []string
BuildDepends []string
}
// extractPackageName extracts package name from dependency string
func extractPackageName(dep string) string {
// Parse dependency constraints using existing deps package
req, err := deps.ParseRequirement(dep)
if err != nil {
return ""
}
if len(req.Alternatives) > 0 {
return req.Alternatives[0].Name // Take first alternative
}
return ""
}
// isSystemPackage checks if a package is a system package that doesn't need building
func isSystemPackage(name string) bool {
systemPackages := map[string]bool{
"gcc": true,
"g++": true,
"make": true,
"bash": true,
"glibc": true,
"libc6": true,
"libc-bin": true,
"base-files": true,
"base-passwd": true,
"coreutils": true,
"dash": true,
"debianutils": true,
"diffutils": true,
"dpkg": true,
"findutils": true,
"grep": true,
"gzip": true,
"hostname": true,
"init-system-helpers": true,
"login": true,
"ncurses-base": true,
"ncurses-bin": true,
"perl-base": true,
"sed": true,
"sysvinit": true,
"util-linux": true,
}
return systemPackages[name]
}