Upgrading Your C Project Build to Use Nix Flakes: A Modern Guide
Hello again, fellow C enthusiasts! In our previous blog post, we explored building a C program with external library dependencies using Nix. Today, we’re taking it a step further by upgrading our Nix setup to use Nix flakes, a more structured and modern way to manage dependencies and ensure reproducibility. This post will guide you through updating your existing Nix build to a Nix flake and explain each component in detail. Let’s dive in!
What Are Nix Flakes?
Nix flakes introduce a standardized way to define Nix projects, providing better dependency management, versioning, and reproducibility. They offer:
- Consistency: Ensures builds are reproducible across different environments.
- Ease of Use: Simplifies the setup and management of dependencies.
- Modularity: Allows sharing and composing Nix expressions easily.
For more details, you can check out the Nix Flakes documentation.
Transitioning from a Classic Nix Setup to Nix Flakes
We’ll start with our simple-parse-example
C program, which uses the jansson
library for JSON parsing. We’ll convert our existing default.nix
to a flake.nix
file.
Original default.nix
Here’s a quick look at our original default.nix
:
{ pkgs ? import <nixpkgs> {} }:
let
jansson = pkgs.jansson;
in
pkgs.stdenv.mkDerivation {
pname = "simple-parse-example";
version = "1.0";
src = ./.;
nativeBuildInputs = [ pkgs.clang ];
buildInputs = [ jansson ];
buildPhase = ''
clang -g -Wall -o simple-parse-example simple-parse-example.c -ljansson
'';
installPhase = ''
mkdir -p $out/bin
cp simple-parse-example $out/bin/
'';
meta = with pkgs.lib; {
description = "A simple program using jansson";
license = licenses.mit;
maintainers = [ maintainers.yourname ];
platforms = platforms.unix;
};
}
Converting to flake.nix
We’ll now convert this setup to use Nix flakes.
Step 1: Creating flake.nix
Create a file named flake.nix
in your project directory with the following content:
{
description = "A simple C program using jansson built with Nix flakes";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, nixpkgs }: {
packages = nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-linux" ] (system:
let
pkgs = import nixpkgs { inherit system; };
in
rec {
simple-parse-example = pkgs.stdenv.mkDerivation {
pname = "simple-parse-example";
version = "1.0";
src = ./.;
nativeBuildInputs = [ pkgs.clang ];
buildInputs = [ pkgs.jansson ];
buildPhase = ''
clang -g -Wall -o simple-parse-example simple-parse-example.c -ljansson
'';
installPhase = ''
mkdir -p $out/bin
cp simple-parse-example $out/bin/
'';
meta = with pkgs.lib; {
description = "A simple program using jansson";
license = licenses.mit;
maintainers = [ maintainers.yourname ];
platforms = platforms.unix;
};
};
});
defaultPackage = {
aarch64-darwin = self.packages.aarch64-darwin.simple-parse-example;
x86_64-linux = self.packages.x86_64-linux.simple-parse-example;
};
defaultApp = {
forAllSystems = nixpkgs.lib.mapAttrs' (system: pkg: {
inherit system;
defaultApp = {
type = "app";
program = "${pkg.simple-parse-example}/bin/simple-parse-example";
};
}) self.packages;
};
};
}
Breaking Down the Flake Configuration
Let’s break down each component of the flake.nix
file to understand what it does.
1. description
description = "A simple C program using jansson built with Nix flakes";
This provides a brief description of the flake. It’s useful for documentation and understanding the purpose of the flake.
2. inputs
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
The inputs
section defines dependencies for the flake. Here, we are specifying that we want to use the unstable branch of the Nixpkgs repository. This is where we’ll get our packages like clang
and jansson
.
- Documentation: Nixpkgs Input
3. outputs
outputs = { self, nixpkgs }: {
packages = nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-linux" ] (system:
let
pkgs = import nixpkgs { inherit system; };
in
rec {
simple-parse-example = pkgs.stdenv.mkDerivation {
pname = "simple-parse-example";
version = "1.0";
src = ./.;
nativeBuildInputs = [ pkgs.clang ];
buildInputs = [ pkgs.jansson ];
buildPhase = ''
clang -g -Wall -o simple-parse-example simple-parse-example.c -ljansson
'';
installPhase = ''
mkdir -p $out/bin
cp simple-parse-example $out/bin/
'';
meta = with pkgs.lib; {
description = "A simple program using jansson";
license = licenses.mit;
maintainers = [ maintainers.yourname ];
platforms = platforms.unix;
};
};
});
defaultPackage = {
aarch64-darwin = self.packages.aarch64-darwin.simple-parse-example;
x86_64-linux = self.packages.x86_64-linux.simple-parse-example;
};
defaultApp = {
forAllSystems = nixpkgs.lib.mapAttrs' (system: pkg: {
inherit system;
defaultApp = {
type = "app";
program = "${pkg.simple-parse-example}/bin/simple-parse-example";
};
}) self.packages;
};
};
-
packages: This section uses
nixpkgs.lib.genAttrs
to define packages for multiple systems (aarch64-darwin
andx86_64-linux
). For each system, we import the appropriate version ofnixpkgs
and define a derivation forsimple-parse-example
. -
defaultPackage: Specifies the default package for each system. This is used to tell Nix which package to build by default for the current system.
-
defaultApp: Specifies the default application for each system using
mapAttrs'
to iterate over systems and create default apps. -
Documentation:
Building and Running with Nix Flakes
To ensure that everything works as expected, follow these steps:
-
Navigate to your project directory:
cd ~/dev/learning-c/parsing-json
-
Enable Flakes: Make sure flakes are enabled in your Nix configuration:
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
-
Build the project: Build the project using:
nix build .#simple-parse-example
-
Run the binary: After building, you can run the binary with:
./result/bin/simple-parse-example
Conclusion
Congratulations! You've successfully updated your C project to use Nix flakes. This modern approach ensures your builds are reproducible and dependencies are well-managed. Nix flakes provide a powerful way to handle complex projects with ease, making your development process smoother and more efficient.
Exercise Prompt
Try converting another one of your existing projects to use Nix flakes. Start by writing a flake.nix
file that includes all your dependencies and build instructions. Share your experience, any challenges you encountered, and how Nix flakes improved your build process.
Happy coding, and may your builds always be reproducible!