15 KiB
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:
# Gentoo
USE="ssl http2 -ipv6" emerge nginx
# NIP equivalent
nip install nginx +ssl +http2 -ipv6
Nix package variants → NIP variants:
# 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:
- Parse package manifest (from .npk, PKGBUILD, ebuild, or Nix expression)
- Recursively fetch dependencies
- Build complete dependency graph
- 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):
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:
- Unit Propagation: Derive forced choices
- Decision: Make a choice when no forced moves
- Conflict Detection: Detect unsatisfiable constraints
- Conflict Analysis: Learn why the conflict occurred
- Backjumping: Jump back to the decision that caused the conflict
- 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:
pkgname=nginx
pkgver=1.24.0
depends=('pcre' 'zlib' 'openssl')
makedepends=('cmake')
NIP manifest:
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:
DEPEND="
ssl? ( dev-libs/openssl:= )
http2? ( net-libs/nghttp2 )
"
NIP manifest:
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:
{ 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:
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:
- Update version constraints to be more flexible
- Test with multiple versions of dependencies
- 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:
- Make libc choice configurable if possible
- Document which libc your package requires
- 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:
- Review your dependency tree
- Make build-time dependencies optional where possible
- Consider splitting large packages into smaller components
Best Practices for Package Maintainers
1. Write Flexible Version Constraints
❌ Bad:
openssl { version "=3.0.0"; required true } // Too strict
✅ Good:
openssl { version ">=3.0.0 <4.0.0"; required true } // Flexible
2. Document Variant Requirements
❌ Bad:
// No documentation about what variants do
variants {
ssl { default true }
http2 { default true }
}
✅ Good:
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:
dependencies {
cmake { version ">=3.20"; required true } // Build tool
openssl { version ">=3.0.0"; required true } // Runtime
}
✅ Good:
build_dependencies {
cmake { version ">=3.20"; required true }
}
dependencies {
openssl { version ">=3.0.0"; required true }
}
4. Test with Multiple Dependency Versions
# 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:
dependencies {
openssl { version ">=3.0.0"; required true }
// Always required, even if SSL is disabled
}
✅ Good:
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:
# 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:
- Group demands by package: Collect all variant requirements for each package
- Check exclusivity: Detect conflicting exclusive variants (e.g., libc)
- Merge non-exclusive: Combine non-exclusive variants (e.g., +ssl +http2)
- Calculate hash: Generate deterministic variant hash
- 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:
- Check repository configuration:
nip repo list - Update repository metadata:
nip update - 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:
- Review dependency tree:
nip show --tree <package> - Make one dependency optional
- Split packages to break the cycle
Problem: "Variant conflict"
Cause: Two packages require incompatible variants
Solution:
- Use NipCells to isolate:
nip cell create env1 - Build one package with compatible variants
- Update package to support both variants
Problem: "Version conflict"
Cause: Two packages require incompatible versions
Solution:
- Update version constraints to be more flexible
- Check for newer package versions
- Use NipCells for isolation
References
Academic Papers
- PubGrub Algorithm: Dart's pub solver
- CDCL Solving: Conflict-Driven Clause Learning
Related Documentation
Source Code
nip/src/nip/resolver/cdcl_solver.nim- CDCL solver implementationnip/src/nip/resolver/cnf_translator.nim- CNF translationnip/src/nip/resolver/dependency_graph.nim- Graph constructionnip/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