Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions eeps/dynamic-libraries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
Title: Dynamic Libraries
Author: interacsion
Status: Draft
Type: Informational
Created: 2025-05-11
---

# Preface

*Dynamic Libraries* (also referred to as *Shared Libraries*, *Shared Objects* or just *Libraries*) are files containing executable code (*Executables*) that can be loaded into a process at runtime. This is generally done in one of 2 ways:

### 1. *Dynamic Linking*

An *Executable* (either a *Binary*, which contains an entry point, or a *Library*) can be compiled against a Dynamic Library. When such Binary is run it will first invoke the *Dynamic Linker* (also referred to as *Dynamic Loader* or *ELF Interpreter*) which will in turn recursively load all required Dynamic Libraries into process memory.

### 2. *Dynamic Loading*

A Dynamic Library can also be loaded into process memory by calling `dlopen` (or `dlmopen`, more on that later). The Executable does not need to be compiled against the Library, and can conditionally load different Libraries.

## Dynamic Libraries in Nix

On traditional Linux distributions, Dynamic Libraries are usually linked to by their *`SONAME`* (The filename if the Shared Library). The Dynamic Linker then searches a predefined set of system directories to find it.

Nix, on the other hand, takes a different approach; since it doesn't install packages into standard directories, it links Dynamic Libraries by their full filepath, and this is where the problem starts.

# Problem Statement

Most Dynamic Libraries assume that the Library and the Executable that uses it have access to the same instance of a Dynamic Library they both depend on.

The most obvious example is a Library which allocates memory and expects the User to free it. This requires that both the Library and the User share the same instance of, usually, glibc.

The way the Dynamic linker accommodates this is as following: Before a Library is loaded it checks if a Library with the same `SONAME` is already loaded into the process, disregarding the full filepath, if specified. if so, it substitutes it for the one that is already loaded.

This works fine on traditional Linux distributions, but **actively undermines** the purity guarantees Nix tries to make. For example, if a Binary is linked to a certain version of glibc and to a Library that's linked to a different version of glibc, the first one that gets loaded will trump the other.

Nixpkgs manages to mostly avoid this problem due to its monorepo nature; there's a single attribute set containing all the packages and as long as a Binary only links to dependencies from a single Nixpkgs revision, no conflict can occur.

Ekapkgs, on the other hand, would have to manage this more carefully due to its polyrepo structure. For example, a binary from `pkgs` could depend on one `corepkgs` revision and on a library from `ocaml-pkgs` which depends on a different `corepkgs` revision, which would attempt to load two different glibc versions.

Comment on lines +39 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The composition of the different repositories will occur through overlays, from the nix perspective, the instantiated package set should be very similar to nixpkgs

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I didn't consider that. I guess this paragraph is void then

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still a valid concern with "flakes" (not specifically, just that they make it easy), where you can reference very divergent DAGs for the same "package". E.g. two versions of openssl

Copy link

@GrizzlT GrizzlT May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The composition of the different repositories will occur through overlays, from the nix perspective, the instantiated package set should be very similar to nixpkgs

@jonringer Doesn't this idea pose a conflict with the idea of atoms? Something like corepkgs will inevitably change over time. And an overlay that depends on corepkgs cannot always magically assume that what it needs is available. I think you would need some kind of compatibility-checking either way.

I've been working on a prototype that allows for different corepkgs revisions to be used at the same time, this is good for easy predictability but would hit the problem described here. Maybe if there's a versioning system and good public-facing documentation of the packages in corepkgs (and available attributes) in place both issues could be addressed? Version-checking the different repos guarantees predictability but also makes sure there's no conflicting dependencies. Sound right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I raised this point briefly in the discord and I tend to agree. We would potentially have to implement some sort of dependency resolver for Atoms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to flakes, I will probably provide multiple entrypoints. But at the end of the day, corepkgs will just be a "nix package set"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be open to including metadata required for Atoms to work, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm fine with non-intrusive inclusion.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to flakes, I will probably provide multiple entrypoints. But at the end of the day, corepkgs will just be a "nix package set"

I'll take this into consideration for my prototype. How will all the overlays be collected together?

Copy link
Member

@nrdxp nrdxp May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original plan was to not invade Jon's design space really at all, and instead just build up something that would naturally compliment it as it develops.

I raised this point briefly in the discord and I tend to agree. We would potentially have to implement some sort of dependency resolver for Atoms.

That's the idea, yes. For simplicities sake, and for simple projects, a "shallow" algorithm which simply locks direct dependencies based on a version request should suffice. For large derivations, a deep algo which descends the entire dependency tree might come in handy.

Either way, the point of atoms is the keep the dependency tree well abstracted and explicit. There is a lot of incidental complexity involved with all the overlaying and overriding going on everywhere, and the tight coupling between a specific checkout of nixpkgs and virtually everything else sort of makes this feel unavoidable.

atoms should alleviate this, and make updating the package repository more of a granular step by step process, instead of an all or nothing slog. At least, that's what I'm betting on, and as the project grows in complexity, and if I suceed, hopefully that becomes clear all on its own.

## System Libraries

A small subset of Dynamic Libraries must be installed as part of the system; most notable are graphics drivers (which are loaded using `libvulkan`, `libglvnd` or `libgbm`), but GPGPU and sound systems probably fall into this category as well.

Since these Libraries are installed separately than the applications using them, they cannot be expected to link to the same version of glibc as the application. This is a problem that actually manifests in NixOS (Citation Needed).

# Non-Solutions

### 1. Patching the Dynamic Linker to load multiple versions of the same Library

This is not a solution since *Most Dynamic Libraries assume that the Library and the Executable that uses it have access to the same instance of a Dynamic Library they both depend on.*

# Proposed Solution

The following is a concept of a 3-part system which should ultimately resolve all the aforementioned problems.

### 1. Dependency tree analysis

At either package evaluation or build time, we would analyze the tree of *Linked* Dynamic Libraries and ensure only one version of each Library is present.

### 2. Runtime Loading validation

We would have to patch glibc and make `dlopen` error out when a different version of a Library is already loaded, to prevent accidental impure behavior.

### 3. System Library isolation

System Libraries would have to run in a separate *link-map list* than the rest of the process, which would allow them to link to a different glibc version. This can be done in 2 ways:

1. We can patch Libraries like `libvulkan` to use `dlmopen` instead of `dlopen` when loading drivers.

2. We can write shims for Libraries like `libvulkan` that would load the real Library using `dlmopen`.