Files
komorebi/flake.nix
T
LGUG2Z 3a3a0f8f24 feat(wm): JSON schemas, and the module's typed options
komorebi.json is described as a JSON Schema, made from the Rust types that read
it: komorebic prints the schema of komorebi.json, of an applications.json, of a
notification and of a message, and the daemon answers the same four questions on
its socket. schema.json and schema.asc.json are committed (just jsonschema).

The module's config option is typed from the schema: schemars-nixgen makes
nix/komorebi-options.nix from schema.json (just nixgen), so a layout komorebi
does not have, or a value of the wrong kind, does not evaluate. The module
writes config as a komorebi.json and gives it to the daemon with --config.
configFile and the file the module writes are checked against the schema when
the system is built. applicationSpecificConfiguration takes files from GitHub,
and there are none by default, since no community file for Plasma is known.

Two checks keep the generated files honest without the tools that make them.
schema-fresh compares the schemas with what komorebic prints, and the hash on
the first line of the options with the schema. nix-options-validation evaluates
the options against the example configuration, written in Nix and in JSON.

nixos-module-eval also evaluates the module with the sources turned off: with
applicationSpecificConfiguration.enable = false the sources are left out of the
config that is written, and with no config either there is nothing to write and
the unit passes no --config. ARCHITECTURE has the guarantee row that names the
two checks.
2026-10-28 07:20:37 -07:00

366 lines
13 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, the configurations
# the tests load, and the schemas
|| (pkgs.lib.hasSuffix ".json" path)
# the page of the docs that shows the example configuration, which a test compares
# with the example file
|| (pkgs.lib.hasSuffix "docs/example-configurations.md" 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";
# komorebic with the constant commit of the checks, whose output the schemas are
# checked against: it is the same for every commit
komorebicForChecks = craneLib.buildPackage (
individualCrateArgs
// {
pname = "komorebic-for-checks";
cargoExtraArgs = "--package komorebic --bin 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 schemas, the options made from one of them and the example are what the Rust types
# say: `just jsonschema` and `just nixgen` write them
schema-fresh =
pkgs.runCommand "komorebi-schema-fresh"
{
nativeBuildInputs = [
build.komorebicForChecks
pkgs.diffutils
];
}
''
komorebic static-config-schema | diff -u ${./schema.json} - \
|| { echo "schema.json is stale: run just jsonschema"; exit 1; }
komorebic application-specific-configuration-schema | diff -u ${./schema.asc.json} - \
|| { echo "schema.asc.json is stale: run just jsonschema"; exit 1; }
expected=$(sha256sum ${./schema.json} | cut -d' ' -f1)
head -n 1 ${./nix/komorebi-options.nix} | grep -qx "# schema-sha256: $expected" \
|| { echo "nix/komorebi-options.nix is stale: run just nixgen"; exit 1; }
touch $out
'';
# the options the module is typed with, evaluated against the example configuration
nix-options-validation = import ./nix/tests/default.nix { inherit pkgs; };
# 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.check-jsonschema
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;
};
};
}