nandi/frqpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

The last of the Clojure 284b59c · on main · nandi · 7h ago
container.toml · 140 lines · 6.6 KBTOML Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
[container]
name        = "frq-dev"
description = "the Flutter desktop build, incremental, from a pinned toolchain"
# `debian:13-slim` and not `arch-nix`: there is no nix in this container any
# more, and no flake left in the tree for it to enter. The build is
# `just build desktop`, which gets Flutter, a JDK, the Clojure CLI and Nim out
# of `tools/toolchain.sh` by pinned sha256 -- so what the image owes it is the
# C and GTK half Flutter's Linux target links against, and nothing else.
registry    = "debian:13-slim"
# A Sandbox, not a Function: runs on a real VM, and the command is
# the sandbox's own process so it dies when the command does.
runtime     = "sandbox"

[build]
# The container lives inside the repo it builds, so the copy is rooted two
# levels up and `.` is the whole tree.
context = "../.."
include = ["."]
# The whole image build, and it is one apt line. What used to be here -- a nix
# store to populate and a devShell to print into /etc/devshell.sh, so that a
# shell attached to this container *was* the devShell -- is gone with the nix
# it was for. The toolchain script now plays that part, and it lives on the
# volume rather than in a layer.
#
# The first half is what the toolchain itself needs: git, because Flutter
# shells out to it against its own SDK checkout and refuses to run without
# one; unzip and xz-utils for the tarballs; ca-certificates so curl can verify
# what it fetches; rsync for the sync below.
#
# The second half is Flutter's Linux target: CMake, Ninja and pkg-config drive
# the build, GTK 3 is what the runner links, and a C toolchain compiles both
# that and whatever `nim c` is asked for. libssl is Nim's: nim.cfg is
# `-d:ssl`, and std/net resolves -lssl and -lcrypto through dynlib at run time.
setup = [
  "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/*",
  "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/*",
]
# The build state a local checkout carries: 395MB of a 441MB repo, uploaded on
# every start and wanted by nothing out there. Flutter builds into a volume of
# its own, and the Nim cache is this machine's.
ignore  = [
    "flutter/build", "flutter/.home", "flutter/.dart_tool",
    # The ClojureDart compiler's output. Uploading a laptop's copy would make
    # the rsync below overwrite the one the last container compiled, and every
    # file whose content differed would look new to the compiler and to
    # Flutter -- the incremental build undone by the thing meant to feed it.
    # The toolchain, which is a gigabyte of Flutter SDK and lives on the
    # volume out here.
    ".toolchain",
    ".cpcache", "result", "build", ".git",
    # An editor's linter rewrites this while the upload is reading it, and
    # Modal fails the whole run with "was modified during build process".
    ".clj-kondo",
]

# One volume now, where there were two: the nix binary cache went with nix.
# `devshell` is the working state of an incremental loop, shared by every
# container that has one -- each gets its own directory under it, named for
# what it belongs to, so two containers never write the
# same tree. Modal Volumes have no locking, so those directory names are the
# only thing keeping them apart, and two runs of the *same* container must not
# overlap.
[volumes]
devshell = "/devshell"

[resources]
cpu     = 8
memory  = 16384
timeout = 3600

[run]
workdir = "/app"
# Source in, toolchain out of the volume, build in place. None of it
# evaluates anything: the old command spent its first minutes entering a
# devShell, printing an environment, caching that environment against
# flake.lock and copying a nix closure back afterwards, all to arrive at a
# PATH. A PATH is what `tools/toolchain.sh env` prints, out of a directory
# already on the volume.
#
# An incremental build is the point, and `nix build` could not give one: a
# derivation is all-or-nothing, so any edit is a fresh sandbox and a fresh
# compile of everything. Here the toolchain supplies the compiler and
# `flutter build` decides what is stale -- which is the whole reason
# `just build desktop` exists as the working-tree loop.
command = """
set -e
SHELL_DIR=/devshell/frq-desktop

# Beside the working tree and NOT inside it: the rsync below runs with
# --delete, so anything under $SHELL_DIR that is not in /app is removed on
# every run. A cache kept in there would be deleted moments before it was
# consulted.
export FRQ_TOOLCHAIN=/devshell/frq-desktop.toolchain
mkdir -p "$SHELL_DIR" "$FRQ_TOOLCHAIN"

echo "sync: /app -> $SHELL_DIR"
# rsync and not cp, with --checksum and not mtimes: Modal copies the source in
# with fresh timestamps every run, so a plain copy looks entirely new to
# Flutter and rebuilds the lot. --checksum compares content and leaves the
# unchanged files' timestamps alone, which is the whole basis of the
# incremental build.
#
# The excludes are the state we are here to keep -- overwriting them from /app
# would defeat the volume.
rsync -a --checksum --delete \
    --exclude 'flutter/.home/' \
    --exclude 'flutter/build/' \
    --exclude 'flutter/.dart_tool/' \
    --exclude '.toolchain/' \
    --exclude '.git' \
    /app/ "$SHELL_DIR/"

cd "$SHELL_DIR"
# What survived from the last run, by presence and not by size: `du` here
# walked the pub cache, the toolchain and every object of the last build over
# a network volume, for numbers nobody acts on.
for d in "$FRQ_TOOLCHAIN" build/nim flutter/build; do
    [ -d "$d" ] && echo "  carried over: $d"
done

# Run from the volume, where the state it reuses lives. `just` is not in this
# image and is not worth an apt line for one call: the recipe is a wrapper
# around the toolchain, and this is that wrapper.
# The Nim core first: the app dlopens it at startup, and a missing .so is a
# blank window with a StateError behind it.
tools/toolchain.sh exec -- bash -euo pipefail -c '
    cd nim && nim c --app:lib --mm:orc -d:release --hints:off --path:src \
        --out:../build/nim/libfrqcore.so src/frq_core.nim
    cd ../flutter
    flutter pub get
    flutter build linux --debug'

echo "built:"
du -sh flutter/build
"""
# Flutter keeps its settings under XDG_CONFIG_HOME and its own caches under
# XDG_CACHE_HOME. Both point into the volume so a second run finds what the
# first one decided. Set here rather than in the command so a shell into this
# container gets them too.
env     = { XDG_CACHE_HOME = "/devshell/frq-desktop.toolchain/.cache", XDG_CONFIG_HOME = "/devshell/frq-desktop.toolchain/.config", FRQ_TOOLCHAIN = "/devshell/frq-desktop.toolchain" }