| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 1 | [container] |
| 2 | name = "frq-flutter-dev" |
| 3 | description = "the Flutter desktop build, incremental, in a nix devShell" |
| 4 | base = "arch-nix" |
| 5 | # A Sandbox, not a Function: runs on a real VM, and the command is |
| 6 | # the sandbox's own process so it dies when the command does. |
| 7 | runtime = "sandbox" |
| 8 | |
| 9 | [build] |
| 10 | # The container lives inside the repo it builds, so the copy is rooted two |
| 11 | # levels up and `.` is the whole tree. |
| 12 | context = "../.." |
| 13 | include = ["."] |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago | 14 | # `dev` is the shell you actually want. There is no default shell in this |
| 15 | # flake any more -- the one that used to be there belonged to the retired |
| 16 | # libcosmic frontend -- so a bare `nix develop` here fails rather than |
| 17 | # resolving to the wrong tree. |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 18 | # The devShell, baked in rather than entered. `print-dev-env` writes the whole |
| 19 | # environment out as shell -- PATH, the compiler, every variable mkShell sets |
| 20 | # -- and realises its inputs on the way, so the closure becomes an image layer |
| 21 | # instead of a fetch every container pays for. Sourcing it from .bashrc means a |
| 22 | # shell attached to this container *is* the devShell: no `nix develop`, no |
| 23 | # clone of the flake's git inputs, no wait. |
| 24 | # |
| 25 | # `dev` stays for the case where the baked env is stale against a flake edit. |
| 26 | commands = [ |
| A shell that is already the devShell, and two recipes to reach it e853593 nandi 5d ago | 27 | "nix print-dev-env /app#flutter-desktop --accept-flake-config --extra-substituters file:///nix-cache > /etc/devshell.sh", |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 28 | "echo '. /etc/devshell.sh' >> /root/.bashrc", |
| 29 | "printf '#!/bin/sh\\nexec nix develop /app#flutter-desktop \"$@\"\\n' > /usr/local/bin/dev && chmod +x /usr/local/bin/dev", |
| 30 | ] |
| 31 | # The build state a local checkout carries: 395MB of a 441MB repo, uploaded on |
| 32 | # every start and wanted by nothing out there. Flutter builds into a volume of |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago | 33 | # its own, and the clojure caches are this machine's. |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 34 | ignore = [ |
| 35 | "flutter/build", "flutter/.home", "flutter/.dart_tool", |
| 36 | "flutter/.clojuredart", "flutter/.cpcache", |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago | 37 | ".cpcache", "result", "build", ".git", |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 38 | ] |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 39 | |
| 40 | # Two volumes doing two different jobs. `nix-cache` is the binary cache every |
| 41 | # container here reads from and writes back to. `devshell` is the working |
| 42 | # state of a `nix develop` loop, and it is shared by every container that has |
| 43 | # one -- each gets its own directory under it, named for the devShell it |
| 44 | # belongs to, so two projects (or two shells of one project) never write the |
| 45 | # same tree. Modal Volumes have no locking, so the directories are the only |
| 46 | # thing keeping them apart, and two runs of the *same* devshell must not |
| 47 | # overlap. |
| 48 | [volumes] |
| 49 | nix-cache = "/nix-cache" |
| 50 | devshell = "/devshell" |
| 51 | |
| 52 | [resources] |
| 53 | cpu = 8 |
| 54 | memory = 16384 |
| 55 | timeout = 3600 |
| 56 | |
| 57 | [run] |
| 58 | workdir = "/app" |
| 59 | # Nix for the dependencies, the ordinary toolchain for the build. `nix build` |
| 60 | # cannot do this: a derivation is all-or-nothing, so any edit is a fresh |
| 61 | # sandbox and a fresh compile of everything. Here the devShell supplies the |
| 62 | # compiler and the libraries, and `flutter build` decides what is stale -- |
| 63 | # which is the whole reason `just flutter-desktop` exists as the working-tree |
| 64 | # loop rather than as another `nix build`. |
| 65 | # |
| 66 | # rsync and not cp, with --checksum and not mtimes: Modal copies the source in |
| 67 | # with fresh timestamps on every run, so a plain copy would look entirely new |
| 68 | # to Flutter and rebuild the lot. --checksum compares content, leaves the |
| 69 | # unchanged files' timestamps alone, and lets the incremental build work. |
| 70 | # |
| 71 | # The excludes are the state that must NOT be overwritten from /app -- it is |
| 72 | # what we are here to keep. `just flutter-desktop` seeds those caches only |
| 73 | # when they are missing, so finding them warm is all it takes. |
| 74 | command = """ |
| 75 | set -e |
| 76 | # This container's own directory on the shared devshell volume, named for the |
| 77 | # devShell it keeps the state of. Anything else using this volume picks its |
| 78 | # own name and the two never meet. |
| 79 | SHELL_DIR=/devshell/frq-flutter-desktop |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 80 | mkdir -p "$SHELL_DIR" "$SHELL_DIR/.cache" |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 81 | |
| 82 | # A worktree's `.git` is a *file* naming a gitdir back on the machine that |
| 83 | # copied it in, and nix believes it and goes looking for a path that is not |
| 84 | # here. It has to go before any flake reference to /app. |
| 85 | rm -rf /app/.git |
| 86 | |
| 87 | echo "sync: /app -> $SHELL_DIR" |
| 88 | # `nix shell --command` and not `nix profile install`: a profile install puts |
| 89 | # rsync in ~/.nix-profile/bin, which is not on the PATH of the shell already |
| 90 | # running, so the very next line said `rsync: command not found`. |
| 91 | # |
| 92 | # rsync and not cp, with --checksum and not mtimes: Modal copies the source in |
| 93 | # with fresh timestamps every run, so a plain copy looks entirely new to |
| 94 | # Flutter and rebuilds the lot. --checksum compares content and leaves the |
| 95 | # unchanged files' timestamps alone, which is the whole basis of the |
| 96 | # incremental build. |
| 97 | # |
| 98 | # The excludes are the state we are here to keep -- overwriting them from /app |
| 99 | # would defeat the volume. `just flutter-desktop` seeds those caches only when |
| 100 | # they are missing, so finding them warm is all it takes. |
| 101 | nix shell nixpkgs#rsync --accept-flake-config \ |
| 102 | --extra-substituters file:///nix-cache --command \ |
| 103 | rsync -a --checksum --delete \ |
| 104 | --exclude 'flutter/.home/' \ |
| 105 | --exclude 'flutter/.clojuredart/' \ |
| 106 | --exclude 'flutter/build/' \ |
| 107 | --exclude 'flutter/.dart_tool/' \ |
| 108 | --exclude '.git' \ |
| 109 | /app/ "$SHELL_DIR/" |
| 110 | |
| 111 | cd "$SHELL_DIR" |
| 112 | echo "state carried over:" |
| 113 | du -sh flutter/.home flutter/.clojuredart flutter/build 2>/dev/null \ |
| 114 | || echo " (none yet -- first run)" |
| 115 | |
| 116 | # Nix for the dependencies, the ordinary toolchain for the build. `nix build` |
| 117 | # cannot do this: a derivation is all-or-nothing, so any edit is a fresh |
| 118 | # sandbox and a fresh compile of everything. Here the devShell supplies the |
| 119 | # compiler and the libraries and `flutter build` decides what is stale. |
| 120 | # Evaluated from /app and built in the volume. Both halves matter: /app is the |
| 121 | # pristine copy, so nix stores a source tree of the repo rather than one |
| 122 | # carrying gigabytes of flutter/build, while the recipe still runs where the |
| 123 | # state it reuses lives -- `just -f` is what puts it there, since the recipe |
| 124 | # cds to its own justfile's directory. |
| 125 | nix develop /app#flutter-desktop --accept-flake-config \ |
| 126 | --extra-substituters file:///nix-cache \ |
| 127 | --max-jobs auto --command just -f "$SHELL_DIR/justfile" flutter-desktop |
| 128 | |
| 129 | echo "built:" |
| 130 | du -sh flutter/build |
| 131 | |
| 132 | # The devShell's closure is gigabytes of Flutter, Dart, clang and GTK, and the |
| 133 | # store it landed in belongs to the image rather than to a volume -- so |
| 134 | # without this every run re-fetches it from upstream. Written back, the next |
| 135 | # run substitutes it from file:///nix-cache instead. |
| 136 | if [ -f /nix-cache/nix-cache-info ]; then |
| 137 | echo "cache: writing the devShell closure back" |
| 138 | nix copy --no-check-sigs --all --to file:///nix-cache |
| 139 | fi |
| 140 | """ |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 141 | # Nix's own cache, on the volume rather than in the container. Without it |
| 142 | # every Sandbox starts empty and `nix develop` re-clones the flake's git |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago | 143 | # inputs -- nixgl and its transitives -- |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 144 | # because flake.lock pins which revision to fetch, not whether it is already |
| 145 | # on disk. Set here rather than in the command so an interactive shell into |
| 146 | # this container gets it too. |
| 147 | env = { XDG_CACHE_HOME = "/devshell/frq-flutter-desktop/.cache" } |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 148 | |
| 149 | [nix] |
| Stop shipping the build tree, and let a shell be the devShell 013945f nandi 5d ago | 150 | # Every nix command in the container reads the mounted cache, including one |
| 151 | # typed by hand in a shell. Passing --extra-substituters per command only ever |
| 152 | # covered the scripts. |
| 153 | substituters = ["file:///nix-cache"] |
| The window opens, and a devShell that remembers what it built c20643a nandi 5d ago | 154 | # No devShell warming at image build time: this enters `nix develop` at run |
| 155 | # time, on the VM, where the cache answers for its closure. The ptyshim that |
| 156 | # warming would need under gVisor is deprecated and does not come back. |
| 157 | flake = false |
| 158 | shim = false |
| 159 | |
| 160 | # [experimental] overrides the sandbox default of vm_runtime = true. |