269 lines
6.6 KiB
Go
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]
|
|
}
|