~andrew/qmk

872b1dfb47b16693637cab95a6d3c5e0c2db43ce — Andrew Reisner 5 months ago 8b45663
add nix flake.
4 files changed, 442 insertions(+), 0 deletions(-)

M .gitignore
M README.md
A flake.lock
A flake.nix
M .gitignore => .gitignore +5 -0
@@ 14,3 14,8 @@
compile_commands.json
.clangd/
.cache/

# Nix
result
result-*
.direnv/

M README.md => README.md +64 -0
@@ 57,3 57,67 @@ This can also be used to control which fork is used, though only upstream `qmk_f
1. (First time only) `git submodule add https://github.com/qmk/qmk_firmware.git`
1. (To update) `git submodule update --init --recursive`
1. Commit your changes to your userspace repository

## Building with Nix

This repository includes a Nix flake for reproducible builds. The flake automatically fetches the correct QMK firmware fork for each keyboard type:

| Keyboard | QMK Fork |
|----------|----------|
| ZSA Voyager | [zsa/qmk_firmware](https://github.com/zsa/qmk_firmware) |
| Keyball39 | [holykeebs/qmk_firmware](https://github.com/holykeebs/qmk_firmware) |
| Ploopyco Trackball | [qmk/qmk_firmware](https://github.com/qmk/qmk_firmware) (upstream) |

### Available packages

| Package | Keyboard | Keymap |
|---------|----------|--------|
| `voyager-pers` | zsa/voyager | pers |
| `voyager-ball` | zsa/voyager | ball |
| `voyager-mod` | zsa/voyager | mod |
| `keyball39-init` | keyball/keyball39 | init |
| `ploopyco-trackball-rev1_005-pers` | ploopyco/trackball/rev1_005 | pers |
| `ploopyco-trackball-rev1_007-pers` | ploopyco/trackball/rev1_007 | pers |
| `all` | All keyboards | - |

### Build a specific keyboard

```bash
nix build .#voyager-pers
nix build .#keyball39-init
nix build .#ploopyco-trackball-rev1_007-pers
```

Firmware files will be in `./result/`.

### Build all keyboards

```bash
nix build
```

### Development shell

Enter a shell with all QMK tooling (AVR/ARM toolchains, Python dependencies, flashing utilities):

```bash
nix develop
```

### Flash firmware

```bash
# ZSA Voyager (using wally-cli)
nix run .#flash-voyager

# Or manually after building
wally-cli ./result/zsa_voyager_pers.bin
```

### Update QMK firmware versions

```bash
nix flake update
```

This updates all QMK firmware forks to their latest versions. The `flake.lock` file pins specific commits for reproducibility.

A flake.lock => flake.lock +118 -0
@@ 0,0 1,118 @@
{
  "nodes": {
    "flake-utils": {
      "inputs": {
        "systems": "systems"
      },
      "locked": {
        "lastModified": 1731533236,
        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
        "owner": "numtide",
        "repo": "flake-utils",
        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
        "type": "github"
      },
      "original": {
        "owner": "numtide",
        "repo": "flake-utils",
        "type": "github"
      }
    },
    "keyball-qmk-firmware": {
      "flake": false,
      "locked": {
        "lastModified": 1763193823,
        "narHash": "sha256-MB3MnvXprF4LsnFB0tTHRi8SWiBaCfrWXjQWX1BQYNA=",
        "ref": "refs/heads/hk-master",
        "rev": "19fd205b8411ce787d26c899cc5ad09ee4d77cb6",
        "revCount": 28836,
        "submodules": true,
        "type": "git",
        "url": "https://github.com/holykeebs/qmk_firmware"
      },
      "original": {
        "submodules": true,
        "type": "git",
        "url": "https://github.com/holykeebs/qmk_firmware"
      }
    },
    "nixpkgs": {
      "locked": {
        "lastModified": 1770562336,
        "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "d6c71932130818840fc8fe9509cf50be8c64634f",
        "type": "github"
      },
      "original": {
        "owner": "NixOS",
        "ref": "nixos-unstable",
        "repo": "nixpkgs",
        "type": "github"
      }
    },
    "qmk-firmware": {
      "flake": false,
      "locked": {
        "lastModified": 1770841410,
        "narHash": "sha256-BCLpMbcO3IS8qxRcfLff7HvjnsBKAlBweAVTGLpKDZc=",
        "ref": "refs/heads/master",
        "rev": "45ccd2e9351b979f8eb8fa359db732e5374b0d53",
        "revCount": 29267,
        "submodules": true,
        "type": "git",
        "url": "https://github.com/qmk/qmk_firmware"
      },
      "original": {
        "submodules": true,
        "type": "git",
        "url": "https://github.com/qmk/qmk_firmware"
      }
    },
    "root": {
      "inputs": {
        "flake-utils": "flake-utils",
        "keyball-qmk-firmware": "keyball-qmk-firmware",
        "nixpkgs": "nixpkgs",
        "qmk-firmware": "qmk-firmware",
        "zsa-qmk-firmware": "zsa-qmk-firmware"
      }
    },
    "systems": {
      "locked": {
        "lastModified": 1681028828,
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
        "owner": "nix-systems",
        "repo": "default",
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
        "type": "github"
      },
      "original": {
        "owner": "nix-systems",
        "repo": "default",
        "type": "github"
      }
    },
    "zsa-qmk-firmware": {
      "flake": false,
      "locked": {
        "lastModified": 1769675113,
        "narHash": "sha256-JQ2A7UHvClZg2PhIbIxWMW8+3qWyPZpcNsCqSqkjY28=",
        "ref": "refs/heads/firmware25",
        "rev": "2f7463bddd017600910958528a2632f26bd67a70",
        "revCount": 28639,
        "submodules": true,
        "type": "git",
        "url": "https://github.com/zsa/qmk_firmware"
      },
      "original": {
        "submodules": true,
        "type": "git",
        "url": "https://github.com/zsa/qmk_firmware"
      }
    }
  },
  "root": "root",
  "version": 7
}

A flake.nix => flake.nix +255 -0
@@ 0,0 1,255 @@
{
  description = "QMK Userspace - Personal keyboard firmware configurations";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";

    # Upstream QMK firmware (for Ploopyco and other standard keyboards)
    qmk-firmware = {
      url = "git+https://github.com/qmk/qmk_firmware?submodules=1";
      flake = false;
    };

    # ZSA fork of QMK firmware (for Voyager, Moonlander, Planck EZ, Ergodox EZ)
    zsa-qmk-firmware = {
      url = "git+https://github.com/zsa/qmk_firmware?submodules=1";
      flake = false;
    };

    # Holykeebs fork of QMK firmware (for Keyball keyboards)
    keyball-qmk-firmware = {
      url = "git+https://github.com/holykeebs/qmk_firmware?submodules=1";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, flake-utils, qmk-firmware, zsa-qmk-firmware, keyball-qmk-firmware }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          config.allowUnfree = true;
        };

        # Common QMK build dependencies
        qmkBuildInputs = with pkgs; [
          # AVR toolchain (for ATmega-based keyboards like Ploopyco)
          pkgsCross.avr.buildPackages.gcc
          avrdude

          # ARM toolchain (for ARM-based keyboards like Voyager, Keyball)
          gcc-arm-embedded

          # Build tools
          gnumake
          git
          which
          diffutils
          util-linux

          # Python with QMK dependencies
          (python3.withPackages (ps: with ps; [
            pip
            setuptools
            wheel
            pillow
            qmk
            hid
            pyusb
            hjson
            jsonschema
            milc
            pygments
            dotty-dict
          ]))

          # Additional dependencies
          libusb1
          dfu-programmer
          dfu-util
          teensy-loader-cli
          qmk
        ];

        # Helper to link userspace into QMK firmware
        setupUserspace = qmkSrc: pkgs.runCommand "qmk-with-userspace" {} ''
          cp -r ${qmkSrc} $out
          chmod -R +w $out

          # Link keyboards from userspace
          if [ -d ${self}/keyboards ]; then
            for kb in ${self}/keyboards/*; do
              kbname=$(basename $kb)
              if [ -d "$out/keyboards/$kbname" ]; then
                # Merge with existing keyboard (copy keymaps)
                cp -r $kb/* $out/keyboards/$kbname/ 2>/dev/null || true
              else
                # New keyboard, copy entirely
                cp -r $kb $out/keyboards/
              fi
            done
          fi

          # Link users directory
          if [ -d ${self}/users ]; then
            cp -r ${self}/users/* $out/users/ 2>/dev/null || true
          fi

          # Link layouts
          if [ -d ${self}/layouts ]; then
            for layout in ${self}/layouts/*; do
              layoutname=$(basename $layout)
              mkdir -p $out/layouts/$layoutname
              cp -r $layout/* $out/layouts/$layoutname/ 2>/dev/null || true
            done
          fi
        '';

        # QMK sources with userspace integrated
        qmkUpstream = setupUserspace qmk-firmware;
        qmkZsa = setupUserspace zsa-qmk-firmware;
        qmkKeyball = setupUserspace keyball-qmk-firmware;

        # Build a QMK firmware
        buildFirmware = { name, keyboard, keymap, qmkSource }:
          pkgs.stdenv.mkDerivation {
            pname = "qmk-${name}";
            version = "0.1.0";

            src = qmkSource;

            nativeBuildInputs = qmkBuildInputs;

            buildPhase = ''
              runHook preBuild

              # QMK needs a writable home directory
              export HOME="$NIX_BUILD_TOP/home"
              mkdir -p $HOME

              # Build the firmware
              make ${keyboard}:${keymap}

              runHook postBuild
            '';

            installPhase = ''
              runHook preInstall

              mkdir -p $out
              # Copy all generated firmware files
              find . -maxdepth 1 \( -name "*.hex" -o -name "*.bin" -o -name "*.uf2" \) \
                -exec cp {} $out/ \;

              runHook postInstall
            '';

            # Don't try to strip firmware files
            dontStrip = true;
            dontPatchELF = true;
            dontFixup = true;
          };

        # Define keyboard builds
        keyboards = {
          # ZSA Voyager - uses ZSA QMK fork
          voyager-pers = buildFirmware {
            name = "voyager-pers";
            keyboard = "zsa/voyager";
            keymap = "pers";
            qmkSource = qmkZsa;
          };

          voyager-ball = buildFirmware {
            name = "voyager-ball";
            keyboard = "zsa/voyager";
            keymap = "ball";
            qmkSource = qmkZsa;
          };

          voyager-mod = buildFirmware {
            name = "voyager-mod";
            keyboard = "zsa/voyager";
            keymap = "mod";
            qmkSource = qmkZsa;
          };

          # Keyball39 - uses holykeebs QMK fork
          keyball39-init = buildFirmware {
            name = "keyball39-init";
            keyboard = "keyball/keyball39";
            keymap = "init";
            qmkSource = qmkKeyball;
          };

          # Ploopyco Trackball - uses upstream QMK
          ploopyco-trackball-rev1_005-pers = buildFirmware {
            name = "ploopyco-trackball-rev1_005-pers";
            keyboard = "ploopyco/trackball/rev1_005";
            keymap = "pers";
            qmkSource = qmkUpstream;
          };

          ploopyco-trackball-rev1_007-pers = buildFirmware {
            name = "ploopyco-trackball-rev1_007-pers";
            keyboard = "ploopyco/trackball/rev1_007";
            keymap = "pers";
            qmkSource = qmkUpstream;
          };
        };

      in {
        # Individual keyboard packages
        packages = keyboards // {
          # Build all keyboards
          all = pkgs.symlinkJoin {
            name = "qmk-all-keyboards";
            paths = builtins.attrValues keyboards;
          };

          default = self.packages.${system}.all;
        };

        # Development shell with QMK tooling
        devShells.default = pkgs.mkShell {
          buildInputs = qmkBuildInputs ++ (with pkgs; [
            # Additional dev tools
            qmk
          ]);

          shellHook = ''
            echo "QMK Userspace Development Environment"
            echo ""
            echo "Available keyboards:"
            echo "  ZSA (use zsa/qmk_firmware):"
            echo "    - zsa/voyager:pers"
            echo "    - zsa/voyager:ball"
            echo "    - zsa/voyager:mod"
            echo ""
            echo "  Keyball (use holykeebs/qmk_firmware):"
            echo "    - keyball/keyball39:init"
            echo ""
            echo "  Ploopyco (use upstream qmk_firmware):"
            echo "    - ploopyco/trackball/rev1_005:pers"
            echo "    - ploopyco/trackball/rev1_007:pers"
            echo ""
            echo "Build with: nix build .#<keyboard-name>"
            echo "  e.g.: nix build .#voyager-pers"
            echo "        nix build .#keyball39-init"
            echo "        nix build .#all"
            echo ""
          '';
        };

        # Apps for flashing firmware
        apps = {
          flash-voyager = flake-utils.lib.mkApp {
            drv = pkgs.writeShellScriptBin "flash-voyager" ''
              ${pkgs.wally-cli}/bin/wally-cli ${self.packages.${system}.voyager-pers}/*.bin
            '';
          };
        };
      }
    );
}