635 lines
15 KiB
Markdown
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
|