15 KiB
Dependency Resolver Integration Guide
Version: 1.0
Last Updated: November 25, 2025
Status: Active Development
Overview
This guide explains how to integrate all components of the NIP dependency resolver into a cohesive system. It covers the complete resolution workflow from package specification to installed artifacts.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ NIP CLI Interface │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Resolution Orchestrator │
│ - Coordinates all resolver components │
│ - Manages cache lifecycle │
│ - Handles error reporting │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Variant │ │ Graph │ │ Solver │
│ Unification │ │ Builder │ │ (CDCL) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└─────────────────────┼─────────────────────┘
▼
┌──────────────┐
│ Cache │
│ (3-Tier) │
└──────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ L1 (Memory) │ │ L2 (CAS) │ │ L3 (SQLite) │
└──────────────┘ └──────────────┘ └──────────────┘
Component Integration
1. Resolution Orchestrator
The orchestrator coordinates all resolver components and manages the resolution workflow.
# nip/src/nip/resolver/orchestrator.nim
import ./types
import ./variant
import ./graph_builder
import ./solver
import ./conflict
import ./build_synthesis
import ./resolution_cache
import ../cas/storage
type
ResolutionOrchestrator* = ref object
cache: ResolutionCache
casStorage: CASStorage
repositories: seq[Repository]
config: ResolverConfig
ResolverConfig* = object
enableCache: bool
enableParallel: bool
maxRetries: int
timeout: Duration
proc newResolutionOrchestrator*(
casStorage: CASStorage,
repositories: seq[Repository],
config: ResolverConfig
): ResolutionOrchestrator =
result = ResolutionOrchestrator(
cache: newResolutionCache(casStorage, enabled = config.enableCache),
casStorage: casStorage,
repositories: repositories,
config: config
)
proc resolve*(
orchestrator: ResolutionOrchestrator,
rootPackage: string,
constraint: string,
variantDemand: VariantDemand
): Result[DependencyGraph, ResolutionError] =
## Main resolution entry point
# 1. Check cache
let repoHash = calculateGlobalRepoStateHash(orchestrator.repositories)
orchestrator.cache.updateRepoHash(repoHash)
let cacheKey = CacheKey(
rootPackage: rootPackage,
rootConstraint: constraint,
repoStateHash: repoHash,
variantDemand: variantDemand
)
let cached = orchestrator.cache.get(cacheKey)
if cached.value.isSome:
return ok(cached.value.get)
# 2. Build dependency graph
let graphResult = buildDependencyGraph(
rootPackage,
constraint,
variantDemand,
orchestrator.repositories
)
if graphResult.isErr:
return err(graphResult.error)
let graph = graphResult.get
# 3. Solve constraints
let solverResult = solve(graph)
if solverResult.isErr:
# Detect and report conflicts
let conflicts = detectConflicts(graph)
return err(ResolutionError(
kind: ConflictError,
conflicts: conflicts
))
# 4. Synthesize builds
for node in graph.nodes:
let buildResult = synthesizeBuild(node, variantDemand)
if buildResult.isErr:
return err(buildResult.error)
# 5. Cache result
orchestrator.cache.put(cacheKey, graph)
return ok(graph)
2. CLI Integration
Connect the orchestrator to the CLI interface.
# nip/src/nip/cli/resolve.nim
import ../resolver/orchestrator
import ../resolver/types
import ../cas/storage
proc resolveCommand*(args: seq[string]): int =
## Handle 'nip resolve <package>' command
if args.len < 1:
echo "Usage: nip resolve <package> [constraint]"
return 1
let packageName = args[0]
let constraint = if args.len > 1: args[1] else: "*"
# Load configuration
let config = loadResolverConfig()
# Initialize components
let cas = newCASStorage(config.casPath)
let repos = loadRepositories(config.repoPath)
let orchestrator = newResolutionOrchestrator(cas, repos, config)
# Resolve dependencies
echo fmt"Resolving {packageName} {constraint}..."
let variantDemand = VariantDemand(
useFlags: @[],
libc: "musl",
allocator: "jemalloc",
targetArch: "x86_64",
buildFlags: @[]
)
let result = orchestrator.resolve(packageName, constraint, variantDemand)
if result.isErr:
echo "❌ Resolution failed:"
echo formatError(result.error)
return 1
let graph = result.get
# Display results
echo "✅ Resolution successful!"
echo fmt"Packages: {graph.nodes.len}"
echo ""
echo "Installation order:"
let sorted = topologicalSort(graph)
for i, node in sorted:
echo fmt" {i+1}. {node.packageId.name} {node.packageId.version}"
return 0
3. Error Handling Integration
Provide comprehensive error reporting.
# nip/src/nip/resolver/error_reporting.nim
import ./types
import ./conflict
proc formatError*(error: ResolutionError): string =
## Format resolution error for user display
case error.kind:
of ConflictError:
result = "Dependency conflicts detected:\n\n"
for conflict in error.conflicts:
result &= formatConflict(conflict)
result &= "\n"
result &= "\nSuggestions:\n"
result &= " • Try relaxing version constraints\n"
result &= " • Use NipCell for conflicting packages\n"
result &= " • Check for circular dependencies\n"
of PackageNotFoundError:
result = fmt"Package not found: {error.packageName}\n\n"
result &= "Suggestions:\n"
result &= " • Check package name spelling\n"
result &= " • Update repository metadata: nip update\n"
result &= " • Search for similar packages: nip search {error.packageName}\n"
of BuildFailureError:
result = fmt"Build failed for {error.packageName}:\n"
result &= error.buildLog
result &= "\n\nSuggestions:\n"
result &= " • Check build dependencies\n"
result &= " • Review build log for errors\n"
result &= " • Try different variant flags\n"
of TimeoutError:
result = "Resolution timeout exceeded\n\n"
result &= "Suggestions:\n"
result &= " • Increase timeout: nip config set timeout 600\n"
result &= " • Check network connectivity\n"
result &= " • Simplify dependency constraints\n"
Workflow Examples
Example 1: Simple Package Resolution
# Resolve nginx with default settings
let orchestrator = newResolutionOrchestrator(cas, repos, defaultConfig)
let result = orchestrator.resolve(
"nginx",
">=1.24.0",
VariantDemand(
useFlags: @["ssl", "http2"],
libc: "musl",
allocator: "jemalloc",
targetArch: "x86_64",
buildFlags: @[]
)
)
if result.isOk:
let graph = result.get
echo fmt"Resolved {graph.nodes.len} packages"
Example 2: Complex Resolution with Caching
# First resolution (cold cache)
let start1 = cpuTime()
let result1 = orchestrator.resolve("complex-app", "*", demand)
let time1 = cpuTime() - start1
echo fmt"Cold cache: {time1 * 1000:.2f}ms"
# Second resolution (warm cache)
let start2 = cpuTime()
let result2 = orchestrator.resolve("complex-app", "*", demand)
let time2 = cpuTime() - start2
echo fmt"Warm cache: {time2 * 1000:.2f}ms"
let speedup = time1 / time2
echo fmt"Speedup: {speedup:.2f}x"
Example 3: Conflict Handling
let result = orchestrator.resolve("conflicting-app", "*", demand)
if result.isErr:
let error = result.error
if error.kind == ConflictError:
echo "Conflicts detected:"
for conflict in error.conflicts:
case conflict.kind:
of VersionConflict:
echo fmt" • {conflict.package1} requires {conflict.constraint1}"
echo fmt" {conflict.package2} requires {conflict.constraint2}"
of VariantConflict:
echo fmt" • Incompatible variants for {conflict.packageName}"
echo fmt" {conflict.variant1} vs {conflict.variant2}"
# Suggest NipCell fallback
echo "\nConsider using NipCell for isolation:"
echo " nip cell create app1-env"
echo " nip install --cell=app1-env conflicting-app"
Testing Integration
Unit Tests
Test each component independently:
# Test variant unification
suite "Variant Integration":
test "Unify compatible variants":
let v1 = VariantDemand(useFlags: @["ssl"])
let v2 = VariantDemand(useFlags: @["http2"])
let result = unifyVariants(v1, v2)
check result.isOk
check result.get.useFlags == @["ssl", "http2"]
Integration Tests
Test complete workflows:
# Test end-to-end resolution
suite "Resolution Integration":
test "Resolve simple package":
let orchestrator = setupTestOrchestrator()
let result = orchestrator.resolve("test-pkg", "*", defaultDemand)
check result.isOk
check result.get.nodes.len > 0
Performance Tests
Validate performance targets:
# Test resolution performance
suite "Performance Integration":
test "Simple package resolves in <50ms":
let orchestrator = setupTestOrchestrator()
let start = cpuTime()
let result = orchestrator.resolve("simple-pkg", "*", defaultDemand)
let elapsed = cpuTime() - start
check result.isOk
check elapsed < 0.050 # 50ms
Configuration
Resolver Configuration File
// nip-resolver.kdl
resolver {
version "1.0"
cache {
enabled true
l1_capacity 100
l2_enabled true
l3_enabled true
l3_path "/var/lib/nip/cache.db"
}
performance {
parallel_enabled false // Enable when ready
max_parallel_jobs 4
timeout 300 // seconds
max_retries 3
}
repositories {
update_interval "24h"
verify_signatures true
}
variants {
default_libc "musl"
default_allocator "jemalloc"
default_arch "x86_64"
}
}
Deployment Checklist
Pre-Deployment
- All unit tests passing
- All integration tests passing
- Performance benchmarks meet targets
- Cache invalidation tested
- Error handling comprehensive
- Documentation complete
Deployment
- Deploy resolver components
- Initialize cache database
- Configure repositories
- Set up monitoring
- Enable profiling (optional)
Post-Deployment
- Monitor cache hit rates
- Track resolution times
- Collect error reports
- Analyze performance metrics
- Optimize based on real usage
Monitoring and Observability
Metrics to Track
type
ResolverMetrics* = object
totalResolutions*: int
successfulResolutions*: int
failedResolutions*: int
avgResolutionTime*: float
cacheHitRate*: float
conflictRate*: float
proc collectMetrics*(orchestrator: ResolutionOrchestrator): ResolverMetrics =
let cacheMetrics = orchestrator.cache.getMetrics()
return ResolverMetrics(
totalResolutions: orchestrator.totalResolutions,
successfulResolutions: orchestrator.successCount,
failedResolutions: orchestrator.failureCount,
avgResolutionTime: orchestrator.totalTime / orchestrator.totalResolutions.float,
cacheHitRate: cacheMetrics.totalHitRate,
conflictRate: orchestrator.conflictCount.float / orchestrator.totalResolutions.float
)
Logging
import logging
# Configure logging
let logger = newConsoleLogger(lvlInfo)
addHandler(logger)
# Log resolution events
info("Starting resolution", packageName, constraint)
debug("Cache lookup", cacheKey, cacheResult)
warn("Conflict detected", conflictType, packages)
error("Resolution failed", errorMessage, stackTrace)
Troubleshooting
Common Issues
Issue: Cache not working
Solution: Check cache is enabled in config, verify CAS storage accessible
Issue: Slow resolution
Solution: Enable profiling, identify hot paths, optimize bottlenecks
Issue: Conflicts not detected
Solution: Verify conflict detection enabled, check conflict rules
Issue: Memory usage high
Solution: Reduce L1 cache capacity, enable LRU eviction
Next Steps
- Complete Integration: Connect all components in orchestrator
- Add CLI Commands: Implement resolve, explain, conflicts commands
- Test End-to-End: Run complete workflows with real packages
- Optimize Performance: Profile and optimize hot paths
- Deploy and Monitor: Deploy to production, track metrics
Document Version: 1.0
Last Updated: November 25, 2025
Status: Active Development