Turning some C and a Run Script into a Nix Flake
Part 1: Tooling

Table of Contents

Reading time: 6 minute

Back Home.

dried-summer-flowers-crop.webp

a small, flat, very thin piece of something, typically one which has broken away or been peeled off from a larger piece.

— Flake definition, Oxford Languages

1. Flakes

One experimental, but powerful feature of Nix is flakes. It provides a more standardized and reproducible way to manage Nix projects. A flake is essentially just a bunch of inputs and outputs, as well as a lock file that “pins” version of packages.

While that is super useful, flakes also have some other cool features that makes it worth it to use. For example, in my last blog post, instead of having to write installation instructions and having to potentially deal with a multitude of edge cases for people that wanted to try out finding incorrect unix file outputs, I could simply tell them to run one command.

nix run github:cafkafk/file-fuzzer

Now, to be fair, you’d need to have Nix installed on your machine to run that. But even then, I could just locally, in my repo bundle this into a single executable:

nix bundle -o bundled-file-fuzzer

Here, we need the output flag -o as the default output overlaps with the file-fuzzer script in the root of the repo. But after doing this, we get a file that links to a thing in the store. What thing? We can find out with file, our beloved unix tool.

file /nix/store/qh4qwyiwybc4n6bqlnnnl803xximvrsh-arx
/nix/store/qh4qwyiwybc4n6bqlnnnl803xximvrsh-arx: POSIX shell script executable (binary data)

What is this?

nix bundle, by default, packs the closure of the installable into a single self-extracting executable. See the bundlers homepage for more details.

That is quite useful for distributing our software outside of nix.

2. Developing a simple flake: Tooling

Now that I’ve shown you some tangible reason why, let’s get into how. I’m gonna use this file-fuzzer flake as the example, and explain how it was made, so that you might do something similar whenever you need to.

First, this was based on one of the official flake templates. These are a good reference to get started with flakes, although, they have, at least in my experience, sometimes not been sufficient.

We can list these templates:

nix flake show templates
github:NixOS/templates/0edaa0637331e9d8acca5c8ec67936a2c8b8749b
├───defaultTemplate: template: A very basic flake
└───templates
    ├───bash-hello: template: An over-engineered Hello World in bash
    ├───c-hello: template: An over-engineered Hello World in C
    ├───rust: template: Rust template, using Naersk
    ...
    └───trivial: template: A very basic flake

The one I started from can be downloaded to your current folder like this:

nix flake init --template templates#c-hello
wrote: /home/nixlover/someproject/flake.nix
wrote: /home/nixlover/someproject/Makefile.in
wrote: /home/nixlover/someproject/flake.lock
wrote: /home/nixlover/someproject/hello.c
wrote: /home/nixlover/someproject/configure.ac

As we can see, it added a few files.

  • flake.nix: the flake definition.
  • flake.lock: the file specifying pinned versions of the flakes inputs.
  • hello.c: the template C code, we will remove this shortly.
  • Makefile.in: a makefile, to compile the C code.
  • configure.ac: autoreconf file, not that important right now.

Now, with this alone, we can test the flake:

nix flake check

When that is finished downloading, it should not output anything (except some warnings). This means it worked.

Now, we can try building the flake, but notice that we don’t type nix flake build, no that would be way too intuitive! Instead, we just type nix build.

nix build

Again, if you didn’t see any output except warnings, everything went well!

Now, if you were to take a look in the directory, you’d see that there is a new folder, result. It currently should have the following structure.

result/
└── bin
    └── hello

Now, if we wanted to run the program, we could just run this hello binary:

./result/bin/hello
Hello Nixers!

But let’s be real, this is an unacceptable amount of typing, and our laziness aside, when a project grows, the bin folder might not contain just a single file, making it hard to find out what we’re supposed to run. Fret not, the devs thought of this, just do:

nix run
Hello Nixers!

Something else that might bother us, if we take a look at the flake.lock file:

cat flake.lock
{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1626834727,
        "narHash": "sha256-ToGgus+UImnLNaLgv+xfo/cI3J/NQl2KB5kvyErXays=",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "63ee5cd99a2e193d5e4c879feb9683ddec23fa03",
        "type": "github"
      },
      "original": {
        "id": "nixpkgs",
        "ref": "nixos-21.05",
        "type": "indirect"
      }
    },
    "root": {
      "inputs": {
        "nixpkgs": "nixpkgs"
      }
    }
  },
  "root": "root",
  "version": 7
}

What you’re looking at is the flake.lock, which is actually a JSON file (as the unix file command will verify). Also if we look here, we can see that nixpkgs is specified, at nixos version 21.05! That’s not very bleeding edge! Let’s fix that!

nix flake update
warning: updating lock file '/home/ces/testdir/flake.lock':
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/63ee5c...83ddec23fa03' (2021-07-21)
  → 'github:NixOS/nixpkgs/022caa...be69fb0c3faf' (2022-08-07)

Ohh, I’m pretty sure the newest version of nixpkgs isn’t from 2022… That’s because, while we updated the flake input to the “latest version”, there are additional constraints in the flake.nix file about what kind of input we want.

At the start of the flake.nix, we see the following.

{
  description = "An over-engineered Hello World in C";

  # Nixpkgs / NixOS version to use.
  inputs.nixpkgs.url = "nixpkgs/nixos-21.05";

...

Seems like the flake.nix is just declaring that we use 21.05, but it doesn’t care what the actual “version” or revision of 21.05 that is. Thus, we could imagine it would be enough to change this to a newer version.

{
  description = "An over-engineered Hello World in C";

  # Nixpkgs / NixOS version to use.
  inputs.nixpkgs.url = "nixpkgs/nixos-23.05";

...

As of writing, 23.05 is the latest version. Now, let’s try to update with the new version!

nix flake update
warning: updating lock file '/home/ces/testdir/flake.lock':
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/022caabb5f2265ad4006c1fa5b1ebe69fb0c3faf' (2022-08-07)
  → 'github:NixOS/nixpkgs/9462344318b376e157c94fa60c20a25b913b2381' (2023-07-27)

2023-07-27, as of writing that’s yesterday. Now we’re cooking with gas!

This should be about enough information to start wrangling a Nix Flake.

3. Concluding Remarks

This sums up the first part of our journey to understanding the basics of Nix flakes. From here, you have seen how to initiate a flake, update its versions, and even run a simple executable. You’ve had a taste of the power of reproducibility and streamlined project management.

In the next part of this series, we will be diving deeper, exploring how we can customize our flake, and integrate it with some custom C code and a run script. We will unveil the process of creating the file-fuzzer flake step by step, and hopefully, inspire you to implement similar or even more complex solutions with your own projects.

Until next time!

Back Home.

4. References

Created: 2024-04-14 Sun 10:06