# 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 ` ### 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 ` 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