build(nix): build and check for macOS and Windows

A flake that builds the recorder for macOS and cross-compiles it for Windows
from a Mac (xwin for the SDK, LLVM for the toolchain), with a dev shell for
each, and `nix flake check` running clippy for both targets, rustfmt, taplo,
cargo-deny and the tests.

The sources of komorebi for Mac and komorebi for Windows are flake inputs,
pinned in `flake.lock`: the recorder indexes them so that a line of komorebi's
log can be traced to the statement that wrote it, and the dev shells and every
derivation export where they are.
This commit is contained in:
LGUG2Z committed 2026-10-27 22:22:56 -07:00
1 parent af3c080ace
commit 76bae0630e
7 files changed
+665 -7

No files matched your search

+1
View File
@@ -0,0 +1 @@
use flake
+17 -3
View File
@@ -46,12 +46,26 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
## Development environment
The project builds with the stable Rust toolchain, and is formatted with nightly `rustfmt` (`cargo +nightly fmt`).
The project is built with Nix. `nix develop` (or direnv, whose `.envrc` is `use flake`) provides the
Rust toolchain with nightly `rustfmt`, `cargo-deny`, `cargo-nextest`, `ffmpeg`, `jq`, `just` and
`ripgrep`, and sets `KOMOREBI_MAC_SRC` and `KOMOREBI_WINDOWS_SRC` (with `_COMMIT`) to the komorebi
sources pinned in `flake.lock`. The tests that read komorebi's source need them, so run `cargo` inside
the shell.
The Windows code is type-checked from a Mac with `nix develop .#windows`, whose target is
`x86_64-pc-windows-msvc`. Nothing there runs it: a change to a `cfg(windows)` line is first run by the
`windows` job in CI.
## Checks
Before pushing, run `just check`, which runs formatting, clippy, the tests, rustdoc with its warnings as errors and
`cargo deny`. `just --list` shows the other recipes.
Before pushing, run
```sh
just check
```
which runs formatting, clippy and the tests for macOS, clippy for Windows, rustdoc with its warnings as
errors for both hosts, and `cargo deny`. `just --list` shows the other recipes.
## Commits
+8 -2
View File
@@ -13,12 +13,17 @@ There are no releases yet: build it from source, below.
### From source
With a stable Rust toolchain, in a checkout:
The repository is a Nix flake. In a checkout:
```sh
nix develop # the toolchain and the pinned komorebi sources
cargo install --path komorebi-recorder --locked
```
`nix build` makes the macOS binary in `./result/bin`, and `nix build .#komorebi-recorder-windows` cross-compiles
`komorebi-recorder.exe` and, with it, `komorebi-recorder-gui.exe` from a Mac. `nix run github:LGUG2Z/komorebi-recorder`
runs the latest commit without a checkout.
## How it fits together
Two things vary independently: the _host_ the recorder runs on (macOS or Windows) and the _flavor_ of komorebi it
@@ -33,7 +38,8 @@ rewritten after they are written.
## Checks
`just check` runs formatting, clippy, the tests, rustdoc with its warnings as errors, and `cargo deny`.
`just check` runs formatting, clippy and the tests for macOS, clippy for Windows (the Windows code is type-checked
from a Mac, not run), rustdoc with its warnings as errors for both hosts, and `cargo deny`. It needs `nix develop`.
CI runs the same checks, builds both platforms and, for Windows, the installer.
## Documentation
Generated
+191
View File
@@ -0,0 +1,191 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1792350552,
"narHash": "sha256-292lvpvxTGKryKxh3/3tcIVi85CwYGdl4oOi113Sob8=",
"owner": "ipetkov",
"repo": "crane",
"rev": "e675a36b7377bebe41d548388a2501cbb16d380d",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1790898470,
"narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "024633cd702b10285db5cb19b40ad48d2399ba60",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1791909798,
"narHash": "sha256-VAhpCOmrun/KXimYfR8NM88bciKCui7CqHHhThSzgag=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "1ba1dbfe28738c9a71c266be6efa5b89e75491a9",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"komorebi-mac": {
"flake": false,
"locked": {
"lastModified": 1777946799,
"narHash": "sha256-uDprEfYaPugY2eJpKRIHJQoyxYKwXmnu/r8VQ7j9Fts=",
"owner": "LGUG2Z",
"repo": "komorebi-for-mac",
"rev": "bdf10452f10ebf15ec1978cb05d0aac79ef5611f",
"type": "github"
},
"original": {
"owner": "LGUG2Z",
"repo": "komorebi-for-mac",
"type": "github"
}
},
"komorebi-windows": {
"flake": false,
"locked": {
"lastModified": 1787433983,
"narHash": "sha256-L17PiaT8KH+2lEmsWJYnPV4HVifRi44tXJTCpCf4kwc=",
"owner": "LGUG2Z",
"repo": "komorebi",
"rev": "e0709f02bfae4e503bf4640f58ee75ecbbfdbb97",
"type": "github"
},
"original": {
"owner": "LGUG2Z",
"repo": "komorebi",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1792842277,
"narHash": "sha256-pGPnZGi8y9d76xNeracniXO3dsYVROEmMkMc8jD0Ihc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d3b13ad75cc492690728f9307e443dec9ef9a20e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1790477367,
"narHash": "sha256-oujbxmVKDhci2E4EM3KyAbRUgp5/IsCoiA/LfEF7OOk=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f7cd230690d9fc982d06129f9189b101fddcfa3b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-parts": "flake-parts",
"git-hooks-nix": "git-hooks-nix",
"komorebi-mac": "komorebi-mac",
"komorebi-windows": "komorebi-windows",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1793086596,
"narHash": "sha256-ObKS44vw4IIVpQ69YJzUGjZ0IwnpWoUlwjJSY7gefdo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c7e9d512653a35ef4d5879c3df4d44d7b78d0e6d",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1791885023,
"narHash": "sha256-C3alQ2MEt0zVrUSho5NUDo3jmhbff72++bfwoqT9Omo=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "0d6d739a833ba9a9c93034862e4a7b061a062a1c",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+359
View File
@@ -0,0 +1,359 @@
{
description = "komorebi-recorder: synchronized screen, log and trace capture for komorebi bug reproduction";
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";
# komorebi for Mac and komorebi for Windows: only their sources, which the recorder indexes so
# that `code` can map a line of a session's log to the statement that wrote it
komorebi-mac = {
url = "github:LGUG2Z/komorebi-for-mac";
flake = false;
};
komorebi-windows = {
url = "github:LGUG2Z/komorebi";
flake = false;
};
};
outputs =
inputs@{
nixpkgs,
flake-parts,
crane,
rust-overlay,
...
}:
let
windows = import ./nix/windows.nix;
# the sources the recorder indexes for each flavor of komorebi (see komorebi-recorder/build.rs),
# which are also the trees the tests that read komorebi's source read
sourceEnv = {
KOMOREBI_MAC_SRC = "${inputs.komorebi-mac}";
KOMOREBI_MAC_COMMIT = inputs.komorebi-mac.rev;
KOMOREBI_WINDOWS_SRC = "${inputs.komorebi-windows}";
KOMOREBI_WINDOWS_COMMIT = inputs.komorebi-windows.rev;
};
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version;
# what the build needs of the repository: Cargo sources, and the files the crate embeds or
# its tests read
mkSource =
{ pkgs, craneLib }:
pkgs.lib.cleanSourceWith {
src = ./.;
filter =
path: type:
(craneLib.filterCargoSources path type)
|| (pkgs.lib.hasSuffix "/komorebi-recorder/src/guide.md" path)
|| (pkgs.lib.hasInfix "/komorebi-recorder/tests/fixtures/" path);
};
mkPackages =
{ pkgs }:
let
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
src = mkSource { inherit pkgs craneLib; };
commonArgs = {
inherit src version;
pname = "komorebi-recorder";
strictDeps = true;
inherit (sourceEnv)
KOMOREBI_MAC_SRC
KOMOREBI_MAC_COMMIT
KOMOREBI_WINDOWS_SRC
KOMOREBI_WINDOWS_COMMIT
;
nativeBuildInputs = [
pkgs.darwin.cctools
];
postFixup = ''
for bin in $out/bin/*; do
if [ -f "$bin" ] && [ -x "$bin" ]; then
# Get all linked libraries and fix libiconv references
otool -L "$bin" | grep -o '/nix/store/[^/]*/lib/libiconv[^ ]*' | while read -r lib; do
install_name_tool -change "$lib" /usr/lib/libiconv.dylib "$bin"
done
fi
done
'';
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
individualCrateArgs = commonArgs // {
inherit cargoArtifacts;
doCheck = false;
doDoc = false;
};
fullBuild = craneLib.buildPackage individualCrateArgs;
# `komorebi-recorder-gui` is the same program as `komorebi-recorder` on a Mac
komorebi-recorder = pkgs.runCommand "komorebi-recorder" { meta = fullBuild.meta // { }; } ''
mkdir -p $out/bin
cp ${fullBuild}/bin/komorebi-recorder $out/bin/
'';
in
{
inherit
craneLib
src
individualCrateArgs
fullBuild
komorebi-recorder
;
};
# komorebi-recorder for Windows, cross-compiled from this host
mkWindowsPackages =
{ pkgs, windowsSdk }:
let
toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
targets = [ "x86_64-pc-windows-msvc" ];
};
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
msvcEnv = windows.mkMsvcEnv { inherit pkgs windowsSdk; };
src = mkSource { inherit pkgs craneLib; };
commonArgs = {
inherit src version;
pname = "komorebi-recorder-windows";
strictDeps = true;
cargoExtraArgs = "--locked -p komorebi-recorder";
inherit (sourceEnv)
KOMOREBI_MAC_SRC
KOMOREBI_MAC_COMMIT
KOMOREBI_WINDOWS_SRC
KOMOREBI_WINDOWS_COMMIT
;
nativeBuildInputs = [
pkgs.llvmPackages.clang-unwrapped
pkgs.llvmPackages.lld
pkgs.llvmPackages.llvm
];
}
// msvcEnv;
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
individualCrateArgs = commonArgs // {
inherit cargoArtifacts;
doCheck = false;
doDoc = false;
};
komorebi-recorder = craneLib.buildPackage individualCrateArgs;
in
{
inherit
craneLib
individualCrateArgs
msvcEnv
komorebi-recorder
;
};
mkPkgs =
system:
import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
in
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [
"aarch64-darwin"
"x86_64-darwin"
];
imports = [
inputs.treefmt-nix.flakeModule
inputs.git-hooks-nix.flakeModule
];
perSystem =
{ config, system, ... }:
let
pkgs = mkPkgs system;
build = mkPackages { inherit pkgs; };
windowsSdk = windows.mkWindowsSdk {
inherit pkgs;
inherit (windows) windowsSdkVersion windowsCrtVersion;
};
windowsBuild = mkWindowsPackages { inherit pkgs windowsSdk; };
rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt;
rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin {
name = "rust-toolchain-with-nightly-rustfmt";
paths = [
nightlyRustfmt
rustToolchain
];
};
in
{
treefmt = {
projectRootFile = "flake.nix";
programs = {
deadnix.enable = true;
just.enable = true;
nixfmt.enable = true;
taplo.enable = true;
rustfmt = {
enable = true;
package = pkgs.rust-bin.nightly.latest.rustfmt;
};
};
};
checks = {
komorebi-recorder-clippy = build.craneLib.cargoClippy (
build.individualCrateArgs
// {
cargoClippyExtraArgs = "--all-targets -- -D warnings";
}
);
komorebi-recorder-fmt = build.craneLib.cargoFmt {
inherit (build) src;
pname = "komorebi-recorder";
};
komorebi-recorder-toml-fmt = build.craneLib.taploFmt {
src = pkgs.lib.sources.sourceFilesBySuffices build.src [ ".toml" ];
pname = "komorebi-recorder";
};
komorebi-recorder-deny = build.craneLib.cargoDeny {
inherit (build) src;
pname = "komorebi-recorder";
};
komorebi-recorder-nextest = build.craneLib.cargoNextest build.individualCrateArgs;
# `--all-targets` so that every line of `cfg(windows)` code, tests included, is type-checked
# without a Windows machine
komorebi-recorder-windows-clippy = windowsBuild.craneLib.cargoClippy (
windowsBuild.individualCrateArgs
// {
cargoClippyExtraArgs = "--all-targets -- -D warnings";
}
);
};
packages = {
inherit (build) komorebi-recorder;
inherit windowsSdk;
komorebi-recorder-windows = windowsBuild.komorebi-recorder;
default = build.komorebi-recorder;
};
apps = {
komorebi-recorder = {
type = "app";
program = "${build.komorebi-recorder}/bin/komorebi-recorder";
};
default = {
type = "app";
program = "${build.komorebi-recorder}/bin/komorebi-recorder";
};
};
devShells.default = pkgs.mkShell {
name = "komorebi-recorder";
RUST_BACKTRACE = "full";
inherit (sourceEnv)
KOMOREBI_MAC_SRC
KOMOREBI_MAC_COMMIT
KOMOREBI_WINDOWS_SRC
KOMOREBI_WINDOWS_COMMIT
;
inputsFrom = [ build.fullBuild ];
packages = [
rustToolchainWithNightlyRustfmt
pkgs.cargo-deny
pkgs.cargo-nextest
# `komorebi-recorder frames` and `selftest` extract frames with ffmpeg; it is never
# used for capture and is not bundled into the packaged binary
pkgs.ffmpeg-headless
pkgs.jq
pkgs.just
pkgs.ripgrep
];
};
# For working on the Windows side from a Mac: `nix develop .#windows`, then plain
# `cargo clippy --all-targets` (the target is already `x86_64-pc-windows-msvc`).
devShells.windows = pkgs.mkShell {
name = "komorebi-recorder-windows";
RUST_BACKTRACE = "full";
inherit (windowsBuild.msvcEnv)
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER
CC_x86_64_pc_windows_msvc
CXX_x86_64_pc_windows_msvc
AR_x86_64_pc_windows_msvc
CFLAGS_x86_64_pc_windows_msvc
CXXFLAGS_x86_64_pc_windows_msvc
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS
CARGO_BUILD_TARGET
;
inherit (sourceEnv)
KOMOREBI_MAC_SRC
KOMOREBI_MAC_COMMIT
KOMOREBI_WINDOWS_SRC
KOMOREBI_WINDOWS_COMMIT
;
packages = [
(rustToolchain.override { targets = [ "x86_64-pc-windows-msvc" ]; })
pkgs.llvmPackages.clang-unwrapped # provides clang-cl
pkgs.llvmPackages.lld # provides lld-link
pkgs.llvmPackages.llvm # provides llvm-lib
pkgs.cargo-deny
pkgs.just
];
};
pre-commit = {
check.enable = true;
settings.hooks.treefmt = {
enable = true;
package = config.treefmt.build.wrapper;
pass_filenames = false;
};
};
};
flake = {
overlays.default =
final: _:
let
pkgs = mkPkgs final.system;
build = mkPackages { inherit pkgs; };
in
{
inherit (build) komorebi-recorder;
};
};
};
}
+9 -2
View File
@@ -1,7 +1,7 @@
export RUST_BACKTRACE := "full"
# everything CI checks, in order
check: fmt-check clippy test doc deny
# everything CI checks, in order; run it inside `nix develop` (direnv does)
check: fmt-check clippy check-windows test doc deny
# nightly rustfmt, which the devShell provides, and the macros this project does not allow
fmt-check:
@@ -20,6 +20,9 @@ fix:
clippy:
cargo clippy --workspace --all-targets --locked -- -D warnings
# type-check every `cfg(windows)` line, tests included, from a Mac (the Windows devShell targets x86_64-pc-windows-msvc)
check-windows:
nix develop .#windows --command cargo clippy --workspace --all-targets --locked -- -D warnings
# lib, integration and doc tests; the ones that read komorebi's source need KOMOREBI_MAC_SRC, which `nix develop` sets
test:
@@ -28,6 +31,7 @@ test:
# rustdoc with its warnings as errors, for both hosts: links in `cfg(windows)` docs are only checked on Windows
doc:
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked
nix develop .#windows --command env RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked
deny:
cargo deny check
@@ -48,6 +52,9 @@ run target:
record *args:
cargo run --bin komorebi-recorder --locked -- {{ args }}
# cross-compile komorebi-recorder.exe and komorebi-recorder-gui.exe into ./result-windows
build-windows:
nix build .#komorebi-recorder-windows --out-link result-windows
deps:
cargo update
+80
View File
@@ -0,0 +1,80 @@
# Building for Windows (x86_64-pc-windows-msvc) from any host.
#
# The MSVC CRT and the Windows SDK come from xwin, the compiler and linker from LLVM, so nothing
# here needs a Windows machine. Copied from komorebi for Windows' own flake, with the same pinned
# versions.
{
windowsSdkVersion = "10.0.26100";
windowsCrtVersion = "14.44.17.14";
mkWindowsSdk =
{
pkgs,
windowsSdkVersion,
windowsCrtVersion,
}:
pkgs.stdenvNoCC.mkDerivation {
name = "windows-sdk-${windowsSdkVersion}-crt-${windowsCrtVersion}";
nativeBuildInputs = [ pkgs.xwin ];
outputHashAlgo = "sha256";
outputHashMode = "recursive";
# NOT the hash komorebi for Windows' own flake pins (sha256-6cLS5q1BDRpLPScfmmKpTTEHUzsgKTKD1+mKvGX9Deo=):
# that did not reproduce on aarch64-darwin on 2026-10-27 with xwin 0.7.0, which produced this one.
# Whether Microsoft re-published that SDK or the splat differs by host is not known.
outputHash = "sha256-YjmEfoxtcmQXvUc+WtaElnyP/4osNvtSI7X6MDaNTjg=";
buildCommand = ''
export HOME=$(mktemp -d)
xwin --accept-license \
--sdk-version ${windowsSdkVersion} \
--crt-version ${windowsCrtVersion} \
splat --output $out
'';
};
mkMsvcEnv =
{ pkgs, windowsSdk }:
let
clangVersion = pkgs.lib.versions.major pkgs.llvmPackages.clang.version;
includes = [
# IMPORTANT: libclang include path MUST come first to avoid header conflicts
"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include"
"/imsvc${windowsSdk}/crt/include"
"/imsvc${windowsSdk}/sdk/include/ucrt"
"/imsvc${windowsSdk}/sdk/include/um"
"/imsvc${windowsSdk}/sdk/include/shared"
];
cflags = builtins.concatStringsSep " " (
[
"--target=x86_64-pc-windows-msvc"
"-Wno-unused-command-line-argument"
"-fuse-ld=lld-link"
]
++ includes
);
in
{
# linker for the windows target
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER = "lld-link";
# c/c++ compiler
CC_x86_64_pc_windows_msvc = "clang-cl";
CXX_x86_64_pc_windows_msvc = "clang-cl";
AR_x86_64_pc_windows_msvc = "llvm-lib";
CFLAGS_x86_64_pc_windows_msvc = cflags;
CXXFLAGS_x86_64_pc_windows_msvc = cflags;
# target-specific rust flags with linker flavor and library search paths
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS = builtins.concatStringsSep " " [
"-Clinker-flavor=lld-link"
"-Lnative=${windowsSdk}/crt/lib/x86_64"
"-Lnative=${windowsSdk}/sdk/lib/um/x86_64"
"-Lnative=${windowsSdk}/sdk/lib/ucrt/x86_64"
];
# cargo target
CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc";
};
}