142 lines
3.3 KiB
Go
142 lines
3.3 KiB
Go
package deps
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// PackageRepository provides access to installed packages
|
|
type PackageRepository interface {
|
|
GetInstalled() (map[string]*PackageInfo, error)
|
|
GetPackage(name string) (*PackageInfo, error)
|
|
}
|
|
|
|
// PackageInfo represents package metadata
|
|
type PackageInfo struct {
|
|
Name string
|
|
Version string
|
|
Dependencies []string
|
|
}
|
|
|
|
// DependencyResolver handles dependency resolution
|
|
type DependencyResolver struct {
|
|
repo PackageRepository
|
|
}
|
|
|
|
// NewDependencyResolver creates a new resolver
|
|
func NewDependencyResolver(repo PackageRepository) *DependencyResolver {
|
|
return &DependencyResolver{repo: repo}
|
|
}
|
|
|
|
// ResolveOrder determines installation order using topological sort
|
|
func (r *DependencyResolver) ResolveOrder(packages []*PackageInfo) ([]*PackageInfo, error) {
|
|
// Create name to package map
|
|
pkgMap := make(map[string]*PackageInfo)
|
|
for _, pkg := range packages {
|
|
pkgMap[pkg.Name] = pkg
|
|
}
|
|
|
|
// Build dependency graph
|
|
indegree := make(map[string]int)
|
|
graph := make(map[string][]string)
|
|
|
|
// Initialize
|
|
for _, pkg := range packages {
|
|
indegree[pkg.Name] = 0
|
|
graph[pkg.Name] = []string{}
|
|
}
|
|
|
|
// Calculate indegrees
|
|
for _, pkg := range packages {
|
|
reqs, err := ParseRequirements(pkg.Dependencies)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid dependencies in %s: %w", pkg.Name, err)
|
|
}
|
|
|
|
for _, req := range reqs {
|
|
for _, alt := range req.Alternatives {
|
|
if depPkg, exists := pkgMap[alt.Name]; exists {
|
|
// Check if version constraint is satisfied
|
|
if alt.MatchesVersion(depPkg.Version) {
|
|
graph[alt.Name] = append(graph[alt.Name], pkg.Name)
|
|
indegree[pkg.Name]++
|
|
break // Only one alternative needed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Topological sort using Kahn's algorithm
|
|
queue := make([]string, 0)
|
|
for name, deg := range indegree {
|
|
if deg == 0 {
|
|
queue = append(queue, name)
|
|
}
|
|
}
|
|
|
|
result := make([]*PackageInfo, 0)
|
|
for len(queue) > 0 {
|
|
// Get first element
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
// Add to result
|
|
result = append(result, pkgMap[current])
|
|
|
|
// Remove edges
|
|
for _, neighbor := range graph[current] {
|
|
indegree[neighbor]--
|
|
if indegree[neighbor] == 0 {
|
|
queue = append(queue, neighbor)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(result) != len(packages) {
|
|
return nil, fmt.Errorf("dependency cycle detected")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// CheckDependencies verifies if dependencies are satisfied
|
|
func (r *DependencyResolver) CheckDependencies(pkg *PackageInfo) error {
|
|
installed, err := r.repo.GetInstalled()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get installed packages: %w", err)
|
|
}
|
|
|
|
missing := r.findMissingDependencies(pkg, installed)
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("missing dependencies: %s", strings.Join(missing, ", "))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *DependencyResolver) findMissingDependencies(pkg *PackageInfo, installed map[string]*PackageInfo) []string {
|
|
var missing []string
|
|
|
|
reqs, err := ParseRequirements(pkg.Dependencies)
|
|
if err != nil {
|
|
return []string{err.Error()}
|
|
}
|
|
|
|
for _, req := range reqs {
|
|
satisfied := false
|
|
for _, alt := range req.Alternatives {
|
|
if installedPkg, exists := installed[alt.Name]; exists {
|
|
if alt.MatchesVersion(installedPkg.Version) {
|
|
satisfied = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !satisfied {
|
|
missing = append(missing, req.Raw)
|
|
}
|
|
}
|
|
|
|
return missing
|
|
}
|