diff --git a/flake.lock b/flake.lock index 383acebe..2d0b9219 100644 --- a/flake.lock +++ b/flake.lock @@ -17,98 +17,6 @@ } }, "crane": { - "locked": { - "lastModified": 1766774972, - "narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=", - "owner": "ipetkov", - "repo": "crane", - "rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82", - "type": "github" - }, - "original": { - "owner": "ipetkov", - "repo": "crane", - "type": "github" - } - }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1761588595, - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, - "locked": { - "lastModified": 1765835352, - "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "a34fae9c08a15ad73f295041fec82323541400a9", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "git-hooks-nix": { - "inputs": { - "flake-compat": "flake-compat", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1765911976, - "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=", - "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "git-hooks.nix", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "git-hooks-nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "kfde-crane": { "locked": { "lastModified": 1792350552, "narHash": "sha256-292lvpvxTGKryKxh3/3tcIVi85CwYGdl4oOi113Sob8=", @@ -120,11 +28,10 @@ "original": { "owner": "ipetkov", "repo": "crane", - "rev": "e675a36b7377bebe41d548388a2501cbb16d380d", "type": "github" } }, - "kfde-flake-compat": { + "flake-compat": { "flake": false, "locked": { "lastModified": 1767039857, @@ -137,15 +44,12 @@ "original": { "owner": "NixOS", "repo": "flake-compat", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" } }, - "kfde-flake-parts": { + "flake-parts": { "inputs": { - "nixpkgs-lib": [ - "kfde-nixpkgs-lib" - ] + "nixpkgs-lib": "nixpkgs-lib" }, "locked": { "lastModified": 1790898470, @@ -158,17 +62,14 @@ "original": { "owner": "hercules-ci", "repo": "flake-parts", - "rev": "024633cd702b10285db5cb19b40ad48d2399ba60", "type": "github" } }, - "kfde-git-hooks-nix": { + "git-hooks-nix": { "inputs": { - "flake-compat": [ - "kfde-flake-compat" - ], + "flake-compat": "flake-compat", "nixpkgs": [ - "kfde-nixpkgs" + "nixpkgs" ] }, "locked": { @@ -182,11 +83,10 @@ "original": { "owner": "cachix", "repo": "git-hooks.nix", - "rev": "1ba1dbfe28738c9a71c266be6efa5b89e75491a9", "type": "github" } }, - "kfde-nixpkgs": { + "nixpkgs": { "locked": { "lastModified": 1793136825, "narHash": "sha256-2pkE69JpFRInaARSczZytcfPw83IL82E2KdhbWGwdMs=", @@ -197,12 +97,12 @@ }, "original": { "owner": "NixOS", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", - "rev": "f05535a0efcb2134c478d1e88434fadf486d42b5", "type": "github" } }, - "kfde-nixpkgs-lib": { + "nixpkgs-lib": { "locked": { "lastModified": 1790477367, "narHash": "sha256-oujbxmVKDhci2E4EM3KyAbRUgp5/IsCoiA/LfEF7OOk=", @@ -214,155 +114,10 @@ "original": { "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "f7cd230690d9fc982d06129f9189b101fddcfa3b", "type": "github" } }, - "kfde-rust-overlay": { - "inputs": { - "nixpkgs": [ - "kfde-nixpkgs" - ] - }, - "locked": { - "lastModified": 1793174968, - "narHash": "sha256-u5nUf4cewaT36iIzG1s/VECktqUMiZaGF5BsgervsvQ=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "19ecdd08213836b36c4296a3a4f4af5a3d5308db", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "19ecdd08213836b36c4296a3a4f4af5a3d5308db", - "type": "github" - } - }, - "kfde-treefmt-nix": { - "inputs": { - "nixpkgs": [ - "kfde-nixpkgs" - ] - }, - "locked": { - "lastModified": 1791885023, - "narHash": "sha256-C3alQ2MEt0zVrUSho5NUDo3jmhbff72++bfwoqT9Omo=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "0d6d739a833ba9a9c93034862e4a7b061a062a1c", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "0d6d739a833ba9a9c93034862e4a7b061a062a1c", - "type": "github" - } - }, - "kfm-crane": { - "locked": { - "lastModified": 1767461147, - "narHash": "sha256-TH/xTeq/RI+DOzo+c+4F431eVuBpYVwQwBxzURe7kcI=", - "owner": "ipetkov", - "repo": "crane", - "rev": "7d59256814085fd9666a2ae3e774dc5ee216b630", - "type": "github" - }, - "original": { - "owner": "ipetkov", - "repo": "crane", - "rev": "7d59256814085fd9666a2ae3e774dc5ee216b630", - "type": "github" - } - }, - "kfm-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", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", - "type": "github" - } - }, - "kfm-flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "kfm-nixpkgs-lib" - ] - }, - "locked": { - "lastModified": 1765835352, - "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "a34fae9c08a15ad73f295041fec82323541400a9", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "a34fae9c08a15ad73f295041fec82323541400a9", - "type": "github" - } - }, - "kfm-git-hooks-nix": { - "inputs": { - "flake-compat": [ - "kfm-flake-compat" - ], - "gitignore": [ - "kfm-gitignore" - ], - "nixpkgs": [ - "kfm-nixpkgs" - ] - }, - "locked": { - "lastModified": 1767281941, - "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", - "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", - "type": "github" - } - }, - "kfm-gitignore": { - "inputs": { - "nixpkgs": [ - "kfm-nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - } - }, - "kfm-nixpkgs": { + "nixpkgs-x86_64-darwin": { "locked": { "lastModified": 1767364772, "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", @@ -378,119 +133,14 @@ "type": "github" } }, - "kfm-nixpkgs-lib": { - "locked": { - "lastModified": 1765674936, - "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", - "owner": "nix-community", - "repo": "nixpkgs.lib", - "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "nixpkgs.lib", - "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", - "type": "github" - } - }, - "kfm-rust-overlay": { - "inputs": { - "nixpkgs": [ - "kfm-nixpkgs" - ] - }, - "locked": { - "lastModified": 1793174968, - "narHash": "sha256-u5nUf4cewaT36iIzG1s/VECktqUMiZaGF5BsgervsvQ=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "19ecdd08213836b36c4296a3a4f4af5a3d5308db", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "19ecdd08213836b36c4296a3a4f4af5a3d5308db", - "type": "github" - } - }, - "kfm-treefmt-nix": { - "inputs": { - "nixpkgs": [ - "kfm-nixpkgs" - ] - }, - "locked": { - "lastModified": 1767468822, - "narHash": "sha256-MpffQxHxmjVKMiQd0Tg2IM/bSjjdQAM+NDcX6yxj7rE=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "d56486eb9493ad9c4777c65932618e9c2d0468fc", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "d56486eb9493ad9c4777c65932618e9c2d0468fc", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1766870016, - "narHash": "sha256-fHmxAesa6XNqnIkcS6+nIHuEmgd/iZSP/VXxweiEuQw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "5c2bc52fb9f8c264ed6c93bd20afa2ff5e763dce", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-lib": { - "locked": { - "lastModified": 1765674936, - "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", - "owner": "nix-community", - "repo": "nixpkgs.lib", - "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "nixpkgs.lib", - "type": "github" - } - }, "root": { "inputs": { "asc": "asc", "crane": "crane", "flake-parts": "flake-parts", "git-hooks-nix": "git-hooks-nix", - "kfde-crane": "kfde-crane", - "kfde-flake-compat": "kfde-flake-compat", - "kfde-flake-parts": "kfde-flake-parts", - "kfde-git-hooks-nix": "kfde-git-hooks-nix", - "kfde-nixpkgs": "kfde-nixpkgs", - "kfde-nixpkgs-lib": "kfde-nixpkgs-lib", - "kfde-rust-overlay": "kfde-rust-overlay", - "kfde-treefmt-nix": "kfde-treefmt-nix", - "kfm-crane": "kfm-crane", - "kfm-flake-compat": "kfm-flake-compat", - "kfm-flake-parts": "kfm-flake-parts", - "kfm-git-hooks-nix": "kfm-git-hooks-nix", - "kfm-gitignore": "kfm-gitignore", - "kfm-nixpkgs": "kfm-nixpkgs", - "kfm-nixpkgs-lib": "kfm-nixpkgs-lib", - "kfm-rust-overlay": "kfm-rust-overlay", - "kfm-treefmt-nix": "kfm-treefmt-nix", "nixpkgs": "nixpkgs", + "nixpkgs-x86_64-darwin": "nixpkgs-x86_64-darwin", "rust-overlay": "rust-overlay", "treefmt-nix": "treefmt-nix" } @@ -522,11 +172,11 @@ ] }, "locked": { - "lastModified": 1766000401, - "narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=", + "lastModified": 1791885023, + "narHash": "sha256-C3alQ2MEt0zVrUSho5NUDo3jmhbff72++bfwoqT9Omo=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd", + "rev": "0d6d739a833ba9a9c93034862e4a7b061a062a1c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 614f8bdf..2fdb27c6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,15 @@ { - description = "komorebi for Windows"; + description = "komorebi, a tiling window manager for Windows, KDE Plasma and macOS"; inputs = { + # One nixpkgs for the three platforms. The KDE VM tests run against the Plasma and KWin of this + # revision (nix/linux/supported-kwin.nix says which KWin that is), so it is the one an update has + # to keep green for the VM suites; see docs/dev/flake.md. nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + # Intel Macs only: nixpkgs 26.11 dropped x86_64-darwin (evaluating it throws), so the x86_64-darwin + # outputs use the last revision that still supported it, the one the macOS flake was pinned to. + # Nothing else uses it; drop it with x86_64-darwin when Intel Macs are no longer supported. + nixpkgs-x86_64-darwin.url = "github:NixOS/nixpkgs/16c7794d0a28b5a37904d55bcca36003b9109aaa"; flake-parts.url = "github:hercules-ci/flake-parts"; crane.url = "github:ipetkov/crane"; rust-overlay.url = "github:oxalica/rust-overlay"; @@ -11,718 +18,21 @@ treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; git-hooks-nix.url = "github:cachix/git-hooks.nix"; git-hooks-nix.inputs.nixpkgs.follows = "nixpkgs"; + # the application-specific configuration that komorebic embeds on Windows asc = { url = "github:LGUG2Z/komorebi-application-specific-configuration"; flake = false; }; - - # the other platforms' flakes (komorebi-for-kde/flake.nix, komorebi-for-mac/flake.nix) are not inputs: their - # flake.nix files are imported below and called with these inputs, which are pinned to exactly the - # revisions of their flake.lock (so nothing here pins the content of those directories) - # inputs of komorebi-for-kde, pinned to the revisions in komorebi-for-kde/flake.lock (keep them equal to it) - kfde-nixpkgs.url = "github:NixOS/nixpkgs/f05535a0efcb2134c478d1e88434fadf486d42b5"; - kfde-nixpkgs-lib.url = "github:nix-community/nixpkgs.lib/f7cd230690d9fc982d06129f9189b101fddcfa3b"; - kfde-flake-compat = { url = "github:NixOS/flake-compat/5edf11c44bc78a0d334f6334cdaf7d60d732daab"; flake = false; }; - kfde-flake-parts.url = "github:hercules-ci/flake-parts/024633cd702b10285db5cb19b40ad48d2399ba60"; - kfde-flake-parts.inputs.nixpkgs-lib.follows = "kfde-nixpkgs-lib"; - kfde-crane.url = "github:ipetkov/crane/e675a36b7377bebe41d548388a2501cbb16d380d"; - kfde-rust-overlay.url = "github:oxalica/rust-overlay/19ecdd08213836b36c4296a3a4f4af5a3d5308db"; - kfde-rust-overlay.inputs.nixpkgs.follows = "kfde-nixpkgs"; - kfde-treefmt-nix.url = "github:numtide/treefmt-nix/0d6d739a833ba9a9c93034862e4a7b061a062a1c"; - kfde-treefmt-nix.inputs.nixpkgs.follows = "kfde-nixpkgs"; - kfde-git-hooks-nix.url = "github:cachix/git-hooks.nix/1ba1dbfe28738c9a71c266be6efa5b89e75491a9"; - kfde-git-hooks-nix.inputs.nixpkgs.follows = "kfde-nixpkgs"; - kfde-git-hooks-nix.inputs.flake-compat.follows = "kfde-flake-compat"; - - # inputs of komorebi-for-mac, pinned to the revisions in komorebi-for-mac/flake.lock (keep them equal to it) - kfm-nixpkgs.url = "github:NixOS/nixpkgs/16c7794d0a28b5a37904d55bcca36003b9109aaa"; - kfm-nixpkgs-lib.url = "github:nix-community/nixpkgs.lib/2075416fcb47225d9b68ac469a5c4801a9c4dd85"; - kfm-flake-compat = { url = "github:NixOS/flake-compat/5edf11c44bc78a0d334f6334cdaf7d60d732daab"; flake = false; }; - kfm-flake-parts.url = "github:hercules-ci/flake-parts/a34fae9c08a15ad73f295041fec82323541400a9"; - kfm-flake-parts.inputs.nixpkgs-lib.follows = "kfm-nixpkgs-lib"; - kfm-crane.url = "github:ipetkov/crane/7d59256814085fd9666a2ae3e774dc5ee216b630"; - kfm-rust-overlay.url = "github:oxalica/rust-overlay/19ecdd08213836b36c4296a3a4f4af5a3d5308db"; - kfm-rust-overlay.inputs.nixpkgs.follows = "kfm-nixpkgs"; - kfm-treefmt-nix.url = "github:numtide/treefmt-nix/d56486eb9493ad9c4777c65932618e9c2d0468fc"; - kfm-treefmt-nix.inputs.nixpkgs.follows = "kfm-nixpkgs"; - kfm-git-hooks-nix.url = "github:cachix/git-hooks.nix/f0927703b7b1c8d97511c4116eb9b4ec6645a0fa"; - kfm-git-hooks-nix.inputs.nixpkgs.follows = "kfm-nixpkgs"; - kfm-git-hooks-nix.inputs.flake-compat.follows = "kfm-flake-compat"; - kfm-gitignore = { url = "github:hercules-ci/gitignore.nix/637db329424fd7e46cf4185293b9cc8c88c95394"; inputs.nixpkgs.follows = "kfm-nixpkgs"; }; - kfm-git-hooks-nix.inputs.gitignore.follows = "kfm-gitignore"; }; outputs = - inputs@{ - self, - nixpkgs, - flake-parts, - crane, - rust-overlay, - ... - }: - let - windowsSdkVersion = "10.0.26100"; - windowsCrtVersion = "14.44.17.14"; - - mkWindowsSdk = - pkgs: - pkgs.stdenvNoCC.mkDerivation { - name = "windows-sdk-${windowsSdkVersion}-crt-${windowsCrtVersion}"; - - nativeBuildInputs = [ - pkgs.xwin - pkgs.cacert - ]; - - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; - # hash of `xwin splat --disable-symlinks`: without the flag xwin makes - # case-alias symlinks on a case-sensitive filesystem and none on a - # case-insensitive one, so the hash depends on the host - outputHash = "sha256-YjmEfoxtcmQXvUc+WtaElnyP/4osNvtSI7X6MDaNTjg="; - - buildCommand = '' - export HOME=$(mktemp -d) - # keep the cache on the same filesystem as $out, as xwin moves - # files from the cache into the output and rename(2) fails with - # EXDEV when the build dir and the store are separate mounts - mkdir -p $out - xwin --accept-license \ - --cache-dir $out/.xwin-cache \ - --sdk-version ${windowsSdkVersion} \ - --crt-version ${windowsCrtVersion} \ - splat --disable-symlinks --output $out - rm -rf $out/.xwin-cache - ''; - }; - - # the linker asks for `kernel32.lib` of a file that is `Kernel32.lib`, so - # on a case-sensitive host add a lowercase alias for every name with a - # capital in it, and the spellings the headers include each other by (the - # SDK itself is made without symlinks) - caseFolded = - { pkgs, windowsSdk }: - if pkgs.stdenv.hostPlatform.isDarwin then - windowsSdk - else - pkgs.runCommandLocal "${windowsSdk.name}-case-folded" { } '' - cp -rs ${windowsSdk} $out - chmod -R u+w $out - cd $out - find . -name '*[A-Z]*' | while read -r path; do - directory=$(dirname "$path") - name=$(basename "$path") - lower=$(echo "$name" | tr 'A-Z' 'a-z') - if [ ! -e "$directory/$lower" ] && [ ! -L "$directory/$lower" ]; then - ln -s "$name" "$directory/$lower" - fi - done - - # headers include each other with spellings that differ from the - # file name (`DriverSpecs.h` for `driverspecs.h`), which xwin - # covers with symlinks unless told not to - grep -Rhoi -E '^[[:space:]]*#[[:space:]]*include[[:space:]]*[<"][^>"]+[>"]' \ - crt/include sdk/include 2>/dev/null \ - | sed -E 's/.*[<"]([^>"]+)[>"]/\1/; s#\\#/#g; s#.*/##' \ - | sort -u > $TMPDIR/spellings - find crt/include sdk/include \( -type f -o -type l \) \ - | awk -v spellings=$TMPDIR/spellings ' - BEGIN { - while ((getline line < spellings) > 0) - known[tolower(line)] = known[tolower(line)] "\n" line - } - { - n = split($0, parts, "/"); base = parts[n] - dir = substr($0, 1, length($0) - length(base)) - m = split(known[tolower(base)], names, "\n") - for (i = 1; i <= m; i++) - if (names[i] != "" && names[i] != base) - print dir "\t" base "\t" names[i] - }' \ - | while IFS=$'\t' read -r directory name spelling; do - if [ ! -e "$directory$spelling" ] && [ ! -L "$directory$spelling" ]; then - ln -s "$name" "$directory$spelling" - fi - done - ''; - - mkMsvcEnv = - { pkgs, windowsSdk }: - let - sdk = caseFolded { inherit pkgs windowsSdk; }; - clangVersion = pkgs.lib.versions.major pkgs.llvmPackages.clang.version; - 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"; - - # IMPORTANT: libclang include path MUST come first to avoid header conflicts - CFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [ - "--target=x86_64-pc-windows-msvc" - "-Wno-unused-command-line-argument" - "-fuse-ld=lld-link" - "/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include" - "/imsvc${sdk}/crt/include" - "/imsvc${sdk}/sdk/include/ucrt" - "/imsvc${sdk}/sdk/include/um" - "/imsvc${sdk}/sdk/include/shared" - ]; - - CXXFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [ - "--target=x86_64-pc-windows-msvc" - "-Wno-unused-command-line-argument" - "-fuse-ld=lld-link" - "/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include" - "/imsvc${sdk}/crt/include" - "/imsvc${sdk}/sdk/include/ucrt" - "/imsvc${sdk}/sdk/include/um" - "/imsvc${sdk}/sdk/include/shared" - ]; - - # 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=${sdk}/crt/lib/x86_64" - "-Lnative=${sdk}/sdk/lib/um/x86_64" - "-Lnative=${sdk}/sdk/lib/ucrt/x86_64" - ]; - - # cargo target - CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc"; - }; - - mkKomorebiPackages = - { - pkgs, - windowsSdk, - # the commit stamped into the binaries; a build for the e2e guest - # gives a constant one, so that a new commit does not rebuild it - commit ? self.rev or (pkgs.lib.removeSuffix "-dirty" (self.dirtyRev or "unknown")), - }: - let - # toolchain with windows msvc target - toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { - targets = [ "x86_64-pc-windows-msvc" ]; - }; - craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; - version = "0.1.0"; - - msvcEnv = mkMsvcEnv { inherit pkgs windowsSdk; }; - - # the files of the Windows crates; the KDE and macOS crates are in the same Cargo workspace and - # are added as stubs (nix/workspace-src.nix), so that an edit of one changes nothing here - real = pkgs.lib.cleanSourceWith { - src = ./.; - filter = - path: type: - # the other platforms' crates are added as stubs below - !(pkgs.lib.hasInfix "/komorebi-for-kde" path || pkgs.lib.hasInfix "/komorebi-for-mac" path) - # the Linux and macOS files of komorebic are not read when building for Windows - && !(builtins.match ".*/komorebi(c/(src/(linux|macos)|tests/(linux|macos)_)|-client/(src/(linux|macos)\\.rs|tests/(linux|macos)_)|-bar/src/macos|-daemon/tests/(linux|macos)_)[^/]*.*" path != null) - && ( - (craneLib.filterCargoSources path type) - || (pkgs.lib.hasInfix "/docs/" path) - || (builtins.match ".*/docs/.*" path != null) - || (builtins.match ".*/komorebic/fixtures/.*" path != null) - ); - }; - - src = import ./nix/workspace-src.nix { - inherit pkgs real; - repoRoot = ./.; - isReal = member: !(pkgs.lib.hasPrefix "komorebi-for-" member); - }; - - lockFile = builtins.path { - path = ./Cargo.lock; - name = "Cargo.lock"; - }; - tomlFile = builtins.path { - path = ./Cargo.toml; - name = "Cargo.toml"; - }; - - commonArgs = { - inherit src version; - # the lock and the root manifest as files of their own: a path inside the filtered source would put the - # whole filtered source into the context of every derivation, and then an edit of any file - # would change the dependencies - cargoLock = lockFile; - cargoToml = tomlFile; - cargoVendorDir = craneLib.vendorCargoDeps { cargoLock = lockFile; }; - strictDeps = true; - # applications.json is committed now; this copies the pinned asc input over it, so that the - # embedded file is the pinned one even when the committed copy is older - preBuild = "mkdir -p komorebic && cp ${inputs.asc}/applications.json komorebic/applications.json"; - # a constant, so that the dependencies and every check are the same - # derivations for every commit; only fullBuild stamps the real one - COMMIT_HASH = "nix-check"; - - # build inputs for cross-compilation - nativeBuildInputs = [ - pkgs.llvmPackages.clang-unwrapped - pkgs.llvmPackages.lld - pkgs.llvmPackages.llvm - # komorebic build script uses reqwest, which runs on the build host - pkgs.pkg-config - pkgs.openssl - pkgs.openssl.dev - ]; - - # point the host-side openssl-sys build at the nix openssl - OPENSSL_NO_VENDOR = "1"; - OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib"; - OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include"; - - # cross-compilation environment - inherit (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 - ; - }; - - cargoArtifacts = craneLib.buildDepsOnly commonArgs; - - individualCrateArgs = commonArgs // { - inherit cargoArtifacts; - doCheck = false; - doDoc = false; - }; - - fullBuild = craneLib.buildPackage ( - individualCrateArgs - // { - pname = "komorebi-workspace"; - COMMIT_HASH = commit; - } - ); - - extractBinary = - binaryName: - pkgs.runCommand "komorebi-${binaryName}" - { - meta = fullBuild.meta // { }; - } - '' - mkdir -p $out/bin - cp ${fullBuild}/bin/${binaryName}.exe $out/bin/ - ''; - in - { - inherit - craneLib - src - real - individualCrateArgs - fullBuild - msvcEnv - ; - komorebi = extractBinary "komorebi"; - komorebic = extractBinary "komorebic"; - komorebic-no-console = extractBinary "komorebic-no-console"; - komorebi-bar = extractBinary "komorebi-bar"; - komorebi-gui = extractBinary "komorebi-gui"; - komorebi-shortcuts = extractBinary "komorebi-shortcuts"; - komorebi-testwin = extractBinary "komorebi-testwin"; - komorebi-e2e-agent = extractBinary "komorebi-e2e-agent"; - }; - - mkPkgs = - system: - import nixpkgs { - inherit system; - overlays = [ (import rust-overlay) ]; - }; - in + inputs@{ flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } { - systems = [ - "aarch64-darwin" - "x86_64-linux" - "aarch64-linux" - ]; - imports = [ - inputs.treefmt-nix.flakeModule - inputs.git-hooks-nix.flakeModule - # komorebi for KDE Plasma (kfde) and komorebi for macOS (kfm), re-exported with a prefix. - # Their flakes keep their own inputs and lock files (inputs..inputs), but their - # outputs are made here, from the whole repository as the source: the Rust workspaces of - # both depend on the crates of this tree by path, which a flake of their own directory - # cannot see (their `lib.mkOutputs`). The VM tests of kfde are not re-exported as checks, - # so that `nix flake check` of this flake does not boot them; they are reachable as - # legacyPackages..kfde-vm. - ( - { lib, ... }: - let - # the nested flake.nix files are plain attribute sets: import them and call their - # `lib.mkOutputs` with the inputs declared above (same revisions as their own locks). - # `self` of a nested flake: its own outputs, with the revision and the path of this one - nestedOutputs = - name: dir: - let - nestedInputs = lib.genAttrs [ - "nixpkgs" - "flake-parts" - "crane" - "rust-overlay" - "treefmt-nix" - "git-hooks-nix" - ] (n: inputs."${name}-${n}"); - mkOutputs = - ((import (self.outPath + "/${dir}/flake.nix")).outputs (nestedInputs // { inherit self; })).lib.mkOutputs; - nestedSelf = - outputs - // lib.filterAttrs (n: _: n == "rev" || n == "dirtyRev") self - // { - outPath = self.outPath + "/${dir}"; - inputs = nestedInputs; - }; - outputs = mkOutputs { - self = nestedSelf; - inputs = nestedInputs // { - self = nestedSelf; - }; - repoRoot = self; - platformDir = dir; - }; - in - outputs; - kfde = nestedOutputs "kfde" "komorebi-for-kde"; - kfm = nestedOutputs "kfm" "komorebi-for-mac"; - in - { - flake = { - nixosModules.komorebi-for-kde = kfde.nixosModules.default; - overlays.komorebi-for-kde = kfde.overlays.default; - darwinModules.komorebi-for-mac = kfm.darwinModules.default; - }; - - perSystem = - { system, ... }: - let - prefixed = prefix: lib.mapAttrs' (name: lib.nameValuePair "${prefix}-${name}"); - withoutVmTests = lib.filterAttrs (name: _: !(lib.hasPrefix "vm-" name)); - in - { - packages = - prefixed "kfde" (kfde.packages.${system} or { }) // prefixed "kfm" (kfm.packages.${system} or { }); - checks = - prefixed "kfde" (withoutVmTests (kfde.checks.${system} or { })) - // prefixed "kfm" (kfm.checks.${system} or { }); - legacyPackages.kfde-vm = (kfde.legacyPackages.${system} or { }).vm or { }; - }; - } - ) + ./nix/common.nix + ./nix/windows/flake-module.nix + ./nix/linux/flake-module.nix + ./nix/macos/flake-module.nix ]; - - perSystem = - { config, system, ... }: - let - pkgs = mkPkgs system; - windowsSdk = mkWindowsSdk pkgs; - build = mkKomorebiPackages { inherit pkgs windowsSdk; }; - # the binaries the e2e guest runs: the same build, but with a - # constant commit stamp, so a new commit (or a dirty tree) that does - # not change a source file reuses it. The commit under test is - # KOMOREBI_E2E_COMMIT, not what the binary says. - e2eBuild = mkKomorebiPackages { - inherit pkgs windowsSdk; - commit = "e2e"; - }; - - # OVMF without SMM and with the TPM support Windows 11 Setup asks for - ovmfWithTpm = (pkgs.OVMF.override { tpmSupport = true; }).fd; - e2eWindows = pkgs.writeShellApplication { - name = "e2e-windows"; - runtimeInputs = [ - pkgs.qemu - pkgs.swtpm - (pkgs.python3.withPackages (p: [ p.pillow ])) - ]; - text = '' - : "''${E2E_GOLDEN:?set E2E_GOLDEN to the golden image directory}" - out=''${1:?usage: e2e-windows OUT SCENARIO...} - shift - # the binaries under test: this checkout's, or those of another - # commit, made by e2e/platforms/qemu_windows/build-ref.sh - if [ -n "''${KOMOREBI_E2E_BINS:-}" ]; then - bins=$KOMOREBI_E2E_BINS - if [ -f "$bins/commit" ]; then - KOMOREBI_E2E_COMMIT=$(cat "$bins/commit") - fi - else - bins=$(mktemp -d) - trap 'rm -rf "$bins"' EXIT - cp ${e2eBuild.komorebi}/bin/komorebi.exe "$bins" - cp ${e2eBuild.komorebic}/bin/komorebic.exe "$bins" - cp ${e2eBuild.komorebi-testwin}/bin/komorebi-testwin.exe "$bins" - cp ${e2eBuild.komorebi-bar}/bin/komorebi-bar.exe "$bins" - fi - export OVMF_FD=${ovmfWithTpm} - export KOMOREBI_E2E_BAR_SCHEMA=${./schema.bar.json} - export KOMOREBI_E2E_COMMIT=''${KOMOREBI_E2E_COMMIT:-${self.rev or "dirty"}} - export PYTHONPATH=${./e2e/lib}:${./e2e} - exec python -m platforms.qemu_windows.run \ - "$E2E_GOLDEN" "$bins" "$out" "$@" - ''; - }; - - # toolchain with windows target and nightly rustfmt - rustToolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { - targets = [ "x86_64-pc-windows-msvc" ]; - }; - nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt; - rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin { - name = "rust-toolchain-with-nightly-rustfmt"; - paths = [ - nightlyRustfmt - rustToolchain - ]; - }; - nightlyToolchain = pkgs.rust-bin.nightly.latest.default.override { - targets = [ "x86_64-pc-windows-msvc" ]; - }; - 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 = pkgs.rust-bin.nightly.latest.rustfmt; - }; - }; - }; - - checks = { - komorebi-workspace-clippy = build.craneLib.cargoClippy ( - build.individualCrateArgs - // { - cargoClippyExtraArgs = "--all-targets -- -D warnings"; - } - ); - - # the nightly rustfmt over the Windows files, as tools/format-all --tree windows does locally. Not - # `cargo fmt`: it ran the stable rustfmt (which ignores the nightly options of rustfmt.toml) over - # the stub sources of the other platforms and followed `mod linux;` into files the Windows source - # does not have. - komorebi-workspace-fmt = - pkgs.runCommand "komorebi-workspace-fmt" - { - nativeBuildInputs = [ nightlyRustfmt ]; - } - '' - RUSTFMT=rustfmt bash ${./tools/format-all} --check --tree windows --root ${build.real} - touch $out - ''; - - komorebi-workspace-toml-fmt = build.craneLib.taploFmt { - src = pkgs.lib.sources.sourceFilesBySuffices build.real [ ".toml" ]; - }; - - # cargo deny sees every member of the workspace: leave out the KDE and macOS packages, which - # have their own policy (komorebi-for-*/deny.toml) in their own checks - komorebi-workspace-deny = build.craneLib.cargoDeny { - inherit (build) src; - pname = "komorebi-workspace"; - version = "0.1.0"; - cargoToml = "${build.real}/Cargo.toml"; - cargoLock = "${build.real}/Cargo.lock"; - cargoDenyExtraArgs = - "--workspace " - + pkgs.lib.concatMapStringsSep " " (p: "--exclude ${p}") [ - "komorebi-linux" - "komorebic-linux" - "komorebi-client-linux" - "komorebi-hotkeys" - "version-linux" - "komorebi-macos" - "komorebic-macos" - "komorebi-client-macos" - "komorebi-bar-macos" - "version-macos" - ]; - }; - - komorebi-workspace-nextest = build.craneLib.cargoNextest build.individualCrateArgs; - - # the Python of the Windows e2e guest that needs no guest: the - # vendored core's tests and those of the runner - e2e-tests = - pkgs.runCommand "e2e-tests" - { - nativeBuildInputs = [ - pkgs.git - (pkgs.python3.withPackages (p: [ - p.pillow - p.pytest - ])) - ]; - } - '' - cp -r ${./e2e} e2e - chmod -R u+w e2e - cd e2e - export HOME=$TMPDIR PYTHONPATH=$PWD/lib:$PWD - python -m pytest -q -p no:cacheprovider lib/tests platforms/qemu_windows/tests - touch $out - ''; - }; - - packages = { - inherit (build) - komorebi - komorebic - komorebic-no-console - komorebi-bar - komorebi-gui - komorebi-shortcuts - komorebi-testwin - komorebi-e2e-agent - ; - inherit windowsSdk; - # the test window of the Windows e2e guest, built with the constant - # stamp: a new commit does not rebuild what it depends on - komorebi-testwin-e2e = e2eBuild.komorebi-testwin; - komorebi-full = build.fullBuild; - default = build.fullBuild; - }; - - apps = { - komorebi = { - type = "app"; - program = "${build.komorebi}/bin/komorebi.exe"; - }; - komorebic = { - type = "app"; - program = "${build.komorebic}/bin/komorebic.exe"; - }; - komorebic-no-console = { - type = "app"; - program = "${build.komorebic-no-console}/bin/komorebic-no-console.exe"; - }; - komorebi-bar = { - type = "app"; - program = "${build.komorebi-bar}/bin/komorebi-bar.exe"; - }; - komorebi-gui = { - type = "app"; - program = "${build.komorebi-gui}/bin/komorebi-gui.exe"; - }; - komorebi-shortcuts = { - type = "app"; - program = "${build.komorebi-shortcuts}/bin/komorebi-shortcuts.exe"; - }; - default = { - type = "app"; - program = "${build.fullBuild}/bin/komorebi.exe"; - }; - } - // pkgs.lib.optionalAttrs (system == "x86_64-linux") { - # the neutral scenarios against a throwaway Windows guest: - # E2E_GOLDEN= nix run .#e2e-windows -- \ - # OUT SCENARIO... - # KOMOREBI_E2E_BINS= tests another commit - # the golden image is built once with - # e2e/platforms/qemu_windows/build-golden.sh - e2e-windows = { - type = "app"; - program = pkgs.lib.getExe e2eWindows; - }; - }; - - devShells.default = pkgs.mkShell { - name = "komorebi"; - - RUST_BACKTRACE = "full"; - - # komorebic's build script runs on the host and wants OpenSSL, as in - # the nix build - OPENSSL_NO_VENDOR = "1"; - OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib"; - OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include"; - - # the Windows e2e guest (e2e/platforms/qemu_windows): the build - # scripts and the runner find OVMF here, and the vendored core - # and the platform on PYTHONPATH - OVMF_FD = "${ovmfWithTpm}"; - shellHook = '' - export PYTHONPATH="$PWD/e2e/lib:$PWD/e2e''${PYTHONPATH:+:$PYTHONPATH}" - ''; - - # cross-compilation environment - inherit (build.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 - ; - - packages = [ - rustToolchainWithNightlyRustfmt - cargo-udeps - - # cross-compilation tooling - pkgs.llvmPackages.clang-unwrapped # provides clang-cl - pkgs.llvmPackages.lld # provides lld-link - pkgs.llvmPackages.llvm # provides llvm-lib - - # komorebic's build script (reqwest) runs on the build host - pkgs.pkg-config - pkgs.openssl - - pkgs.cargo-deny - pkgs.cargo-nextest - pkgs.cargo-outdated - pkgs.jq - pkgs.just - pkgs.prettier - - # the Windows e2e guest - pkgs.qemu - pkgs.swtpm - pkgs.xorriso - pkgs.socat - pkgs.git - (pkgs.python3.withPackages (p: [ - p.pillow - p.pytest # the tests of e2e/lib - ])) - ]; - }; - - pre-commit = { - check.enable = true; - settings.hooks.treefmt = { - enable = true; - package = config.treefmt.build.wrapper; - pass_filenames = false; - }; - }; - }; }; } diff --git a/nix/common.nix b/nix/common.nix new file mode 100644 index 00000000..a9067eb6 --- /dev/null +++ b/nix/common.nix @@ -0,0 +1,206 @@ +# What the three platforms share: the systems, nixpkgs with the rust overlay, the formatter, the +# pre-commit hook and the dev shell. +{ inputs, self, ... }: +let + lib' = import ./lib.nix { inherit inputs; }; +in +{ + imports = [ + # pkgs of every perSystem module (a module of its own: one that takes `pkgs` cannot define it) + { + perSystem = + { system, ... }: + { + _module.args.pkgs = lib'.mkPkgs system; + }; + } + inputs.treefmt-nix.flakeModule + inputs.git-hooks-nix.flakeModule + ]; + + # Linux and Windows (cross) builds on both Linux architectures, macOS on both Apple ones. + # x86_64-darwin uses an older nixpkgs (flake.nix, input nixpkgs-x86_64-darwin). + systems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + "x86_64-darwin" + ]; + + flake = { + # the packages of the platform of the system the overlay is applied to: the Linux ones (overlays.linux, + # nix/linux/flake-module.nix) or the macOS ones (overlays.macos) + overlays.default = + final: prev: + (if final.stdenv.hostPlatform.isDarwin then self.overlays.macos else self.overlays.linux) final + prev; + overlays.komorebi = self.overlays.default; + }; + + perSystem = + { + config, + pkgs, + system, + msvcEnv, + ovmfWithTpm, + ... + }: + let + crossToWindows = builtins.elem system lib'.windowsSystems; + # the toolchain of rust-toolchain.toml, with the Windows target on the systems that cross-build + rustToolchain = + let + stable = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml; + in + if crossToWindows then stable.override { targets = [ "x86_64-pc-windows-msvc" ]; } else stable; + nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin { + name = "rust-toolchain-with-nightly-rustfmt"; + paths = [ + nightlyRustfmt + rustToolchain + ]; + }; + nightlyToolchain = pkgs.rust-bin.nightly.latest.default.override { + targets = pkgs.lib.optional crossToWindows "x86_64-pc-windows-msvc"; + }; + # cargo-udeps needs a nightly compiler + cargo-udeps = pkgs.writeShellScriptBin "cargo-udeps" '' + export PATH="${nightlyToolchain}/bin:$PATH" + exec ${pkgs.cargo-udeps}/bin/cargo-udeps "$@" + ''; + + # the tools of every platform's development + tools = [ + rustToolchainWithNightlyRustfmt + cargo-udeps + + # cross-compilation tooling for Windows (xwin): clang-cl, lld-link, llvm-lib + pkgs.llvmPackages.clang-unwrapped + pkgs.llvmPackages.lld + pkgs.llvmPackages.llvm + + # komorebic's build script (reqwest) runs on the build host + pkgs.pkg-config + pkgs.openssl + + pkgs.cargo-deny + pkgs.cargo-nextest + pkgs.cargo-outdated + pkgs.check-jsonschema + pkgs.jq + pkgs.just + pkgs.prettier + pkgs.ripgrep + + # the tests of the sleep and lock signals of the KDE daemon start private buses (`kwin::power`) + pkgs.dbus + + # the KDE bridge's type check and tests (`just check-js`) + pkgs.nodejs + pkgs.typescript + + # the VM test library of the KDE suites + pkgs.ruff + pkgs.ty + + # the Windows e2e guest (e2e/platforms/qemu_windows) and the tests of e2e/lib + pkgs.qemu + pkgs.swtpm + pkgs.xorriso + pkgs.socat + pkgs.git + (pkgs.python3.withPackages (p: [ + p.pillow + p.pytest + ])) + ] + # publishes the schema site of the macOS tree + ++ pkgs.lib.optional pkgs.stdenv.hostPlatform.isDarwin pkgs.wrangler; + + mkDevShell = + { + name, + # the cross-compilation environment of the Windows build: CARGO_BUILD_TARGET makes every + # `cargo` command build for Windows, so a shell that builds the Linux or macOS packages + # natively leaves it out + cross, + }: + pkgs.mkShell ( + { + inherit name; + + RUST_BACKTRACE = "full"; + + # komorebic's build script runs on the host and wants OpenSSL, as in + # the nix build + OPENSSL_NO_VENDOR = "1"; + OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib"; + OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include"; + + packages = tools; + } + // pkgs.lib.optionalAttrs (crossToWindows) { + # the Windows e2e guest (e2e/platforms/qemu_windows): the build + # scripts and the runner find OVMF here, and the vendored core + # and the platform on PYTHONPATH + OVMF_FD = "${ovmfWithTpm}"; + shellHook = '' + export PYTHONPATH="$PWD/e2e/lib:$PWD/e2e''${PYTHONPATH:+:$PYTHONPATH}" + ''; + } + // pkgs.lib.optionalAttrs (cross && crossToWindows) { + # cross-compilation environment + inherit (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 + ; + } + ); + in + { + treefmt = { + projectRootFile = "flake.nix"; + programs = { + deadnix.enable = true; + just.enable = true; + nixfmt.enable = true; + taplo.enable = true; + rustfmt = { + enable = true; + package = nightlyRustfmt; + }; + }; + }; + + # `nix develop`: everything the three platforms need, and the Windows cross-compilation + # environment (cargo builds for x86_64-pc-windows-msvc by default). `nix develop .#native` is the + # same without it, for `cargo` commands on the Linux or macOS packages (-p komorebi-linux, ...). + devShells = { + default = mkDevShell { + name = "komorebi"; + cross = true; + }; + native = mkDevShell { + name = "komorebi-native"; + cross = false; + }; + }; + + pre-commit = { + check.enable = true; + settings.hooks.treefmt = { + enable = true; + package = config.treefmt.build.wrapper; + pass_filenames = false; + }; + }; + }; +} diff --git a/nix/lib.nix b/nix/lib.nix new file mode 100644 index 00000000..3cb46aa1 --- /dev/null +++ b/nix/lib.nix @@ -0,0 +1,65 @@ +# Helpers shared by the platform modules of the flake. +{ inputs }: +rec { + # the repository, as the source of the flake + repoRoot = ./..; + + # a file or directory of the repository as a store path of its own: `./tools/format-all` would name + # a place in the store path of the whole flake source, which changes with every edit of the + # repository and so would rebuild the derivation that reads it + repoFile = + rel: + builtins.path { + path = repoRoot + "/${rel}"; + name = builtins.baseNameOf rel; + }; + + # the version of the workspace: every package says `version.workspace = true` + version = + (builtins.fromTOML (builtins.readFile (repoRoot + "/Cargo.toml"))).workspace.package.version; + + # the systems that cross-build for Windows, build the Linux packages and build the macOS packages + windowsSystems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + ]; + linuxSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + # Apple silicon also gets the checks of the KDE crates (they run against a fake desktop) + linuxCheckSystems = linuxSystems ++ [ "aarch64-darwin" ]; + macosSystems = [ + "aarch64-darwin" + "x86_64-darwin" + ]; + + # The manifests (Cargo.toml, Cargo.lock, build scripts, .cargo/config.toml) of every member of the + # workspace, of all three platforms, and nothing else: crane makes the dummy source of the dependency + # derivations from it. A pure path (no derivation), so that crane reads it at evaluation without + # building anything, which `nix flake check --all-systems` needs on a host that cannot build for the + # other systems; and the same for every platform, so that the dependencies do not depend on the stubs. + manifestSource = + lib: + lib.fileset.toSource { + root = repoRoot; + fileset = lib.fileset.fileFilter ( + file: + builtins.elem file.name [ + "Cargo.toml" + "Cargo.lock" + "build.rs" + "config.toml" + ] + ) repoRoot; + }; + + # nixpkgs with the rust overlay, for a system (x86_64-darwin: see the input nixpkgs-x86_64-darwin) + mkPkgs = + system: + import (if system == "x86_64-darwin" then inputs.nixpkgs-x86_64-darwin else inputs.nixpkgs) { + inherit system; + overlays = [ (import inputs.rust-overlay) ]; + }; +} diff --git a/nix/linux/build.nix b/nix/linux/build.nix new file mode 100644 index 00000000..018edb0c --- /dev/null +++ b/nix/linux/build.nix @@ -0,0 +1,115 @@ +# The crane derivations of the Linux (KDE Plasma) packages, from the Linux members of the one Cargo +# workspace (nix/workspace-src.nix). +{ inputs, self }: +let + lib' = import ../lib.nix { inherit inputs; }; + inherit (lib') version; + inherit (inputs) crane; +in +{ + mkKomorebiPackages = + { pkgs }: + let + toolchain = pkgs.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml; + craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; + + inherit + (import ../workspace-src.nix { + inherit pkgs craneLib; + repoRoot = ../..; + platform = "linux"; + }) + real + src + stubbedPackages + ; + + lockFile = lib'.repoFile "Cargo.lock"; + tomlFile = lib'.repoFile "Cargo.toml"; + packages = "-p komorebi -p komorebi-linux -p komorebic -p komorebi-client -p komorebi-hotkeys"; + workspaceArgs = { + pname = "komorebi-workspace-linux"; + inherit version; + # the lock and the root manifest as files of their own: a path inside the filtered source + # would put all of it into the context of every derivation, and an edit of any file + # would then change the dependencies + cargoLock = lockFile; + cargoToml = tomlFile; + cargoVendorDir = craneLib.vendorCargoDeps { cargoLock = lockFile; }; + }; + + commonArgs = workspaceArgs // { + cargoExtraArgs = "--locked ${packages}"; + inherit src; + 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"; + }; + + # the dummy source is of the manifests alone (nix/lib.nix): the dependencies are the same + # derivations for every source file and every stub, and nothing is built at evaluation + cargoArtifacts = craneLib.buildDepsOnly ( + builtins.removeAttrs commonArgs [ "src" ] + // { + dummySrc = craneLib.mkDummySrc { + src = lib'.manifestSource pkgs.lib; + cargoLock = lockFile; + }; + } + ); + + individualCrateArgs = commonArgs // { + inherit cargoArtifacts; + doCheck = false; + doDoc = false; + }; + + fullBuild = craneLib.buildPackage ( + individualCrateArgs + // { + COMMIT_HASH = self.rev or (pkgs.lib.removeSuffix "-dirty" (self.dirtyRev or "unknown")); + } + ); + + # the same build with the constant commit of the checks: the VM tests install this one, so + # that a new commit does not build the daemon again + vmBuild = craneLib.buildPackage individualCrateArgs; + + extractBinaryOf = + package: binaryName: + pkgs.runCommand "komorebi-${binaryName}" { meta = package.meta // { }; } '' + mkdir -p $out/bin + cp ${package}/bin/${binaryName} $out/bin/ + ''; + extractBinary = extractBinaryOf fullBuild; + in + { + inherit + craneLib + src + real + workspaceArgs + stubbedPackages + packages + commonArgs + individualCrateArgs + fullBuild + vmBuild + ; + komorebi = extractBinary "komorebi"; + komorebic = extractBinary "komorebic"; + komorebiForVm = extractBinaryOf vmBuild "komorebi"; + komorebicForVm = extractBinaryOf vmBuild "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"; + } + ); + }; +} diff --git a/nix/linux/flake-module.nix b/nix/linux/flake-module.nix new file mode 100644 index 00000000..04e00a13 --- /dev/null +++ b/nix/linux/flake-module.nix @@ -0,0 +1,526 @@ +# komorebi for Linux (KDE Plasma): the native packages, the `linux-*` checks, the VM tests (`vm-*`), +# the NixOS module and the Plasma guests. The Rust packages are built on Linux; Apple silicon gets +# the checks and the VM guests' definitions, which run the same tests against the fake desktop. +{ + inputs, + self, + lib, + ... +}: +let + lib' = import ../lib.nix { inherit inputs; }; + inherit (inputs) nixpkgs; + linux = import ./build.nix { inherit inputs self; }; + inherit (lib') mkPkgs; +in +{ + perSystem = + { + config, + pkgs, + system, + ... + }: + let + build = linux.mkKomorebiPackages { inherit pkgs; }; + isLinux = pkgs.stdenv.hostPlatform.isLinux; + # the guests install komorebi through the module, as a NixOS user does + komorebiModule = self.nixosModules.forVmTests; + # a Mac runs the guests under HVF, which is slower than KVM. A Linux host is no faster + # than a Mac to the suites that time the daemon: on a 24-core WSL2 machine, under nested + # KVM and with nothing else running, `latency` measured a p95 of 129 ms against its + # 100 ms times `scale`, where x86-1 had passed it at 1 + vm = import ./tests/vm { + hostPkgs = pkgs; + inherit komorebiModule; + scale = 1.5; + }; + # the probe of a NixOS-WSL2 machine (docs/linux/dev/wsl2-probe.md); the checks use a constant + # revision, so that they are the same derivations for every commit + wsl2Probe = import ./dev/wsl2-probe.nix { + inherit pkgs; + rev = self.rev or self.dirtyRev or "unknown"; + }; + wsl2ProbeForChecks = import ./dev/wsl2-probe.nix { + inherit pkgs; + rev = "nix-check"; + }; + # `run-kde` starts the demo guest of the host's architecture (docs/linux/trying-it.md). The guest's + # runner is named in the program, not depended on: the command works out what is missing + # and builds it, with its progress shown, instead of `nix run` fetching 4 GB first. + runKde = + let + arch = if system == "x86_64-linux" then "x86_64" else "aarch64"; + guest = self.nixosConfigurations."run-kde-${arch}"; + runner = builtins.unsafeDiscardStringContext "${guest.config.system.build.vm}"; + in + pkgs.writeShellApplication { + name = "run-kde"; + runtimeInputs = with pkgs; [ + coreutils + e2fsprogs + gawk + git + gnugrep + gnused + mesa-demos + openssh + qemu-utils + virtiofsd + ]; + text = '' + : "''${RUN_KDE_GUEST:=run-kde-${arch}}" + : "''${RUN_KDE_RUNNER_BIN:=${runner}/bin/run-${guest.config.networking.hostName}-vm}" + '' + + builtins.readFile ../../scripts/linux/run-kde; + }; + # rustfmt.toml uses options only a nightly rustfmt knows + nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + checkSystem = builtins.elem system lib'.linuxCheckSystems; + + # PLACEHOLDER until the structure of the check sets is decided (agent flake-checks): the checks that + # evaluate a NixOS system or a VM (vm-*, the Plasma guests, the module option tests, run-kde) take + # minutes and many GB to evaluate, so `nix flake check --no-build --all-systems` cannot hold them. They + # are `legacyPackages..heavyChecks.` (same names as before), not `checks.`. + isHeavy = + name: + lib.hasPrefix "vm-" name + || builtins.elem name [ + "linux-nixos-module-eval" + "linux-plasma-guests-eval" + "linux-run-kde" + "linux-run-kde-doctor" + "linux-run-kde-dry-run" + ]; + split = + r: + if r ? allChecks then + removeAttrs r [ "allChecks" ] + // { + checks = lib.filterAttrs (n: _: !isHeavy n) r.allChecks; + legacyPackages = r.legacyPackages // { + heavyChecks = lib.filterAttrs (n: _: isHeavy n) r.allChecks; + }; + } + else + r; + in + split ( + lib.optionalAttrs checkSystem { + allChecks = { + # tools/format-all: the nightly rustfmt over the files of the Linux source, file by file. `cargo fmt` + # followed `mod windows;` of the shared entry files into files this source does not have. + linux-fmt = + pkgs.runCommand "komorebi-linux-fmt" + { + nativeBuildInputs = [ nightlyRustfmt ]; + } + '' + RUSTFMT=rustfmt bash ${lib'.repoFile "tools/format-all"} --check --root ${build.real} + touch $out + ''; + + linux-clippy = build.craneLib.cargoClippy ( + build.individualCrateArgs + // { + cargoClippyExtraArgs = "--all-targets -- -D warnings"; + } + ); + + linux-doc = build.craneLib.cargoDoc ( + build.individualCrateArgs + // { + cargoDocExtraArgs = "--no-deps"; + env.RUSTDOCFLAGS = "-D warnings"; + } + ); + + linux-toml-fmt = build.craneLib.taploFmt ( + build.workspaceArgs + // { + src = lib.sources.sourceFilesBySuffices build.real [ ".toml" ]; + } + ); + + # cargo deny sees every member of the workspace: leave out the other platforms' packages (stubs + # here); the policy is the one deny.toml of the repository + linux-deny = build.craneLib.cargoDeny ( + build.workspaceArgs + // { + inherit (build) src; + cargoDenyExtraArgs = + "--workspace " + lib.concatMapStringsSep " " (p: "--exclude ${p}") build.stubbedPackages; + cargoDenyChecks = "bans licenses sources"; + } + ); + + linux-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. + linux-bridge-js = + pkgs.runCommand "komorebi-bridge-js" + { + nativeBuildInputs = [ + pkgs.nodejs + pkgs.typescript + ]; + src = lib.fileset.toSource { + root = ../../komorebi-linux/src/kwin; + fileset = lib.fileset.fileFilter (file: !file.hasExt "rs") ../../komorebi-linux/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-linux` write them + linux-schema-fresh = + pkgs.runCommand "komorebi-schema-fresh" + { + nativeBuildInputs = [ + build.komorebicForChecks + pkgs.diffutils + ]; + } + '' + komorebic static-config-schema | diff -u ${lib'.repoFile "schemas/linux/schema.json"} - \ + || { echo "schemas/linux/schema.json is stale: run just jsonschema"; exit 1; } + komorebic application-specific-configuration-schema | diff -u ${lib'.repoFile "schemas/linux/schema.asc.json"} - \ + || { echo "schemas/linux/schema.asc.json is stale: run just jsonschema"; exit 1; } + expected=$(sha256sum ${lib'.repoFile "schemas/linux/schema.json"} | cut -d' ' -f1) + head -n 1 ${./komorebi-options.nix} | grep -qx "# schema-sha256: $expected" \ + || { echo "nix/linux/komorebi-options.nix is stale: run just nixgen-linux"; exit 1; } + touch $out + ''; + + # the options the module is typed with, evaluated against the example configuration + linux-nix-options-validation = import ./tests/default.nix { inherit pkgs; }; + + # every file of the key binding corpus through `komorebic hotkeys compile`, as the + # module runs it + linux-keybindings-fixtures = import ./tests/keybindings.nix { + inherit pkgs; + komorebic = build.fullBuild; + }; + + # The NixOS module in real NixOS systems, evaluated and never built, so a Mac checks it + # too. A VM suite, `vm-module`, runs it. + linux-nixos-module-eval = import ./tests/nixos-module.nix { + inherit pkgs; + inherit (nixpkgs.lib) nixosSystem; + module = self.nixosModules.default; + komorebiFor = guestSystem: self.packages.${guestSystem}.komorebi-full; + borderEffectFor = guestSystem: self.packages.${guestSystem}.kde-rounded-corners-komorebi; + animationEffectFor = guestSystem: self.packages.${guestSystem}.kwin-effect-geometry-change-komorebi; + }; + + # The Plasma guests to develop against, evaluated and never built, so that every system + # sees an option that nixpkgs renamed or an assertion that fails. The QEMU guests are + # systems in a VM (`vmVariant`), with a runner for the machine that runs QEMU; UTM's is + # a system of its own. Each guest is for one kind of machine, which is also checked: the + # architecture of the guest, the machine that runs its QEMU, and that a guest of a Linux + # machine has KVM and one display device only. + linux-plasma-guests-eval = + let + guests = self.nixosConfigurations; + machines = + let + mac = { + guest = "aarch64-linux"; + host = "aarch64-darwin"; + }; + linux = system: { + guest = system; + host = system; + }; + # the runner of the demo is given its display device, its ports and whether to + # insist on KVM when it starts, by `run-kde` + demo = machine: machine // { demo = true; }; + in + { + plasma-vm = mac; + plasma-vm-session = mac; + plasma-vm-x86_64 = linux "x86_64-linux"; + plasma-vm-session-x86_64 = linux "x86_64-linux"; + plasma-vm-aarch64 = linux "aarch64-linux"; + plasma-vm-session-aarch64 = linux "aarch64-linux"; + run-kde-x86_64 = demo (linux "x86_64-linux"); + run-kde-aarch64 = demo (linux "aarch64-linux"); + }; + isDisplay = + option: lib.hasPrefix "-device virtio-vga" option || lib.hasPrefix "-device virtio-gpu" option; + qemuGuest = + name: + let + machine = machines.${name}; + demo = machine.demo or false; + vm = guests.${name}.config.virtualisation.vmVariant.virtualisation; + in + assert lib.assertMsg ( + guests.${name}.pkgs.stdenv.hostPlatform.system == machine.guest + ) "${name} is not a ${machine.guest} guest"; + assert lib.assertMsg ( + vm.host.pkgs.stdenv.hostPlatform.system == machine.host + ) "${name} does not run on a ${machine.host} host"; + assert lib.assertMsg ( + vm.qemu.forceAccel != demo + ) "${name} would run without KVM or HVF, which is too slow to use"; + assert lib.assertMsg ( + builtins.length (builtins.filter isDisplay vm.qemu.options) == (if demo then 0 else 1) + ) "${name} does not have the display devices it should have"; + assert lib.assertMsg + # an x86_64 machine has a VGA of its own, which would be a second display + ((machine.guest == "x86_64-linux" && !demo) == builtins.elem "-vga none" vm.qemu.options) + "${name} has `-vga none` if and only if it is an x86_64 guest with a display device"; + assert lib.assertMsg ( + demo -> (vm.forwardPorts == [ ] && vm.sharedDirectories.run-kde.target == "/run/run-kde") + ) "${name} does not leave its ports and its shared directory to the runner's environment"; + { + ${name} = guests.${name}.config.virtualisation.vmVariant.system.build.toplevel; + "${name}, its runner" = guests.${name}.config.system.build.vm; + }; + builds = lib.foldl' (builds: name: builds // qemuGuest name) { } (lib.attrNames machines) // { + plasma-utm = guests.plasma-utm.config.system.build.toplevel; + }; + lines = lib.mapAttrsToList ( + name: drv: "${name}: ${builtins.unsafeDiscardStringContext drv.drvPath}" + ) builds; + in + pkgs.runCommand "plasma-guests-eval" { } '' + ${lib.concatMapStringsSep "\n" (line: "echo ${lib.escapeShellArg line}") lines} + touch $out + ''; + } + # The probe builds (shellcheck runs over the script and over the guest's init as it + # does) and its dry run works without a machine to probe, so that it cannot rot between + # the times somebody runs it. + // lib.optionalAttrs isLinux { + linux-wsl2-probe = wsl2ProbeForChecks.probe; + linux-wsl2-probe-dry-run = + pkgs.runCommand "wsl2-probe-dry-run" { nativeBuildInputs = [ wsl2ProbeForChecks.probe ]; } + '' + wsl2-probe --dry-run --no-prompt --report report.txt > out.txt + cat out.txt + for probe in P0 P1 P2 P3 P4 P5 P6; do + grep -q "^$probe " out.txt || { echo "no verdict line for $probe"; exit 1; } + done + grep -q "PASTE THIS" out.txt + grep -q "would run" out.txt + # the report file has what the screen has + grep -q "PASTE THIS" report.txt + # a mistake in the options is refused + if wsl2-probe --nonsense 2> err.txt; then exit 1; fi + grep -q "unknown option" err.txt + touch $out + ''; + } + # `run-kde` is shellchecked when it is built, and its output is what a test says it is + // lib.optionalAttrs isLinux ( + lib.mapAttrs' (name: lib.nameValuePair "linux-${name}") ( + { + run-kde = config.packages.run-kde; + } + // import ./tests/run-kde.nix { + inherit pkgs; + runKde = config.packages.run-kde; + } + ) + ) + # The VM tests need QEMU with KVM, so only Linux checks them. A Mac runs the same tests + # through legacyPackages..vm, see docs/linux/dev/vm-tests.md. + // lib.optionalAttrs isLinux ( + lib.mapAttrs' (name: test: lib.nameValuePair "vm-${name}" test) vm.suites + ) + # the KWin effects komorebi drives, installed and inspected without a session + // lib.optionalAttrs isLinux ( + lib.mapAttrs' (name: lib.nameValuePair "linux-${name}") ( + import ./tests/effects.nix { + inherit pkgs; + inherit (config) packages; + } + ) + ); + + legacyPackages = lib.optionalAttrs checkSystem ( + { + # 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 ./tests/vm { + hostPkgs = pkgs; + inherit komorebiModule; + scale = 8; + }).suites; + } + ); + } + ) + // lib.optionalAttrs (builtins.elem system lib'.linuxSystems) { + packages = { + inherit (build) komorebi komorebic; + komorebi-full = build.fullBuild; + default = build.fullBuild; + kde-rounded-corners-komorebi = pkgs.callPackage ../linux/kde-rounded-corners.nix { }; + kwin-effect-geometry-change-komorebi = + pkgs.callPackage ../linux/kwin-effect-geometry-change.nix + { }; + wsl2-probe = wsl2Probe.probe; + wsl2-probe-guest = wsl2Probe.guest; + run-kde = runKde; + }; + + apps = { + 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"; + }; + wsl2-probe = { + type = "app"; + program = lib.getExe wsl2Probe.probe; + meta.description = "Probes a NixOS-WSL2 machine for KVM, WSLg, a GPU and RDP"; + }; + run-kde = { + type = "app"; + program = lib.getExe config.packages.run-kde; + meta.description = "Start the komorebi demo in a virtual machine"; + }; + default = config.apps.komorebi; + }; + }; + + flake = { + overlays.linux = + final: _: + let + build = linux.mkKomorebiPackages { pkgs = mkPkgs final.stdenv.hostPlatform.system; }; + in + { + inherit (build) komorebi komorebic; + komorebi-full = build.fullBuild; + # with the consumer's KWin, so that the plugin links the libkwin it is loaded into + kde-rounded-corners-komorebi = final.callPackage ./kde-rounded-corners.nix { }; + kwin-effect-geometry-change-komorebi = final.callPackage ./kwin-effect-geometry-change.nix { }; + }; + # the same packages built with the constant commit of the checks, for the VM tests + overlays.forVmTests = + final: prev: + let + build = linux.mkKomorebiPackages { pkgs = mkPkgs final.stdenv.hostPlatform.system; }; + in + (self.overlays.linux final prev) + // { + komorebi = build.komorebiForVm; + komorebic = build.komorebicForVm; + komorebi-full = build.vmBuild; + }; + + # `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/linux/installation.md) + nixosModules.default = { + imports = [ ./nixos-module.nix ]; + nixpkgs.overlays = [ self.overlays.linux ]; + }; + nixosModules.komorebi = self.nixosModules.default; + # the names of the root flake before the nested flakes were merged into it + nixosModules.komorebi-for-kde = self.nixosModules.default; + overlays.komorebi-for-kde = self.overlays.linux; + # `services.komorebi` with the packages the VM tests install + nixosModules.forVmTests = { + imports = [ ./nixos-module.nix ]; + nixpkgs.overlays = [ self.overlays.forVmTests ]; + }; + + # Plasma guests to develop against (docs/linux/dev/utm.md). For an Apple silicon Mac, QEMU runs + # the first two under HVF, and UTM boots the third from a disk image. For a Linux machine + # with KVM, QEMU runs the other four: the same two guests for an x86_64 host and for an + # aarch64 one, each of which runs the guest of its own architecture. The last two are the + # demo that `run-kde` starts. + nixosConfigurations = + let + # `guest` is the system of the guest, and `host` the pkgs of the machine that runs QEMU + mkPlasmaGuest = + { guest, host }: + modules: + nixpkgs.lib.nixosSystem { + specialArgs.hostPkgs = host; + modules = [ + self.nixosModules.default + { nixpkgs.hostPlatform = guest; } + # `nix flake check` evaluates the toplevel of every configuration, which needs a root file + # system and a boot loader; the guests run as VMs (vmVariant), which replace both + ( + { lib, ... }: + { + fileSystems."/" = lib.mkDefault { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + boot.loader.grub.devices = lib.mkDefault [ "nodev" ]; + } + ) + ] + ++ modules; + }; + onMac = mkPlasmaGuest { + guest = "aarch64-linux"; + host = nixpkgs.legacyPackages.aarch64-darwin; + }; + onLinux = + system: + mkPlasmaGuest { + guest = system; + host = nixpkgs.legacyPackages.${system}; + }; + session = [ + ./dev/plasma-vm.nix + { komorebi.plasmaVm.session = true; } + ]; + # the session guest with the demo of `run-kde` (nix/linux/dev/run-kde.nix) and no display + # device, which the command gives it when it starts the guest + demo = [ + ./dev/plasma-vm.nix + { + komorebi.plasmaVm = { + session = true; + display = false; + }; + } + ./dev/run-kde.nix + ({ lib, ... }: { networking.hostName = lib.mkOverride 40 "komorebi-demo"; }) + ]; + in + { + plasma-vm = onMac [ ./dev/plasma-vm.nix ]; + plasma-vm-session = onMac session; + plasma-utm = onMac [ ./dev/plasma-utm.nix ]; + + plasma-vm-x86_64 = onLinux "x86_64-linux" [ ./dev/plasma-vm.nix ]; + plasma-vm-session-x86_64 = onLinux "x86_64-linux" session; + plasma-vm-aarch64 = onLinux "aarch64-linux" [ ./dev/plasma-vm.nix ]; + plasma-vm-session-aarch64 = onLinux "aarch64-linux" session; + + run-kde-x86_64 = onLinux "x86_64-linux" demo; + run-kde-aarch64 = onLinux "aarch64-linux" demo; + }; + }; +} diff --git a/nix/macos/build.nix b/nix/macos/build.nix new file mode 100644 index 00000000..3440b266 --- /dev/null +++ b/nix/macos/build.nix @@ -0,0 +1,114 @@ +# The crane derivations of the macOS packages, from the macOS members of the one Cargo workspace +# (nix/workspace-src.nix). +{ inputs, self }: +let + lib' = import ../lib.nix { inherit inputs; }; + inherit (lib') version; + inherit (inputs) crane; +in +{ + mkKomorebiPackages = + { pkgs }: + let + toolchain = pkgs.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml; + craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; + + inherit + (import ../workspace-src.nix { + inherit pkgs craneLib; + repoRoot = ../..; + platform = "macos"; + }) + real + src + stubbedPackages + ; + + lockFile = lib'.repoFile "Cargo.lock"; + tomlFile = lib'.repoFile "Cargo.toml"; + packages = "-p komorebi -p komorebi-macos -p komorebic -p komorebi-client -p komorebi-bar"; + workspaceArgs = { + pname = "komorebi-workspace-macos"; + inherit version; + # the lock and the root manifest as files of their own: a path inside the filtered source + # would put all of it into the context of every derivation, and an edit of any file + # would then change the dependencies + cargoLock = lockFile; + cargoToml = tomlFile; + cargoVendorDir = craneLib.vendorCargoDeps { cargoLock = lockFile; }; + }; + + commonArgs = workspaceArgs // { + cargoExtraArgs = "--locked ${packages}"; + inherit src; + strictDeps = true; + # a constant, so that the dependencies and every check are the same derivations for + # every commit; only fullBuild stamps the real one + COMMIT_HASH = "nix-check"; + 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 + ''; + }; + + # the dummy source is of the manifests alone (nix/lib.nix): the dependencies are the same + # derivations for every source file and every stub, and nothing is built at evaluation + cargoArtifacts = craneLib.buildDepsOnly ( + builtins.removeAttrs commonArgs [ "src" ] + // { + dummySrc = craneLib.mkDummySrc { + src = lib'.manifestSource pkgs.lib; + cargoLock = lockFile; + }; + } + ); + + individualCrateArgs = commonArgs // { + inherit cargoArtifacts; + doCheck = false; + doDoc = false; + }; + + fullBuild = craneLib.buildPackage ( + individualCrateArgs + // { + COMMIT_HASH = self.rev or (pkgs.lib.removeSuffix "-dirty" (self.dirtyRev or "unknown")); + } + ); + + extractBinary = + binaryName: + pkgs.runCommand "komorebi-${binaryName}" + { + meta = fullBuild.meta // { }; + } + '' + mkdir -p $out/bin + cp ${fullBuild}/bin/${binaryName} $out/bin/ + ''; + in + { + inherit + craneLib + src + real + workspaceArgs + stubbedPackages + packages + individualCrateArgs + fullBuild + ; + komorebi = extractBinary "komorebi"; + komorebic = extractBinary "komorebic"; + komorebi-bar = extractBinary "komorebi-bar"; + }; +} diff --git a/nix/macos/flake-module.nix b/nix/macos/flake-module.nix new file mode 100644 index 00000000..fc9fa84e --- /dev/null +++ b/nix/macos/flake-module.nix @@ -0,0 +1,108 @@ +# komorebi for macOS: the native packages on Apple machines, the `macos-*` checks and the +# nix-darwin module. The Rust packages cannot be built on Linux; `nix flake check --no-build +# --all-systems` evaluates them there. +{ + inputs, + self, + lib, + ... +}: +let + lib' = import ../lib.nix { inherit inputs; }; + macos = import ./build.nix { inherit inputs self; }; + inherit (lib') mkPkgs; +in +{ + perSystem = + { pkgs, system, ... }: + let + build = macos.mkKomorebiPackages { inherit pkgs; }; + # rustfmt.toml uses options only a nightly rustfmt knows + nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + in + lib.optionalAttrs (builtins.elem system lib'.macosSystems) { + checks = { + macos-clippy = build.craneLib.cargoClippy build.individualCrateArgs; + + # the nightly rustfmt over the macOS files only (the source of this platform has no others), with + # the 2024 edition of the manifests. `cargo fmt` would follow the `mod windows;` of the shared + # entry files (komorebi-bar, komorebi-client), which the macOS source does not have. + macos-fmt = + pkgs.runCommand "komorebi-macos-fmt" + { + nativeBuildInputs = [ nightlyRustfmt ]; + } + '' + RUSTFMT=rustfmt bash ${lib'.repoFile "tools/format-all"} --check --root ${build.real} + touch $out + ''; + + macos-toml-fmt = build.craneLib.taploFmt ( + build.workspaceArgs + // { + src = lib.sources.sourceFilesBySuffices build.real [ ".toml" ]; + } + ); + + # cargo deny sees every member of the workspace: leave out the other platforms' packages (stubs + # here); the policy is the one deny.toml of the repository + macos-deny = build.craneLib.cargoDeny ( + build.workspaceArgs + // { + inherit (build) src; + cargoDenyExtraArgs = + "--workspace " + lib.concatMapStringsSep " " (p: "--exclude ${p}") build.stubbedPackages; + cargoDenyChecks = "bans licenses sources"; + } + ); + + macos-nextest = build.craneLib.cargoNextest build.individualCrateArgs; + + macos-nix-options-validation = import ./tests { inherit pkgs; }; + + macos-darwin-module-validation = import ./tests/darwin-module.nix { inherit pkgs; }; + }; + + packages = { + inherit (build) komorebi komorebic komorebi-bar; + komorebi-full = build.fullBuild; + default = build.fullBuild; + }; + + apps = { + komorebi = { + type = "app"; + program = "${build.komorebi}/bin/komorebi"; + }; + komorebic = { + type = "app"; + program = "${build.komorebic}/bin/komorebic"; + }; + komorebi-bar = { + type = "app"; + program = "${build.komorebi-bar}/bin/komorebi-bar"; + }; + default = { + type = "app"; + program = "${build.fullBuild}/bin/komorebi"; + }; + }; + }; + + flake = { + overlays.macos = + final: _: + let + build = macos.mkKomorebiPackages { pkgs = mkPkgs final.stdenv.hostPlatform.system; }; + in + { + inherit (build) komorebi komorebic komorebi-bar; + komorebi-full = build.fullBuild; + }; + + darwinModules.default = import ./darwin-module.nix; + darwinModules.komorebi = import ./darwin-module.nix; + # the name of the root flake before the nested flakes were merged into it + darwinModules.komorebi-for-mac = import ./darwin-module.nix; + }; +} diff --git a/nix/windows/build.nix b/nix/windows/build.nix new file mode 100644 index 00000000..7e9db655 --- /dev/null +++ b/nix/windows/build.nix @@ -0,0 +1,286 @@ +# The Windows cross build: xwin's SDK and CRT, the clang-cl/lld-link environment and the crane +# derivations of the Windows workspace (target x86_64-pc-windows-msvc), for the systems that build it. +{ inputs, self }: +let + lib' = import ../lib.nix { inherit inputs; }; + inherit (lib') version; + inherit (inputs) crane; + windowsSdkVersion = "10.0.26100"; + windowsCrtVersion = "14.44.17.14"; + + mkWindowsSdk = + pkgs: + pkgs.stdenvNoCC.mkDerivation { + name = "windows-sdk-${windowsSdkVersion}-crt-${windowsCrtVersion}"; + + nativeBuildInputs = [ + pkgs.xwin + pkgs.cacert + ]; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + # hash of `xwin splat --disable-symlinks`: without the flag xwin makes + # case-alias symlinks on a case-sensitive filesystem and none on a + # case-insensitive one, so the hash depends on the host + outputHash = "sha256-YjmEfoxtcmQXvUc+WtaElnyP/4osNvtSI7X6MDaNTjg="; + + buildCommand = '' + export HOME=$(mktemp -d) + # keep the cache on the same filesystem as $out, as xwin moves + # files from the cache into the output and rename(2) fails with + # EXDEV when the build dir and the store are separate mounts + mkdir -p $out + xwin --accept-license \ + --cache-dir $out/.xwin-cache \ + --sdk-version ${windowsSdkVersion} \ + --crt-version ${windowsCrtVersion} \ + splat --disable-symlinks --output $out + rm -rf $out/.xwin-cache + ''; + }; + + # the linker asks for `kernel32.lib` of a file that is `Kernel32.lib`, so + # on a case-sensitive host add a lowercase alias for every name with a + # capital in it, and the spellings the headers include each other by (the + # SDK itself is made without symlinks) + caseFolded = + { pkgs, windowsSdk }: + if pkgs.stdenv.hostPlatform.isDarwin then + windowsSdk + else + pkgs.runCommandLocal "${windowsSdk.name}-case-folded" { } '' + cp -rs ${windowsSdk} $out + chmod -R u+w $out + cd $out + find . -name '*[A-Z]*' | while read -r path; do + directory=$(dirname "$path") + name=$(basename "$path") + lower=$(echo "$name" | tr 'A-Z' 'a-z') + if [ ! -e "$directory/$lower" ] && [ ! -L "$directory/$lower" ]; then + ln -s "$name" "$directory/$lower" + fi + done + + # headers include each other with spellings that differ from the + # file name (`DriverSpecs.h` for `driverspecs.h`), which xwin + # covers with symlinks unless told not to + grep -Rhoi -E '^[[:space:]]*#[[:space:]]*include[[:space:]]*[<"][^>"]+[>"]' \ + crt/include sdk/include 2>/dev/null \ + | sed -E 's/.*[<"]([^>"]+)[>"]/\1/; s#\\#/#g; s#.*/##' \ + | sort -u > $TMPDIR/spellings + find crt/include sdk/include \( -type f -o -type l \) \ + | awk -v spellings=$TMPDIR/spellings ' + BEGIN { + while ((getline line < spellings) > 0) + known[tolower(line)] = known[tolower(line)] "\n" line + } + { + n = split($0, parts, "/"); base = parts[n] + dir = substr($0, 1, length($0) - length(base)) + m = split(known[tolower(base)], names, "\n") + for (i = 1; i <= m; i++) + if (names[i] != "" && names[i] != base) + print dir "\t" base "\t" names[i] + }' \ + | while IFS=$'\t' read -r directory name spelling; do + if [ ! -e "$directory$spelling" ] && [ ! -L "$directory$spelling" ]; then + ln -s "$name" "$directory$spelling" + fi + done + ''; + + mkMsvcEnv = + { pkgs, windowsSdk }: + let + sdk = caseFolded { inherit pkgs windowsSdk; }; + clangVersion = pkgs.lib.versions.major pkgs.llvmPackages.clang.version; + 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"; + + # IMPORTANT: libclang include path MUST come first to avoid header conflicts + CFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [ + "--target=x86_64-pc-windows-msvc" + "-Wno-unused-command-line-argument" + "-fuse-ld=lld-link" + "/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include" + "/imsvc${sdk}/crt/include" + "/imsvc${sdk}/sdk/include/ucrt" + "/imsvc${sdk}/sdk/include/um" + "/imsvc${sdk}/sdk/include/shared" + ]; + + CXXFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep " " [ + "--target=x86_64-pc-windows-msvc" + "-Wno-unused-command-line-argument" + "-fuse-ld=lld-link" + "/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include" + "/imsvc${sdk}/crt/include" + "/imsvc${sdk}/sdk/include/ucrt" + "/imsvc${sdk}/sdk/include/um" + "/imsvc${sdk}/sdk/include/shared" + ]; + + # 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=${sdk}/crt/lib/x86_64" + "-Lnative=${sdk}/sdk/lib/um/x86_64" + "-Lnative=${sdk}/sdk/lib/ucrt/x86_64" + ]; + + # cargo target + CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc"; + }; + + mkKomorebiPackages = + { + pkgs, + windowsSdk, + # the commit stamped into the binaries; a build for the e2e guest + # gives a constant one, so that a new commit does not rebuild it + commit ? self.rev or (pkgs.lib.removeSuffix "-dirty" (self.dirtyRev or "unknown")), + }: + let + # toolchain with windows msvc target + toolchain = (pkgs.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml).override { + targets = [ "x86_64-pc-windows-msvc" ]; + }; + craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; + + msvcEnv = mkMsvcEnv { inherit pkgs windowsSdk; }; + + # the Windows members of the one Cargo workspace; the Linux and macOS members are stubs + # (nix/workspace-src.nix), so that an edit of one changes nothing here + inherit + (import ../workspace-src.nix { + inherit pkgs craneLib; + repoRoot = ../..; + platform = "windows"; + }) + real + src + stubbedPackages + ; + + lockFile = lib'.repoFile "Cargo.lock"; + tomlFile = lib'.repoFile "Cargo.toml"; + + workspaceArgs = { + pname = "komorebi-workspace-windows"; + inherit version; + # the lock and the root manifest as files of their own: a path inside the filtered source would put the + # whole filtered source into the context of every derivation, and then an edit of any file + # would change the dependencies + cargoLock = lockFile; + cargoToml = tomlFile; + cargoVendorDir = craneLib.vendorCargoDeps { cargoLock = lockFile; }; + }; + + commonArgs = workspaceArgs // { + inherit src; + strictDeps = true; + # applications.json is committed now; this copies the pinned asc input over it, so that the + # embedded file is the pinned one even when the committed copy is older + preBuild = "mkdir -p komorebic && cp ${inputs.asc}/applications.json komorebic/applications.json"; + # a constant, so that the dependencies and every check are the same + # derivations for every commit; only fullBuild stamps the real one + COMMIT_HASH = "nix-check"; + + # build inputs for cross-compilation + nativeBuildInputs = [ + pkgs.llvmPackages.clang-unwrapped + pkgs.llvmPackages.lld + pkgs.llvmPackages.llvm + # komorebic build script uses reqwest, which runs on the build host + pkgs.pkg-config + pkgs.openssl + pkgs.openssl.dev + ]; + + # point the host-side openssl-sys build at the nix openssl + OPENSSL_NO_VENDOR = "1"; + OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib"; + OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include"; + + # cross-compilation environment + inherit (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 + ; + }; + + # the dummy source is of the manifests alone (nix/lib.nix): the dependencies are the same + # derivations for every source file and every stub, and nothing is built at evaluation + cargoArtifacts = craneLib.buildDepsOnly ( + builtins.removeAttrs commonArgs [ "src" ] + // { + dummySrc = craneLib.mkDummySrc { + src = lib'.manifestSource pkgs.lib; + cargoLock = lockFile; + }; + } + ); + + individualCrateArgs = commonArgs // { + inherit cargoArtifacts; + doCheck = false; + doDoc = false; + }; + + fullBuild = craneLib.buildPackage ( + individualCrateArgs + // { + pname = "komorebi-workspace"; + COMMIT_HASH = commit; + } + ); + + extractBinary = + binaryName: + pkgs.runCommand "komorebi-${binaryName}" + { + meta = fullBuild.meta // { }; + } + '' + mkdir -p $out/bin + cp ${fullBuild}/bin/${binaryName}.exe $out/bin/ + ''; + in + { + inherit + craneLib + src + real + workspaceArgs + stubbedPackages + individualCrateArgs + fullBuild + msvcEnv + ; + komorebi = extractBinary "komorebi"; + komorebic = extractBinary "komorebic"; + komorebic-no-console = extractBinary "komorebic-no-console"; + komorebi-bar = extractBinary "komorebi-bar"; + komorebi-gui = extractBinary "komorebi-gui"; + komorebi-shortcuts = extractBinary "komorebi-shortcuts"; + komorebi-testwin = extractBinary "komorebi-testwin"; + komorebi-e2e-agent = extractBinary "komorebi-e2e-agent"; + }; +in +{ + inherit mkWindowsSdk mkMsvcEnv mkKomorebiPackages; +} diff --git a/nix/windows/flake-module.nix b/nix/windows/flake-module.nix new file mode 100644 index 00000000..e03be98a --- /dev/null +++ b/nix/windows/flake-module.nix @@ -0,0 +1,201 @@ +# komorebi for Windows, cross-built with xwin (x86_64-pc-windows-msvc): the `windows-*` packages, +# apps and checks, and the Windows e2e guest. Built on Linux (and on Apple silicon); the binaries run +# on Windows only. +{ + inputs, + self, + lib, + ... +}: +let + lib' = import ../lib.nix { inherit inputs; }; + windows = import ./build.nix { inherit inputs self; }; +in +{ + perSystem = + { pkgs, system, ... }: + let + windowsSdk = windows.mkWindowsSdk pkgs; + build = windows.mkKomorebiPackages { inherit pkgs windowsSdk; }; + # the binaries the e2e guest runs: the same build, but with a + # constant commit stamp, so a new commit (or a dirty tree) that does + # not change a source file reuses it. The commit under test is + # KOMOREBI_E2E_COMMIT, not what the binary says. + e2eBuild = windows.mkKomorebiPackages { + inherit pkgs windowsSdk; + commit = "e2e"; + }; + + # OVMF without SMM and with the TPM support Windows 11 Setup asks for + ovmfWithTpm = (pkgs.OVMF.override { tpmSupport = true; }).fd; + e2eWindows = pkgs.writeShellApplication { + name = "e2e-windows"; + runtimeInputs = [ + pkgs.qemu + pkgs.swtpm + (pkgs.python3.withPackages (p: [ p.pillow ])) + ]; + text = '' + : "''${E2E_GOLDEN:?set E2E_GOLDEN to the golden image directory}" + out=''${1:?usage: e2e-windows OUT SCENARIO...} + shift + # the binaries under test: this checkout's, or those of another + # commit, made by e2e/platforms/qemu_windows/build-ref.sh + if [ -n "''${KOMOREBI_E2E_BINS:-}" ]; then + bins=$KOMOREBI_E2E_BINS + if [ -f "$bins/commit" ]; then + KOMOREBI_E2E_COMMIT=$(cat "$bins/commit") + fi + else + bins=$(mktemp -d) + trap 'rm -rf "$bins"' EXIT + cp ${e2eBuild.komorebi}/bin/komorebi.exe "$bins" + cp ${e2eBuild.komorebic}/bin/komorebic.exe "$bins" + cp ${e2eBuild.komorebi-testwin}/bin/komorebi-testwin.exe "$bins" + cp ${e2eBuild.komorebi-bar}/bin/komorebi-bar.exe "$bins" + fi + export OVMF_FD=${ovmfWithTpm} + export KOMOREBI_E2E_BAR_SCHEMA=${lib'.repoFile "schemas/windows/schema.bar.json"} + export KOMOREBI_E2E_COMMIT=''${KOMOREBI_E2E_COMMIT:-${self.rev or "dirty"}} + export PYTHONPATH=${lib'.repoFile "e2e/lib"}:${lib'.repoFile "e2e"} + exec python -m platforms.qemu_windows.run \ + "$E2E_GOLDEN" "$bins" "$out" "$@" + ''; + }; + # the nightly rustfmt of the checks is the one of the dev shell: see nix/dev-shell.nix + nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + in + { + # the dev shell (nix/dev-shell.nix) cross-compiles with these + _module.args = { + inherit windowsSdk ovmfWithTpm; + inherit (build) msvcEnv; + }; + } + // lib.optionalAttrs (builtins.elem system lib'.windowsSystems) { + checks = { + windows-clippy = build.craneLib.cargoClippy ( + build.individualCrateArgs + // { + cargoClippyExtraArgs = "--all-targets -- -D warnings"; + } + ); + + # the nightly rustfmt over the Windows files, as tools/format-all does locally. Not `cargo fmt`: + # it runs the stable rustfmt (which ignores the nightly options of rustfmt.toml) and follows + # `mod linux;` into files the Windows source does not have. + windows-fmt = + pkgs.runCommand "komorebi-windows-fmt" + { + nativeBuildInputs = [ nightlyRustfmt ]; + } + '' + RUSTFMT=rustfmt bash ${lib'.repoFile "tools/format-all"} --check --root ${build.real} + touch $out + ''; + + windows-toml-fmt = build.craneLib.taploFmt ( + build.workspaceArgs + // { + src = pkgs.lib.sources.sourceFilesBySuffices build.real [ ".toml" ]; + } + ); + + # cargo deny sees every member of the workspace: leave out the Linux and macOS packages, which + # are stubs here and have their own checks (linux-deny, macos-deny) + windows-deny = build.craneLib.cargoDeny ( + build.workspaceArgs + // { + inherit (build) src; + cargoDenyExtraArgs = + "--workspace " + pkgs.lib.concatMapStringsSep " " (p: "--exclude ${p}") build.stubbedPackages; + } + ); + + windows-nextest = build.craneLib.cargoNextest build.individualCrateArgs; + } + // { + # the Python of the Windows e2e guest that needs no guest: the + # vendored core's tests and those of the runner + windows-e2e-tests = + pkgs.runCommand "windows-e2e-tests" + { + nativeBuildInputs = [ + pkgs.git + (pkgs.python3.withPackages (p: [ + p.pillow + p.pytest + ])) + ]; + } + '' + cp -r ${lib'.repoFile "e2e"} e2e + chmod -R u+w e2e + cd e2e + export HOME=$TMPDIR PYTHONPATH=$PWD/lib:$PWD + python -m pytest -q -p no:cacheprovider lib/tests platforms/qemu_windows/tests + touch $out + ''; + }; + + packages = { + windows-komorebi = build.komorebi; + windows-komorebic = build.komorebic; + windows-komorebic-no-console = build.komorebic-no-console; + windows-komorebi-bar = build.komorebi-bar; + windows-komorebi-gui = build.komorebi-gui; + windows-komorebi-shortcuts = build.komorebi-shortcuts; + windows-komorebi-testwin = build.komorebi-testwin; + windows-komorebi-e2e-agent = build.komorebi-e2e-agent; + windows-sdk = windowsSdk; + # the test window of the Windows e2e guest, built with the constant + # stamp: a new commit does not rebuild what it depends on + windows-komorebi-testwin-e2e = e2eBuild.komorebi-testwin; + windows-komorebi-full = build.fullBuild; + windows-default = build.fullBuild; + }; + + apps = { + windows-komorebi = { + type = "app"; + program = "${build.komorebi}/bin/komorebi.exe"; + }; + windows-komorebic = { + type = "app"; + program = "${build.komorebic}/bin/komorebic.exe"; + }; + windows-komorebic-no-console = { + type = "app"; + program = "${build.komorebic-no-console}/bin/komorebic-no-console.exe"; + }; + windows-komorebi-bar = { + type = "app"; + program = "${build.komorebi-bar}/bin/komorebi-bar.exe"; + }; + windows-komorebi-gui = { + type = "app"; + program = "${build.komorebi-gui}/bin/komorebi-gui.exe"; + }; + windows-komorebi-shortcuts = { + type = "app"; + program = "${build.komorebi-shortcuts}/bin/komorebi-shortcuts.exe"; + }; + windows-default = { + type = "app"; + program = "${build.fullBuild}/bin/komorebi.exe"; + }; + } + // lib.optionalAttrs (system == "x86_64-linux") { + # the neutral scenarios against a throwaway Windows guest: + # E2E_GOLDEN= nix run .#e2e-windows -- \ + # OUT SCENARIO... + # KOMOREBI_E2E_BINS= tests another commit + # the golden image is built once with + # e2e/platforms/qemu_windows/build-golden.sh + e2e-windows = { + type = "app"; + program = pkgs.lib.getExe e2eWindows; + }; + }; + }; +} diff --git a/nix/workspace-src.nix b/nix/workspace-src.nix index a0e8cfed..a1424241 100644 --- a/nix/workspace-src.nix +++ b/nix/workspace-src.nix @@ -1,22 +1,168 @@ -# The source of a build from the one Cargo workspace of this repository. +# The source of a build of one platform, cut from the one Cargo workspace of this repository. # -# Cargo has to load every member of the workspace, but a build of one platform needs the sources of -# that platform's crates only. `real` is the filtered source (the platform's crates, the crates of the -# root workspace they depend on, the root manifest and lock); every other member (`isReal` is false) -# is added as its own manifest with an empty library and a `main` that does nothing. An edit of a -# crate of another platform then changes nothing here, and an edit of another platform's manifest -# changes only the stub copy of it. +# import ./workspace-src.nix { pkgs; craneLib; repoRoot; platform = "windows" | "linux" | "macos"; } # -# workspaceSrc { pkgs; repoRoot; real; isReal = member: bool; } +# Cargo has to load every member of the workspace (the root manifest lists the members of all three +# platforms), but a build of one platform needs the sources of that platform's crates only. The result: +# +# real the filtered source: the members of the platform, the root manifest and lock, the +# platform's files of the crates the platforms share, and the data files they read +# src `real` plus, for every other member, its own manifest with an empty library and a `main` +# that does nothing (a stub: cargo refuses a workspace with a member that is not there, and +# a lock file that has packages the workspace does not have fails under --locked) +# members the members of the platform; stubbed the others; stubbedPackages their package names (for +# `cargo deny --exclude`) +# +# An edit under one platform therefore changes nothing in the derivations of the others, except where +# the edit is in a crate they share or in the root manifest, lock or toolchain files. { pkgs, + craneLib, repoRoot, - real, - isReal, + platform, }: let inherit (pkgs) lib; - members = (builtins.fromTOML (builtins.readFile "${real}/Cargo.toml")).workspace.members; + root = toString repoRoot; + + allMembers = (builtins.fromTOML (builtins.readFile (repoRoot + "/Cargo.toml"))).workspace.members; + + # the crates of the root workspace that the KDE packages depend on, by path (nix/linux/root-crates.json, + # kept equal to `cargo tree` by tools/kde-closure-check); komorebi-bar and komorebi-bar-core (the egui + # bar) are not in it, so an edit of the bar does not rebuild the KDE packages + linuxShared = builtins.fromJSON (builtins.readFile ./linux/root-crates.json) ++ [ + "komorebi-daemon" + ]; + + linuxOnly = [ + "komorebi-linux" + "komorebi-hotkeys" + ]; + macosOnly = [ "komorebi-macos" ]; + + membersOf = { + # everything but the other two platforms' daemons + windows = builtins.filter (m: !(builtins.elem m (linuxOnly ++ macosOnly))) allMembers; + linux = linuxOnly ++ linuxShared; + macos = + macosOnly + ++ [ + "komorebic" + "komorebi-client" + "komorebi-bar" + "komorebi-daemon" + "komorebi-layouts" + "komorebi-scrolling" + "komorebi-themes" + ] + ++ builtins.filter (m: lib.hasPrefix "crates/" m) allMembers; + }; + + members = builtins.filter (m: builtins.elem m membersOf.${platform}) allMembers; + stubbed = builtins.filter (m: !(builtins.elem m members)) allMembers; + stubbedPackages = map ( + m: (builtins.fromTOML (builtins.readFile (repoRoot + "/${m}/Cargo.toml"))).package.name + ) stubbed; + + # the crates one entry point serves for every platform: each keeps its own platform's modules and + # tests (named by prefix) and drops the other platforms' + sharedCrates = [ + "komorebic" + "komorebi-client" + "komorebi-bar" + "komorebi-daemon" + ]; + markers = { + windows = "(src/windows(\\.rs|/.*)|tests/windows_.*)"; + linux = "(src/linux(\\.rs|/.*)|tests/linux_.*)"; + macos = "(src/macos(\\.rs|/.*)|tests/macos_.*)"; + }; + otherMarkers = builtins.attrValues (removeAttrs markers [ platform ]); + ofOtherPlatform = + rel: + builtins.any ( + crate: + lib.hasPrefix "${crate}/" rel + && builtins.any ( + marker: builtins.match marker (lib.removePrefix "${crate}/" rel) != null + ) otherMarkers + ) sharedCrates; + + # the directories of the source (a file is kept only under one of them) + roots = + members + ++ [ + "docs" + "schemas" + ".cargo" + ] + # the configurations that the config_parity test of komorebi-linux loads + ++ lib.optional (platform == "linux") "spikes/config-parity"; + underRoot = rel: builtins.any (r: rel == r || lib.hasPrefix "${r}/" rel) roots; + # a directory above a root (`crates` for `crates/komorebi-core`) + aboveRoot = rel: builtins.any (r: lib.hasPrefix "${rel}/" r) roots; + + # data files that the Rust sources and tests read, outside the Cargo sources + extra = + rel: + # the test inputs of every crate + (builtins.match ".*/(tests|fixtures|golden)/.*" rel != null) + || { + windows = + # the whole of docs/ (they are embedded in komorebic and the bar) except the other platforms' and + # the developer notes; the macOS CLI pages are read by a test of komorebic-core + ( + lib.hasPrefix "docs/" rel + && !(builtins.any (p: lib.hasPrefix p rel) [ + "docs/linux/" + "docs/macos/" + "docs/dev/" + ]) + ) + || lib.hasPrefix "docs/macos/cli/" rel + || lib.hasPrefix "schemas/" rel + || lib.hasPrefix "komorebic/fixtures/" rel; + linux = + # the script komorebi loads into KWin and the JSON the tests and the daemon read + (lib.hasSuffix ".js" rel || lib.hasSuffix ".json" rel) + && !(lib.hasPrefix "docs/" rel || lib.hasPrefix "schemas/" rel) + || lib.hasPrefix "schemas/linux/" rel + || lib.hasPrefix "spikes/config-parity/" rel + || builtins.elem rel [ + # the example configuration and the sample key bindings that `komorebic quickstart` includes + "docs/linux/komorebi.example.json" + "docs/linux/skhdrc.sample" + # the page that shows the example configuration, which a test compares with the file + "docs/linux/example-configurations.md" + # the example a test loads, to see that the docs show one that works + "docs/linux/usage/wallpapers.md" + ]; + macos = + lib.hasSuffix ".json" rel && !(lib.hasPrefix "docs/" rel || lib.hasPrefix "schemas/" rel) + || lib.hasPrefix "docs/macos/" rel + || lib.hasPrefix "schemas/macos/" rel + || lib.hasPrefix "komorebic/fixtures/" rel; + } + .${platform}; + + real = lib.cleanSourceWith { + src = repoRoot; + filter = + path: type: + let + rel = lib.removePrefix "${root}/" path; + isRootFile = !(lib.hasInfix "/" rel); + in + if type == "directory" then + rel == root || aboveRoot rel || (underRoot rel && !(ofOtherPlatform rel)) + else + # the files at the top: Cargo.toml, Cargo.lock, rust-toolchain.toml, rustfmt.toml, deny.toml + (isRootFile && craneLib.filterCargoSources path type) + || ( + underRoot rel && !(ofOtherPlatform rel) && ((craneLib.filterCargoSources path type) || extra rel) + ); + }; + stub = member: '' mkdir -p $out/${member}/src cp ${ @@ -28,9 +174,19 @@ let : > $out/${member}/src/lib.rs echo 'fn main() {}' > $out/${member}/src/main.rs ''; + + src = pkgs.runCommand "komorebi-${platform}-src" { } '' + cp -r ${real} $out + chmod -R u+w $out + ${lib.concatMapStrings stub stubbed} + ''; in -pkgs.runCommand "komorebi-workspace-src" { } '' - cp -r ${real} $out - chmod -R u+w $out - ${lib.concatMapStrings stub (builtins.filter (m: !(isReal m)) members)} -'' +{ + inherit + real + src + members + stubbed + stubbedPackages + ; +}