| 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 = ["."] |
| 14 | |
| 15 | # Two volumes doing two different jobs. `nix-cache` is the binary cache every |
| 16 | # container here reads from and writes back to. `devshell` is the working |
| 17 | # state of a `nix develop` loop, and it is shared by every container that has |
| 18 | # one -- each gets its own directory under it, named for the devShell it |
| 19 | # belongs to, so two projects (or two shells of one project) never write the |
| 20 | # same tree. Modal Volumes have no locking, so the directories are the only |
| 21 | # thing keeping them apart, and two runs of the *same* devshell must not |
| 22 | # overlap. |
| 23 | [volumes] |
| 24 | nix-cache = "/nix-cache" |
| 25 | devshell = "/devshell" |
| 26 | |
| 27 | [resources] |
| 28 | cpu = 8 |
| 29 | memory = 16384 |
| 30 | timeout = 3600 |
| 31 | |
| 32 | [run] |
| 33 | workdir = "/app" |
| 34 | # Nix for the dependencies, the ordinary toolchain for the build. `nix build` |
| 35 | # cannot do this: a derivation is all-or-nothing, so any edit is a fresh |
| 36 | # sandbox and a fresh compile of everything. Here the devShell supplies the |
| 37 | # compiler and the libraries, and `flutter build` decides what is stale -- |
| 38 | # which is the whole reason `just flutter-desktop` exists as the working-tree |
| 39 | # loop rather than as another `nix build`. |
| 40 | # |
| 41 | # rsync and not cp, with --checksum and not mtimes: Modal copies the source in |
| 42 | # with fresh timestamps on every run, so a plain copy would look entirely new |
| 43 | # to Flutter and rebuild the lot. --checksum compares content, leaves the |
| 44 | # unchanged files' timestamps alone, and lets the incremental build work. |
| 45 | # |
| 46 | # The excludes are the state that must NOT be overwritten from /app -- it is |
| 47 | # what we are here to keep. `just flutter-desktop` seeds those caches only |
| 48 | # when they are missing, so finding them warm is all it takes. |
| 49 | command = """ |
| 50 | set -e |
| 51 | # This container's own directory on the shared devshell volume, named for the |
| 52 | # devShell it keeps the state of. Anything else using this volume picks its |
| 53 | # own name and the two never meet. |
| 54 | SHELL_DIR=/devshell/frq-flutter-desktop |
| 55 | mkdir -p "$SHELL_DIR" |
| 56 | |
| 57 | # A worktree's `.git` is a *file* naming a gitdir back on the machine that |
| 58 | # copied it in, and nix believes it and goes looking for a path that is not |
| 59 | # here. It has to go before any flake reference to /app. |
| 60 | rm -rf /app/.git |
| 61 | |
| 62 | echo "sync: /app -> $SHELL_DIR" |
| 63 | # `nix shell --command` and not `nix profile install`: a profile install puts |
| 64 | # rsync in ~/.nix-profile/bin, which is not on the PATH of the shell already |
| 65 | # running, so the very next line said `rsync: command not found`. |
| 66 | # |
| 67 | # rsync and not cp, with --checksum and not mtimes: Modal copies the source in |
| 68 | # with fresh timestamps every run, so a plain copy looks entirely new to |
| 69 | # Flutter and rebuilds the lot. --checksum compares content and leaves the |
| 70 | # unchanged files' timestamps alone, which is the whole basis of the |
| 71 | # incremental build. |
| 72 | # |
| 73 | # The excludes are the state we are here to keep -- overwriting them from /app |
| 74 | # would defeat the volume. `just flutter-desktop` seeds those caches only when |
| 75 | # they are missing, so finding them warm is all it takes. |
| 76 | nix shell nixpkgs#rsync --accept-flake-config \ |
| 77 | --extra-substituters file:///nix-cache --command \ |
| 78 | rsync -a --checksum --delete \ |
| 79 | --exclude 'flutter/.home/' \ |
| 80 | --exclude 'flutter/.clojuredart/' \ |
| 81 | --exclude 'flutter/build/' \ |
| 82 | --exclude 'flutter/.dart_tool/' \ |
| 83 | --exclude '.git' \ |
| 84 | /app/ "$SHELL_DIR/" |
| 85 | |
| 86 | cd "$SHELL_DIR" |
| 87 | echo "state carried over:" |
| 88 | du -sh flutter/.home flutter/.clojuredart flutter/build 2>/dev/null \ |
| 89 | || echo " (none yet -- first run)" |
| 90 | |
| 91 | # Nix for the dependencies, the ordinary toolchain for the build. `nix build` |
| 92 | # cannot do this: a derivation is all-or-nothing, so any edit is a fresh |
| 93 | # sandbox and a fresh compile of everything. Here the devShell supplies the |
| 94 | # compiler and the libraries and `flutter build` decides what is stale. |
| 95 | # Evaluated from /app and built in the volume. Both halves matter: /app is the |
| 96 | # pristine copy, so nix stores a source tree of the repo rather than one |
| 97 | # carrying gigabytes of flutter/build, while the recipe still runs where the |
| 98 | # state it reuses lives -- `just -f` is what puts it there, since the recipe |
| 99 | # cds to its own justfile's directory. |
| 100 | nix develop /app#flutter-desktop --accept-flake-config \ |
| 101 | --extra-substituters file:///nix-cache \ |
| 102 | --max-jobs auto --command just -f "$SHELL_DIR/justfile" flutter-desktop |
| 103 | |
| 104 | echo "built:" |
| 105 | du -sh flutter/build |
| 106 | |
| 107 | # The devShell's closure is gigabytes of Flutter, Dart, clang and GTK, and the |
| 108 | # store it landed in belongs to the image rather than to a volume -- so |
| 109 | # without this every run re-fetches it from upstream. Written back, the next |
| 110 | # run substitutes it from file:///nix-cache instead. |
| 111 | if [ -f /nix-cache/nix-cache-info ]; then |
| 112 | echo "cache: writing the devShell closure back" |
| 113 | nix copy --no-check-sigs --all --to file:///nix-cache |
| 114 | fi |
| 115 | """ |
| 116 | env = { } |
| 117 | |
| 118 | [nix] |
| 119 | # No devShell warming at image build time: this enters `nix develop` at run |
| 120 | # time, on the VM, where the cache answers for its closure. The ptyshim that |
| 121 | # warming would need under gVisor is deprecated and does not come back. |
| 122 | flake = false |
| 123 | shim = false |
| 124 | |
| 125 | # [experimental] overrides the sandbox default of vm_runtime = true. |