| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 1 | # Six verbs over one tree. |
| Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago | 2 | # |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 3 | # There is no nix here any more. `tools/toolchain.sh` fetches Flutter (which |
| 4 | # carries Dart), a JDK, the Clojure CLI and Nim as sha256-pinned tarballs into |
| 5 | # `.toolchain/`, and every recipe below runs inside the environment that |
| 6 | # script prints. One mechanism, and the same one the containers in `.modal/` |
| 7 | # use — which is what lets a plain Debian image build this. |
| 8 | # |
| 9 | # What the host still brings: a C compiler (Nim shells out to one), OpenSSL |
| 10 | # (nim.cfg is `-d:ssl`), git, curl, unzip, python3. For `run desktop` and the |
| 11 | # other Linux builds, GTK and the usual CMake/Ninja/pkg-config. |
| 12 | # |
| 13 | # just build TARGET apk desktop web lib ui app |
| 14 | # just run TARGET apk desktop web ui app |
| 15 | # just test SUITE all nim dart common live |
| 16 | # just modal CONTAINER dev web |
| 17 | # just serve [PORT] the Modal-built web bundle, on localhost |
| 18 | # just tools ... the toolchain itself |
| Take glimmer-vidya from gitlab, so a stranger can build this 5c40e75 nandi 19d ago | 19 | |
| Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago | 20 | set shell := ["bash", "-euo", "pipefail", "-c"] |
| A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago | 21 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 22 | # Every recipe is a `#!` script. Without this, "$@" is empty in one of them — |
| 23 | # just interpolates into a shebang recipe rather than handing it argv. |
| Give the terminal's classpath an nREPL, and hand recipes their arguments 664397d nandi 10d ago | 24 | set positional-arguments |
| 25 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 26 | root := justfile_directory() |
| 27 | tc := justfile_directory() / "tools/toolchain.sh" |
| Inline the babashka scripts into the justfile 76dcc6c nandi 11d ago | 28 | |
| A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago | 29 | default: |
| 30 | @just --list |
| 31 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 32 | # The toolchain, directly: `just tools versions`, `just tools android`, |
| 33 | # `just tools exec -- flutter doctor`. |
| 34 | [doc('the toolchain itself: versions, android, exec -- CMD')] |
| 35 | tools *args: |
| 36 | #!/usr/bin/env bash |
| 37 | cd "{{root}}" |
| 38 | exec "{{tc}}" "$@" |
| 39 | |
| 40 | # Build a target. |
| 41 | # |
| 42 | # apk the Android app, via Gradle |
| 43 | # desktop the ClojureDart app on Flutter's Linux target |
| 44 | # web the same screens compiled to JavaScript, into build/web |
| 45 | # lib the Nim core as build/nim/libfrqcore.so |
| 46 | # ui Nim owning the state and the screens, painted by Flutter |
| 47 | # app the ClojureDart app with the Nim core as its transport only |
| 48 | [doc('build a target: apk desktop web lib ui app')] |
| 49 | build target="desktop": |
| Build the APK, out of ClojureDart and Flutter e2b0e6b nandi 7d ago | 50 | #!/usr/bin/env bash |
| 51 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 52 | cd "{{root}}" |
| 53 | case "{{target}}" in |
| 54 | apk) just _flutter apk build ;; |
| 55 | desktop) just _flutter desktop build ;; |
| 56 | web) exec tools/build-web.sh build ;; |
| 57 | lib) just _nim-lib ;; |
| 58 | ui) just _flutter ui build ;; |
| 59 | app) just _flutter app build ;; |
| 60 | *) echo "usage: just build [apk|desktop|web|lib|ui|app]" >&2; exit 1 ;; |
| Build the APK, out of ClojureDart and Flutter e2b0e6b nandi 7d ago | 61 | esac |
| 62 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 63 | # Build a target and start it. |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 64 | # |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 65 | # apk install on a connected device and launch it |
| 66 | # web serve build/web on ARG (default 8080) |
| 67 | # the rest open the window |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 68 | # |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 69 | # just run apk |
| 70 | # just run web 3000 |
| 71 | [doc('build a target and start it')] |
| 72 | run target="desktop" arg="": |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 73 | #!/usr/bin/env bash |
| 74 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 75 | cd "{{root}}" |
| 76 | case "{{target}}" in |
| 77 | apk) just _flutter apk run ;; |
| 78 | desktop) just _flutter desktop run ;; |
| 79 | web) port="${2:-}"; exec tools/build-web.sh serve "${port:-8080}" ;; |
| 80 | ui) just _flutter ui run ;; |
| 81 | app) just _flutter app run ;; |
| 82 | log) just tools exec -- adb logcat -s flutter ;; |
| 83 | *) echo "usage: just run [apk|desktop|web|ui|app|log]" >&2; exit 1 ;; |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 84 | esac |
| A shell that is already the devShell, and two recipes to reach it e853593 nandi 5d ago | 85 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 86 | # Test a suite. |
| 87 | # |
| 88 | # common what may appear in common/, read off the source. Needs no |
| 89 | # toolchain at all, which is why CI runs exactly this. |
| 90 | # nim the Nim core. No Flutter, no Dart, no SDK — a rule about the IRC |
| 91 | # wire format is checkable in a second. |
| 92 | # dart the Dart side of the FFI boundary, on the plain Dart VM. Passing |
| 93 | # `nim` and failing this one is a marshalling bug, which is why the |
| 94 | # two are separate suites. |
| 95 | # live the whole stack against a real freeq. Not in `all`: it wants a |
| 96 | # network and a running server. |
| 97 | # |
| 98 | # just test all of them |
| 99 | # just test nim tircparse one Nim file |
| 100 | [doc('run a suite: all common nim dart live')] |
| 101 | test suite="all" *args: |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 102 | #!/usr/bin/env bash |
| 103 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 104 | cd "{{root}}" |
| 105 | shift |
| 106 | case "{{suite}}" in |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi 14h ago | 107 | all) just test common && just test nim && just test dart \ |
| 108 | && just test layout ;; |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 109 | common) exec python3 tools/check-common.py common ;; |
| Four more modules into Nim: members, glyphs, emoji, store 3975b4b nandi 10h ago | 110 | emoji) exec python3 tools/emoji2nim.py ;; |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi 14h ago | 111 | layout) just _nim-lib |
| 112 | just _flutter layout test ;; |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 113 | nim) just _nim-test "$@" ;; |
| 114 | dart) just _nim-lib |
| 115 | exec "{{tc}}" exec -- bash -c \ |
| 116 | 'cd dart/frq_core && dart pub get && dart test -r expanded' ;; |
| 117 | live) just _nim-lib |
| 118 | exec "{{tc}}" exec -- bash -c \ |
| 119 | 'cd dart/frq_core && dart pub get >/dev/null && dart run tool/live_ui.dart "$@"' _ "$@" ;; |
| 120 | *) echo "usage: just test [all|common|nim|dart|live]" >&2; exit 1 ;; |
| 121 | esac |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 122 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 123 | # The containers in `.modal/`, run on Modal rather than here: this machine |
| 124 | # evaluates and Modal builds. See CLAUDE.md, which says so rather more firmly. |
| A shell that is already the devShell, and two recipes to reach it e853593 nandi 5d ago | 125 | # |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 126 | # `--shell` leaves a sandbox running with the container's own image, volumes |
| 127 | # and environment, and prints the command to attach to it. It blocks — an |
| 128 | # ephemeral app takes its sandbox down when the entrypoint returns — so attach |
| 129 | # from a second terminal and Ctrl-C here when done. The sandbox bills until |
| 130 | # you do. |
| A shell that is already the devShell, and two recipes to reach it e853593 nandi 5d ago | 131 | # |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 132 | # just modal dev |
| 133 | # just modal web --shell |
| 134 | [doc('run a .modal/ container on Modal')] |
| 135 | modal container="dev" *args: |
| A shell that is already the devShell, and two recipes to reach it e853593 nandi 5d ago | 136 | #!/usr/bin/env bash |
| 137 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 138 | cd "{{root}}" |
| 139 | shift || true |
| A shell that is already the devShell, and two recipes to reach it e853593 nandi 5d ago | 140 | exec modal run ".modal/{{container}}/container.py" "$@" |
| 141 | |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 142 | # The Modal-built web bundle, served from this machine on localhost. |
| 143 | # |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 144 | # Not a local build: `modal volume get` pulls down what `just modal |
| 145 | # web` already compiled, so this needs only python3. |
| 146 | # |
| 147 | # It exists for the auth broker rather than for convenience. freeq's broker |
| 148 | # finishes an OAuth login by redirecting to `return_to`, and only to an origin |
| 149 | # on its allowlist: its own https hosts, and http://localhost or |
| 150 | # http://127.0.0.1 on ANY port. Served from anywhere else — the Modal URL |
| 151 | # included — a Bluesky sign-in gets `400 Invalid return_to URL` and can never |
| 152 | # complete. Guest and app-password sign-in work on the deployed URL; neither |
| 153 | # goes near the broker. |
| 154 | [doc('serve the Modal-built web bundle on localhost')] |
| 155 | serve port="8080": |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 156 | #!/usr/bin/env bash |
| 157 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 158 | cd "{{root}}" |
| 159 | out="{{root}}/.web-local" |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 160 | mkdir -p "$out" |
| 161 | echo "fetching the Modal-built bundle…" |
| 162 | # --force: this is a mirror of the volume, and a stale file left behind |
| 163 | # would be served in preference to the one just built. |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 164 | modal volume get --force devshell frq-web/flutter/build/web "$out" |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 165 | echo |
| 166 | echo " http://localhost:{{port}}" |
| 167 | echo |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 168 | echo "Bluesky sign-in works here and not on the Modal URL. Put" |
| 169 | echo "wss://irc.freeq.at/irc in the Server field — a browser has no TCP." |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 170 | exec python3 -m http.server {{port}} --bind 127.0.0.1 --directory "$out/web" |
| 171 | |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 172 | # --- the work behind the verbs ------------------------------------------ |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 19h ago | 173 | |
| 174 | # The Nim core as a shared library, into build/nim. |
| 175 | # |
| The renderer, and a transport bug that took four tries 58e6c97 nandi 16h ago | 176 | # `--mm:orc` and not refc, and it is not a preference: refc gives each thread |
| 177 | # its own GC heap, so the Socket the reader and writer threads share is a ref |
| 178 | # from another heap and dereferencing it segfaults. ORC's heap is shared. |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 19h ago | 179 | # |
| 180 | # `-d:release` and not `-d:danger`: the bounds checks are what turn a |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 181 | # malformed line off a socket into an exception rather than a read past the |
| 182 | # end of a buffer, and this parses exactly that. |
| 183 | [private] |
| 184 | _nim-lib: |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 19h ago | 185 | #!/usr/bin/env bash |
| 186 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 187 | cd "{{root}}" |
| 188 | out="{{root}}/build/nim" |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 19h ago | 189 | mkdir -p "$out" |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 190 | exec "{{tc}}" exec -- bash -euo pipefail -c ' |
| 191 | cd nim |
| 192 | nim c --app:lib --mm:orc -d:release --hints:off \ |
| 193 | --path:src --out:"'"$out"'/libfrqcore.so" src/frq_core.nim |
| 194 | echo "built '"$out"'/libfrqcore.so" |
| 195 | nm -D --defined-only "'"$out"'/libfrqcore.so" | grep " T frq_" || true' |
| 196 | |
| 197 | [private] |
| 198 | _nim-test *args: |
| The binding is Dart, and it works f7aea3b nandi 19h ago | 199 | #!/usr/bin/env bash |
| 200 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 201 | cd "{{root}}" |
| 202 | exec "{{tc}}" exec -- bash -euo pipefail -c ' |
| 203 | cd nim |
| 204 | if [ -n "${1:-}" ]; then |
| 205 | exec nim c -r --hints:off --path:src "tests/$1.nim" |
| 206 | fi |
| 207 | for t in tests/t*.nim; do |
| 208 | echo "== $t" |
| 209 | nim c -r --hints:off --path:src "$t" |
| 210 | done' _ "$@" |
| 211 | |
| 212 | # The three Flutter targets, which differ only in what they compile and which |
| 213 | # entry point they paint. |
| 214 | # |
| 215 | # apk ClojureDart, then Gradle. Impure on purpose: Gradle resolves its |
| 216 | # own dependencies over the network and has sdkmanager install a |
| 217 | # platform and build-tools into ANDROID_HOME as it goes, so the |
| 218 | # SDK has to be writable — which is what `just tools android` gets |
| 219 | # it. Everything it leaves behind is under `.toolchain/` and |
| 220 | # gitignored. |
| 221 | # desktop the same `clojure -M:cljd compile`, Flutter's Linux target. |
| 222 | # ui no ClojureDart at all: `lib/main_nim.dart` asks the Nim core for |
| 223 | # a widget tree and paints it. |
| 224 | # app `frq.main-nim` is `frq.main` with one line changed — |
| 225 | # `frq.net.nim/install!` where it said `frq.net.dart/install!`. |
| 226 | # Every screen, cell and action is the one that was already there. |
| 227 | [private] |
| 228 | _flutter target action: |
| Nim under the existing UI, not instead of it 56551a8 nandi 18h ago | 229 | #!/usr/bin/env bash |
| 230 | set -euo pipefail |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 231 | cd "{{root}}" |
| 232 | case "{{target}}" in |
| 233 | apk) "{{tc}}" android ;; |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi 14h ago | 234 | ui|app|layout) just _nim-lib ;; |
| Nim under the existing UI, not instead of it 56551a8 nandi 18h ago | 235 | esac |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 236 | exec "{{tc}}" exec -- bash -euo pipefail -c ' |
| 237 | cd flutter |
| 238 | # Nim resolves OpenSSL through dynlib at run time; without the host |
| 239 | # library on the loader path `newContext` dies in a SIGSEGV that says |
| 240 | # nothing about SSL. |
| 241 | export LD_LIBRARY_PATH="${FRQ_OPENSSL_LIB:-}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" |
| 242 | cljd() { clojure -Sdeps "{:mvn/local-repo \"$FRQ_M2\"}" -M:cljd compile "$@"; } |
| 243 | |
| 244 | case "$1:$2" in |
| 245 | apk:*) |
| 246 | cljd |
| 247 | # Rewritten every run: it carries absolute paths. |
| 248 | flutter config --android-sdk "$ANDROID_HOME" >/dev/null |
| 249 | flutter build apk --debug |
| 250 | apk=build/app/outputs/flutter-apk/app-debug.apk |
| 251 | [ "$2" = run ] || exit 0 |
| 252 | adb install -r "$apk" |
| 253 | exec adb shell monkey -p uk.nandi.frq \ |
| 254 | -c android.intent.category.LAUNCHER 1 ;; |
| 255 | desktop:build) cljd; exec flutter build linux --debug ;; |
| 256 | desktop:run) cljd; exec flutter run -d linux ;; |
| 257 | ui:build) flutter pub get |
| 258 | exec flutter build linux --debug -t lib/main_nim.dart ;; |
| 259 | ui:run) flutter pub get |
| 260 | exec flutter run -d linux -t lib/main_nim.dart ;; |
| 261 | app:build) flutter pub get; cljd frq.main-nim |
| 262 | exec flutter build linux --debug -t lib/main_nim_app.dart ;; |
| 263 | app:run) flutter pub get; cljd frq.main-nim |
| 264 | exec flutter run -d linux -t lib/main_nim_app.dart ;; |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi 14h ago | 265 | # Widget tests, which lay every screen out for real. Headless: no |
| 266 | # GL, no window, which is what makes them the check a Wayland |
| 267 | # window cannot be. |
| 268 | layout:test) flutter pub get |
| 269 | exec flutter test test/nim_layout_test.dart ;; |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 270 | esac' _ "{{target}}" "{{action}}" |