Using Nix as a library

Published 2025-08-17 on Farid Zakaria's Blog

I have been actively trying to contribute to CppNix – mostly because using it brings me joy and it turns out so does contributing. 🤗

Stepping into any new codebase can be overwhelming. You are trying to navigate new nomenclature, coding standards, tooling and overall architecture. Nix is over 20 years old and has its fair share of warts in a codebase. Knowledge of the codebase is bimodal either being very diffuse or consolidated to a few minds (i.e. @ericson2314). Thankfully everyone on the Matrix channel has been extremely welcoming.

I have been actively following Snix, a modern Rust re-implementation of the components of the Nix package manager. I like the ideals from the project authors of communicating over well-defined API boundaries via separate processes and a library-first type of design. I was wondering however whether we could leverage CppNix as a library as well. 🤔

Is there a need to throw the baby out with the bath water? 👶

Turns out using Nix as a library is incredibly straightforward!

To start, let’s create a devShell that will include our necessary packages: nix (duh), meson (build tool) and pkg-config.

flake.nix
{
  description = "Example of how to use Nix as a library.";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/25.05";
    devshell.url = "github:numtide/devshell";
  };

  outputs = { self, nixpkgs, devshell }:
    let
      lib = nixpkgs.lib;
      systems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];
    in {
      devShells = lib.genAttrs systems (system:
        let
          pkgs = import nixpkgs {
            inherit system;
          };
        in {
          default = pkgs.mkShell {
            packages = with pkgs; [
              nix
              meson
              ninja
              pkg-config
              # I am surprised I need this
              # I think this is a bug
              # https://github.com/NixOS/nix/issues/13782
              boost
            ];
          };
        });
    };
}

</summary>

Adding pkg-config to our devShell will initiate a buildHook for any package that contains a dev output and set up the necessary environment variables. This will be the mechanism with which our build tool meson finds the necessary shared-objects and header files.

> env | grep PKG_CONFIG_PATH
PKG_CONFIG_PATH=/nix/store/dxar61b2ig87cfdvsylfcnyz6ajls91v-nix-2.28.3-dev/lib/pkgconfig:/nix/store/sgsi5d3z14ygk1f2nlgnlj5w4vl0z8gc-boehm-gc-8.2.8-dev/lib/pkgconfig:/nix/store/l6wng97amh2h2saa5dpvbx5gavjv95r4-nlohmann_json-3.11.3/share/pkgconfig:/nix/store/8kyckzscivn03liyw8fwx93lm3h21z9c-libarchive-3.7.8-dev/lib/pkgconfig:/nix/store/d003f74y8hj2xw9gw480nb54vq99h5r3-attr-2.5.2-dev/lib/pkgconfig:/nix/store/rrgb780yg822kwc779qrxhk60nmj8f6q-acl-2.3.2-dev/lib/pkgconfig:/nix/store/ammv4hfx001g454rn0dlgibj1imn9rkw-boost-1.87.0-dev/lib/pkgconfig

We can also run pkg-config --list to see that they can be discovered.

> pkg-config --list-all | head
nix-flake              Nix - Nix Package Manager
nix-store              Nix - Nix Package Manager
nix-main               Nix - Nix Package Manager
nix-cmd                Nix - Nix Package Manager
nix-store-c            Nix - Nix Package Manager
nix-util-test-support  Nix - Nix Package Manager
nix-expr-test-support  Nix - Nix Package Manager
nix-fetchers           Nix - Nix Package Manager
...

Let’s now create a trivial meson.build file. Since we have our pkg-config setup, we can declare “system dependencies” that we expect to be present, knowing that we are including these dependencies from our devShell.

project('nix-example', 'cpp',
  version : '0.1',
  default_options : ['warning_level=3', 'cpp_std=c++14'])

deps = [
  dependency('nix-store'),
  dependency('boost'),
]

executable('nix-example',
           'main.cc',
           dependencies: deps,
           install : true)

For our sample project I will recreate functionality that is already present in the nix command. We will write a function that accepts a /nix/store path, resolve its derivation and prints it as JSON.

#include <iostream>

#include "nix/main/shared.hh"
#include "nix/store/store-api.hh"
#include "nix/store/derivations.hh"
#include "nix/store/store-dir-config.hh"
#include <nlohmann/json.hpp>

int main(int argc, char **argv) {
    if (argc != 2) {
        std::cerr << "usage: " << argv[0]
                  << " /nix/store/<hash>-<name>"
                  << std::endl;
        return 1;
    }

    nix::initLibStore(true);
    nix::ref<nix::Store> store = nix::openStore();
    std::string s = argv[1];
    nix::StorePath sp = store->parseStorePath(s);
    auto drvPath = sp.isDerivation() ? sp : *store->queryPathInfo(sp)->deriver;
    auto drv = store->readDerivation(drvPath);
    const nix::StoreDirConfig & config = *store;
    auto json = drv.toJSON(config);
    std::cout << json.dump(2) << std::endl;
    return 0;
}

We can now build our project and run it! 🔥

> meson setup build

> meson compile -C build

> ./build/nix-example "/nix/store/bbyp6vkdszn6a14gqnfx8l5j3mhfcnfs-python3-3.12.11" | head
{
  "args": [
    "-e",
    "/nix/store/vj1c3wf9c11a0qs6p3ymfvrnsdgsdcbq-source-stdenv.sh",
    "/nix/store/shkw4qm9qcw5sc5n1k5jznc83ny02r39-default-builder.sh"
  ],
  "builder": "/nix/store/cfqbabpc7xwg8akbcchqbq3cai6qq2vs-bash-5.2p37/bin/bash",
...

That feels pretty cool! Lots of projects end up augmenting Nix by wrapping it with fancy bash scripts, however we can just as easily leverage it as a library and write native-first code.

Learning the necessary functions to call is a little obtuse however I was able to reason through the necessary APIs by looking at unit-tests in the repository.

What idea do you want to leverage Nix for but maybe put off since you thought doing it on top of Nix would be too hacky?

Special thanks to @xokdvium who helped me through some learnings on meson and how to leverage Nix as a library. 🙇


Improve this page @ 187ffdc
The content for this site is CC-BY-SA.