| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 1 | [container] |
| 2 | name = "frq-dev" |
| 3 | description = "the Flutter desktop build, incremental, from a pinned toolchain" |
| 4 | # `debian:13-slim` and not `arch-nix`: there is no nix in this container any |
| 5 | # more, and no flake left in the tree for it to enter. The build is |
| 6 | # `just build desktop`, which gets Flutter, a JDK, the Clojure CLI and Nim out |
| 7 | # of `tools/toolchain.sh` by pinned sha256 -- so what the image owes it is the |
| 8 | # C and GTK half Flutter's Linux target links against, and nothing else. |
| 9 | registry = "debian:13-slim" |
| 10 | # A Sandbox, not a Function: runs on a real VM, and the command is |
| 11 | # the sandbox's own process so it dies when the command does. |
| 12 | runtime = "sandbox" |
| 13 | |
| 14 | [build] |
| 15 | # The container lives inside the repo it builds, so the copy is rooted two |
| 16 | # levels up and `.` is the whole tree. |
| 17 | context = "../.." |
| 18 | include = ["."] |
| 19 | # The whole image build, and it is one apt line. What used to be here -- a nix |
| 20 | # store to populate and a devShell to print into /etc/devshell.sh, so that a |
| 21 | # shell attached to this container *was* the devShell -- is gone with the nix |
| 22 | # it was for. The toolchain script now plays that part, and it lives on the |
| 23 | # volume rather than in a layer. |
| 24 | # |
| 25 | # The first half is what the toolchain itself needs: git, because Flutter |
| 26 | # shells out to it against its own SDK checkout and refuses to run without |
| 27 | # one; unzip and xz-utils for the tarballs; ca-certificates so curl can verify |
| 28 | # what it fetches; rsync for the sync below. |
| 29 | # |
| 30 | # The second half is Flutter's Linux target: CMake, Ninja and pkg-config drive |
| 31 | # the build, GTK 3 is what the runner links, and a C toolchain compiles both |
| 32 | # that and whatever `nim c` is asked for. libssl is Nim's: nim.cfg is |
| 33 | # `-d:ssl`, and std/net resolves -lssl and -lcrypto through dynlib at run time. |
| 34 | setup = [ |
| 35 | "apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git rsync tar unzip xz-utils && rm -rf /var/lib/apt/lists/*", |
| 36 | "apt-get update && apt-get install -y --no-install-recommends build-essential clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libssl-dev && rm -rf /var/lib/apt/lists/*", |
| 37 | ] |
| 38 | # The build state a local checkout carries: 395MB of a 441MB repo, uploaded on |
| 39 | # every start and wanted by nothing out there. Flutter builds into a volume of |
| The last of the Clojure 284b59c nandi 8h ago | 40 | # its own, and the Nim cache is this machine's. |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 41 | ignore = [ |
| 42 | "flutter/build", "flutter/.home", "flutter/.dart_tool", |
| 43 | # The ClojureDart compiler's output. Uploading a laptop's copy would make |
| 44 | # the rsync below overwrite the one the last container compiled, and every |
| 45 | # file whose content differed would look new to the compiler and to |
| 46 | # Flutter -- the incremental build undone by the thing meant to feed it. |
| 47 | # The toolchain, which is a gigabyte of Flutter SDK and lives on the |
| 48 | # volume out here. |
| 49 | ".toolchain", |
| 50 | ".cpcache", "result", "build", ".git", |
| 51 | # An editor's linter rewrites this while the upload is reading it, and |
| 52 | # Modal fails the whole run with "was modified during build process". |
| 53 | ".clj-kondo", |
| 54 | ] |
| 55 | |
| 56 | # One volume now, where there were two: the nix binary cache went with nix. |
| 57 | # `devshell` is the working state of an incremental loop, shared by every |
| 58 | # container that has one -- each gets its own directory under it, named for |
| The last of the Clojure 284b59c nandi 8h ago | 59 | # what it belongs to, so two containers never write the |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 60 | # same tree. Modal Volumes have no locking, so those directory names are the |
| 61 | # only thing keeping them apart, and two runs of the *same* container must not |
| 62 | # overlap. |
| 63 | [volumes] |
| 64 | devshell = "/devshell" |
| 65 | |
| 66 | [resources] |
| 67 | cpu = 8 |
| 68 | memory = 16384 |
| 69 | timeout = 3600 |
| 70 | |
| 71 | [run] |
| 72 | workdir = "/app" |
| 73 | # Source in, toolchain out of the volume, build in place. None of it |
| 74 | # evaluates anything: the old command spent its first minutes entering a |
| 75 | # devShell, printing an environment, caching that environment against |
| 76 | # flake.lock and copying a nix closure back afterwards, all to arrive at a |
| 77 | # PATH. A PATH is what `tools/toolchain.sh env` prints, out of a directory |
| 78 | # already on the volume. |
| 79 | # |
| 80 | # An incremental build is the point, and `nix build` could not give one: a |
| 81 | # derivation is all-or-nothing, so any edit is a fresh sandbox and a fresh |
| 82 | # compile of everything. Here the toolchain supplies the compiler and |
| 83 | # `flutter build` decides what is stale -- which is the whole reason |
| 84 | # `just build desktop` exists as the working-tree loop. |
| 85 | command = """ |
| 86 | set -e |
| 87 | SHELL_DIR=/devshell/frq-desktop |
| 88 | |
| 89 | # Beside the working tree and NOT inside it: the rsync below runs with |
| 90 | # --delete, so anything under $SHELL_DIR that is not in /app is removed on |
| 91 | # every run. A cache kept in there would be deleted moments before it was |
| 92 | # consulted. |
| 93 | export FRQ_TOOLCHAIN=/devshell/frq-desktop.toolchain |
| 94 | mkdir -p "$SHELL_DIR" "$FRQ_TOOLCHAIN" |
| 95 | |
| 96 | echo "sync: /app -> $SHELL_DIR" |
| 97 | # rsync and not cp, with --checksum and not mtimes: Modal copies the source in |
| 98 | # with fresh timestamps every run, so a plain copy looks entirely new to |
| 99 | # Flutter and rebuilds the lot. --checksum compares content and leaves the |
| 100 | # unchanged files' timestamps alone, which is the whole basis of the |
| 101 | # incremental build. |
| 102 | # |
| 103 | # The excludes are the state we are here to keep -- overwriting them from /app |
| 104 | # would defeat the volume. |
| 105 | rsync -a --checksum --delete \ |
| 106 | --exclude 'flutter/.home/' \ |
| 107 | --exclude 'flutter/build/' \ |
| 108 | --exclude 'flutter/.dart_tool/' \ |
| 109 | --exclude '.toolchain/' \ |
| 110 | --exclude '.git' \ |
| 111 | /app/ "$SHELL_DIR/" |
| 112 | |
| 113 | cd "$SHELL_DIR" |
| 114 | # What survived from the last run, by presence and not by size: `du` here |
| 115 | # walked the pub cache, the toolchain and every object of the last build over |
| 116 | # a network volume, for numbers nobody acts on. |
| The last of the Clojure 284b59c nandi 8h ago | 117 | for d in "$FRQ_TOOLCHAIN" build/nim flutter/build; do |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 118 | [ -d "$d" ] && echo " carried over: $d" |
| 119 | done |
| 120 | |
| 121 | # Run from the volume, where the state it reuses lives. `just` is not in this |
| 122 | # image and is not worth an apt line for one call: the recipe is a wrapper |
| 123 | # around the toolchain, and this is that wrapper. |
| The last of the Clojure 284b59c nandi 8h ago | 124 | # The Nim core first: the app dlopens it at startup, and a missing .so is a |
| 125 | # blank window with a StateError behind it. |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 126 | tools/toolchain.sh exec -- bash -euo pipefail -c ' |
| The last of the Clojure 284b59c nandi 8h ago | 127 | cd nim && nim c --app:lib --mm:orc -d:release --hints:off --path:src \ |
| 128 | --out:../build/nim/libfrqcore.so src/frq_core.nim |
| 129 | cd ../flutter |
| 130 | flutter pub get |
| Six verbs, and the last of the nix 2e24e64 nandi 15h ago | 131 | flutter build linux --debug' |
| 132 | |
| 133 | echo "built:" |
| 134 | du -sh flutter/build |
| 135 | """ |
| 136 | # Flutter keeps its settings under XDG_CONFIG_HOME and its own caches under |
| 137 | # XDG_CACHE_HOME. Both point into the volume so a second run finds what the |
| 138 | # first one decided. Set here rather than in the command so a shell into this |
| 139 | # container gets them too. |
| 140 | env = { XDG_CACHE_HOME = "/devshell/frq-desktop.toolchain/.cache", XDG_CONFIG_HOME = "/devshell/frq-desktop.toolchain/.config", FRQ_TOOLCHAIN = "/devshell/frq-desktop.toolchain" } |