I’ve been mulling over this interface/implementation separation notion, and it’s given me an odd idea.
Linux distributions other than NixOS have already solved the problem of separating out the interface of a library from its implementation: the ABI name of a library, as part of its path in /usr/lib
, serves as the interface, and the contents of that library the implementation. Nix abandons this structure so that it can have all the nice things that drew us to Nix. Nix packages depend directly on their dependencies’ implementations, which makes rebuilds costly.
But many packages in Nixpkgs are two-part (cheap) wrapper and (expensive) wrapped pairs. One advantage of that sort of split is that dependencies that can be isolated to the wrapper layer don’t cause the wrapped package to rebuild.
What if we generally used this pattern to provide library dependencies? What if Nix compiled binary packages that reference /usr/lib/libwhatever.so.12
like every other distro does, but all the user-facing executables came from wrapper packages that:
- contained a file with a list of library mappings like
/usr/lib/libwhatever.so.12 /nix/store/...-libwhatever-3.4.5/lib/libwhatever.so.12
- contained wrappers that used some sleight of hand (patched loader? chroot or fakechroot? mount namespace trickery? All of these have different practical trade-offs but any could work for this plan) to make the exec’d process load the mapped libraries
The final piece of the puzzle would be allowing library mappings to be provided at build time impurely, without appearing as part of the derivation (so that compilers have some concrete implementation to build against). Instead, derivations would contain only the interface half of the mappings, because if that half changes then the package needs to be rebuilt. (I know, getting an impure feature into Nix is probably an insurmountable mountain, even relative to making global changes in Nixpkgs.)
The result: when a library needs to be patched, it gets rebuilt. Packages that depend on that library only get rebuilt when the library’s ABI name changes; otherwise, it’s only the mapping files in the wrapper packages that point directly to the library’s store path, and rebuilding wrappers is cheap. So security patches get shipped much faster.
But we still keep everything we like about Nix: multiple versions of a package can coexist, system rollbacks are atomic, there isn’t some special location in the filesystem we have to trust and protect outside of the Nix store.
Extra advantages:
- All builds get faster, not just emergency patches!
- Less Nix store bloat, even with Nix store deduplication, because we won’t need multiple copies of big binary files that differ only in the library paths they point to.
- Once the necessary feature is in Nix, applying this pattern to Nixpkgs can be done package-by-package, to minimize risk or accommodate cases where this pattern is undesirable for some reason.
- Many non-free, binary-only packages could just work the same way as Nix packages, without having to be patchelf’d. This is good for verifying the integrity of the non-free binary you’re running, if you care about that.