zmk/nix/zmk.nix
Chris Andreae c7fc3432cd
Docker container and lambda function for performing firmware builds
Provides an entry point that builds and returns a combined LH + RH keyboard
firmware when provided a keymap via a POST body.

Wraps compilation with ccache, and includes a pre-warmed cache of the build in /tmp/ccache.
To maximize chance of a direct cache hit, changes the lambda driver to always build in /tmp/build.

some back of the envelope measurements (2012 xeon e3-1230v2, nixos)
clean build, no cache -> 21.308
clean build, cache -> 7.145
modified keymap, clean build, cache -> 12.127
2022-12-24 13:00:14 +09:00

106 lines
3.1 KiB
Nix

{ stdenvNoCC, lib, buildPackages
, cmake, ninja, dtc, gcc-arm-embedded
, zephyr
, board ? "glove80_lh"
, shield ? null
, keymap ? null
}:
let
# from zephyr/scripts/requirements-base.txt
packageOverrides = pyself: pysuper: {
can = pysuper.can.overrideAttrs (_: {
# horribly flaky test suite full of assertions about timing.
# > assert 0.1 <= took < inc(0.3)
# E assert 0.31151700019836426 < 0.3
# E + where 0.3 = inc(0.3)
doCheck = false;
doInstallCheck = false;
});
canopen = pysuper.can.overrideAttrs (_: {
# Also has timing sensitive tests
# task = self.network.send_periodic(0x123, [1, 2, 3], 0.01)
# time.sleep(0.1)
# > self.assertTrue(9 <= bus.queue.qsize() <= 11)
# E AssertionError: False is not true
doCheck = false;
doInstallCheck = false;
});
};
python = (buildPackages.python3.override { inherit packageOverrides; }).withPackages (ps: with ps; [
pyelftools
pyyaml
canopen
packaging
progress
anytree
intelhex
# TODO: this was required but not in shell.nix
pykwalify
]);
requiredZephyrModules = [
"cmsis" "hal_nordic" "tinycrypt" "littlefs"
];
zephyrModuleDeps = builtins.filter (x: builtins.elem x.name requiredZephyrModules) zephyr.modules;
in
stdenvNoCC.mkDerivation {
name = "zmk_${board}";
sourceRoot = "source/app";
src = builtins.path {
name = "source";
path = ./..;
filter = path: type:
let relPath = lib.removePrefix (toString ./.. + "/") (toString path);
in (lib.cleanSourceFilter path type) && ! (
# Meta files
relPath == "nix" || lib.hasSuffix ".nix" path ||
# Transient state
relPath == "build" || relPath == ".west" ||
# Fetched by west
relPath == "modules" || relPath == "tools" || relPath == "zephyr" ||
# Not part of ZMK
relPath == "lambda" || relPath == ".github"
);
};
preConfigure = ''
cmakeFlagsArray+=("-DUSER_CACHE_DIR=$TEMPDIR/.cache")
'';
cmakeFlags = [
# "-DZephyrBuildConfiguration_ROOT=${zephyr}/zephyr"
# TODO: is this required? if not, why not?
# "-DZEPHYR_BASE=${zephyr}/zephyr"
"-DBOARD_ROOT=."
"-DBOARD=${board}"
"-DZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb"
"-DGNUARMEMB_TOOLCHAIN_PATH=${gcc-arm-embedded}"
# TODO: maybe just use a cross environment for this gcc
"-DCMAKE_C_COMPILER=${gcc-arm-embedded}/bin/arm-none-eabi-gcc"
"-DCMAKE_CXX_COMPILER=${gcc-arm-embedded}/bin/arm-none-eabi-g++"
"-DCMAKE_AR=${gcc-arm-embedded}/bin/arm-none-eabi-ar"
"-DCMAKE_RANLIB=${gcc-arm-embedded}/bin/arm-none-eabi-ranlib"
"-DZEPHYR_MODULES=${lib.concatStringsSep ";" zephyrModuleDeps}"
] ++
(lib.optional (shield != null) "-DSHIELD=${shield}") ++
(lib.optional (keymap != null) "-DKEYMAP_FILE=${keymap}");
nativeBuildInputs = [ cmake ninja python dtc gcc-arm-embedded ];
buildInputs = [ zephyr ];
installPhase = ''
mkdir $out
cp zephyr/zmk.{uf2,hex,bin,elf} $out
'';
passthru = { inherit zephyrModuleDeps; };
}