229 lines
4.9 KiB
Go
229 lines
4.9 KiB
Go
package resolver
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
// DependencyGraph represents a dependency graph for packages
|
|
type DependencyGraph struct {
|
|
nodes map[string]*GraphNode
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// GraphNode represents a node in the dependency graph
|
|
type GraphNode struct {
|
|
Package *SourcePackage
|
|
Dependencies []string
|
|
Dependents []string
|
|
Level int // -1 means not calculated yet
|
|
Built bool
|
|
}
|
|
|
|
// NewDependencyGraph creates a new dependency graph
|
|
func NewDependencyGraph() *DependencyGraph {
|
|
return &DependencyGraph{
|
|
nodes: make(map[string]*GraphNode),
|
|
}
|
|
}
|
|
|
|
// AddPackage adds a package to the graph
|
|
func (g *DependencyGraph) AddPackage(pkg *SourcePackage) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
if _, exists := g.nodes[pkg.Name]; !exists {
|
|
g.nodes[pkg.Name] = &GraphNode{
|
|
Package: pkg,
|
|
Dependencies: []string{},
|
|
Dependents: []string{},
|
|
Level: -1,
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddDependency adds a dependency relationship: pkg depends on dep
|
|
func (g *DependencyGraph) AddDependency(pkg, dep string) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
// Ensure both nodes exist
|
|
if _, exists := g.nodes[pkg]; !exists {
|
|
g.nodes[pkg] = &GraphNode{
|
|
Package: &SourcePackage{Name: pkg},
|
|
Dependencies: []string{},
|
|
Dependents: []string{},
|
|
Level: -1,
|
|
}
|
|
}
|
|
|
|
if _, exists := g.nodes[dep]; !exists {
|
|
g.nodes[dep] = &GraphNode{
|
|
Package: &SourcePackage{Name: dep},
|
|
Dependencies: []string{},
|
|
Dependents: []string{},
|
|
Level: -1,
|
|
}
|
|
}
|
|
|
|
// Add dependency relationship
|
|
pkgNode := g.nodes[pkg]
|
|
depNode := g.nodes[dep]
|
|
|
|
// Add dep to pkg's dependencies if not already there
|
|
found := false
|
|
for _, d := range pkgNode.Dependencies {
|
|
if d == dep {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
pkgNode.Dependencies = append(pkgNode.Dependencies, dep)
|
|
}
|
|
|
|
// Add pkg to dep's dependents if not already there
|
|
found = false
|
|
for _, d := range depNode.Dependents {
|
|
if d == pkg {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
depNode.Dependents = append(depNode.Dependents, pkg)
|
|
}
|
|
}
|
|
|
|
// CalculateBuildOrder performs topological sort to determine build order
|
|
func (g *DependencyGraph) CalculateBuildOrder() error {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
// Reset all levels
|
|
for _, node := range g.nodes {
|
|
node.Level = -1
|
|
}
|
|
|
|
// Find nodes with no dependencies (level 0)
|
|
queue := make([]*GraphNode, 0)
|
|
for _, node := range g.nodes {
|
|
if len(node.Dependencies) == 0 {
|
|
node.Level = 0
|
|
queue = append(queue, node)
|
|
}
|
|
}
|
|
|
|
// BFS to calculate levels
|
|
for len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
for _, depName := range current.Dependents {
|
|
depNode := g.nodes[depName]
|
|
|
|
// Calculate the maximum level of all dependencies
|
|
maxDepLevel := -1
|
|
for _, dep := range depNode.Dependencies {
|
|
if depNode, exists := g.nodes[dep]; exists {
|
|
if depNode.Level > maxDepLevel {
|
|
maxDepLevel = depNode.Level
|
|
}
|
|
}
|
|
}
|
|
|
|
newLevel := maxDepLevel + 1
|
|
if depNode.Level == -1 || depNode.Level < newLevel {
|
|
depNode.Level = newLevel
|
|
queue = append(queue, depNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for cycles (nodes that still have level -1)
|
|
for _, node := range g.nodes {
|
|
if node.Level == -1 {
|
|
return fmt.Errorf("dependency cycle detected involving package %s", node.Package.Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetBuildOrder returns packages in build order (dependencies first)
|
|
func (g *DependencyGraph) GetBuildOrder() []*SourcePackage {
|
|
g.mu.RLock()
|
|
defer g.mu.RUnlock()
|
|
|
|
// Group nodes by level
|
|
levels := make(map[int][]*GraphNode)
|
|
for _, node := range g.nodes {
|
|
levels[node.Level] = append(levels[node.Level], node)
|
|
}
|
|
|
|
// Sort levels
|
|
var sortedLevels []int
|
|
for level := range levels {
|
|
sortedLevels = append(sortedLevels, level)
|
|
}
|
|
sort.Ints(sortedLevels)
|
|
|
|
// Build result
|
|
result := make([]*SourcePackage, 0)
|
|
for _, level := range sortedLevels {
|
|
nodes := levels[level]
|
|
|
|
// Sort nodes within level for deterministic order
|
|
sort.Slice(nodes, func(i, j int) bool {
|
|
return nodes[i].Package.Name < nodes[j].Package.Name
|
|
})
|
|
|
|
for _, node := range nodes {
|
|
result = append(result, node.Package)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetStats returns graph statistics
|
|
func (g *DependencyGraph) GetStats() (nodeCount int, maxLevel int) {
|
|
g.mu.RLock()
|
|
defer g.mu.RUnlock()
|
|
|
|
nodeCount = len(g.nodes)
|
|
maxLevel = 0
|
|
|
|
for _, node := range g.nodes {
|
|
if node.Level > maxLevel {
|
|
maxLevel = node.Level
|
|
}
|
|
}
|
|
|
|
return nodeCount, maxLevel
|
|
}
|
|
|
|
// GetDependencies returns all dependencies for a package
|
|
func (g *DependencyGraph) GetDependencies(pkgName string) []string {
|
|
g.mu.RLock()
|
|
defer g.mu.RUnlock()
|
|
|
|
if node, exists := g.nodes[pkgName]; exists {
|
|
return append([]string{}, node.Dependencies...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDependents returns all packages that depend on the given package
|
|
func (g *DependencyGraph) GetDependents(pkgName string) []string {
|
|
g.mu.RLock()
|
|
defer g.mu.RUnlock()
|
|
|
|
if node, exists := g.nodes[pkgName]; exists {
|
|
return append([]string{}, node.Dependents...)
|
|
}
|
|
|
|
return nil
|
|
}
|