Files
komorebi/flake.nix
T
LGUG2Z a86e2bdc63 build(nix): a NixOS module for komorebi
On NixOS, running komorebi should take one flake input and one module line. The
flake exports `services.komorebi` as nixosModules.default (and .komorebi). It
adds the flake's overlay, which gains komorebi-full, installs komorebi and
komorebic, and runs the daemon in every Plasma session as a systemd user unit,
wanted by plasma-workspace.target and ordered after plasma-core.target, which
Plasma orders after KWin. systemd starts a daemon that fails again; the exit
status of a SIGTERM (130) counts as success, so a stopped unit is not failed.
The unit's PATH is the one a terminal in the session has, instead of the few
store paths NixOS gives a unit.

The options are enable, package, logLevel and a read-only execStart, the command
line the unit runs. There is no configFile: the daemon reads no configuration
file yet, and an option that linked one would do nothing.

nixos-module-eval evaluates seven scenarios as real NixOS systems and checks
their assertions, the unit and execStart, with builtins.tryEval for the two that
must not evaluate. Nothing in it builds a Linux package, so it runs on a Mac as
well.

The VM nodes import the module, so every suite runs komorebi as the module
installs it. The headless node's komorebi@NAME runs execStart, which has
--log-level, so the nodes set logLevel to debug and the suites stop passing it;
latency, which times the daemon, keeps the default level. The session node
starts the module's unit at login; render-smoke turns it off, since a tiled
terminal would cover the desktop it looks at. A new fast suite, vm-module, logs
in to Plasma and checks that the unit runs the daemon with the module's log
level and PATH, that a window opened in the session is tiled, that a killed
daemon is started again, and that a stop is not a failure.

docs/installation.md covers NixOS first, and building from source on other
distributions. There is no binary cache: the Komorebi license does not allow
third parties to redistribute binaries.
2026-10-28 07:20:37 -07:00

328 lines
11 KiB
Nix

{
description = "komorebi for KDE Plasma";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
crane.url = "github:ipetkov/crane";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
git-hooks-nix.url = "github:cachix/git-hooks.nix";
git-hooks-nix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
inputs@{
self,
nixpkgs,
flake-parts,
crane,
rust-overlay,
...
}:
let
mkPkgs =
system:
import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
mkKomorebiPackages =
{ pkgs }:
let
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
# rustfmt.toml uses options only a nightly rustfmt knows
craneLibFmt = (crane.mkLib pkgs).overrideToolchain (
pkgs.rust-bin.nightly.latest.minimal.override { extensions = [ "rustfmt" ]; }
);
src = pkgs.lib.cleanSourceWith {
src = ./.;
filter =
path: type:
(craneLib.filterCargoSources path type)
# the script komorebi loads into KWin, which the daemon includes
|| (pkgs.lib.hasSuffix ".js" path)
# the messages the tests of the bridge and of the daemon share
|| (pkgs.lib.hasSuffix ".json" path);
};
commonArgs = {
inherit src;
pname = "komorebi-workspace";
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version;
strictDeps = true;
# The version crate wants a commit at compile time. It is a constant here, so that the
# dependencies and every check are the same derivations for every commit; only the final
# build below knows the real one.
COMMIT_HASH = "nix-check";
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
individualCrateArgs = commonArgs // {
inherit cargoArtifacts;
doCheck = false;
doDoc = false;
};
fullBuild = craneLib.buildPackage (
individualCrateArgs
// {
COMMIT_HASH = self.rev or (pkgs.lib.removeSuffix "-dirty" self.dirtyRev);
}
);
extractBinary =
binaryName:
pkgs.runCommand "komorebi-${binaryName}" { meta = fullBuild.meta // { }; } ''
mkdir -p $out/bin
cp ${fullBuild}/bin/${binaryName} $out/bin/
'';
in
{
inherit
craneLib
craneLibFmt
src
commonArgs
individualCrateArgs
fullBuild
;
komorebi = extractBinary "komorebi";
komorebic = extractBinary "komorebic";
};
in
flake-parts.lib.mkFlake { inherit inputs; } {
# Linux is what komorebi runs on. An Apple silicon Mac gets the dev shell, the formatter and the
# checks, which run the same tests against the fake desktop. nixpkgs dropped x86_64-darwin in
# 26.11.
systems = [
"aarch64-linux"
"x86_64-linux"
"aarch64-darwin"
];
imports = [
inputs.treefmt-nix.flakeModule
inputs.git-hooks-nix.flakeModule
];
perSystem =
{
config,
lib,
system,
...
}:
let
pkgs = mkPkgs system;
build = mkKomorebiPackages { inherit pkgs; };
isLinux = pkgs.stdenv.hostPlatform.isLinux;
# the guests install komorebi through the module, as a NixOS user does
komorebiModule = self.nixosModules.default;
# a Mac runs the guests under HVF, which is slower than KVM
vm = import ./nix/tests/vm {
hostPkgs = pkgs;
inherit komorebiModule;
scale = if isLinux then 1 else 1.5;
};
nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt;
nightlyToolchain = pkgs.rust-bin.nightly.latest.default;
rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin {
name = "rust-toolchain-with-nightly-rustfmt";
paths = [
nightlyRustfmt
(pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
];
};
# cargo-udeps needs a nightly compiler
cargo-udeps = pkgs.writeShellScriptBin "cargo-udeps" ''
export PATH="${nightlyToolchain}/bin:$PATH"
exec ${pkgs.cargo-udeps}/bin/cargo-udeps "$@"
'';
in
{
treefmt = {
projectRootFile = "flake.nix";
programs = {
deadnix.enable = true;
just.enable = true;
nixfmt.enable = true;
taplo.enable = true;
rustfmt = {
enable = true;
package = nightlyRustfmt;
};
};
};
checks = {
komorebi-workspace-fmt = build.craneLibFmt.cargoFmt { inherit (build) src; };
komorebi-workspace-clippy = build.craneLib.cargoClippy (
build.individualCrateArgs
// {
cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
}
);
komorebi-workspace-doc = build.craneLib.cargoDoc (
build.individualCrateArgs
// {
cargoDocExtraArgs = "--workspace --no-deps";
env.RUSTDOCFLAGS = "-D warnings";
}
);
komorebi-workspace-toml-fmt = build.craneLib.taploFmt {
src = lib.sources.sourceFilesBySuffices build.src [ ".toml" ];
};
komorebi-workspace-deny = build.craneLib.cargoDeny { inherit (build) src; };
komorebi-workspace-nextest = build.craneLib.cargoNextest (
build.individualCrateArgs // { doCheck = true; }
);
# The bridge is not Rust: tsc checks its types from its JSDoc comments, and node runs
# its tests against a fake KWin. No package.json and no build step.
komorebi-bridge-js =
pkgs.runCommand "komorebi-bridge-js"
{
nativeBuildInputs = [
pkgs.nodejs
pkgs.typescript
];
src = lib.fileset.toSource {
root = ./komorebi/src/kwin;
fileset = lib.fileset.fileFilter (file: !file.hasExt "rs") ./komorebi/src/kwin;
};
}
''
cd $src
tsc -p tsconfig.json
node --test bridge.test.js
touch $out
'';
vm-lib = vm.lib;
# The NixOS module in real NixOS systems, evaluated and never built, so a Mac checks it
# too. A VM suite, `vm-module`, runs it.
nixos-module-eval = import ./nix/tests/nixos-module.nix {
inherit pkgs;
inherit (nixpkgs.lib) nixosSystem;
module = self.nixosModules.default;
komorebiFor = guestSystem: self.packages.${guestSystem}.komorebi-full;
};
}
# The VM tests need QEMU with KVM, so only Linux checks them. A Mac runs the same tests
# through legacyPackages.<system>.vm, see docs/dev/vm-tests.md.
// lib.optionalAttrs isLinux (
lib.mapAttrs' (name: test: lib.nameValuePair "vm-${name}" test) vm.suites
);
legacyPackages = {
# every suite, as a test with a `driver`, on every system
vm = vm.suites;
# the suites of each tier, for the CI matrix
vmTiers = vm.tiers;
# the guests of every suite, to build on a Linux builder when the host is a Mac
vmGuests = vm.guests;
}
// lib.optionalAttrs isLinux {
# the same suites on a machine with no KVM, which is slow but works anywhere
vmTcg =
(import ./nix/tests/vm {
hostPkgs = pkgs;
inherit komorebiModule;
scale = 8;
}).suites;
};
packages = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
inherit (build) komorebi komorebic;
komorebi-full = build.fullBuild;
default = build.fullBuild;
};
apps = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
komorebi = {
type = "app";
program = "${build.komorebi}/bin/komorebi";
meta.description = "The komorebi daemon";
};
komorebic = {
type = "app";
program = "${build.komorebic}/bin/komorebic";
meta.description = "The command line interface of komorebi";
};
default = config.apps.komorebi;
};
devShells.default = pkgs.mkShell {
name = "komorebi";
RUST_BACKTRACE = "full";
inputsFrom = [ build.fullBuild ];
packages = [
rustToolchainWithNightlyRustfmt
cargo-udeps
pkgs.cargo-deny
pkgs.cargo-nextest
pkgs.jq
pkgs.just
pkgs.ripgrep
# the bridge's type check and tests (`just check-js`)
pkgs.nodejs
pkgs.typescript
# the VM test library
pkgs.ruff
pkgs.ty
(pkgs.python3.withPackages (ps: [ ps.pytest ]))
];
};
pre-commit = {
check.enable = true;
settings.hooks.treefmt = {
enable = true;
package = config.treefmt.build.wrapper;
pass_filenames = false;
};
};
};
flake = {
overlays.default =
final: _:
let
build = mkKomorebiPackages { pkgs = mkPkgs final.stdenv.hostPlatform.system; };
in
{
inherit (build) komorebi komorebic;
komorebi-full = build.fullBuild;
};
overlays.komorebi = self.overlays.default;
# `services.komorebi`, with the overlay that provides its package, so that a NixOS
# configuration needs this flake as an input and one module line (docs/installation.md)
nixosModules.default = {
imports = [ ./nix/nixos-module.nix ];
nixpkgs.overlays = [ self.overlays.default ];
};
nixosModules.komorebi = self.nixosModules.default;
};
};
}