nip/docs/dependency-resolution.md

635 lines
15 KiB
Markdown

# Dependency Resolution in NIP
**Audience:** Package maintainers migrating packages from other distributions
**Purpose:** Understand how NIP resolves dependencies and handles conflicts
**Last Updated:** November 23, 2025
---
## Overview
NIP uses a **PubGrub-style CDCL (Conflict-Driven Clause Learning)** solver for dependency resolution. This is the same algorithm family used by Dart's pub and Rust's Cargo, adapted for NexusOS's unique requirements including variant unification and multi-source package management.
**Why this matters for package maintainers:**
- Understand why certain dependency combinations fail
- Learn how to write portable package definitions
- Avoid common pitfalls when migrating from Arch, Gentoo, Nix, or PKGSRC
- Debug dependency conflicts effectively
---
## Key Concepts
### 1. Package Terms
A **package term** is the fundamental unit in dependency resolution:
```
PackageTerm = Package Name + Version + Variant Profile + Source
```
**Example:**
```
nginx-1.24.0-{+ssl,+http2,libc=musl}-pacman
```
This represents:
- Package: `nginx`
- Version: `1.24.0`
- Variants: SSL enabled, HTTP/2 enabled, musl libc
- Source: Grafted from Arch Linux (pacman)
### 2. Variant Profiles
Unlike traditional package managers, NIP tracks **build variants** as part of package identity. This is crucial when migrating packages:
**Gentoo USE flags → NIP variants:**
```bash
# Gentoo
USE="ssl http2 -ipv6" emerge nginx
# NIP equivalent
nip install nginx +ssl +http2 -ipv6
```
**Nix package variants → NIP variants:**
```nix
# Nix
nginx.override { openssl = openssl_3; http2Support = true; }
# NIP equivalent
nip install nginx +ssl +http2 --with-openssl=3
```
### 3. Dependency Graph
NIP builds a complete dependency graph before attempting resolution:
```
nginx-1.24.0
├── openssl-3.0.0 (+ssl)
│ └── zlib-1.2.13
├── pcre-8.45
└── zlib-1.2.13
```
**Key insight:** The graph is built BEFORE solving, allowing NIP to detect circular dependencies early.
---
## The Resolution Pipeline
### Phase 1: Graph Construction
**What happens:**
1. Parse package manifest (from .npk, PKGBUILD, ebuild, or Nix expression)
2. Recursively fetch dependencies
3. Build complete dependency graph
4. Detect circular dependencies
**For package maintainers:**
- Ensure your package manifest lists ALL dependencies
- Include build-time AND runtime dependencies
- Specify version constraints clearly
**Example manifest (KDL format):**
```kdl
package "nginx" {
version "1.24.0"
dependencies {
openssl {
version ">=3.0.0"
variants "+ssl"
required true
}
pcre {
version ">=8.0"
required true
}
zlib {
version ">=1.2.0"
required true
}
}
}
```
### Phase 2: Variant Unification
**What happens:**
NIP attempts to merge variant requirements from multiple packages:
```
Package A requires: openssl +ssl +ipv6
Package B requires: openssl +ssl +http2
Result: openssl +ssl +ipv6 +http2 ✅ Success
```
**Conflict example:**
```
Package A requires: openssl libc=musl
Package B requires: openssl libc=glibc
Result: CONFLICT ❌ (exclusive domain)
```
**For package maintainers:**
- Use **non-exclusive variants** for features (ssl, ipv6, http2)
- Use **exclusive variants** for fundamental choices (libc, init system)
- Document variant requirements clearly
### Phase 3: CNF Translation
**What happens:**
The dependency graph is translated into a boolean satisfiability (SAT) problem:
```
Dependencies become implications:
nginx → openssl (if nginx then openssl)
Conflicts become exclusions:
¬(openssl-musl ∧ openssl-glibc) (not both)
```
**For package maintainers:**
- This is automatic, but understanding it helps debug conflicts
- Each package+version+variant becomes a boolean variable
- Dependencies become logical implications
### Phase 4: CDCL Solving
**What happens:**
The CDCL solver finds a satisfying assignment:
1. **Unit Propagation:** Derive forced choices
2. **Decision:** Make a choice when no forced moves
3. **Conflict Detection:** Detect unsatisfiable constraints
4. **Conflict Analysis:** Learn why the conflict occurred
5. **Backjumping:** Jump back to the decision that caused the conflict
6. **Clause Learning:** Remember this conflict to avoid it in the future
**For package maintainers:**
- The solver is very efficient (50 packages in ~14ms)
- Conflicts are reported with clear explanations
- The solver learns from conflicts, making subsequent attempts faster
### Phase 5: Topological Sort
**What happens:**
Once a solution is found, packages are sorted for installation:
```
Installation order:
1. zlib-1.2.13 (no dependencies)
2. pcre-8.45 (no dependencies)
3. openssl-3.0.0 (depends on zlib)
4. nginx-1.24.0 (depends on openssl, pcre, zlib)
```
**For package maintainers:**
- Dependencies are ALWAYS installed before dependents
- Circular dependencies are detected and rejected
- Installation order is deterministic
---
## Common Migration Scenarios
### Scenario 1: Migrating from Arch Linux (PKGBUILD)
**Arch PKGBUILD:**
```bash
pkgname=nginx
pkgver=1.24.0
depends=('pcre' 'zlib' 'openssl')
makedepends=('cmake')
```
**NIP manifest:**
```kdl
package "nginx" {
version "1.24.0"
dependencies {
pcre { version ">=8.0"; required true }
zlib { version ">=1.2.0"; required true }
openssl { version ">=3.0.0"; required true }
}
build_dependencies {
cmake { version ">=3.20"; required true }
}
}
```
**Key differences:**
- NIP separates runtime and build dependencies
- Version constraints are explicit
- Variants can be specified per-dependency
### Scenario 2: Migrating from Gentoo (ebuild)
**Gentoo ebuild:**
```bash
DEPEND="
ssl? ( dev-libs/openssl:= )
http2? ( net-libs/nghttp2 )
"
```
**NIP manifest:**
```kdl
package "nginx" {
version "1.24.0"
dependencies {
openssl {
version ">=3.0.0"
variants "+ssl"
required true
condition "ssl" // Only if +ssl variant enabled
}
nghttp2 {
version ">=1.50.0"
required true
condition "http2" // Only if +http2 variant enabled
}
}
}
```
**Key differences:**
- USE flags become variant conditions
- Conditional dependencies are explicit
- Slot dependencies (`:=`) become version constraints
### Scenario 3: Migrating from Nix
**Nix expression:**
```nix
{ stdenv, fetchurl, openssl, pcre, zlib
, http2Support ? true
, sslSupport ? true
}:
stdenv.mkDerivation {
pname = "nginx";
version = "1.24.0";
buildInputs = [ pcre zlib ]
++ lib.optional sslSupport openssl
++ lib.optional http2Support nghttp2;
}
```
**NIP manifest:**
```kdl
package "nginx" {
version "1.24.0"
dependencies {
pcre { version ">=8.0"; required true }
zlib { version ">=1.2.0"; required true }
openssl {
version ">=3.0.0"
required true
condition "ssl"
}
nghttp2 {
version ">=1.50.0"
required true
condition "http2"
}
}
variants {
ssl { default true; description "Enable SSL support" }
http2 { default true; description "Enable HTTP/2 support" }
}
}
```
**Key differences:**
- Nix's optional dependencies become conditional dependencies
- Build inputs are separated by type
- Variants are explicitly declared
---
## Debugging Dependency Conflicts
### Conflict Type 1: Version Conflict
**Error message:**
```
❌ [VersionConflict] Cannot satisfy conflicting version requirements
🔍 Context:
- Package A requires openssl >=3.0.0
- Package B requires openssl <2.0.0
💡 Suggestions:
• Update Package B to support openssl 3.x
• Use NipCells to isolate conflicting packages
• Check if Package B has a newer version available
```
**Solution for package maintainers:**
1. Update version constraints to be more flexible
2. Test with multiple versions of dependencies
3. Document minimum and maximum supported versions
### Conflict Type 2: Variant Conflict
**Error message:**
```
❌ [VariantConflict] Cannot unify conflicting variant demands
🔍 Context:
- Package A requires openssl libc=musl
- Package B requires openssl libc=glibc
💡 Suggestions:
• These packages cannot coexist in the same environment
• Use NipCells to create separate environments
• Consider building one package with compatible variants
```
**Solution for package maintainers:**
1. Make libc choice configurable if possible
2. Document which libc your package requires
3. Test with both musl and glibc
### Conflict Type 3: Circular Dependency
**Error message:**
```
❌ [CircularDependency] Circular dependency detected
🔍 Context: A → B → C → A
💡 Suggestions:
• Break the circular dependency by making one dependency optional
• Check if this is a bug in package metadata
• Consider splitting packages to break the cycle
```
**Solution for package maintainers:**
1. Review your dependency tree
2. Make build-time dependencies optional where possible
3. Consider splitting large packages into smaller components
---
## Best Practices for Package Maintainers
### 1. Write Flexible Version Constraints
**❌ Bad:**
```kdl
openssl { version "=3.0.0"; required true } // Too strict
```
**✅ Good:**
```kdl
openssl { version ">=3.0.0 <4.0.0"; required true } // Flexible
```
### 2. Document Variant Requirements
**❌ Bad:**
```kdl
// No documentation about what variants do
variants {
ssl { default true }
http2 { default true }
}
```
**✅ Good:**
```kdl
variants {
ssl {
default true
description "Enable SSL/TLS support via OpenSSL"
requires "openssl >=3.0.0"
}
http2 {
default true
description "Enable HTTP/2 protocol support"
requires "nghttp2 >=1.50.0"
}
}
```
### 3. Separate Build and Runtime Dependencies
**❌ Bad:**
```kdl
dependencies {
cmake { version ">=3.20"; required true } // Build tool
openssl { version ">=3.0.0"; required true } // Runtime
}
```
**✅ Good:**
```kdl
build_dependencies {
cmake { version ">=3.20"; required true }
}
dependencies {
openssl { version ">=3.0.0"; required true }
}
```
### 4. Test with Multiple Dependency Versions
```bash
# Test with minimum supported version
nip install --test mypackage openssl=3.0.0
# Test with latest version
nip install --test mypackage openssl=3.2.0
# Test with different variants
nip install --test mypackage +ssl -ipv6
nip install --test mypackage +ssl +ipv6
```
### 5. Use Conditional Dependencies Wisely
**❌ Bad:**
```kdl
dependencies {
openssl { version ">=3.0.0"; required true }
// Always required, even if SSL is disabled
}
```
**✅ Good:**
```kdl
dependencies {
openssl {
version ">=3.0.0"
required true
condition "ssl" // Only when +ssl variant enabled
}
}
```
---
## Performance Considerations
### Resolution Speed
NIP's resolver is designed for speed:
- **Small packages** (5-10 deps): < 10ms
- **Medium packages** (20-50 deps): < 50ms
- **Large packages** (100+ deps): < 200ms
**For package maintainers:**
- Keep dependency trees shallow when possible
- Avoid unnecessary dependencies
- Use optional dependencies for features
### Caching
NIP caches resolution results:
```bash
# First resolution (cold cache)
nip install nginx # ~50ms
# Second resolution (warm cache)
nip install nginx # ~5ms
```
**For package maintainers:**
- Resolution results are cached per variant combination
- Changing variants invalidates the cache
- Cache is shared across all packages
---
## Advanced Topics
### Variant Unification Algorithm
NIP uses a sophisticated variant unification algorithm:
1. **Group demands by package:** Collect all variant requirements for each package
2. **Check exclusivity:** Detect conflicting exclusive variants (e.g., libc)
3. **Merge non-exclusive:** Combine non-exclusive variants (e.g., +ssl +http2)
4. **Calculate hash:** Generate deterministic variant hash
5. **Return result:** Unified profile or conflict report
**Example:**
```
Input:
Package A wants: nginx +ssl +ipv6
Package B wants: nginx +ssl +http2
Process:
1. Group: nginx {+ssl, +ipv6} and nginx {+ssl, +http2}
2. Check exclusivity: None (all non-exclusive)
3. Merge: nginx {+ssl, +ipv6, +http2}
4. Hash: xxh3-abc123...
Output: nginx-1.24.0-{+ssl,+ipv6,+http2}
```
### Multi-Source Resolution
NIP can resolve dependencies from multiple sources:
```
nginx (native .npk)
├── openssl (grafted from Nix)
├── pcre (grafted from Arch)
└── zlib (native .npk)
```
**For package maintainers:**
- Specify preferred sources in package metadata
- Test with packages from different sources
- Document source compatibility
---
## Troubleshooting Guide
### Problem: "Package not found"
**Cause:** Package doesn't exist in any configured repository
**Solution:**
1. Check repository configuration: `nip repo list`
2. Update repository metadata: `nip update`
3. Search for similar packages: `nip search <name>`
### Problem: "Circular dependency detected"
**Cause:** Package A depends on B, B depends on C, C depends on A
**Solution:**
1. Review dependency tree: `nip show --tree <package>`
2. Make one dependency optional
3. Split packages to break the cycle
### Problem: "Variant conflict"
**Cause:** Two packages require incompatible variants
**Solution:**
1. Use NipCells to isolate: `nip cell create env1`
2. Build one package with compatible variants
3. Update package to support both variants
### Problem: "Version conflict"
**Cause:** Two packages require incompatible versions
**Solution:**
1. Update version constraints to be more flexible
2. Check for newer package versions
3. Use NipCells for isolation
---
## References
### Academic Papers
- **PubGrub Algorithm:** [Dart's pub solver](https://github.com/dart-lang/pub/blob/master/doc/solver.md)
- **CDCL Solving:** [Conflict-Driven Clause Learning](https://en.wikipedia.org/wiki/Conflict-driven_clause_learning)
### Related Documentation
- [Variant System Guide](VARIANT_SYSTEM_GUIDE.md)
- [Package Format Specification](formats_and_concepts.md)
- [Grafting External Packages](gentoo-nix-guide.md)
- [NipCells for Isolation](nipcells.md)
### Source Code
- `nip/src/nip/resolver/cdcl_solver.nim` - CDCL solver implementation
- `nip/src/nip/resolver/cnf_translator.nim` - CNF translation
- `nip/src/nip/resolver/dependency_graph.nim` - Graph construction
- `nip/src/nip/resolver/resolver_integration.nim` - End-to-end pipeline
---
## Glossary
- **CDCL:** Conflict-Driven Clause Learning - SAT solving technique
- **CNF:** Conjunctive Normal Form - Boolean logic representation
- **PubGrub:** Modern dependency resolution algorithm
- **SAT:** Boolean Satisfiability Problem
- **Term:** Package + Version + Variant combination
- **Variant:** Build configuration option (like Gentoo USE flags)
- **Unification:** Merging compatible variant requirements
---
**Document Version:** 1.0
**Last Updated:** November 23, 2025
**Maintainer:** NexusOS Core Team
**Feedback:** Submit issues to the NIP repository