| Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago | 1 | #!/bin/sh |
| 2 | #_( |
| 3 | exec "$(dirname "$0")/../scripts/bb" "$0" "$@" |
| 4 | ) |
| 5 | |
| 6 | ;; frq's Scheme, cross-compiled to an arm64 Chez boot image. |
| 7 | ;; |
| 8 | ;; build-jolt-boot.bb OUTPUT_DIRECTORY build the image |
| 9 | ;; build-jolt-boot.bb --stamp print what it would be built from |
| 10 | ;; |
| 11 | ;; The image is built from :paths alone — there is no dependency resolution |
| 12 | ;; inside a cross compile — so every source root deps.edn would have resolved |
| 13 | ;; is named here instead. Two of them are git dependencies, which means the |
| 14 | ;; jolt cache rather than a checkout; the shas come out of deps.edn so there is |
| 15 | ;; one place to bump them. |
| 16 | (require '[babashka.classpath :as cp]) |
| 17 | (cp/add-classpath (str (babashka.fs/path (babashka.fs/parent *file*) ".." "scripts"))) |
| 18 | (require '[frq.paths :as paths] |
| 19 | '[babashka.fs :as fs] |
| 20 | '[babashka.process :as p] |
| 21 | '[clojure.string :as str]) |
| 22 | |
| 23 | (def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) |
| 24 | (def jolt-native (paths/jolt-native root)) |
| 25 | (def args *command-line-args*) |
| 26 | (def stamp-only? (= "--stamp" (first args))) |
| 27 | |
| 28 | (def glimmer |
| 29 | (paths/env "GLIMMER" |
| 30 | (str (fs/path (fs/home) ".jolt" "gitlibs" |
| 31 | "https___gitlab.com_nandithebull_glimmer" |
| 32 | (paths/dep-sha root "https://gitlab.com/nandithebull/glimmer") |
| 33 | "src")))) |
| 34 | |
| 35 | ;; glimmer-vidya lives inside jolt-native, so a sibling checkout answers for it |
| 36 | ;; the way it answers for libvidya; the cache is the fallback, at the sha |
| 37 | ;; deps.edn pins. |
| 38 | (def glimmer-vidya |
| 39 | (or (paths/env "GLIMMER_VIDYA" nil) |
| 40 | (let [in-checkout (fs/path jolt-native "jolt" "glimmer-vidya" "src")] |
| 41 | (if (fs/directory? in-checkout) |
| 42 | (str in-checkout) |
| 43 | (str (fs/path (fs/home) ".jolt" "gitlibs" |
| 44 | "https___gitlab.com_nandithebull_jolt-native" |
| 45 | (paths/dep-sha root "https://gitlab.com/nandithebull/jolt-native") |
| 46 | "jolt" "glimmer-vidya" "src")))))) |
| 47 | |
| 48 | (paths/require-paths! "Jolt source root" [glimmer glimmer-vidya] |
| 49 | "run `jolt -M:frq --help` once to populate the git cache") |
| 50 | |
| 51 | ;; The DotSlash-pinned jolt, not whatever is on PATH: an upstream jolt cannot |
| 52 | ;; open a TLS connection on Android — it reads the socket address out of |
| 53 | ;; `struct addrinfo` at glibc's offset, which is Bionic's `ai_canonname` — so a |
| 54 | ;; build made with one produces an APK that cannot sign in or send a picture. |
| 55 | ;; Override with JOLT= to use another. |
| 56 | ;; |
| 57 | ;; DOTSLASH and JOLT_MANIFEST are set when buck runs this: the manifest and the |
| 58 | ;; fetcher are inputs to that action, so the machine running it needs neither |
| 59 | ;; jolt nor DotSlash installed, and a remote worker resolves the same pin |
| 60 | ;; against the same digest. Without them the shim beside this script answers, |
| 61 | ;; which is what a person at a terminal gets. |
| 62 | (def jolt |
| 63 | (or (paths/env "JOLT" nil) |
| 64 | (let [dotslash (paths/env "DOTSLASH" nil) |
| 65 | manifest (paths/env "JOLT_MANIFEST" nil)] |
| 66 | (if (and dotslash manifest) |
| 67 | (paths/out dotslash "--" "fetch" manifest) |
| 68 | (str (fs/path root "scripts" "jolt")))))) |
| 69 | |
| 70 | (def module (paths/env "MODULE" "frq.app")) |
| 71 | (def chez (paths/env "CHEZ_ANDROID" (str (fs/path (fs/home) ".cache" "vidya-chez-android")))) |
| 72 | (def host-scheme (str (fs/path chez "ta6le" "bin" "ta6le" "scheme"))) |
| 73 | (def target-boot (fs/path chez "boot" "tarm64le")) |
| 74 | (def xpatch (str (fs/path chez "xc-tarm64le" "s" "xpatch"))) |
| 75 | |
| 76 | (paths/require-paths! "Android Chez artifact" |
| 77 | [host-scheme (fs/path target-boot "petite.boot") |
| 78 | (fs/path target-boot "scheme.boot") |
| 79 | (fs/path target-boot "scheme.h") xpatch] |
| 80 | "Build Chez's tarm64le cross target first.") |
| 81 | |
| 82 | (when-not (or (fs/executable? jolt) (fs/which jolt)) |
| 83 | (paths/die (str "Jolt executable not found: " jolt))) |
| 84 | |
| 85 | ;; The boot image is a pure function of the Scheme sources, the module name, |
| 86 | ;; the flat-split flag and Chez's own boot files — all static. Hash them, and |
| 87 | ;; skip the whole thing when the stamp still matches: a Rust-only APK rebuild |
| 88 | ;; has no reason to spend fifteen single-threaded seconds recompiling Scheme. |
| 89 | ;; |
| 90 | ;; The flag is part of the stamp on purpose. JOLT_NO_FLAT_SPLIT changes the |
| 91 | ;; shape of what `jolt build` emits, so an app.build/ left by an ordinary build |
| 92 | ;; is not reusable here; a stamp miss wipes the tree below, which is what the |
| 93 | ;; unconditional delete used to be defending against. |
| 94 | |
| 95 | ;; The hash itself. Every input is hashed here rather than shelled out to |
| 96 | ;; sha256sum, so the stamp is one function to read. |
| 97 | (defn sha256 [^bytes bs] |
| 98 | (->> (.digest (java.security.MessageDigest/getInstance "SHA-256") bs) |
| 99 | (map #(format "%02x" (bit-and % 0xff))) |
| 100 | (apply str))) |
| 101 | |
| 102 | (defn file-line [f] |
| 103 | (str (sha256 (fs/read-all-bytes (str f))) " " f)) |
| 104 | |
| 105 | (defn stamp [] |
| 106 | (let [sources (->> [(fs/path root "src") glimmer glimmer-vidya] |
| 107 | (mapcat #(fs/glob % "**.{jolt,edn}")) |
| 108 | (map str) |
| 109 | sort) |
| 110 | lines (concat [module "JOLT_NO_FLAT_SPLIT=1" |
| 111 | (try (paths/out jolt "--version") (catch Exception _ ""))] |
| 112 | (map file-line sources) |
| 113 | (map file-line [(fs/path target-boot "petite.boot") |
| 114 | (fs/path target-boot "scheme.boot") |
| 115 | xpatch]))] |
| 116 | (sha256 (.getBytes (str/join "\n" lines) "UTF-8")))) |
| 117 | |
| 118 | ;; buck needs this before the work rather than after: the sources it hashes |
| 119 | ;; live in the jolt cache and in jolt-native, outside this cell, so nothing |
| 120 | ;; else makes them reach an action's digest. See the `buck` recipe in the |
| 121 | ;; justfile. |
| 122 | (when stamp-only? |
| 123 | (println (stamp)) |
| 124 | (System/exit 0)) |
| 125 | |
| 126 | (def out-dir (or (first args) |
| 127 | (paths/die "usage: build-jolt-boot.bb OUTPUT_DIRECTORY | --stamp"))) |
| 128 | (def stamp-file (fs/path out-dir "jolt.boot.stamp")) |
| 129 | (def want (stamp)) |
| 130 | |
| 131 | (when (and (fs/exists? (fs/path out-dir "jolt.boot")) |
| 132 | (fs/exists? (fs/path out-dir "scheme.h")) |
| 133 | (fs/exists? stamp-file) |
| 134 | (= want (str/trim (slurp (str stamp-file))))) |
| 135 | (binding [*out* *err*] (println "jolt boot image up to date")) |
| 136 | (System/exit 0)) |
| 137 | |
| 138 | (fs/delete-if-exists stamp-file) |
| 139 | (fs/delete-tree (fs/path out-dir "project")) |
| 140 | (fs/delete-tree (fs/path out-dir "cross")) |
| 141 | (fs/create-dirs (fs/path out-dir "project")) |
| 142 | (fs/create-dirs (fs/path out-dir "cross")) |
| 143 | (spit (str (fs/path out-dir "project" "deps.edn")) |
| 144 | (str "{:paths [\"" (fs/path root "src") "\" \"" glimmer "\" \"" glimmer-vidya "\"]}\n")) |
| 145 | |
| 146 | (p/shell {:dir (str (fs/path out-dir "project")) |
| 147 | :extra-env {"JOLT_NO_FLAT_SPLIT" "1"}} |
| 148 | jolt "build" "-m" module "-o" "app") |
| 149 | |
| 150 | (spit (str (fs/path out-dir "cross" "compile.ss")) |
| 151 | (str "(import (chezscheme))\n" |
| 152 | "(load \"" xpatch "\")\n" |
| 153 | "(optimize-level 2)\n" |
| 154 | "(generate-inspector-information #f)\n" |
| 155 | "(compile-file \"" (fs/path out-dir "project" "app.build" "flat.ss") "\"" |
| 156 | " \"" (fs/path out-dir "cross" "flat.so") "\")\n" |
| 157 | "(make-boot-file \"" (fs/path out-dir "jolt.boot") "\" '()\n" |
| 158 | " \"" (fs/path target-boot "petite.boot") "\"\n" |
| 159 | " \"" (fs/path target-boot "scheme.boot") "\"\n" |
| 160 | " \"" (fs/path out-dir "cross" "flat.so") "\")\n")) |
| 161 | |
| 162 | (p/shell {:extra-env {"SCHEMEHEAPDIRS" (str (fs/path chez "ta6le" "boot" "ta6le"))}} |
| 163 | host-scheme "--script" (str (fs/path out-dir "cross" "compile.ss"))) |
| 164 | |
| 165 | (fs/copy (fs/path target-boot "scheme.h") (fs/path out-dir "scheme.h") |
| 166 | {:replace-existing true}) |
| 167 | |
| 168 | ;; Last, so an interrupted build leaves no stamp and the next run redoes it. |
| 169 | (spit (str stamp-file) (str want "\n")) |