539 lines
15 KiB
Markdown
539 lines
15 KiB
Markdown
# 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.
|
|
|
|
```nim
|
|
# 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.
|
|
|
|
```nim
|
|
# 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.
|
|
|
|
```nim
|
|
# 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
|
|
|
|
```nim
|
|
# 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
|
|
|
|
```nim
|
|
# 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
|
|
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
# 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:
|
|
|
|
```nim
|
|
# 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:
|
|
|
|
```nim
|
|
# 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
|
|
|
|
```kdl
|
|
// 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
|
|
|
|
```nim
|
|
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
|
|
|
|
```nim
|
|
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
|
|
|
|
1. **Complete Integration:** Connect all components in orchestrator
|
|
2. **Add CLI Commands:** Implement resolve, explain, conflicts commands
|
|
3. **Test End-to-End:** Run complete workflows with real packages
|
|
4. **Optimize Performance:** Profile and optimize hot paths
|
|
5. **Deploy and Monitor:** Deploy to production, track metrics
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** November 25, 2025
|
|
**Status:** Active Development
|