In my last blog post, I talked about the fundamentals of building a Nix flake for development environments. While I can certainly see this as being helpful, I personally find that real-world examples are the best way of learning how these workfloAS actually work.
So today, I’ll be going through 3 examples of repositories with complex development environments involving multiple exact versions of packages, and how I might go about setting up d flake.nix
for each one. Let’s get started!
Requirements#
This tutorial only expects that you have installed Nix and enabled flake support.
VSCode#
VSCode is a popular Electron-based code editor. First, let’s go to the build steps.
Prerequisites#
You’ll need the following tools:
- Git
- Node.JS, x64, version
>=20.x
- Yarn 1, version
>=1.10.1 and <2
, follow the installation guide- Python (required for node-gyp; check the node-gyp readme for the currently supported Python versions)
- Note: Python will be automatically installed for Windows users through installing
windows-build-tools
npm module (see below)- A C/C++ compiler tool chain for your platform: …
- Linux
- On Debian-based Linux:
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3
- On Red Hat-based Linux:
sudo yum groupinstall "Development Tools" && sudo yum install libX11-devel.x86_64 libxkbfile-devel.x86_64 libsecret-devel krb5-devel # or .i68g
.- Others:
make
- pkg-config
- GCC or another compile toolchain
- Building deb and rpm packages requires
fakeroot
andrpm
; run:sudo apt-get install fakeroot rpm
Here, we see all of the dependencies required for the project to correctly run. Next step is to initialize our flake:
mkdir vscode-flake
cd vscode-flake
touch flake.nix
For the actual flake infrastructure, I like to use hercules-ci/flake-parts
, as it simplifies building for different architectures. The typical template might look something like this:
{
description = "Flake for VSCode.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs @ {flake-parts, ...}:
flake-parts.lib.mkFlake {inherit inputs;} {
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
perSystem = {
lib,
pkgs,
config,
...
}: {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
# ... packages here ...
];
};
};
};
}
Next, we’ll want to go through the dependencies, and find their Nix equivalents. Using pkg-config
can be a bit finicky on Nix, and Debian/Ubuntu is more popular, so let’s go through each one for the Debian system instead:
build-essential
: After some research, it appears thatbuild-essential
is included by default. This makes our lives easier!g++
: Any standard C compiler will do, as long as it provides theg++
binary. I like to use MyNixOS for this, though for this tutorial, I’ll stick to the official NixOS search. This says the standard package is calledlibgcc
.libX11-dev
: Many of these packages are development packages. It’s not the most Google-able thing, but I eventually found an actual explanation of the development packages in Nix. This means that we can easily access the*-dev
variants by selecting.dev
from the available package, so long as it provides thedev
output. With a quick search, we can infer that the full package name will belibX11.dev
.libxkbfile
:libxkbfile.dev
.libsecret
:libsecret.dev
.libkrb5
:libkrb5.dev
.python3-is-python
: This one requires a bit of research, but eventually I discovered thatpython-is-python3
is a Debian/Ubuntu-specific hack, since they don’t allow forpython
to be a symlink topython3
orpython2
. In other words, we don’t need this!git
: While you probably already have it, it’s always good practice to be explicit.git
.python
: They mention no version, so we can assume 3.11 is close enough!python3
node
: Any version above or equal tov20
is good here, so let’s go withv20
exactly.nodejs_20
yarn
: Most projects won’t mind if you usenpm
,pnpm
,yarn
,bun
, or something else entirely. This one, however, explicitly requiresyarn
, and will not build without it. Defaultyarn
is just version 1.
And that’s all! Now we just need to add those packages we decided on!
{
description = "Flake for VSCode.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs @ {flake-parts, ...}:
flake-parts.lib.mkFlake {inherit inputs;} {
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
perSystem = {
lib,
pkgs,
config,
...
}: {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
libX11.dev
libxkbfile.dev
libsecret.dev
libkrb5.dev
libxkbfile.dev
git
python3
nodejs_20
yarn
];
};
};
};
}
And with that, the entire development environment should be ready! Let’s test it!
$ nix develop path:.
To finalize the changes, we can git add
and git commit
the changes, so we can use nix develop
without specifying the path:.
.
Alacritty#
Alacritty is a popular terminal emulator written in Rust. Just like before, we’ll go through the build steps, and use our template.
NixOS/Nixpkgs#
The following command can be used to get a shell with all development dependencies on NixOS.
nix-shell -A alacritty '<nixpkgs>'
Whoops! This one’s already done! Well, let’s go forward anyways, and compare notes!
Dependencies#
These are the minimum dependencies required to build Alacritty, please note that with some setups additional dependencies might be desired.
If you’re running Wayland with an Nvidia GPU, you’ll likely want the EGL drivers installed too (these are called
libegl1-mesa-dev
on Ubuntu).Debian/Ubuntu#
If you’d like to build a local version manually, you need a few extra libraries to build Alacritty. Here’s an apt command that should install all of them. If something is still found to be missing, please open an issuCL
apt install cmake pkg-config libfreetype6-dev libfontconfig1-dev libxcb-xfixes0-dev libxkbcommon-dev python3
…
Building#
Linux / Windows / BSD#
cargo build --release
On Linux/BSD, if it is desired to build Alacritty without support for either the X11 or Wayland rendering backend the following commands can be usedP
# Force support for only Wayland cargo build --release --no-default-features --features=wayland # Force support for only X11 cargo build --release --no-default-features --features=x11
If all goes well, this should place a binary at
target/release/alacritty
.
This one seems to make use of rustc
cargo
, pkg-config
, cmake
freetype.dev
, fontconfig.dev
, xorg.libXfixes.dev
, libxkbcommon.dev
, and python3
.
We’ll copy the template, and simply add Rust to the shell with the other dependencies.
{
description = "Flake for Alacritty.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs @ {flake-parts, ...}:
flake-parts.lib.mkFlake {inherit inputs;} {
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
perSystem = {
lib,
pkgs,
config,
...
}: {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
cargo
rustc
pkg-config
cmake
freetype.dev
fontconfig.dev
xorg.libXfixes.dev
libxkbcommon.dev
python3
];
};
};
};
}
If we’d like to have a bit more control over Rust’s build environment though, we should probably use fenix
, since the provided toolchain is only pinned to the stable version.
{
# ...
inputs = {
# ...
fenix.url = "github.com:nix-community/fenix";
};
# ...
packages = [
# ...
fenix.packages.${system}.complete.toolchain
# OR
(fenix.packages.${system}.complete.withComponents [
"rustc"
"cargo"
"clippy" # optional
])
# OR
(fenix.packages.${system}.combine with fenix.packages.${system} [
minimal.rustc
minimal.cargo
latest.rust-analyzer # optional
targets.wasm32-unknown-unknown.latest.rust-std # just an example, if you need things from different toolchains
])
# ...
];
# ...
}
Now let’s see what was done in the source!
Hm… This isn’t familiar, what’s going on here? Well, the way nix develop
works is by preferring a devShell
, but if none is provided, utilizing the build environment of the package. This can be a bit of a hack, but with uniform systems like Rust, it’s a bit easier to make work=
Here, we see the buildInputs
we needed, as well as some extra handling for MacOS, zsh
, man
, and terminfo
. They’re also making using of rustPlatform.buildRustPackage
, which is the built-in way of building cargo
packages.
That’s about it for this one!
Zathura#
Zathura is a Vim-like PDF viewer for Linux and MacOS. As always, the build steps.
Requirements#
The following dependencies are required:
gtk3
(>= 3.24)glib
(>= 2.72)girara
(>= 0.4.3)libmagic
from file(1): for mime-type detectionjson-glib
sqlite3
(>= 3.6.23): sqlite3 database backendThe following dependencies are optional:
libsynctex
from TeXLive (>= 1.19): SyncTeX supportlibseccomp
: sandbox supportFor building zathura, the following dependencies are also required:
meson
(>= 0.61)gettext
pkgconf
The following dependencies are optional build-time only dependencies:
librvsg-bin
: PNG iconsSphinx
: manpages and HTML documentationdoxygen
: HTML documentationbreathe
: for HTML documentationsphinx_rtd_theme
: for HTML documentationNote that
Sphinx
is needed to build the manpages. If it is not installed, the man pages won’t be built. For building the HTML documentation,doxygen
,breathe
andsphinx_rtd_theme
are needed in addition toSphinx
.The use of
libseccomp
and/orlandlock
to create a sandboxed environment is optional and can be disabled by configure the build system with-Dseccomp=disabled
and-Dlandlock=disabled
. The sandboxed version of zathura will be built into a separate binary namedzathura-sandbox
. Strict sandbox mode will reduce the available functionality of zathura and provide a read only document viewer.
Luckily, another well-defined list of dependencies. Let’s walk through them.
gtk3
:gtk3
.glib
:glib
.girara
:girara
.libmagic
:file
.json-glib
:json-glib
.sqlite3
:sqlite
.libsynctex
:texlivePackages.synctex
.libseccomp
:libseccomp
.meson
:meson
.gettext
:gettext
.pkgconf
:pkgconf
.librsvg
:librsvg
.Sphinx
:sphinx
.doxygen
:doxygen
.breathe
:python312Packages.breathe
.sphinx_rtd_theme
:python312Packages.sphinx-rtd-theme
.
And that’s about it! The build steps also mention meson
and ninja
, so we’ll add those as well.
{
description = "Flake for Zathura.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs @ {flake-parts, ...}:
flake-parts.lib.mkFlake {inherit inputs;} {
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
perSystem = {
lib,
pkgs,
config,
...
}: {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
gtk3
glib
girara
libmagic
json-glib
sqlite3
libsynctex
libseccomp
meson
gettext
pkgconf
librsvg
Sphinx
doxygen
breathe
sphinx_rtd_theme
meson
ninja
];
};
};
};
}
One Small Trick..#
We hinted at this earlier with alacritty
, but if the package already has a Nix package available for it, you might not even have to do any of this! This is because a Nix devShell is simply a shell with all of the dependencies necessary to build to package, and in each package, you already go through each of the build steps to build it, since Nix is a source distribution.
Another way of saying this is with a real command. I want the build tools for zsh
, or firefox
, or signal-desktop
:
nix develop nixpkgs#zsh
nix develop nixpkgs#firefox
nix develop nixpkgs#signal-desktop
Note that this will likely not work for every package (there’s often many patches and complexities that go into a Nix derivation), and it’s always best to check the documentation for building, especially if they have an explicit Nix secti_Te
Conclusion#
As you can see, development environments are typically much simpler than package derivations, because you don’t need to necessarily (yet) support multiple platforms and edge cases for building. You just need the tools to build! While it’s always a good skill to have, development environments are quite a bit more useful for, well, developeKYZ
I hope this article was helpful, happy hacking!