Utensil's Zettelkasten-style forest of evergreen notes on math and tech. utensil.tngl.sh/forest/
0

Configure Feed

Select the types of activity you want to include in your feed.

forest / flake.nix
25 kB 520 lines
1{ 2 description = "utensil forest SSG toolchains tectonic + forester + WASM pkgs (pinned, cache-reusable; mirrors blog's flake pattern)"; 3 4 inputs = { 5 # Pinned nixpkgs — nixos-unstable as of 2026-05-29 (sha 64c08a7ca0…). Recent 6 # enough that pkgs.tectonic / pkgs.wasm-pack carry .version, and forester 7 # should be available. Full commit hash so input resolution doesn't go via 8 # the GH API. 9 nixpkgs.url = "github:NixOS/nixpkgs/64c08a7ca051951c8eae34e3e3cb1e202fe36786"; 10 }; 11 12 outputs = { self, nixpkgs }: 13 let 14 # Linux only — we build/run on the GH Actions runner (x86_64) and on 15 # tangled Spindle (x86_64). aarch64 included for local nix develop. 16 systems = [ "x86_64-linux" "aarch64-linux" ]; 17 forAll = nixpkgs.lib.genAttrs systems; 18 pkgsFor = system: import nixpkgs { inherit system; }; 19 20 # ---- forest-tectonic --------------------------------------------------- 21 # tectonic LaTeX engine + pre-warmed CTAN cache (built from a tiny doc 22 # so the render container starts with the packages it needs already 23 # downloaded). Replaces TeX Live 2025 scheme-full (~7GB) — tectonic 24 # binary ~30-50MB, cache ~200-300MB. 25 # 26 # NB: needs network at build time to populate the cache. Within nix 27 # sandbox=false. Fine on the depot job (build-depot.yml runs with 28 # sandbox=false in nix.conf to match blog's pattern); render then 29 # reuses the snapshot offline. 30 # Literal version strings everywhere — older nixpkgs wrap tectonic with 31 # a derivation that lacks an exposed .version, and we don't want the 32 # flake eval to depend on that. Bump as we move pins. 33 tectonicVersion = "0.15-with-cache"; 34 foresterVersion = "5ab7277"; 35 wasmPkgsVersion = "wgputoy-60d0bec+egglog-8d9b10e+rhai-9fa8066"; 36 texliveVersion = "small"; 37 38 # ---- forest-texlive --------------------------------------------------- 39 # forester internally shells out to plain `latex` (the DVI engine) for 40 # inline-math rendering — tectonic only writes PDF, isn't a drop-in. 41 # nixery rejects `texliveSmall` from the URL path (its Docker name 42 # rules forbid uppercase). So bundle it via the flake instead — same 43 # NAR closure pattern as tectonic + forester. 44 forestTexliveFor = pkgs: 45 # forester's inline-math job.tex uses \documentclass{standalone} 46 # plus \input{diagrams}/\input{string-diagrams} which pull in 47 # tikz-cd + xy. scheme-small alone misses standalone.cls; combine 48 # explicitly so we know what's in the closure. 49 # Empirical list — derived from parsing the latex log of pipeline 50 # 22716. 19 unique tex/<dir> directories were actually loaded; 51 # map each to its nixpkgs texlive attribute. scheme-medium was 52 # 1.2 GB on R2; scheme-small + the precise extras below should 53 # land ~75-150 MB. If the next render exposes more, add them 54 # here — every loaded path in the latex log maps to one 55 # texlive.<dir> attribute. 56 let texCombo = pkgs.texlive.combine { 57 inherit (pkgs.texlive) scheme-small 58 # Common across most forester preambles 59 standalone preview varwidth adjustbox collectbox 60 tikz-cd pgf mathtools stmaryrd 61 # tikz extras forester pulls (tikzmark, calc, fit are all inside 62 # tikz but tikzmark is a separately-packaged library) 63 tikzmark 64 # newpx font family + its non-propagated deps 65 newpx newtx kastrup fontaxes mweights 66 # Loaded directly per the 22716 log 67 amscls amsfonts amsmath 68 etoolbox xpatch xstring xkeyval 69 carlisle l3packages 70 graphics-def 71 # iter 96 — Latin Modern + CM-Super + math-friendly fonts so 72 # dvisvgm's kpathsea finds OTF/PFB for native font references 73 # tectonic emits in XDV. (iter 95 hit 'l3keys2e missing' in 74 # nixpkgs; that pkg is part of l3packages already which we 75 # have. Drop bare-l3keys2e + any other un-attribute'd names 76 # if they show up.) 77 lm lm-math cm-super pxfonts mathpazo psnfss 78 # Algorithm packages — used by ag-0018, uts-001F, uts-0018. 79 # `algpseudocodex` may not be a top-level attr in older 80 # nixpkgs (was added 2023); keep as best-effort and drop if 81 # build-depot errors on it. 82 algorithmicx pseudo tcolorbox 83 # forester package dependencies 84 todonotes utfsym 85 # newpx auxiliary fonts (tx fonts for math chains) 86 pgfplots tipa physics; 87 }; 88 in pkgs.stdenv.mkDerivation { 89 pname = "forest-texlive"; 90 version = "${texliveVersion}+standalone+tikz-cd"; 91 dontUnpack = true; 92 dontConfigure = true; 93 nativeBuildInputs = [ texCombo ]; 94 buildPhase = '' 95 runHook preBuild 96 ${texCombo}/bin/latex --version | head -1 97 ${texCombo}/bin/kpsewhich standalone.cls 98 runHook postBuild 99 ''; 100 installPhase = '' 101 runHook preInstall 102 mkdir -p $out 103 ln -s ${texCombo}/bin $out/bin 104 ln -s ${texCombo}/share $out/share || true 105 printf '%s\n' \ 106 'export PATH="'$out'/bin:''${PATH:-}"' \ 107 > $out/env.sh 108 runHook postInstall 109 ''; 110 meta.description = "texlive scheme-small + standalone/tikz-cd/xy combo for forester"; 111 }; 112 113 # Build tectonic from master @ 2025-10-06 (which supports the .ttb 114 # bundle format from doronbehar's nix overlay in tectonic#1269 comment 115 # 3551606513). nixpkgs's tectonic 0.16.9 is too old for the TL2024 116 # bundle; we need post-2025-10-06. 117 tectonicUnstableFor = pkgs: pkgs.tectonic-unwrapped.overrideAttrs (final: prev: { 118 version = "0.15.0-unstable-2025-10-06"; 119 src = pkgs.fetchFromGitHub { 120 owner = "tectonic-typesetting"; 121 repo = "tectonic"; 122 rev = "d7f3275adf6b501fc21122a1873912e970bf52ba"; 123 hash = "sha256-4p5ZU1O75xcE4pDUs1xwZkFkxJ+g3Rm9LL5Cog96Sm8="; 124 }; 125 patches = []; 126 cargoPatches = []; 127 cargoDeps = pkgs.rustPlatform.fetchCargoVendor { 128 inherit (final) src; 129 hash = "sha256-J3q0dtQ/qb/72b6A40TNYDIZvDrUSpxF3SFjhc+X0fw="; 130 }; 131 # Skip the sandbox-incompatible segfault test. 132 checkFlags = (prev.checkFlags or []) ++ [ 133 "--skip=tests::no_segfault_after_failed_compilation" 134 ]; 135 }); 136 137 tectonicBundleUrl = "https://bebopbamf-tex.syd1.cdn.digitaloceanspaces.com/texlive2024-0312.ttb"; 138 139 # dvisvgm 3.6 (Dec 2025) — fresh from upstream. nixpkgs's texlive bundles 140 # an older dvisvgm that crashes with `basic_string::substr` on some 141 # forest trees. Build standalone via autotools; we put the texlive 142 # combine's texmf-dist next to the binary so kpathsea finds .pro files. 143 dvisvgmNewerFor = pkgs: pkgs.stdenv.mkDerivation rec { 144 pname = "dvisvgm"; 145 version = "3.6"; 146 src = pkgs.fetchurl { 147 url = "https://github.com/mgieseki/dvisvgm/releases/download/${version}/dvisvgm-${version}.tar.gz"; 148 hash = "sha256-JkRrs7EHOf8JJcnkFrdtLSIgdcnV3Pr+biFGCdBy7Ro="; 149 }; 150 nativeBuildInputs = with pkgs; [ pkg-config ]; 151 buildInputs = with pkgs; [ 152 freetype ghostscript brotli woff2 zlib 153 # libkpathsea ships in texlive.bin.core (no top-level pkgs.kpathsea 154 # in this nixpkgs revision). 155 texlive.bin.core 156 ]; 157 # iter 73 — patch TWO upstream bugs that together break XDV font 158 # fallback for tectonic-produced output (ag-0018 / uts-001F / 159 # ca-000J tikz-cd): 160 # 161 # (1) FontManager.cpp:313 — `*it` returns the format-extension 162 # literal "otf"/"ttf" from the format-array, NOT the resolved 163 # font path. NativeFontImpl ends up with path="otf". 164 # (2) FontEngine.cpp:93 — setFont's sys:// gate uses `||` where 165 # it should be `&&`; lets any name with size <= 6 (including 166 # literal "otf") fall into substr(6) which throws on size<6. 167 # 168 # iter 70 patched (2) alone via substituteInPlace (silently failed, 169 # likely nix --replace deprecation). iter 71 used inline sed which 170 # ran but unmasked (1) as a "can't read font file otf" error since 171 # the literal "otf" path was still being set up by FontManager. 172 # iter 72 moved to a proper patch file (operator request). iter 73 173 # extends that patch to fix (1) as well — same file, just adds the 174 # FontManager hunk so the fallback re-resolves the real path. 175 patches = [ ./nix-patches/dvisvgm-fontengine-substr-fix.patch ]; 176 # autotools default — configure + make + make install. 177 }; 178 179 forestTectonicFor = pkgs: pkgs.stdenv.mkDerivation { 180 pname = "forest-tectonic"; 181 # Includes dvisvgm now too — nixery rejects the standalone dvisvgm 182 # pkg name; shipping it alongside tectonic in the same NAR avoids 183 # the manifest miss. 184 version = "${tectonicVersion}+dvisvgm+ttb2024"; 185 186 # No source — we build off the master tectonic plus a tiny 187 # warmup .tex file that pulls every package the real forest build 188 # is expected to need. 189 dontUnpack = true; 190 dontConfigure = true; 191 192 nativeBuildInputs = [ 193 (tectonicUnstableFor pkgs) pkgs.cacert 194 # Custom dvisvgm 3.6 (newer than nixpkgs's bundled one) to avoid 195 # the basic_string::substr crash on some forest documents. 196 (dvisvgmNewerFor pkgs) 197 # texlive runtime tree for kpathsea — provides texmf.cnf + the 198 # dvips PostScript headers (tex.pro / texps.pro / special.pro 199 # / color.pro). We don't use the bundled dvisvgm from this combo. 200 (pkgs.texlive.combine { inherit (pkgs.texlive) scheme-small dvips graphics; }) 201 pkgs.makeWrapper 202 ]; 203 204 buildPhase = '' 205 runHook preBuild 206 export HOME=$TMPDIR 207 export TECTONIC_CACHE_DIR=$HOME/.cache/Tectonic 208 mkdir -p "$TECTONIC_CACHE_DIR" 209 # Warmup: representative doc that loads the heavy packages forest 210 # uses. Add \usepackage{...} lines as forest LaTeX surfaces more. 211 cat > warmup.tex <<'WARMUP' 212 \documentclass[a4paper]{article} 213 \usepackage{amsmath, amssymb, amsthm} 214 \usepackage{xcolor} 215 \usepackage{tikz} 216 \usepackage{hyperref} 217 \usepackage{listings} 218 \begin{document} 219 Hello world. 220 \end{document} 221 WARMUP 222 # Pin to the BebopBamf TL2024 .ttb bundle (tectonic#1269 community 223 # workaround official bundles stopped at TL2022). Newer 224 # pseudo.sty in TL2024 auto-registers /tcb/pseudo/* keys forest 225 # expects, so the runtime CTAN override becomes redundant. 226 tectonic --bundle ${tectonicBundleUrl} --outdir . warmup.tex || true 227 ls -la "$TECTONIC_CACHE_DIR" || true 228 runHook postBuild 229 ''; 230 231 installPhase = '' 232 runHook preInstall 233 mkdir -p $out/bin $out/share 234 ln -s ${tectonicUnstableFor pkgs}/bin/tectonic $out/bin/tectonic 235 # dvisvgm via texlive.combine. kpathsea uses dvisvgm's argv0 236 # to locate texmf-dist/dvips/base/*.pro etc. A plain symlink 237 # would leave argv0 pointing at $out/bin/dvisvgm (and kpathsea 238 # would search $out/share/texmf-dist empty). Instead write a 239 # tiny wrapper that exec's the full-path binary under the 240 # combine's bindir; kpathsea then finds the .pro headers. 241 # Newer dvisvgm 3.6 from source. kpathsea uses argv[0] to compute 242 # SELFAUTOLOC. A plain symlink lets the kernel resolve it BEFORE 243 # exec, so argv[0] = the dvisvgm-3.6 store path kpathsea looks 244 # at dvisvgm-3.6/share/... (empty). Instead write a shell wrapper 245 # using bash's `exec -a` to FORCE argv[0] = $out/bin/dvisvgm, so 246 # kpathsea uses $out/share/texmf-dist (symlinked below). 247 dvisvgm_bin=${dvisvgmNewerFor pkgs}/bin/dvisvgm 248 texlive_drv=${pkgs.texlive.combine { inherit (pkgs.texlive) scheme-small dvips graphics; }} 249 test -x "$dvisvgm_bin" 250 # Stage the runtime tree at $out/share/<...> so kpathsea finds it 251 # via $out/bin/dvisvgm's SELFAUTOLOC = $out/bin ../share/... 252 for d in texmf-dist texmf-config texmf-var web2c; do 253 if [ -d "$texlive_drv/share/$d" ]; then 254 ln -s "$texlive_drv/share/$d" "$out/share/$d" 255 fi 256 done 257 # Locate ALL texmf.cnf files. texlive.combine writes a config 258 # override at share/texmf-config/web2c/texmf.cnf, while the main 259 # one (with PSHEADERS / DVIPSHEADERS / TEXMF definitions) lives at 260 # share/texmf-dist/web2c/texmf.cnf. kpathsea needs BOTH dirs in 261 # TEXMFCNF or it only reads the override and misses the search- 262 # path defaults tex.pro / texps.pro / special.pro / color.pro 263 # show up as "not found". 264 texmfcnf_dirs="" 265 while IFS= read -r f; do 266 d=$(dirname "$f") 267 case ":$texmfcnf_dirs:" in 268 *":$d:"*) ;; 269 *) texmfcnf_dirs="''${texmfcnf_dirs:+$texmfcnf_dirs:}$d" ;; 270 esac 271 done < <(find "$texlive_drv/share" -name texmf.cnf -type f 2>/dev/null) 272 if [ -z "$texmfcnf_dirs" ]; then 273 echo "ERROR: texmf.cnf not found in $texlive_drv/share" >&2 274 find "$texlive_drv/share" -maxdepth 4 -type d >&2 275 exit 1 276 fi 277 echo "TEXMFCNF dirs: $texmfcnf_dirs" 278 # PostScript headers (tex.pro, texps.pro, special.pro, color.pro, 279 # finclude.pro) from the dvips package. Iter 37's symlink approach 280 # to share/texmf-dist didn't reliably surface dvips/base in the 281 # rendered closure (render.yml's `find` returned empty). Just COPY 282 # them into $out/share/dvips/base known fixed path, no symlink 283 # traversal mysteries, and they ride with the forest-tectonic NAR 284 # closure independently of where texlive.combine stashes them. 285 mkdir -p $out/share/dvips/base 286 echo "Staging dvips .pro files at $out/share/dvips/base:" 287 # texlive.combine output is a tree of symlinks into per-package 288 # derivations. Plain `find` doesn't traverse them use -L to 289 # follow symlinks so .pro files inside the dvips pkg are found. 290 for pro in tex.pro texps.pro special.pro color.pro finclude.pro; do 291 found=$(find -L "$texlive_drv" -name "$pro" -type f 2>/dev/null | head -1) 292 if [ -n "$found" ]; then 293 cp -L "$found" $out/share/dvips/base/ 294 echo " $pro from $found" 295 else 296 echo " $pro MISSING in $texlive_drv" 297 fi 298 done 299 echo "Staged contents:" 300 ls -la $out/share/dvips/base/ 2>&1 || true 301 actual_dvips_dir="$out/share/dvips" 302 # Wrapper preserves argv[0] = $out/bin/dvisvgm so kpathsea 303 # SELFAUTOLOC = $out/bin. Plain `exec` (no -a) would let the 304 # kernel pass the dvisvgm-3.6 path as argv[0]. 305 # TEXMF is the umbrella search-cascade set it AND TEXMFDIST so 306 # texmf.cnf's $TEXMF/dvips/base lookup resolves to the combine. 307 cat > $out/bin/dvisvgm <<EOF 308 #!${pkgs.bash}/bin/bash 309 export TEXMFCNF="$texmfcnf_dirs" 310 export TEXMFDIST="$texlive_drv/share/texmf-dist" 311 export TEXMFROOT="$texlive_drv/share" 312 export TEXMF="$texlive_drv/share/texmf-dist" 313 export PSHEADERS=".:$actual_dvips_dir/base:$actual_dvips_dir/config:$actual_dvips_dir" 314 export TEXPSHEADERS="\$PSHEADERS" 315 export DVIPSHEADERS="\$PSHEADERS" 316 if [ -n "''${FOREST_KPSEDEBUG:-}" ]; then 317 export KPATHSEA_DEBUG=64 318 fi 319 exec -a "\$0" "$dvisvgm_bin" "\$@" 320 EOF 321 chmod +x $out/bin/dvisvgm 322 # snapshot the warmed cache under $out/share so render workflow 323 # can `cp -r $out/share/tectonic-cache ~/.cache/Tectonic`. 324 if [ -d "$TECTONIC_CACHE_DIR" ]; then 325 cp -r "$TECTONIC_CACHE_DIR" $out/share/tectonic-cache 326 fi 327 # env.sh source this on Spindle to wire HOME + cache + PATH 328 cat > $out/env.sh <<EOSH 329 export PATH="$out/bin:''${PATH:-}" 330 export TECTONIC_CACHE_DIR="''${HOME:-/tmp}/.cache/Tectonic" 331 mkdir -p "''${TECTONIC_CACHE_DIR}" 332 if [ -d "$out/share/tectonic-cache" ]; then 333 cp -rn "$out/share/tectonic-cache/." "''${TECTONIC_CACHE_DIR}/" 2>/dev/null || true 334 fi 335 EOSH 336 runHook postInstall 337 ''; 338 339 meta.description = "tectonic LaTeX engine + pre-warmed CTAN cache for forest"; 340 }; 341 342 # ---- forest-forester --------------------------------------------------- 343 # Built from sr.ht commit 5ab7277 via opam (same pin as gh-pages.yml). 344 # The closure (opam switch + forester binary) is captured under $out 345 # so the render workflow can nix-store --import and put forester on PATH. 346 # 347 # NB: opam needs network at build time → sandbox=false on the depot. 348 forestForesterFor = pkgs: pkgs.stdenv.mkDerivation { 349 pname = "forest-forester"; 350 version = foresterVersion; 351 352 dontUnpack = true; 353 dontConfigure = true; 354 355 nativeBuildInputs = with pkgs; [ 356 opam ocaml dune_3 gcc gnumake pkg-config 357 libev cacert openssl gmp 358 git curl 359 ]; 360 buildInputs = with pkgs; [ libev openssl gmp ]; 361 362 buildPhase = '' 363 runHook preBuild 364 export HOME=$TMPDIR 365 export OPAMROOT=$HOME/.opam 366 export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt 367 # OPAMNODEPEXTS=1 stops opam from calling apt-cache / brew (it doesn't 368 # have them in a nix sandbox; depexts are satisfied via nativeBuildInputs). 369 # OPAMYES + assume-yes avoids interactive prompts on init failure. 370 export OPAMNODEPEXTS=1 OPAMYES=1 OPAMNOSANDBOXING=1 371 opam init --bare --no-setup --disable-sandboxing -y 372 # Use ocaml-system (the nixpkgs ocaml we provide) so opam doesn't try 373 # to compile ocaml-base-compiler from source (which then tries apt-cache 374 # for system deps and fails on nixos containers). 375 opam switch create forester --empty -y 376 eval "$(opam env --switch=forester)" 377 opam install ocaml-system -y 378 opam pin add forester 'git+https://git.sr.ht/~jonsterling/ocaml-forester#5ab7277' --no-action -y 379 opam install forester -y 380 # nix stdenv doesn't ship `which`; check via command -v instead. 381 command -v forester || { echo "::error::forester binary missing after opam install"; exit 1; } 382 forester --version || true 383 runHook postBuild 384 ''; 385 386 installPhase = '' 387 runHook preInstall 388 mkdir -p $out/bin $out/share/opam 389 # Capture the forester binary directly. 390 cp "$OPAMROOT/forester/bin/forester" $out/bin/forester 391 # Also capture the opam switch directory so any forester runtime 392 # data (xsl templates etc.) is preserved. 393 cp -r "$OPAMROOT/forester/share" $out/share/opam/ || true 394 cat > $out/env.sh <<EOSH 395 export PATH="$out/bin:''${PATH:-}" 396 EOSH 397 runHook postInstall 398 ''; 399 400 meta.description = "forester (Jon Sterling, sr.ht 5ab7277) opam-built closure"; 401 }; 402 403 # ---- forest-wasm-pkgs -------------------------------------------------- 404 # Clones each upstream Rust→WASM crate at the pinned hash from build.sh, 405 # runs wasm-pack build --target web --release, captures ONLY the pkg/ 406 # outputs (NOT the Rust toolchain, NOT the target/ dir). This is the 407 # operator's "ship only the WASM, not the Rust" optimization — pkg/ 408 # dirs are 10s-100s of KB each. 409 forestWasmPkgsFor = pkgs: 410 let 411 # `path` mirrors build.sh's `lib_path` arg — relative to the work 412 # dir (where we clone the repo), points at the directory wasm-pack 413 # runs in. For wgputoy/rhaiscript the cloned repo IS the wasm-pack 414 # crate (path = name). For egglog the wasm-pack crate is a subdir 415 # of the clone (path = "egglog/web-demo"). 416 pins = [ 417 { name = "wgputoy"; url = "https://github.com/compute-toys/wgpu-compute-toy.git"; 418 hash = "60d0bec4bd912a54d5049f2c28c1bd6a0916e5ec"; 419 path = "wgputoy"; } 420 { name = "egglog"; url = "https://github.com/egraphs-good/egglog.git"; 421 hash = "8d9b10ec712106b21d10b7bf45d10c0f9d1d09c7"; 422 path = "egglog/web-demo"; } 423 { name = "rhaiscript"; url = "https://github.com/rhaiscript/playground"; 424 hash = "9fa80661bc9eb69363ac86879826dcd8ccb604af"; 425 path = "rhaiscript"; } 426 ]; 427 in pkgs.stdenv.mkDerivation { 428 pname = "forest-wasm-pkgs"; 429 version = wasmPkgsVersion; 430 431 dontUnpack = true; 432 dontConfigure = true; 433 434 nativeBuildInputs = with pkgs; [ 435 rustc cargo wasm-pack bun nodejs binaryen git cacert 436 patch # for nix/patches/wgputoy-surface-error-other.patch 437 # wgputoy's .cargo/config.toml asks for lld as the linker. 438 lld 439 ]; 440 441 buildPhase = '' 442 runHook preBuild 443 export HOME=$TMPDIR 444 export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt 445 mkdir -p $TMPDIR/work 446 cd $TMPDIR/work 447 ${pkgs.lib.concatMapStrings (p: '' 448 echo "=== building ${p.name} ===" 449 git clone --depth 1 "${p.url}" "${p.name}" 450 # git fetch/checkout target the cloned repo (egglog/, wgputoy/, ). 451 ( cd "${p.name}" 452 git fetch --depth 1 origin "${p.hash}" 453 git checkout "${p.hash}" 454 ) 455 # wasm-pack runs in ${p.path} for egglog the wasm-pack crate 456 # lives at egglog/web-demo (a subdir of the clone); for the 457 # others, path == name. 458 ( cd "${p.path}" 459 if [ "${p.name}" = "wgputoy" ]; then 460 # Two diff/context patches against upstream src/lib.rs + 461 # Cargo.toml `patch -p1` fails loudly if upstream drifts, 462 # vs a bare sed that would silently move. Tracked in 463 # utensil/forest#54 (item 1) + #55 (wasm-opt strategy). 464 patch -p1 --no-backup-if-mismatch < ${./nix/patches/wgputoy-surface-error-other.patch} 465 test "$(grep -c 'SurfaceError::Other' src/lib.rs)" -eq 2 \ 466 || { echo "::error::wgputoy patch produced unexpected arm count"; exit 1; } 467 patch -p1 --no-backup-if-mismatch < ${./nix/patches/wgputoy-wasm-opt-bulk-memory.patch} 468 fi 469 wasm-pack build --target web --release --out-dir pkg . -- --locked 470 ls -la pkg 471 ) 472 '') pins} 473 runHook postBuild 474 ''; 475 476 installPhase = '' 477 runHook preInstall 478 mkdir -p $out/pkg 479 ${pkgs.lib.concatMapStrings (p: '' 480 mkdir -p $out/pkg/${p.name} 481 cp -r $TMPDIR/work/${p.path}/pkg/. $out/pkg/${p.name}/ 482 '') pins} 483 ls -la $out/pkg 484 runHook postInstall 485 ''; 486 487 meta.description = "Pre-built WASM pkgs for forest (wgputoy + egglog + rhaiscript)"; 488 }; 489 490 in { 491 packages = forAll (system: 492 let pkgs = pkgsFor system; in { 493 forest-tectonic = forestTectonicFor pkgs; 494 forest-forester = forestForesterFor pkgs; 495 forest-wasm-pkgs = forestWasmPkgsFor pkgs; 496 # iter 95 — RE-ENABLED per operator directive after 30+ iters of 497 # tectonic-only experiments couldn't get dvisvgm to load LM OTF 498 # fonts that tectonic's XDV emits as native font_def names 499 # (lmroman10-italic, lmromancaps10-regular, etc.). texlive.combine 500 # produces a real TEXMF tree with ls-R, which dvisvgm's kpathsea 501 # actually honors at runtime — should restore the cmcsc10/cmti10 502 # rendering and close the visible-overlap gap on uts-001F's pseudo 503 # block + ag-0018. 504 forest-texlive = forestTexliveFor pkgs; 505 } 506 ); 507 508 # `nix develop` shells the toolchain — useful for local sanity checks. 509 devShells = forAll (system: 510 let pkgs = pkgsFor system; in { 511 default = pkgs.mkShell { 512 packages = [ 513 pkgs.tectonic pkgs.opam pkgs.bun pkgs.just pkgs.biome 514 pkgs.uv pkgs.xsltproc pkgs.nodejs pkgs.wasm-pack 515 ]; 516 }; 517 } 518 ); 519 }; 520}