Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 123 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,33 @@

The goal of this project is to provide a simple Nix derivation to build QMK-based firmwares for your favorite programmable keyboards.

## Features

- Build QMK firmwares reproducibly with Nix
- Flash firmwares directly via `nix run`
- Full `clangd` LSP support via `compile_commands.json` generation

## Usage

### Compiling
### API

nixcaps exposes the following functions via `nixcaps.lib.${system}`:

- `mkQmkFirmware { src, keyboard, variant? }` - Builds the QMK firmware
- `flashQmkFirmware { src, keyboard, variant? }` - Returns a flake app that flashes the firmware
- `mkCompileDb { src, keyboard, variant? }` - Generates `compile_commands.json` for `clangd` LSP support

`compile :: { src, keyboard, variant ? null, target ? "fw", flash ? null }`
Parameters:

Inputs:
- `src` (`Path`): the path to the directory containing your QMK keymap files
- `keyboard` (`String`): the path inside `keyboards` in the `qmk_firmware` repo 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., the `rev3_drop` variant of `preonic`, or the `base` variant of `ergodox_ez`)

- `src` (`Path`): the path to the directory containing your QMK config files
- `keyboard` (`String`): the path inside `keyboards` in the `qmk_firmare` repo that 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., the `rev3_drop` variant of `preonic`, or the `base` variant of `ergodox_ez`).
- `target` (`String`, optional): the basename of the compiled firmware file (i.e., without any extension)
- `flash` (`String -> String`, optional): a function that takes the resolved basename of the compiled firmware and returns a (possibly multiline) script that flashes it into your keyboard. When provided, this will generate a `flash` executable in the derivation's output path.
## Examples

## Example
### Single Keyboard

Here is a minimal example showing how to use `nixcaps` as an input to your flakes and invoke the firmware builder for a given keyboard model/variant (original Ergodox EZ with ATmega32U4).
Here is a minimal example showing how to use `nixcaps` for the Ergodox EZ keyboard:

**flake.nix**:

Expand All @@ -35,60 +45,135 @@ Here is a minimal example showing how to use `nixcaps` as an input to your flake
};

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 =
let
compile_db = nixcaps.mkCompileDb ergodox_ez;
in
''
ln -sf "${compile_db}/compile_commands.json" ./compile_commands.json
'';
};
}
);
}
```

This example is also packaged as a template you can try locally by running:

```bash
$ 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 support
```

### Multiple Keyboards

For projects with multiple keyboards, you can define separate configurations:

```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,
nixcaps,
nixpkgs,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
compile = nixcaps.packages.${system}.compile;
nixcaps = inputs.nixcaps.lib.${system};
moonlander = {
src = ./moonlander;
keyboard = "zsa/moonlander";
};
togkey_pad_plus = {
src = ./togkey_pad_plus;
keyboard = "togkey/pad_plus";
};
in
{
packages.default = compile {
keyboard = "ergodox_ez";
variant = "base";
src = ./.; # path to your keymap files
flash = fw: ''
echo "Flashing ${fw}.hex ..."
${pkgs.teensy-loader-cli}/bin/teensy-loader-cli -mmcu=atmega32u4 -v -w ${fw}.hex
packages = {
moonlander = nixcaps.mkQmkFirmware moonlander;
togkey = nixcaps.mkQmkFirmware togkey_pad_plus;
};
apps = {
moonlander = nixcaps.flashQmkFirmware moonlander;
togkey = nixcaps.flashQmkFirmware togkey_pad_plus;
};
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 togkey_pad_plus}/compile_commands.json" ./togkey_pad_plus/compile_commands.json
'';
};
}
);
}
```

This example is also packaged as a template you can try locally by running:
Build and flash specific keyboards:

```bash
$ # instantiate the template locally
$ nix flake new nixcaps --template github:agustinmista/nixcaps#ergodox_ez
$ cd nixcaps
$ # compile the firmware
$ nix build
$ ls result/bin
flash # flasher script
fw.elf # firmware files
fw.hex # ...
$ # flash the compiled firmware
$ sudo ./result/bin/flash # or nix run
Flashing /nix/store/b5jnf80...-nixcaps-compile/bin/fw.hex ...
Teensy Loader, Command Line, Version 2.3
Read "/nix/store/b5jnf80...-nixcaps-compile/bin/fw.hex": 28074 bytes, 87.0% usage
Waiting for Teensy device...
(hint: press the reset button)
$ nix build .#moonlander # build moonlander firmware
$ nix run .#togkey # flash togkey firmware
```

This example is available as a template:

```bash
$ nix flake new my-keyboards --template github:agustinmista/nixcaps#multiple_keyboards
```

## LSP Support

nixcaps 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.

## Notes

- You can change the version of `qmk_firmware` used by nixcaps by overriding its `qmk_firmware` flake input as follows:

```nix
nixcaps.inputs.qmk_firmware.url = "git+https://github.com/qmk/qmk_firmware?submodules=1&rev=<COMMIT_SHA>";
nixcaps.inputs.qmk_firmware.ref = "<COMMIT_SHA_OR_GIT_TAG>";
```

- Under the hood, this derivation first copies the files in `src` into `keyboards/<keyboard>/keymaps/nixcaps` inside an internal copy of the `qmk_firmware` repo, and then executes `qmk compile --keyboard <keyboard>[/<variant>] --keymap nixcaps`.
Expand Down
4 changes: 2 additions & 2 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 21 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
qmk_firmware = {
url = "git+https://github.com/qmk/qmk_firmware?submodules=1&rev=54e8fad959d6a6e53e08c62ac3a3c4d4bdc6c957";
url = "https://github.com/qmk/qmk_firmware";
ref = "54e8fad959d6a6e53e08c62ac3a3c4d4bdc6c957";
flake = false;
type = "git";
submodules = true;
};
};

Comment thread
mrjones2014 marked this conversation as resolved.
Expand All @@ -21,11 +24,22 @@
system:
let
pkgs = nixpkgs.legacyPackages.${system};
nixcaps = pkgs.callPackage ./nixcaps.nix { inherit qmk_firmware; };
mkQmkFirmware = pkgs.callPackage (import ./nix/build.nix qmk_firmware) { };
mkFlashQmkFirmware = pkgs.callPackage (import ./nix/flash.nix qmk_firmware) { };
mkCompileDb = pkgs.callPackage (import ./nix/compiledb.nix qmk_firmware) { };
flashQmkFirmware =
args:
let
firmware = mkQmkFirmware args;
in
{
type = "app";
program = "${mkFlashQmkFirmware (removeAttrs args [ "src" ] // { inherit firmware; })}/bin/flash";
};
in
{
packages = { inherit (nixcaps) compile; };
formatter = pkgs.nixfmt-rfc-style;
lib = { inherit mkQmkFirmware flashQmkFirmware mkCompileDb; };
}
))
// {
Expand All @@ -35,6 +49,10 @@
path = ./templates/ergodox_ez;
description = "A simple flake to build a firmware for the Ergodox EZ keyboard";
};
multiple_keyboards = {
path = ./templates/multiple_keyboards;
description = "A flake demonstrating how to build firmwares for multiple keyboards";
};
};
};
}
50 changes: 50 additions & 0 deletions nix/build.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
qmk_firmware:
{
lib,
qmk,
stdenv,
python313,
...
}:
{
src,
keyboard,
variant ? null,
target ? null,
}:
let
qmk_args = (import ./qmk-args.nix { inherit lib; }) { inherit keyboard variant target; };
in
stdenv.mkDerivation {
name = "${qmk_args.keymapName}-firmware";
src = qmk_firmware;
buildInputs = [
qmk
python313
];

postPatch = ''
mkdir -p ${qmk_args.keymapDir}
cp -r ${src}/* ${qmk_args.keymapDir}/
patchShebangs --host ${python313}/bin
'';

buildPhase = ''
qmk compile \
-j 4 \
--env SKIP_GIT=true \
--env QMK_HOME=$PWD \
--env QMK_FIRMWARE=$PWD \
--env BUILD_DIR=${qmk_args.buildDir} \
--env TARGET=${qmk_args.targetName} \
--keyboard ${qmk_args.keyboardVariant} \
--keymap ${qmk_args.keymapName}
'';

installPhase = ''
mkdir -p $out/bin
cp ${qmk_args.buildDir}/*.{hex,bin,elf,dfu,uf2,eep} $out/bin 2>/dev/null || true
'';

dontFixup = true;
}
Loading