| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 1 | { |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 2 | # frq is a Flutter app whose source is ClojureDart, so what this flake |
| 3 | # provides is toolchains rather than a built program: the `flutter` shell |
| 4 | # that `just apk` compiles in, the `flutter-desktop` shell for the Linux |
| 5 | # target, and the Android SDK the first of those copies somewhere writable. |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 6 | # |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 7 | # nix develop .#flutter-desktop --command just flutter-desktop run |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 8 | # |
| 9 | # On a machine that is not NixOS the GL driver is the host's and the loader |
| 10 | # will not find it, so the window never opens ("GL display: argument does not |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 11 | # name a valid config"). The recipes handle that themselves: off NixOS they |
| 12 | # hand the process to nixGL, which puts the host's driver ahead of the |
| 13 | # store's. A distrobox/container Arch is the same case as a bare one. |
| 14 | description = "frq — a freeq client in Flutter"; |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 15 | |
| 16 | inputs = { |
| 17 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; |
| Find the host's GL driver when the store's is not the system's 633faad nandi 19d ago | 18 | |
| 19 | # Only ever used off NixOS, to put the host GL driver on the loader path. |
| 20 | nixgl = { |
| 21 | url = "github:nix-community/nixGL"; |
| 22 | inputs.nixpkgs.follows = "nixpkgs"; |
| 23 | }; |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 24 | }; |
| 25 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 26 | outputs = { self, nixpkgs, nixgl }: |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 27 | let |
| 28 | systems = [ "x86_64-linux" "aarch64-linux" ]; |
| 29 | forEachSystem = f: |
| 30 | nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); |
| Run this tree on a native half the builders made a32699e nandi 17d ago | 31 | |
| 32 | # Mesa, despite the name: it covers Intel and AMD alike. The NVIDIA |
| 33 | # wrappers are the ones that need --impure (they read the host kernel |
| 34 | # module's version), which is why this only ever reaches for Intel. |
| Stop carrying the 32-bit GL stack and half of git 040f2c1 nandi 16d ago | 35 | # |
| 36 | # Built from nixGL's default.nix rather than taken from its flake |
| 37 | # outputs, for the one argument the flake hardcodes on: `enable32bits`, |
| 38 | # which on x86_64 puts a second, i686 copy of mesa, its LLVM, and |
| 39 | # intel-media-driver into the wrapper. frq is 64-bit on both halves — |
| 40 | # the Rust cdylibs and the Chez runtime — so nothing here ever opens the |
| 41 | # 32-bit driver, and carrying it is most of the dev shell's closure. |
| 42 | nixGLFor = pkgs: (import nixgl { |
| 43 | inherit pkgs; |
| 44 | enable32bits = false; |
| 45 | }).nixGLIntel; |
| Run this tree on a native half the builders made a32699e nandi 17d ago | 46 | |
| Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago | 47 | # The Android SDK wants two things `nixpkgs.legacyPackages` cannot give: |
| 48 | # `allowUnfree`, because the SDK's own licence is not free, and |
| 49 | # `android_sdk.accept_license`, which is how you say so in a file rather |
| 50 | # than at a prompt a build has no terminal for. Neither can be set on a |
| 51 | # legacyPackages attribute after the fact, so this is a second import of |
| 52 | # the same locked nixpkgs rather than a second nixpkgs. |
| 53 | # |
| 54 | # This used to live in `just apk` as a `nix build --impure --expr` with |
| 55 | # `builtins.getFlake "github:NixOS/nixpkgs/nixos-unstable"` inside it — |
| 56 | # which fetched whatever nixos-unstable was that morning, not what |
| 57 | # flake.lock pins, so the SDK under the APK and the nixpkgs under |
| 58 | # everything else were free to drift apart. Here they are the same rev. |
| 59 | androidPkgsFor = system: import nixpkgs { |
| 60 | inherit system; |
| 61 | config = { |
| 62 | allowUnfree = true; |
| 63 | android_sdk.accept_license = true; |
| 64 | }; |
| 65 | }; |
| 66 | |
| 67 | # Only the floor Gradle stands on. It installs build-tools and a platform |
| 68 | # into ANDROID_HOME itself as it goes — see `just apk` for why that means |
| 69 | # a writable copy — so composing more of them here buys nothing. |
| 70 | # |
| 71 | # includeNDK = false deliberately: the app is Dart and path_provider is |
| 72 | # platform channels, so there is no native code to need one, and asking |
| 73 | # for it is a few hundred megabytes and a Gradle fetch of that exact NDK. |
| 74 | androidSdkFor = system: |
| 75 | let android = androidPkgsFor system; in |
| 76 | (android.androidenv.composeAndroidPackages { |
| 77 | cmdLineToolsVersion = "13.0"; |
| 78 | buildToolsVersions = [ "34.0.0" ]; |
| 79 | platformVersions = [ "35" "34" ]; |
| 80 | includeNDK = false; |
| 81 | }).androidsdk; |
| 82 | |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 83 | in |
| 84 | { |
| 85 | packages = forEachSystem (pkgs: |
| 86 | let |
| 87 | inherit (pkgs) lib; |
| 88 | in |
| 89 | { |
| Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago | 90 | # The Android SDK `just apk` copies into flutter/.home. A package |
| 91 | # rather than something the recipe evaluates inline, so that |
| 92 | # `nix build .#android-sdk` is how you pre-warm it and `nix flake |
| 93 | # show` admits it exists. |
| 94 | android-sdk = androidSdkFor pkgs.stdenv.hostPlatform.system; |
| 95 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 96 | # There were `appimage` outputs here, and what they were for was a |
| 97 | # host without Nix: they squashed the whole closure into one |
| 98 | # runnable file, Mesa included, and the Mesa was not waste — off |
| 99 | # NixOS the launcher goes through nixGL, which needs a store Mesa to |
| 100 | # put the host's driver in front of. Nothing asks for that shape any |
| 101 | # more, and they were the last thing evaluating nix-appimage, which |
| 102 | # is why that input is gone too. |
| Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago | 103 | |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 104 | # Everything `clojure -M:cljd compile` would otherwise reach the |
| 105 | # network for, fetched once and hashed. |
| 106 | # |
| 107 | # The compile needs three caches, and the reason this is one |
| 108 | # derivation rather than three is that only one of them is obvious. |
| 109 | # Maven and gitlibs are the ordinary tools.deps pair. The third is |
| 110 | # ClojureDart's own: `ensure-cljd-analyzer!` writes a *second*, whole |
| 111 | # pub project to `.clojuredart/cache/<cljd sha>/cljd_helper`, runs |
| 112 | # `pub add analyzer` in it, and then runs `bin/analyzer.dart` out of |
| 113 | # it for the duration of the compile — so a sandbox needs that |
| 114 | # project already resolved, not just the app's dependencies. |
| 115 | # |
| 116 | # Fixed-output, so it is allowed the network the rest of the build is |
| 117 | # not. What that costs is a hash to maintain, and the thing worth |
| 118 | # being exact about is *when*: this derivation never sees frq's |
| 119 | # source. It compiles a three-line throwaway project against the same |
| 120 | # `flutter/deps.edn` and the same `flutter/pubspec.yaml`, so the hash |
| 121 | # moves when a dependency moves and not when a screen changes. A |
| 122 | # stub, rather than `-P` and a hand-built analyzer dir, because |
| 123 | # running the real compiler once is the only way to be sure the |
| 124 | # caches are the ones it actually wants. |
| 125 | # |
| 126 | # PUB_CACHE lands in $out on purpose. The package_config.json inside |
| 127 | # cljd_helper carries absolute paths to whatever resolved it, so |
| 128 | # resolving into a build directory would bake in paths that stop |
| 129 | # existing the moment this derivation finishes. Pointed at $out they |
| 130 | # are store paths, and still true. |
| 131 | cljd-deps = |
| 132 | let |
| 133 | flutterPkg = pkgs.flutter; |
| 134 | in |
| 135 | pkgs.stdenvNoCC.mkDerivation { |
| 136 | name = "frq-cljd-deps"; |
| 137 | dontUnpack = true; |
| 138 | |
| 139 | nativeBuildInputs = [ |
| 140 | pkgs.clojure |
| 141 | pkgs.jdk17 |
| 142 | flutterPkg |
| 143 | pkgs.git |
| 144 | pkgs.cacert |
| 145 | ]; |
| 146 | |
| 147 | buildCommand = '' |
| 148 | export HOME="$NIX_BUILD_TOP/home" |
| 149 | # Resolved in the build directory and copied to $out at the |
| 150 | # end, never written there directly. A fixed-output derivation |
| 151 | # may not reference a store path and its own output is a store |
| 152 | # path, so pub writing its cache's absolute location into its |
| 153 | # own metadata is enough to fail the check. |
| 154 | cache="$NIX_BUILD_TOP/cache" |
| 155 | export PUB_CACHE="$cache/pub-cache" |
| 156 | export GITLIBS="$cache/gitlibs" |
| 157 | export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" |
| 158 | mkdir -p "$HOME" "$PUB_CACHE" "$GITLIBS" "$cache/m2" |
| 159 | |
| Make ../common a dependency rather than a path 95d91a2 nandi 6d ago | 160 | # The stub: our dependency files, nothing of our source. |
| 161 | # ../common is a :local/root dependency now, so it has to exist |
| 162 | # *and* carry a deps.edn for tools.deps to resolve — an empty |
| 163 | # directory with that one file in it is enough. |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 164 | proj="$NIX_BUILD_TOP/stub" |
| 165 | mkdir -p "$proj/src/stub" "$NIX_BUILD_TOP/common" |
| Make ../common a dependency rather than a path 95d91a2 nandi 6d ago | 166 | cp ${./common/deps.edn} "$NIX_BUILD_TOP/common/deps.edn" |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 167 | cp ${./flutter/deps.edn} "$proj/deps.edn" |
| 168 | cp ${./flutter/pubspec.yaml} "$proj/pubspec.yaml" |
| Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago | 169 | # The lock, or `pub get` resolves against pub.dev and takes |
| 170 | # whatever satisfies the ranges today. Every build then fetches |
| 171 | # a slightly different set and the fixed-output hash is a |
| 172 | # promise nothing can keep. |
| 173 | cp ${./flutter/pubspec.lock} "$proj/pubspec.lock" |
| 174 | chmod u+w "$proj/deps.edn" "$proj/pubspec.yaml" "$proj/pubspec.lock" |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 175 | cat > "$proj/src/stub/main.cljd" <<'EOF' |
| 176 | (ns stub.main) |
| 177 | (defn main [] nil) |
| 178 | EOF |
| 179 | |
| 180 | cd "$proj" |
| 181 | # `:main` has to name the stub, or the compiler goes looking for |
| 182 | # frq.main in a tree that is not here. |
| 183 | sed -i 's/:main frq\.main/:main stub.main/' deps.edn |
| 184 | |
| 185 | flutter config --no-analytics &>/dev/null || true |
| 186 | flutter config --enable-linux-desktop >/dev/null || true |
| 187 | |
| 188 | clojure -Sdeps '{:mvn/local-repo "'"$cache"'/m2"}' -M:cljd compile |
| 189 | |
| 190 | # What the compile left behind, and only that. The analyzer |
| 191 | # project is keyed by the ClojureDart sha, so the directory |
| 192 | # under cache/ is copied wholesale rather than named here. |
| 193 | mkdir -p "$out/clojuredart" |
| 194 | cp -r .clojuredart/cache "$out/clojuredart/cache" |
| 195 | cp -r "$cache/m2" "$out/m2" |
| 196 | cp -r "$cache/gitlibs" "$out/gitlibs" |
| 197 | cp -r "$PUB_CACHE" "$out/pub-cache" |
| 198 | |
| 199 | # A fixed-output derivation may not reference a store path, and |
| 200 | # a resolved pub project is nothing but store paths: |
| 201 | # package_config.json names the Flutter SDK and every package |
| 202 | # in the cache by absolute path. So the analyzer project ships |
| 203 | # *unresolved* — its pubspec and its analyzer.dart and nothing |
| 204 | # else — and `flutter pub get --offline` re-resolves it against |
| 205 | # this cache at build time, where naming the store is allowed. |
| 206 | find "$out" \( -name '.dart_tool' -o -name '.flutter-plugins' \ |
| 207 | -o -name '.flutter-plugins-dependencies' \) -prune -exec rm -rf {} + |
| 208 | find "$out" -name '.packages' -delete |
| 209 | |
| 210 | |
| 211 | # A fixed-output hash is a promise that two runs agree, so |
| 212 | # everything a tool writes *about* a run rather than about a |
| 213 | # dependency has to go: pub's log carries timestamps, Maven |
| 214 | # rewrites its resolution metadata on every resolve, and |
| 215 | # tools.gitlibs keeps bare clones it only needs in order to |
| 216 | # make a checkout. None of it is read offline. |
| Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago | 217 | # |
| 218 | # active_roots is the one that was actually breaking this. Pub |
| 219 | # records the project directories using the cache, sharded by |
| 220 | # a hash of the path, and $NIX_BUILD_TOP is different on every |
| 221 | # run -- so two builds whose hosted/ trees were byte-identical |
| 222 | # still disagreed, purely over which directory had asked. Four |
| 223 | # builds gave four hashes until this went. |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 224 | rm -rf "$out/pub-cache/log" "$out/pub-cache/_temp" \ |
| 225 | "$out/pub-cache/git" "$out/pub-cache/global_packages" \ |
| Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago | 226 | "$out/pub-cache/bin" "$out/pub-cache/active_roots" |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 227 | |
| 228 | # tools.gitlibs keeps a bare clone per URL under _repos/, and a |
| 229 | # bare clone is packfiles — which two runs of the same fetch do |
| 230 | # not have to produce byte for byte. It cannot simply be |
| 231 | # deleted, because `procure` calls `ensure-git-dir` before it |
| 232 | # looks at anything else and would clone it again, over a |
| 233 | # network this has and the build that uses it does not. |
| 234 | # |
| 235 | # It does not need the objects, though. `procure` finds the sha |
| 236 | # with `match-exact` against the checkout already in libs/, so |
| 237 | # the bare repo only has to exist. Emptied and re-initialised, |
| 238 | # it is a fixed handful of files from the pinned git and the |
| 239 | # same on every run. |
| 240 | find "$out/gitlibs/_repos" -name HEAD | while read -r head; do |
| 241 | repo="$(dirname "$head")" |
| 242 | rm -rf "$repo" |
| 243 | git init --bare -q "$repo" |
| 244 | # The sample hooks are shell scripts, so they carry a |
| 245 | # `#!/nix/store/.../bash` line — which is exactly the kind of |
| 246 | # store reference a fixed-output derivation may not hold. An |
| 247 | # empty bare repo nothing ever runs has no use for them. |
| 248 | rm -rf "$repo/hooks" |
| 249 | done |
| 250 | # pub's version listings, which record when they were fetched. |
| 251 | # This is the one that actually moved between two runs of this |
| 252 | # derivation: the package sources under hosted/ were identical |
| 253 | # and the listings beside them were not. Nothing offline reads |
| 254 | # them — a resolution that already has every package on disk |
| 255 | # never asks pub.dev what versions exist. |
| 256 | find "$out/pub-cache" -name '.cache' -type d -prune -exec rm -rf {} + |
| 257 | find "$out/m2" \( -name '*.lastUpdated' -o -name '_remote.repositories' \ |
| 258 | -o -name 'resolver-status.properties' -o -name '*.part' \ |
| 259 | -o -name 'maven-metadata-*.xml*' \) -delete |
| 260 | find "$out" \( -name '.DS_Store' -o -name '*.log' -o -name '.git' \) \ |
| 261 | -prune -exec rm -rf {} + |
| 262 | find "$out" -type d -empty -delete |
| 263 | chmod -R u+w "$out" |
| 264 | |
| 265 | # Last, after every cleanup above: anything still naming the |
| 266 | # store fails the fixed-output check, and the error names one |
| 267 | # path out of thousands of files. This names the files. |
| 268 | if refs="$(grep -rlI /nix/store "$out" 2>/dev/null)" && [ -n "$refs" ]; then |
| 269 | echo "cljd-deps: these still reference the store:" >&2 |
| 270 | echo "$refs" | head -20 >&2 |
| 271 | fi |
| 272 | |
| 273 | # If two runs disagree, this says which half to look in. Cheap, |
| 274 | # and the alternative is a hash mismatch with nothing attached. |
| 275 | for d in "$out"/*; do |
| 276 | echo "cljd-deps subtree $(basename "$d") $( (cd "$d" && find . -type f \ |
| 277 | -exec sha256sum {} + | sort -k2 | sha256sum) )" >&2 |
| 278 | done |
| 279 | for d in "$out"/pub-cache/*/*; do |
| 280 | [ -d "$d" ] || continue |
| Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago | 281 | # The path relative to $out, not the basename: two runs that |
| 282 | # disagree here disagree about *which* directory exists, and |
| 283 | # a bare `09` names nothing you can go and look at. |
| 284 | echo "cljd-deps pub ''${d#$out/} $( (cd "$d" && find . -type f \ |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 285 | -exec sha256sum {} + | sort -k2 | sha256sum) )" >&2 |
| 286 | done |
| 287 | ''; |
| 288 | |
| 289 | outputHashMode = "recursive"; |
| 290 | outputHashAlgo = "sha256"; |
| 291 | # Moves when flutter/deps.edn or flutter/pubspec.yaml move, and |
| 292 | # not when frq's own source does — see the stub above. |
| Four builds, four hashes, and the one file that was doing it b3b3cf6 nandi 5d ago | 293 | outputHash = "sha256-qSGx7WFdVyV7yu4R+EjiQHZcLqwDjYSlohiZcXB43DY="; |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 294 | }; |
| 295 | |
| 296 | # The Flutter desktop GUI, built rather than run out of the tree. |
| 297 | # |
| 298 | # `just flutter-desktop` is the working-tree loop and this is its |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 299 | # opposite number: the source is the flake's, the output is a store |
| 300 | # path, and the build is a sandbox with no network. It is the first thing here that builds |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 301 | # purely — the APK cannot, because Gradle fetches as it goes. |
| 302 | # |
| 303 | # Two stages, because the Dart does not exist until ClojureDart writes |
| 304 | # it. `preBuild` runs the compiler over `flutter/src` and `common/` |
| 305 | # with `--offline`, out of the caches `cljd-deps` fetched; everything |
| 306 | # after that is an ordinary Flutter application as far as nixpkgs is |
| 307 | # concerned. |
| 308 | # |
| 309 | # The caches are copied in rather than used where they lie. Maven, |
| 310 | # tools.gitlibs and pub all expect to be able to write to their own |
| 311 | # cache — a lock file, a resolved marker — and the store is read-only, |
| 312 | # so pointing them at $out of a fixed-output derivation fails in three |
| 313 | # different ways at three different depths. |
| 314 | # |
| 315 | # `src` is the whole tree and not `flutter/`: `flutter/deps.edn` puts |
| 316 | # `../common` on the classpath, which is the entire point of that |
| 317 | # directory, and a source root of `flutter/` would leave the screens |
| 318 | # outside it. |
| 319 | flutter-desktop-unwrapped = pkgs.flutter.buildFlutterApplication rec { |
| 320 | pname = "frq-flutter"; |
| 321 | version = "0.1.0"; |
| 322 | |
| 323 | src = lib.cleanSourceWith { |
| 324 | src = ./.; |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 325 | # Two trees, and only two: `flutter/` is the app and `common/` |
| 326 | # is the screens its deps.edn puts on the classpath. The root is |
| 327 | # still the source root because of that `../common`, but letting |
| 328 | # the *whole* root in means every file in the repo is an input — |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 329 | # so editing flake.nix or CLAUDE.md |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 330 | # invalidated the entire Dart compile and paid ten minutes for a |
| 331 | # change the Flutter build cannot even see. |
| 332 | # |
| 333 | # Matched on the path relative to the root rather than on |
| 334 | # basename: `src` as a basename would also exclude `flutter/src` |
| 335 | # and `common/src`, which is everything that matters. |
| 336 | filter = |
| 337 | let root = toString ./.; in |
| 338 | path: type: |
| 339 | let |
| 340 | rel = lib.removePrefix (root + "/") (toString path); |
| 341 | inTree = d: rel == d || lib.hasPrefix (d + "/") rel; |
| 342 | in |
| 343 | (inTree "flutter" || inTree "common") |
| 344 | # Build trees and caches, which are large, machine-specific |
| 345 | # and would make every one of them a new store path. |
| 346 | && !(builtins.elem (baseNameOf path) [ |
| 347 | "build" ".home" ".clojuredart" ".cpcache" "cljd-out" |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 348 | ".dart_tool" "result" ".git" "buck-out" |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 349 | ]); |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 350 | }; |
| 351 | sourceRoot = "source/flutter"; |
| 352 | |
| 353 | # Read at eval time, so the lock in git is the lock that is built. |
| 354 | autoPubspecLock = ./flutter/pubspec.lock; |
| 355 | |
| 356 | # git, because tools.deps resolves the ClojureDart dependency through |
| 357 | # tools.gitlibs even when every byte of it is already on disk — see |
| 358 | # the _repos note in cljd-deps. |
| 359 | nativeBuildInputs = [ pkgs.clojure pkgs.jdk17 pkgs.git ]; |
| 360 | |
| 361 | preBuild = '' |
| 362 | export PUB_CACHE="$NIX_BUILD_TOP/pub-cache" |
| 363 | export GITLIBS="$NIX_BUILD_TOP/gitlibs" |
| 364 | cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/pub-cache "$PUB_CACHE" |
| 365 | cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/gitlibs "$GITLIBS" |
| 366 | cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/m2 "$NIX_BUILD_TOP/m2" |
| 367 | mkdir -p .clojuredart |
| 368 | cp -r ${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}/clojuredart/cache .clojuredart/cache |
| 369 | chmod -R u+w "$PUB_CACHE" "$GITLIBS" "$NIX_BUILD_TOP/m2" .clojuredart |
| 370 | |
| 371 | # Resolve the analyzer project here rather than in cljd-deps, |
| 372 | # which was not allowed to name the store. Offline, out of the |
| 373 | # cache that derivation did fetch. ClojureDart only reaches for |
| 374 | # the network when `bin/analyzer.dart` is missing, and it is not. |
| 375 | for helper in .clojuredart/cache/*/cljd_helper; do |
| 376 | ( cd "$helper" && flutter pub get --offline ) |
| 377 | done |
| 378 | |
| 379 | # --offline is what keeps `pub get` out of a sandbox that has no |
| 380 | # network; the analyzer project it would otherwise resolve is |
| 381 | # already in .clojuredart, put there by cljd-deps. |
| 382 | clojure -Sdeps "{:mvn/local-repo \"$NIX_BUILD_TOP/m2\"}" \ |
| 383 | -M:cljd compile --offline |
| 384 | ''; |
| 385 | |
| 386 | meta = { |
| 387 | description = "frq's screens on Flutter's Linux target (no GL launcher)"; |
| 388 | mainProgram = "frq"; |
| 389 | platforms = systems; |
| 390 | }; |
| 391 | }; |
| 392 | |
| 393 | # The same shape as `frq` above: a launcher, and a package that is a |
| 394 | # symlink to it. The reason is the same one `frqScript` gives — on |
| 395 | # NixOS the store's Mesa is the system's and the window opens, and |
| 396 | # anywhere else the real driver is the host's, so the process is |
| 397 | # handed to nixGL. Without it the store build dies on a distrobox |
| 398 | # Arch with "No provider of eglGetPlatformDisplayEXT found", which is |
| 399 | # that failure wearing an EGL hat. |
| 400 | # |
| 401 | # A wrapper *around* the built application rather than a `postFixup` |
| 402 | # inside it, because buildFlutterApplication's own dartFixupHook runs |
| 403 | # after postFixup and rewrites `bin/frq` — so anything done to that |
| 404 | # path from inside is undone on the way out. |
| 405 | flutter-desktop = |
| 406 | let |
| 407 | unwrapped = |
| 408 | self.packages.${pkgs.stdenv.hostPlatform.system}.flutter-desktop-unwrapped; |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 409 | |
| 410 | # buildFlutterApplication's own wrapper appends a bare `/lib` to |
| 411 | # LD_LIBRARY_PATH — the host's, not the bundle's. Off NixOS that |
| 412 | # is a foreign library directory in front of nothing, and the app |
| 413 | # dies in the loader before main: first |
| 414 | # |
| 415 | # /lib/libc.so.6: undefined symbol: __pointer_chk_guard |
| 416 | # |
| 417 | # and, once the store's glibc is put ahead of it, |
| 418 | # |
| 419 | # libc.so.6: version `GLIBC_2.43' not found |
| 420 | # (required by /lib/libglib-2.0.so.0) |
| 421 | # |
| 422 | # which is the same bug wearing the other hat: the host's glib |
| 423 | # against the store's glibc. Ordering cannot fix a mixture, so |
| 424 | # the entry goes rather than moves. The wrapper is generated, so |
| 425 | # this edits a copy and asserts the edit landed — a silent miss |
| 426 | # here is a runtime failure on someone else's machine. |
| 427 | fixed = pkgs.runCommand "frq-flutter-wrapper" { } '' |
| 428 | mkdir -p "$out/bin" |
| 429 | sed "s|'/lib'||g" ${unwrapped}/bin/frq > "$out/bin/frq" |
| 430 | chmod +x "$out/bin/frq" |
| 431 | if grep -q "'/lib'" "$out/bin/frq"; then |
| 432 | echo "the /lib entry outlived the edit; look at the wrapper" >&2 |
| 433 | exit 1 |
| 434 | fi |
| 435 | ''; |
| 436 | |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 437 | script = pkgs.writeShellScript "frq" '' |
| 438 | runner="" |
| 439 | [ -e /run/current-system ] || runner="${nixGLFor pkgs}/bin/nixGLIntel" |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 440 | exec ''${runner} ${fixed}/bin/frq "$@" |
| Build the Flutter desktop GUI with nix, sandbox and all 6ca9b5a nandi 6d ago | 441 | ''; |
| 442 | in |
| 443 | pkgs.runCommand "frq-flutter-0.1.0" |
| 444 | { |
| 445 | meta = { |
| 446 | description = "frq's screens on Flutter's Linux target"; |
| 447 | mainProgram = "frq"; |
| 448 | platforms = systems; |
| 449 | }; |
| 450 | } |
| 451 | '' |
| 452 | mkdir -p "$out/bin" |
| 453 | ln -s ${script} "$out/bin/frq" |
| 454 | ''; |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 455 | }); |
| 456 | |
| Run this tree on a native half the builders made a32699e nandi 17d ago | 457 | devShells = forEachSystem (pkgs: |
| 458 | let |
| 459 | inherit (pkgs) lib; |
| 460 | in |
| 461 | { |
| The binding is Dart, and it works f7aea3b nandi 10h ago | 462 | # Dart on its own, for the tests that need no Flutter: the FFI |
| 463 | # binding to the Nim core runs on the plain VM, and making it wait |
| 464 | # for a Flutter toolchain would throw away the reason it is fast. |
| 465 | # |
| 466 | # Flutter bundles a Dart, so this is a duplicate in one sense. It is |
| 467 | # also thirty times smaller, and the point of the boundary is that |
| 468 | # you can check it without the thing on the other side of it. |
| 469 | dart = pkgs.mkShellNoCC { |
| 470 | name = "frq-dart"; |
| 471 | packages = [ pkgs.dart pkgs.just ]; |
| A message in #test, from Nim 35994d4 nandi 9h ago | 472 | |
| 473 | # libfrqcore.so is linked against OpenSSL, and the process that |
| 474 | # dlopens it has to be able to find one. Named here rather than |
| 475 | # left to the host: a machine whose libssl is a different soname |
| 476 | # fails at `frq_init` with a message about the wrong library. |
| 477 | LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.openssl ]; |
| 478 | |
| The binding is Dart, and it works f7aea3b nandi 10h ago | 479 | FRQ_DART = "1"; |
| 480 | }; |
| 481 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 482 | # Nim, for `nim/` — the portable core as a native library. Just the |
| 483 | # compiler: the core has no dependencies outside Nim's own standard |
| 484 | # library, deliberately, because a dependency here is one that has |
| 485 | # to cross-compile to every target the Dart side runs on. |
| 486 | # |
| 487 | # `nim c` shells out to a C compiler, so this is mkShell and not |
| 488 | # mkShellNoCC: stdenv brings the one Nim will find. |
| 489 | nim = pkgs.mkShell { |
| 490 | name = "frq-nim"; |
| 491 | packages = [ pkgs.nim pkgs.just ]; |
| 492 | |
| A message in #test, from Nim 35994d4 nandi 9h ago | 493 | # OpenSSL, because `-d:ssl` in nim/nim.cfg makes std/net link |
| 494 | # -lssl and -lcrypto: the IRC connection is TLS on :6697, which is |
| 495 | # the only port freeq actually listens on. |
| 496 | buildInputs = [ pkgs.openssl ]; |
| 497 | |
| 498 | # And on the loader path as well as the linker's. Nim resolves the |
| 499 | # OpenSSL entry points through dynlib at run time, so without this |
| 500 | # `newContext` finds nothing behind the symbol and dies with a |
| 501 | # SIGSEGV that says nothing about SSL at all. |
| 502 | LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.openssl ]; |
| 503 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 504 | # The recipe's re-entry test, the way FRQ_FLUTTER_DESKTOP is the |
| 505 | # desktop one's. |
| 506 | FRQ_NIM = "1"; |
| Run this tree on a native half the builders made a32699e nandi 17d ago | 507 | }; |
| Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago | 508 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 509 | # The APK toolchain. It is not in a default shell any more because |
| 510 | # there is no default shell: what used to be one belonged to the |
| 511 | # libcosmic frontend, and a Flutter build asks for a toolchain by |
| 512 | # name. |
| 513 | # |
| 514 | # Flutter brings its own Dart, Gradle and a JDK's worth of |
| 515 | # closure: Flutter brings its own Dart, Gradle and a JDK's worth of |
| Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago | 516 | # closure, and a desktop build has no use for any of it. |
| 517 | # |
| 518 | # `just apk` used to name these as `nix shell nixpkgs#clojure |
| 519 | # nixpkgs#jdk17 nixpkgs#flutter`, which is the flake registry's |
| 520 | # nixpkgs and not this flake's — so the Flutter under the APK |
| 521 | # floated while everything else was locked. Same three packages, |
| 522 | # from flake.lock now. |
| 523 | # |
| 524 | # JDK 17 and not newer on purpose: the Flutter template's Gradle |
| 525 | # plugin pins a Gradle that rejects a JDK it was released before, |
| 526 | # and the failure reads as an unsupported class file version rather |
| 527 | # than as a version mismatch. |
| 528 | flutter = pkgs.mkShellNoCC { |
| 529 | name = "frq-flutter"; |
| Seed the ClojureDart caches out of the flake f2e12af nandi 6d ago | 530 | |
| 531 | # git, because tools.deps resolves the ClojureDart dependency |
| 532 | # through tools.gitlibs even when every byte of it is already in |
| 533 | # the seeded cache — the same reason flutter-desktop-unwrapped |
| 534 | # names it. The host's git has always been there to answer; naming |
| 535 | # it means the shell does not depend on that. |
| 536 | packages = [ pkgs.clojure pkgs.jdk17 pkgs.flutter pkgs.just pkgs.git ]; |
| Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago | 537 | |
| 538 | # Where the recipe copies from. Naming it here is also what makes |
| 539 | # entering the shell build it, so the first `just apk` does not |
| 540 | # stop for a few hundred megabytes of SDK with nothing said about |
| 541 | # why. |
| 542 | FRQ_ANDROID_SDK = |
| 543 | "${androidSdkFor pkgs.stdenv.hostPlatform.system}/libexec/android-sdk"; |
| Seed the ClojureDart caches out of the flake f2e12af nandi 6d ago | 544 | |
| 545 | # The Maven, gitlibs and pub caches the ClojureDart compile would |
| 546 | # otherwise fetch, plus the analyzer project it writes under |
| 547 | # .clojuredart. Here for both of FRQ_ANDROID_SDK's reasons: it is |
| 548 | # where the recipe copies from, and naming it is what makes |
| 549 | # entering the shell build it. |
| 550 | # |
| 551 | # The compile still runs online. These are a warm start and not a |
| 552 | # pin — `--offline` would be, and would turn adding a line to |
| 553 | # flutter/deps.edn into a re-hash of cljd-deps before anything |
| 554 | # compiled again. The sandbox build takes that trade because it |
| 555 | # has no network; the loop someone edits in should not. |
| 556 | FRQ_CLJD_DEPS = "${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}"; |
| Build the APK from the flake, at the rev flake.lock pins 77d4bec nandi 7d ago | 557 | }; |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 558 | |
| 559 | # The other desktop GUI. Same ClojureDart half as the APK — one |
| 560 | # `clojure -M:cljd compile`, one flutter/src — over Flutter's Linux |
| 561 | # target instead of its Android one, so `flutter/linux/` is the |
| 562 | # runner and CMake and Ninja are the build rather than Gradle. |
| 563 | # |
| 564 | # A separate shell from `flutter` rather than one that carries both, |
| 565 | # because the halves are disjoint: this wants GTK and a C++ toolchain |
| 566 | # and no JDK, and the APK wants a JDK and an SDK and no GTK. Sharing |
| 567 | # them would mean every desktop build paying for a few hundred |
| 568 | # megabytes of Android SDK it never opens, which is the same argument |
| 569 | # that keeps Flutter out of the default shell. |
| 570 | # |
| 571 | # mkShell and not mkShellNoCC, unlike every other shell here: this is |
| 572 | # the one that actually compiles C++. stdenv brings the compiler, and |
| 573 | # gtk3 in buildInputs is what puts its .pc file where the runner's |
| 574 | # `pkg_check_modules(GTK gtk+-3.0)` can find it. |
| 575 | flutter-desktop = pkgs.mkShell { |
| 576 | name = "frq-flutter-desktop"; |
| 577 | |
| 578 | # clojure and flutter are the APK shell's, and deliberately the |
| 579 | # same two: the Dart that runs here is generated by the same |
| 580 | # compiler from the same source, and a second Flutter version |
| 581 | # under it would be a second set of engine artifacts and a second |
| 582 | # answer to "does the phone build match the desktop one". |
| 583 | nativeBuildInputs = [ |
| 584 | pkgs.clojure |
| 585 | pkgs.flutter |
| 586 | pkgs.just |
| 587 | pkgs.cmake |
| 588 | pkgs.ninja |
| 589 | pkgs.pkg-config |
| 590 | ]; |
| 591 | |
| 592 | # gtk3 is the runner's own dependency; the rest are url_launcher's |
| 593 | # Linux implementation, which is a GTK plugin compiled into the |
| 594 | # bundle. path_provider needs nothing here — its Linux half is |
| 595 | # pure Dart over the XDG directories. |
| 596 | buildInputs = [ pkgs.gtk3 pkgs.glib ]; |
| 597 | |
| The window connects 1d62d1a nandi 9h ago | 598 | # Where libfrqcore.so's OpenSSL lives. A named variable and NOT |
| 599 | # LD_LIBRARY_PATH, deliberately: this shell also runs Flutter |
| 600 | # through nixGL, which does its own careful things to the loader |
| 601 | # path, and a blanket LD_LIBRARY_PATH here is the sort of thing |
| 602 | # that breaks GL on one machine and not another. The `nim-spike` |
| 603 | # recipe prepends this for the app it launches and nothing else. |
| 604 | FRQ_OPENSSL_LIB = lib.makeLibraryPath [ pkgs.openssl ]; |
| 605 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 606 | # Flutter paints through GL, and off NixOS the driver that can do |
| 607 | # that is the host's, not the store's. |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 608 | NIXGL = "${nixGLFor pkgs}/bin/nixGLIntel"; |
| 609 | |
| Seed the ClojureDart caches out of the flake f2e12af nandi 6d ago | 610 | # The `flutter` shell's, deliberately the same one and for the |
| 611 | # same reason clojure and flutter are: the ClojureDart half of |
| 612 | # both builds is one compile over one deps.edn, so a second set of |
| 613 | # caches would be a second answer to what it resolved against. |
| 614 | # This recipe reads the variable directly — it is inside this |
| 615 | # shell before it does any of the work — where `just apk` reaches |
| 616 | # for the flake output itself. |
| 617 | FRQ_CLJD_DEPS = "${self.packages.${pkgs.stdenv.hostPlatform.system}.cljd-deps}"; |
| 618 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 619 | # The recipe's re-entry test. Nothing else sets it, so `just flutter-desktop` |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 620 | # outside the shell re-enters and lands back on the same recipe — |
| 621 | # no flag to forget, and no second code path for someone who runs |
| 622 | # `nix develop .#flutter-desktop --command just flutter-desktop` |
| 623 | # by hand. |
| 624 | FRQ_FLUTTER_DESKTOP = "1"; |
| 625 | }; |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 626 | |
| Three tarballs where a devShell was 5ce66d5 nandi 13h ago | 627 | # No `flutter-web` shell here any more. The web target was the one |
| 628 | # that needed nothing of the host -- no JDK and no Android SDK as |
| 629 | # the APK wants, no GTK and no C++ and no nixGL as the desktop one |
| 630 | # does -- and a devShell whose only job is to hand over a Dart and |
| 631 | # a JVM is a devShell that a pinned tarball can replace. It did: |
| 632 | # `tools/toolchain.sh` fetches Flutter, a JDK and the Clojure CLI by |
| 633 | # sha256, `tools/build-web.sh` builds out of them, and |
| 634 | # `.modal/flutter-web/` runs that same script on a plain Debian |
| 635 | # image with no store to populate. |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 636 | # |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 10h ago | 637 | # The two shells above stay. What they supply is a host toolchain, |
| Three tarballs where a devShell was 5ce66d5 nandi 13h ago | 638 | # which is exactly what nix is better at than a tarball. |
| Run this tree on a native half the builders made a32699e nandi 17d ago | 639 | }); |
| 640 | |
| Build the client with Nix, not just a shell for it ca3afbe nandi 19d ago | 641 | }; |
| 642 | } |