Nix flake to compile out-of-tree QMK firmwares
The goal of this project is to provide a simple Nix derivation to build QMK-based firmwares for your favorite programmable keyboards.
- Build QMK firmwares reproducibly with Nix
- Flash firmwares directly via
nix run - Full
clangdLSP support viacompile_commands.jsongeneration
nixcaps exposes the following functions via nixcaps.lib.${system}:
mkQmkFirmware { src, keyboard, variant? }- Builds the QMK firmwareflashQmkFirmware { src, keyboard, variant? }- Returns a flake app that flashes the firmwaremkCompileDb { src, keyboard, variant? }- Generatescompile_commands.jsonforclangdLSP support
Parameters:
src(Path): the path to the directory containing your QMK keymap fileskeyboard(String): the path insidekeyboardsin theqmk_firmwarerepo where your keyboard model is defined (e.g.,preonic,zsa/moonlander)variant(String, optional): the concrete variant of your keyboard, in case more than one exists (e.g., therev3_dropvariant ofpreonic, or thebasevariant ofergodox_ez)
Here is a minimal example showing how to use nixcaps for the Ergodox EZ keyboard:
flake.nix:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
nixcaps.url = "github:agustinmista/nixcaps";
};
outputs =
inputs@{
flake-utils,
nixpkgs,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
nixcaps = inputs.nixcaps.lib.${system};
ergodox_ez = {
src = ./.;
keyboard = "ergodox_ez";
variant = "base";
};
in
{
packages.default = nixcaps.mkQmkFirmware ergodox_ez;
apps.default = nixcaps.flashQmkFirmware ergodox_ez;
devShells.default = pkgs.mkShell {
QMK_HOME = "${nixcaps.inputs.qmk_firmware}";
packages = [ pkgs.qmk ];
shellHook = ''
ln -sf "${nixcaps.mkCompileDb ergodox_ez}/compile_commands.json" ./compile_commands.json
'';
};
}
);
}This example is also packaged as a template you can try locally by running:
$ nix flake new my-keyboard --template github:agustinmista/nixcaps#ergodox_ez
$ cd my-keyboard
$ nix build # compile the firmware
$ nix run # flash the firmware
$ nix develop # enter dev shell with LSP supportFor projects with multiple keyboards, you can define separate configurations:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
nixcaps.url = "github:agustinmista/nixcaps";
};
outputs =
inputs@{
flake-utils,
nixpkgs,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
nixcaps = inputs.nixcaps.lib.${system};
moonlander = {
src = ./moonlander;
keyboard = "zsa/moonlander";
};
thekey = {
src = ./thekey;
keyboard = "drop/thekey/v2";
};
in
{
packages = {
moonlander = nixcaps.mkQmkFirmware moonlander;
thekey = nixcaps.mkQmkFirmware thekey;
};
apps = {
moonlander = nixcaps.flashQmkFirmware moonlander;
thekey = nixcaps.flashQmkFirmware thekey;
};
devShells.default = pkgs.mkShell {
QMK_HOME = "${nixcaps.inputs.qmk_firmware}";
packages = [ pkgs.qmk ];
shellHook = ''
ln -sf "${nixcaps.mkCompileDb moonlander}/compile_commands.json" ./moonlander/compile_commands.json
ln -sf "${nixcaps.mkCompileDb thekey}/compile_commands.json" ./thekey/compile_commands.json
'';
};
}
);
}Build and flash specific keyboards:
$ nix build .#moonlander # build moonlander firmware
$ nix run .#thekey # flash thekey firmwareThis example is available as a template:
$ nix flake new my-keyboards --template github:agustinmista/nixcaps#multiple_keyboardsnixcaps provides full clangd LSP support for your keymap files. The mkCompileDb function generates a compile_commands.json file that clangd uses for code intelligence features like:
- Go to definition
- Find references
- Autocompletion
- Diagnostics
To enable LSP support, add a dev shell to your flake that symlinks the generated compile_commands.json (see examples above), then run nix develop (or use direnv) before opening your editor.
- You can change the version of
qmk_firmwareused by nixcaps by overriding itsqmk_firmwareflake input as follows:
# Don't forget the `submodules=1` part
nixcaps.inputs.qmk_firmware.url = "git+https://github.com/qmk/qmk_firmware?submodules=1&rev=<COMMIT_SHA>";Or, alternatively, you can do:
qmk_firmware = {
url = "https://github.com/qmk/qmk_firmware";
ref = "0.31.11"; # you can use git tags here too
flake = false;
type = "git";
submodules = true;
};
nixcaps.inputs.qmk_firmware.follows = "qmk_firmware";-
Under the hood, this derivation first copies the files in
srcintokeyboards/<keyboard>/keymaps/nixcapsinside an internal copy of theqmk_firmwarerepo, and then executesqmk compile --keyboard <keyboard>[/<variant>] --keymap nixcaps. -
While it should be possible to flash the compiled firmwares using
qmk flash, this command is only available while inside theqmk_firmwarerepo. So, it is not enough to provide such a derivation with just the compiled firmware, and one would have to copy the entire (or a large part of) theqmk_firmwarerepo as well. If you know how to implement this in an efficient way, let me know! -
To ensure reproducibility, this builder calls
qmk compilewith theSKIP_GIT=trueenv var. This avoids some of the issues others have observed while trying work withqmk_firmwarein Nix. If your keyboard config relies onqmk compileusing git for any reason, this probably won't work!