Write the build in babashka, and pin the babashka
Every script here was shell, and the justfile was shell with a comment on top. Shell is the wrong language for the work they do: reading a sha out of deps.edn, hashing a file set, holding a path without quoting it three times. Clojure is what the rest of this repo is written in, so the scripts are now Clojure too, run by a babashka pinned the way buck2 and jolt already are — the one on a machine's PATH may be anything, and here it was bazel. The recipes are one line each now, because a recipe body is a script nobody can run on its own, and buck runs two of these as actions. What was duplicated three times — where jolt-native is, in a worktree as in a checkout — is one function in scripts/frq/paths.clj, and the jolt-native sha is read from deps.edn rather than written down a second time. dotslash-to-buck was Python, and emits the same bytes it did before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
34832a8 parent: e8e4fa0 modified
README.md +2 -2 | @@ -97,8 +97,8 @@ C ABI, which owns the event loop as the NativeActivity's own library) and | ||
| 97 | 97 | from the vidya checkout; only the boot image is frq's. |
| 98 | 98 | |
| 99 | 99 | ```bash |
| 100 | -./android/build-apk.sh run # build, install, launch on a connected device | |
| 101 | -./android/build-apk.sh log # logcat, filtered | |
| 100 | +./android/build-apk.bb run # build, install, launch on a connected device | |
| 101 | +./android/build-apk.bb log # logcat, filtered | |
| 102 | 102 | ``` |
| 103 | 103 | |
| 104 | 104 | Needs what vidya's Android build needs — SDK, NDK r29, and a cross-built Chez |
| @@ -97,8 +97,8 @@ C ABI, which owns the event loop as the NativeActivity's own library) and | |||
| 97 | from the vidya checkout; only the boot image is frq's. | 97 | from the vidya checkout; only the boot image is frq's. |
| 98 | 98 | ||
| 99 | ```bash | 99 | ```bash |
| 100 | -./android/build-apk.sh run # build, install, launch on a connected device | 100 | +./android/build-apk.bb run # build, install, launch on a connected device |
| 101 | -./android/build-apk.sh log # logcat, filtered | 101 | +./android/build-apk.bb log # logcat, filtered |
| 102 | ``` | 102 | ``` |
| 103 | 103 | ||
| 104 | Needs what vidya's Android build needs — SDK, NDK r29, and a cross-built Chez | 104 | Needs what vidya's Android build needs — SDK, NDK r29, and a cross-built Chez |
modified
android/BUCK +6 -3 | @@ -1,6 +1,6 @@ | ||
| 1 | 1 | # The APK, as a graph rather than a script. |
| 2 | 2 | # |
| 3 | -# Every step the old build-apk.sh ran in sequence is a target here, so a change | |
| 3 | +# Every step the old build-apk script ran in sequence is a target here, so a change | |
| 4 | 4 | # to one screen rebuilds the boot image and repackages, and touches neither the |
| 5 | 5 | # Rust nor the Java. The two native halves come from jolt-native — libvidya out |
| 6 | 6 | # of its buck2 graph, the glue compiled by the NDK its scripts/ pin — and the |
| @@ -48,8 +48,11 @@ _ABS_OUT = 'd=`dirname "$OUT"` && b=`basename "$OUT"` && d=`cd "$d" && pwd` && o | ||
| 48 | 48 | # buck's own scratch space is not this. |
| 49 | 49 | _TMP = 'tmp=`mktemp -d`' |
| 50 | 50 | |
| 51 | +# A babashka script, run in place: it loads scripts/frq/paths.clj beside it and | |
| 52 | +# resolves its own interpreter through scripts/bb, the DotSlash pin. Referenced | |
| 53 | +# rather than copied for exactly that reason — the tree around it is part of it. | |
| 51 | 54 | export_file( |
| 52 | - name = "build-jolt-boot.sh", | |
| 55 | + name = "build-jolt-boot.bb", | |
| 53 | 56 | mode = "reference", |
| 54 | 57 | ) |
| 55 | 58 | |
| @@ -94,7 +97,7 @@ genrule( | ||
| 94 | 97 | # is no jolt-native checkout. An input, so a new release recompiles |
| 95 | 98 | # the image. |
| 96 | 99 | _GLIMMER_VIDYA + |
| 97 | - "$(location :build-jolt-boot.sh) \"$OUT\" >&2", | |
| 100 | + "$(location :build-jolt-boot.bb) \"$OUT\" >&2", | |
| 98 | 101 | ) |
| 99 | 102 | |
| 100 | 103 | # The image travels as a blob in an object file's data section; the |
| @@ -1,6 +1,6 @@ | |||
| 1 | # The APK, as a graph rather than a script. | 1 | # The APK, as a graph rather than a script. |
| 2 | # | 2 | # |
| 3 | -# Every step the old build-apk.sh ran in sequence is a target here, so a change | 3 | +# Every step the old build-apk script ran in sequence is a target here, so a change |
| 4 | # to one screen rebuilds the boot image and repackages, and touches neither the | 4 | # to one screen rebuilds the boot image and repackages, and touches neither the |
| 5 | # Rust nor the Java. The two native halves come from jolt-native — libvidya out | 5 | # Rust nor the Java. The two native halves come from jolt-native — libvidya out |
| 6 | # of its buck2 graph, the glue compiled by the NDK its scripts/ pin — and the | 6 | # of its buck2 graph, the glue compiled by the NDK its scripts/ pin — and the |
| @@ -48,8 +48,11 @@ _ABS_OUT = 'd=`dirname "$OUT"` && b=`basename "$OUT"` && d=`cd "$d" && pwd` && o | |||
| 48 | # buck's own scratch space is not this. | 48 | # buck's own scratch space is not this. |
| 49 | _TMP = 'tmp=`mktemp -d`' | 49 | _TMP = 'tmp=`mktemp -d`' |
| 50 | 50 | ||
| 51 | +# A babashka script, run in place: it loads scripts/frq/paths.clj beside it and | ||
| 52 | +# resolves its own interpreter through scripts/bb, the DotSlash pin. Referenced | ||
| 53 | +# rather than copied for exactly that reason — the tree around it is part of it. | ||
| 51 | export_file( | 54 | export_file( |
| 52 | - name = "build-jolt-boot.sh", | 55 | + name = "build-jolt-boot.bb", |
| 53 | mode = "reference", | 56 | mode = "reference", |
| 54 | ) | 57 | ) |
| 55 | 58 | ||
| @@ -94,7 +97,7 @@ genrule( | |||
| 94 | # is no jolt-native checkout. An input, so a new release recompiles | 97 | # is no jolt-native checkout. An input, so a new release recompiles |
| 95 | # the image. | 98 | # the image. |
| 96 | _GLIMMER_VIDYA + | 99 | _GLIMMER_VIDYA + |
| 97 | - "$(location :build-jolt-boot.sh) \"$OUT\" >&2", | 100 | + "$(location :build-jolt-boot.bb) \"$OUT\" >&2", |
| 98 | ) | 101 | ) |
| 99 | 102 | ||
| 100 | # The image travels as a blob in an object file's data section; the | 103 | # The image travels as a blob in an object file's data section; the |
added
android/build-apk.bb +163 -0 | new file mode 100755 | ||
| @@ -0,0 +1,163 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/../scripts/bb" "$0" "$@" | |
| 4 | +) | |
| 5 | + | |
| 6 | +;; Glue the two halves of the frq Android app into an APK. | |
| 7 | +;; | |
| 8 | +;; libvidya.so the C ABI on Rust/egui, cross-compiled by buck2, and the | |
| 9 | +;; NativeActivity's own library (it holds android-activity's | |
| 10 | +;; glue, so it owns the event loop) | |
| 11 | +;; libjoltapp.so jolt-native's android/jolt_main.c plus frq's Jolt boot | |
| 12 | +;; image, dlopened by the above | |
| 13 | +;; classes.dex one Java class, and only because a picture chooser answers | |
| 14 | +;; through onActivityResult and a NativeActivity has nowhere | |
| 15 | +;; to deliver that | |
| 16 | +;; libssl.so OpenSSL, because the platform's own is not ours to load: an | |
| 17 | +;; libcrypto.so app's linker namespace refuses /system/lib64/libssl.so, and | |
| 18 | +;; without one there is no TLS at all on the phone | |
| 19 | +;; | |
| 20 | +;; Neither half is built here beyond that last link: the UI library comes from | |
| 21 | +;; jolt-native's `just ffi-android` and the boot image from build-jolt-boot.bb. | |
| 22 | +;; Both native pieces are jolt-native's — only the boot image is frq's. | |
| 23 | +;; | |
| 24 | +;; build-apk.bb [build|install|run|log] | |
| 25 | +(require '[babashka.classpath :as cp]) | |
| 26 | +(cp/add-classpath (str (babashka.fs/path (babashka.fs/parent *file*) ".." "scripts"))) | |
| 27 | +(require '[frq.paths :as paths] | |
| 28 | + '[babashka.fs :as fs] | |
| 29 | + '[babashka.process :as p]) | |
| 30 | + | |
| 31 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | |
| 32 | +(def action (or (first *command-line-args*) "build")) | |
| 33 | + | |
| 34 | +;; Where jolt-native is, answered the way the justfile answers it: a sibling | |
| 35 | +;; checkout wins, and otherwise it is the clone `just lib` leaves under | |
| 36 | +;; .jolt-native. | |
| 37 | +(def jolt-native (paths/jolt-native root)) | |
| 38 | +(when-not (fs/directory? jolt-native) | |
| 39 | + (paths/die (str "no jolt-native at " jolt-native " — run `just lib` to clone it"))) | |
| 40 | + | |
| 41 | +(def android-home (paths/env "ANDROID_HOME" (str (fs/path (fs/home) ".local" "share" "android-sdk")))) | |
| 42 | +(def ndk-home (paths/env "ANDROID_NDK_HOME" (str (fs/path (fs/home) ".local" "share" "android-ndk-r29")))) | |
| 43 | +(def chez (paths/env "CHEZ_ANDROID" (str (fs/path (fs/home) ".cache" "vidya-chez-android")))) | |
| 44 | +(def openssl (paths/env "OPENSSL_ANDROID" (str (fs/path (fs/home) ".cache" "frq-openssl-android" "lib")))) | |
| 45 | +(def build (fs/path root "android" "build")) | |
| 46 | +(def jolt-build (fs/path build "jolt")) | |
| 47 | +(def stage (fs/path build "stage")) | |
| 48 | +(def tools (fs/path android-home "build-tools" "36.0.0")) | |
| 49 | +(def android-jar (fs/path android-home "platforms" "android-36" "android.jar")) | |
| 50 | +(def adb (paths/env "ADB" (str (fs/path android-home "platform-tools" "adb")))) | |
| 51 | +(def ndk-bin (fs/path ndk-home "toolchains" "llvm" "prebuilt" "linux-x86_64" "bin")) | |
| 52 | +(def package "uk.nandi.frq") | |
| 53 | +(def activity (str package "/.FrqActivity")) | |
| 54 | +(def api "28") | |
| 55 | +(def clang (str (fs/path ndk-bin (str "aarch64-linux-android" api "-clang")))) | |
| 56 | +(def arm-lib (fs/path stage "lib" "arm64-v8a")) | |
| 57 | + | |
| 58 | +(paths/require-paths! "Android tool" | |
| 59 | + [clang android-jar | |
| 60 | + (fs/path tools "aapt2") (fs/path tools "zipalign") | |
| 61 | + (fs/path tools "apksigner") (fs/path tools "d8") | |
| 62 | + (fs/path openssl "libssl.so") (fs/path openssl "libcrypto.so")]) | |
| 63 | + | |
| 64 | +;; --- the UI half ----------------------------------------------------------- | |
| 65 | +;; buck2 fetches its own NDK for this, from the pin in jolt-native's | |
| 66 | +;; scripts/android-ndk.dotslash, so the toolchain above is the only one that | |
| 67 | +;; has to be installed by hand. | |
| 68 | +(p/shell {:dir jolt-native :out *err*} "just" "ffi-android") | |
| 69 | +(def vidya-so (fs/path jolt-native "build" "android" "arm64-v8a" "libvidya.so")) | |
| 70 | +(paths/require-paths! "library" [vidya-so]) | |
| 71 | + | |
| 72 | +;; --- the Jolt half --------------------------------------------------------- | |
| 73 | +(p/shell (str (fs/path root "android" "build-jolt-boot.bb")) (str jolt-build)) | |
| 74 | +;; The boot image travels as a blob in the object file's data section; the | |
| 75 | +;; _binary_jolt_boot_{start,end} symbols jolt_main.c reads come from this. | |
| 76 | +(p/shell {:dir (str jolt-build)} | |
| 77 | + (str (fs/path ndk-bin "llvm-objcopy")) | |
| 78 | + "--input-target=binary" | |
| 79 | + "--output-target=elf64-littleaarch64" | |
| 80 | + "--binary-architecture=aarch64" | |
| 81 | + "jolt.boot" "jolt_boot.o") | |
| 82 | + | |
| 83 | +;; --- the Java half --------------------------------------------------------- | |
| 84 | +;; One class: the photo chooser's result has to land somewhere, and native code | |
| 85 | +;; is not somewhere. d8 turns it into the classes.dex the runtime loads. | |
| 86 | +(def java-build (fs/path build "java")) | |
| 87 | +(fs/delete-tree java-build) | |
| 88 | +(fs/create-dirs (fs/path java-build "classes")) | |
| 89 | +;; android.jar on the class path is where every android.* type comes from; the | |
| 90 | +;; JDK's own java.* is what is left, and this class uses nothing of it that | |
| 91 | +;; Android does not have. (`-bootclasspath` would be the stricter way to say | |
| 92 | +;; that, and javac refuses it for a release this recent.) | |
| 93 | +(p/shell "javac" "--release" "17" | |
| 94 | + "--class-path" (str android-jar) | |
| 95 | + "-d" (str (fs/path java-build "classes")) | |
| 96 | + (str (fs/path root "android" "java" "uk" "nandi" "frq" "FrqActivity.java"))) | |
| 97 | +(apply p/shell (str (fs/path tools "d8")) "--min-api" api "--output" (str java-build) | |
| 98 | + (map str (fs/glob (fs/path java-build "classes") "**.class"))) | |
| 99 | + | |
| 100 | +(fs/delete-tree stage) | |
| 101 | +(fs/create-dirs arm-lib) | |
| 102 | +(fs/copy (fs/path java-build "classes.dex") (fs/path stage "classes.dex")) | |
| 103 | +(fs/copy vidya-so (fs/path arm-lib "libvidya.so")) | |
| 104 | +;; jolt.mvn-http dlopens these by name at first use; beside the app's own | |
| 105 | +;; libraries is where an app's namespace will answer for that name. | |
| 106 | +(doseq [lib ["libssl.so" "libcrypto.so"]] | |
| 107 | + (fs/copy (fs/path openssl lib) (fs/path arm-lib lib))) | |
| 108 | + | |
| 109 | +(p/shell clang "-shared" "-fPIC" "-O2" | |
| 110 | + "-o" (str (fs/path arm-lib "libjoltapp.so")) | |
| 111 | + (str (fs/path jolt-native "android" "jolt_main.c")) | |
| 112 | + (str (fs/path jolt-build "jolt_boot.o")) | |
| 113 | + (str "-I" jolt-build) | |
| 114 | + (str "-I" (fs/path jolt-native "crates" "jolt-vidya" "include")) | |
| 115 | + (str "-L" arm-lib) | |
| 116 | + (str (fs/path chez "tarm64le" "boot" "tarm64le" "libkernel.a")) | |
| 117 | + (str (fs/path chez "lz4" "lib" "liblz4.a")) | |
| 118 | + "-lvidya" "-landroid" "-llog" "-lz" "-ldl" "-lm" | |
| 119 | + "-Wl,--no-undefined") | |
| 120 | + | |
| 121 | +;; --- the APK --------------------------------------------------------------- | |
| 122 | +(def unaligned (fs/path build "frq-unaligned.apk")) | |
| 123 | +(def aligned (fs/path build "frq-aligned.apk")) | |
| 124 | +(def apk (fs/path build "frq.apk")) | |
| 125 | +(run! fs/delete-if-exists [unaligned aligned apk]) | |
| 126 | +(p/shell (str (fs/path tools "aapt2")) "link" | |
| 127 | + "-o" (str unaligned) | |
| 128 | + "-I" (str android-jar) | |
| 129 | + "--manifest" (str (fs/path root "android" "AndroidManifest.xml")) | |
| 130 | + "--min-sdk-version" api | |
| 131 | + "--target-sdk-version" "36" | |
| 132 | + "--version-code" "1" | |
| 133 | + "--version-name" "0.1.0") | |
| 134 | +;; Stored, not deflated: the loader maps these straight out of the APK. | |
| 135 | +(p/shell {:dir (str stage)} "zip" "-q" "-0" (str unaligned) | |
| 136 | + "lib/arm64-v8a/libvidya.so" "lib/arm64-v8a/libjoltapp.so" | |
| 137 | + "lib/arm64-v8a/libssl.so" "lib/arm64-v8a/libcrypto.so") | |
| 138 | +;; The dex is read by the runtime rather than mapped, so it may as well deflate. | |
| 139 | +(p/shell {:dir (str stage)} "zip" "-q" (str unaligned) "classes.dex") | |
| 140 | +(p/shell (str (fs/path tools "zipalign")) "-f" "-p" "4" (str unaligned) (str aligned)) | |
| 141 | + | |
| 142 | +(def keystore (fs/path (fs/home) ".android" "debug.keystore")) | |
| 143 | +(when-not (fs/exists? keystore) | |
| 144 | + (fs/create-dirs (fs/parent keystore)) | |
| 145 | + (p/shell "keytool" "-genkeypair" "-v" | |
| 146 | + "-keystore" (str keystore) "-storepass" "android" "-keypass" "android" | |
| 147 | + "-alias" "androiddebugkey" "-keyalg" "RSA" "-keysize" "2048" | |
| 148 | + "-validity" "10000" | |
| 149 | + "-dname" "CN=Android Debug,O=Android,C=US")) | |
| 150 | +(p/shell (str (fs/path tools "apksigner")) "sign" | |
| 151 | + "--ks" (str keystore) "--ks-key-alias" "androiddebugkey" | |
| 152 | + "--ks-pass" "pass:android" "--key-pass" "pass:android" | |
| 153 | + "--out" (str apk) (str aligned)) | |
| 154 | +(p/shell {:out :string} (str (fs/path tools "apksigner")) "verify" (str apk)) | |
| 155 | + | |
| 156 | +(case action | |
| 157 | + "build" (println (str apk)) | |
| 158 | + "install" (p/shell adb "install" "-r" (str apk)) | |
| 159 | + "run" (do (p/shell adb "install" "-r" (str apk)) | |
| 160 | + (p/shell adb "shell" "am" "force-stop" package) | |
| 161 | + (p/shell adb "shell" "am" "start" "-n" activity)) | |
| 162 | + "log" (p/shell adb "logcat" "-s" "VidyaJolt" "Vidya") | |
| 163 | + (paths/die "usage: build-apk.bb [build|install|run|log]")) | |
| new file mode 100755 | |||
| @@ -0,0 +1,163 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +#_( | ||
| 3 | +exec "$(dirname "$0")/../scripts/bb" "$0" "$@" | ||
| 4 | +) | ||
| 5 | + | ||
| 6 | +;; Glue the two halves of the frq Android app into an APK. | ||
| 7 | +;; | ||
| 8 | +;; libvidya.so the C ABI on Rust/egui, cross-compiled by buck2, and the | ||
| 9 | +;; NativeActivity's own library (it holds android-activity's | ||
| 10 | +;; glue, so it owns the event loop) | ||
| 11 | +;; libjoltapp.so jolt-native's android/jolt_main.c plus frq's Jolt boot | ||
| 12 | +;; image, dlopened by the above | ||
| 13 | +;; classes.dex one Java class, and only because a picture chooser answers | ||
| 14 | +;; through onActivityResult and a NativeActivity has nowhere | ||
| 15 | +;; to deliver that | ||
| 16 | +;; libssl.so OpenSSL, because the platform's own is not ours to load: an | ||
| 17 | +;; libcrypto.so app's linker namespace refuses /system/lib64/libssl.so, and | ||
| 18 | +;; without one there is no TLS at all on the phone | ||
| 19 | +;; | ||
| 20 | +;; Neither half is built here beyond that last link: the UI library comes from | ||
| 21 | +;; jolt-native's `just ffi-android` and the boot image from build-jolt-boot.bb. | ||
| 22 | +;; Both native pieces are jolt-native's — only the boot image is frq's. | ||
| 23 | +;; | ||
| 24 | +;; build-apk.bb [build|install|run|log] | ||
| 25 | +(require '[babashka.classpath :as cp]) | ||
| 26 | +(cp/add-classpath (str (babashka.fs/path (babashka.fs/parent *file*) ".." "scripts"))) | ||
| 27 | +(require '[frq.paths :as paths] | ||
| 28 | + '[babashka.fs :as fs] | ||
| 29 | + '[babashka.process :as p]) | ||
| 30 | + | ||
| 31 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | ||
| 32 | +(def action (or (first *command-line-args*) "build")) | ||
| 33 | + | ||
| 34 | +;; Where jolt-native is, answered the way the justfile answers it: a sibling | ||
| 35 | +;; checkout wins, and otherwise it is the clone `just lib` leaves under | ||
| 36 | +;; .jolt-native. | ||
| 37 | +(def jolt-native (paths/jolt-native root)) | ||
| 38 | +(when-not (fs/directory? jolt-native) | ||
| 39 | + (paths/die (str "no jolt-native at " jolt-native " — run `just lib` to clone it"))) | ||
| 40 | + | ||
| 41 | +(def android-home (paths/env "ANDROID_HOME" (str (fs/path (fs/home) ".local" "share" "android-sdk")))) | ||
| 42 | +(def ndk-home (paths/env "ANDROID_NDK_HOME" (str (fs/path (fs/home) ".local" "share" "android-ndk-r29")))) | ||
| 43 | +(def chez (paths/env "CHEZ_ANDROID" (str (fs/path (fs/home) ".cache" "vidya-chez-android")))) | ||
| 44 | +(def openssl (paths/env "OPENSSL_ANDROID" (str (fs/path (fs/home) ".cache" "frq-openssl-android" "lib")))) | ||
| 45 | +(def build (fs/path root "android" "build")) | ||
| 46 | +(def jolt-build (fs/path build "jolt")) | ||
| 47 | +(def stage (fs/path build "stage")) | ||
| 48 | +(def tools (fs/path android-home "build-tools" "36.0.0")) | ||
| 49 | +(def android-jar (fs/path android-home "platforms" "android-36" "android.jar")) | ||
| 50 | +(def adb (paths/env "ADB" (str (fs/path android-home "platform-tools" "adb")))) | ||
| 51 | +(def ndk-bin (fs/path ndk-home "toolchains" "llvm" "prebuilt" "linux-x86_64" "bin")) | ||
| 52 | +(def package "uk.nandi.frq") | ||
| 53 | +(def activity (str package "/.FrqActivity")) | ||
| 54 | +(def api "28") | ||
| 55 | +(def clang (str (fs/path ndk-bin (str "aarch64-linux-android" api "-clang")))) | ||
| 56 | +(def arm-lib (fs/path stage "lib" "arm64-v8a")) | ||
| 57 | + | ||
| 58 | +(paths/require-paths! "Android tool" | ||
| 59 | + [clang android-jar | ||
| 60 | + (fs/path tools "aapt2") (fs/path tools "zipalign") | ||
| 61 | + (fs/path tools "apksigner") (fs/path tools "d8") | ||
| 62 | + (fs/path openssl "libssl.so") (fs/path openssl "libcrypto.so")]) | ||
| 63 | + | ||
| 64 | +;; --- the UI half ----------------------------------------------------------- | ||
| 65 | +;; buck2 fetches its own NDK for this, from the pin in jolt-native's | ||
| 66 | +;; scripts/android-ndk.dotslash, so the toolchain above is the only one that | ||
| 67 | +;; has to be installed by hand. | ||
| 68 | +(p/shell {:dir jolt-native :out *err*} "just" "ffi-android") | ||
| 69 | +(def vidya-so (fs/path jolt-native "build" "android" "arm64-v8a" "libvidya.so")) | ||
| 70 | +(paths/require-paths! "library" [vidya-so]) | ||
| 71 | + | ||
| 72 | +;; --- the Jolt half --------------------------------------------------------- | ||
| 73 | +(p/shell (str (fs/path root "android" "build-jolt-boot.bb")) (str jolt-build)) | ||
| 74 | +;; The boot image travels as a blob in the object file's data section; the | ||
| 75 | +;; _binary_jolt_boot_{start,end} symbols jolt_main.c reads come from this. | ||
| 76 | +(p/shell {:dir (str jolt-build)} | ||
| 77 | + (str (fs/path ndk-bin "llvm-objcopy")) | ||
| 78 | + "--input-target=binary" | ||
| 79 | + "--output-target=elf64-littleaarch64" | ||
| 80 | + "--binary-architecture=aarch64" | ||
| 81 | + "jolt.boot" "jolt_boot.o") | ||
| 82 | + | ||
| 83 | +;; --- the Java half --------------------------------------------------------- | ||
| 84 | +;; One class: the photo chooser's result has to land somewhere, and native code | ||
| 85 | +;; is not somewhere. d8 turns it into the classes.dex the runtime loads. | ||
| 86 | +(def java-build (fs/path build "java")) | ||
| 87 | +(fs/delete-tree java-build) | ||
| 88 | +(fs/create-dirs (fs/path java-build "classes")) | ||
| 89 | +;; android.jar on the class path is where every android.* type comes from; the | ||
| 90 | +;; JDK's own java.* is what is left, and this class uses nothing of it that | ||
| 91 | +;; Android does not have. (`-bootclasspath` would be the stricter way to say | ||
| 92 | +;; that, and javac refuses it for a release this recent.) | ||
| 93 | +(p/shell "javac" "--release" "17" | ||
| 94 | + "--class-path" (str android-jar) | ||
| 95 | + "-d" (str (fs/path java-build "classes")) | ||
| 96 | + (str (fs/path root "android" "java" "uk" "nandi" "frq" "FrqActivity.java"))) | ||
| 97 | +(apply p/shell (str (fs/path tools "d8")) "--min-api" api "--output" (str java-build) | ||
| 98 | + (map str (fs/glob (fs/path java-build "classes") "**.class"))) | ||
| 99 | + | ||
| 100 | +(fs/delete-tree stage) | ||
| 101 | +(fs/create-dirs arm-lib) | ||
| 102 | +(fs/copy (fs/path java-build "classes.dex") (fs/path stage "classes.dex")) | ||
| 103 | +(fs/copy vidya-so (fs/path arm-lib "libvidya.so")) | ||
| 104 | +;; jolt.mvn-http dlopens these by name at first use; beside the app's own | ||
| 105 | +;; libraries is where an app's namespace will answer for that name. | ||
| 106 | +(doseq [lib ["libssl.so" "libcrypto.so"]] | ||
| 107 | + (fs/copy (fs/path openssl lib) (fs/path arm-lib lib))) | ||
| 108 | + | ||
| 109 | +(p/shell clang "-shared" "-fPIC" "-O2" | ||
| 110 | + "-o" (str (fs/path arm-lib "libjoltapp.so")) | ||
| 111 | + (str (fs/path jolt-native "android" "jolt_main.c")) | ||
| 112 | + (str (fs/path jolt-build "jolt_boot.o")) | ||
| 113 | + (str "-I" jolt-build) | ||
| 114 | + (str "-I" (fs/path jolt-native "crates" "jolt-vidya" "include")) | ||
| 115 | + (str "-L" arm-lib) | ||
| 116 | + (str (fs/path chez "tarm64le" "boot" "tarm64le" "libkernel.a")) | ||
| 117 | + (str (fs/path chez "lz4" "lib" "liblz4.a")) | ||
| 118 | + "-lvidya" "-landroid" "-llog" "-lz" "-ldl" "-lm" | ||
| 119 | + "-Wl,--no-undefined") | ||
| 120 | + | ||
| 121 | +;; --- the APK --------------------------------------------------------------- | ||
| 122 | +(def unaligned (fs/path build "frq-unaligned.apk")) | ||
| 123 | +(def aligned (fs/path build "frq-aligned.apk")) | ||
| 124 | +(def apk (fs/path build "frq.apk")) | ||
| 125 | +(run! fs/delete-if-exists [unaligned aligned apk]) | ||
| 126 | +(p/shell (str (fs/path tools "aapt2")) "link" | ||
| 127 | + "-o" (str unaligned) | ||
| 128 | + "-I" (str android-jar) | ||
| 129 | + "--manifest" (str (fs/path root "android" "AndroidManifest.xml")) | ||
| 130 | + "--min-sdk-version" api | ||
| 131 | + "--target-sdk-version" "36" | ||
| 132 | + "--version-code" "1" | ||
| 133 | + "--version-name" "0.1.0") | ||
| 134 | +;; Stored, not deflated: the loader maps these straight out of the APK. | ||
| 135 | +(p/shell {:dir (str stage)} "zip" "-q" "-0" (str unaligned) | ||
| 136 | + "lib/arm64-v8a/libvidya.so" "lib/arm64-v8a/libjoltapp.so" | ||
| 137 | + "lib/arm64-v8a/libssl.so" "lib/arm64-v8a/libcrypto.so") | ||
| 138 | +;; The dex is read by the runtime rather than mapped, so it may as well deflate. | ||
| 139 | +(p/shell {:dir (str stage)} "zip" "-q" (str unaligned) "classes.dex") | ||
| 140 | +(p/shell (str (fs/path tools "zipalign")) "-f" "-p" "4" (str unaligned) (str aligned)) | ||
| 141 | + | ||
| 142 | +(def keystore (fs/path (fs/home) ".android" "debug.keystore")) | ||
| 143 | +(when-not (fs/exists? keystore) | ||
| 144 | + (fs/create-dirs (fs/parent keystore)) | ||
| 145 | + (p/shell "keytool" "-genkeypair" "-v" | ||
| 146 | + "-keystore" (str keystore) "-storepass" "android" "-keypass" "android" | ||
| 147 | + "-alias" "androiddebugkey" "-keyalg" "RSA" "-keysize" "2048" | ||
| 148 | + "-validity" "10000" | ||
| 149 | + "-dname" "CN=Android Debug,O=Android,C=US")) | ||
| 150 | +(p/shell (str (fs/path tools "apksigner")) "sign" | ||
| 151 | + "--ks" (str keystore) "--ks-key-alias" "androiddebugkey" | ||
| 152 | + "--ks-pass" "pass:android" "--key-pass" "pass:android" | ||
| 153 | + "--out" (str apk) (str aligned)) | ||
| 154 | +(p/shell {:out :string} (str (fs/path tools "apksigner")) "verify" (str apk)) | ||
| 155 | + | ||
| 156 | +(case action | ||
| 157 | + "build" (println (str apk)) | ||
| 158 | + "install" (p/shell adb "install" "-r" (str apk)) | ||
| 159 | + "run" (do (p/shell adb "install" "-r" (str apk)) | ||
| 160 | + (p/shell adb "shell" "am" "force-stop" package) | ||
| 161 | + (p/shell adb "shell" "am" "start" "-n" activity)) | ||
| 162 | + "log" (p/shell adb "logcat" "-s" "VidyaJolt" "Vidya") | ||
| 163 | + (paths/die "usage: build-apk.bb [build|install|run|log]")) | ||
deleted
android/build-apk.sh +0 -170 | deleted file mode 100755 | ||
| @@ -1,170 +0,0 @@ | ||
| 1 | -#!/usr/bin/env bash | |
| 2 | -# Glue the two halves of the frq Android app into an APK. | |
| 3 | -# | |
| 4 | -# libvidya.so the C ABI on Rust/egui, cross-compiled by buck2, and the | |
| 5 | -# NativeActivity's own library (it holds android-activity's | |
| 6 | -# glue, so it owns the event loop) | |
| 7 | -# libjoltapp.so jolt-native's android/jolt_main.c plus frq's Jolt boot | |
| 8 | -# image, dlopened by the above | |
| 9 | -# classes.dex one Java class, and only because a picture chooser answers | |
| 10 | -# through onActivityResult and a NativeActivity has nowhere to | |
| 11 | -# deliver that | |
| 12 | -# libssl.so OpenSSL, because the platform's own is not ours to load: an | |
| 13 | -# libcrypto.so app's linker namespace refuses /system/lib64/libssl.so, and | |
| 14 | -# without one there is no TLS at all on the phone | |
| 15 | -# | |
| 16 | -# Neither half is built here beyond that last link: the UI library comes from | |
| 17 | -# jolt-native's `just ffi-android` and the boot image from build-jolt-boot.sh. | |
| 18 | -# Both native pieces are jolt-native's — only the boot image is frq's. | |
| 19 | -set -euo pipefail | |
| 20 | - | |
| 21 | -ROOT="$(cd "$(dirname "$0")/.." && pwd)" | |
| 22 | - | |
| 23 | -# Where jolt-native is, answered the same way the justfile answers it: a | |
| 24 | -# sibling checkout wins, and otherwise it is the clone `just lib` leaves under | |
| 25 | -# .jolt-native. --git-common-dir rather than the working tree because this | |
| 26 | -# script runs from a worktree as readily as from the checkout, and in one of | |
| 27 | -# those "../jolt-native" is not a sibling of anything. | |
| 28 | -CHECKOUT="$(dirname "$(git -C "$ROOT" rev-parse --path-format=absolute --git-common-dir)")" | |
| 29 | -if [[ -z "${JOLT_NATIVE:-}" ]]; then | |
| 30 | - if [[ -d "$CHECKOUT/../jolt-native" ]]; then | |
| 31 | - JOLT_NATIVE="$(cd "$CHECKOUT/../jolt-native" && pwd)" | |
| 32 | - else | |
| 33 | - JOLT_NATIVE="$CHECKOUT/.jolt-native" | |
| 34 | - fi | |
| 35 | -fi | |
| 36 | -[[ -d "$JOLT_NATIVE" ]] || { | |
| 37 | - echo "no jolt-native at $JOLT_NATIVE — run \`just lib\` to clone it" >&2 | |
| 38 | - exit 1 | |
| 39 | -} | |
| 40 | -ANDROID_HOME="${ANDROID_HOME:-$HOME/.local/share/android-sdk}" | |
| 41 | -ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$HOME/.local/share/android-ndk-r29}" | |
| 42 | -CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" | |
| 43 | -OPENSSL_ANDROID="${OPENSSL_ANDROID:-$HOME/.cache/frq-openssl-android/lib}" | |
| 44 | -BUILD="$ROOT/android/build" | |
| 45 | -JOLT_BUILD="$BUILD/jolt" | |
| 46 | -STAGE="$BUILD/stage" | |
| 47 | -TOOLS="$ANDROID_HOME/build-tools/36.0.0" | |
| 48 | -ADB="${ADB:-$ANDROID_HOME/platform-tools/adb}" | |
| 49 | -NDK_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" | |
| 50 | -PACKAGE="uk.nandi.frq" | |
| 51 | -ACTIVITY="$PACKAGE/.FrqActivity" | |
| 52 | -API=28 | |
| 53 | - | |
| 54 | -for path in \ | |
| 55 | - "$NDK_BIN/aarch64-linux-android$API-clang" \ | |
| 56 | - "$ANDROID_HOME/platforms/android-36/android.jar" \ | |
| 57 | - "$TOOLS/aapt2" "$TOOLS/zipalign" "$TOOLS/apksigner" "$TOOLS/d8" \ | |
| 58 | - "$OPENSSL_ANDROID/libssl.so" "$OPENSSL_ANDROID/libcrypto.so"; do | |
| 59 | - [[ -e "$path" ]] || { echo "missing Android tool: $path" >&2; exit 1; } | |
| 60 | -done | |
| 61 | - | |
| 62 | -# --- the UI half ------------------------------------------------------------ | |
| 63 | -# buck2 fetches its own NDK for this, from the pin in jolt-native's | |
| 64 | -# scripts/android-ndk.dotslash, so the toolchain below is the only one that has | |
| 65 | -# to be installed by hand. | |
| 66 | -( cd "$JOLT_NATIVE" && just ffi-android >&2 ) | |
| 67 | -VIDYA_SO="$JOLT_NATIVE/build/android/arm64-v8a/libvidya.so" | |
| 68 | -[[ -f "$VIDYA_SO" ]] || { echo "missing $VIDYA_SO" >&2; exit 1; } | |
| 69 | - | |
| 70 | -# --- the Jolt half ---------------------------------------------------------- | |
| 71 | -"$ROOT/android/build-jolt-boot.sh" "$JOLT_BUILD" | |
| 72 | -( | |
| 73 | - cd "$JOLT_BUILD" | |
| 74 | - # The boot image travels as a blob in the object file's data section; the | |
| 75 | - # _binary_jolt_boot_{start,end} symbols jolt_main.c reads come from this. | |
| 76 | - "$NDK_BIN/llvm-objcopy" \ | |
| 77 | - --input-target=binary \ | |
| 78 | - --output-target=elf64-littleaarch64 \ | |
| 79 | - --binary-architecture=aarch64 \ | |
| 80 | - jolt.boot jolt_boot.o | |
| 81 | -) | |
| 82 | - | |
| 83 | -# --- the Java half ---------------------------------------------------------- | |
| 84 | -# One class: the photo chooser's result has to land somewhere, and native code | |
| 85 | -# is not somewhere. d8 turns it into the classes.dex the runtime loads. | |
| 86 | -JAVA_BUILD="$BUILD/java" | |
| 87 | -rm -rf "$JAVA_BUILD" | |
| 88 | -mkdir -p "$JAVA_BUILD/classes" | |
| 89 | -# android.jar on the class path is where every android.* type comes from; the | |
| 90 | -# JDK's own java.* is what is left, and this class uses nothing of it that | |
| 91 | -# Android does not have. (`-bootclasspath` would be the stricter way to say | |
| 92 | -# that, and javac refuses it for a release this recent.) | |
| 93 | -javac --release 17 \ | |
| 94 | - --class-path "$ANDROID_HOME/platforms/android-36/android.jar" \ | |
| 95 | - -d "$JAVA_BUILD/classes" \ | |
| 96 | - "$ROOT/android/java/uk/nandi/frq/FrqActivity.java" | |
| 97 | -"$TOOLS/d8" --min-api $API --output "$JAVA_BUILD" \ | |
| 98 | - $(find "$JAVA_BUILD/classes" -name '*.class') | |
| 99 | - | |
| 100 | -rm -rf "$STAGE" | |
| 101 | -mkdir -p "$STAGE/lib/arm64-v8a" | |
| 102 | -cp "$JAVA_BUILD/classes.dex" "$STAGE/classes.dex" | |
| 103 | -cp "$VIDYA_SO" "$STAGE/lib/arm64-v8a/libvidya.so" | |
| 104 | -# jolt.mvn-http dlopens these by name at first use; beside the app's own | |
| 105 | -# libraries is where an app's namespace will answer for that name. | |
| 106 | -cp "$OPENSSL_ANDROID/libssl.so" "$OPENSSL_ANDROID/libcrypto.so" \ | |
| 107 | - "$STAGE/lib/arm64-v8a/" | |
| 108 | - | |
| 109 | -"$NDK_BIN/aarch64-linux-android$API-clang" \ | |
| 110 | - -shared -fPIC -O2 \ | |
| 111 | - -o "$STAGE/lib/arm64-v8a/libjoltapp.so" \ | |
| 112 | - "$JOLT_NATIVE/android/jolt_main.c" \ | |
| 113 | - "$JOLT_BUILD/jolt_boot.o" \ | |
| 114 | - -I"$JOLT_BUILD" \ | |
| 115 | - -I"$JOLT_NATIVE/crates/jolt-vidya/include" \ | |
| 116 | - -L"$STAGE/lib/arm64-v8a" \ | |
| 117 | - "$CHEZ_ANDROID/tarm64le/boot/tarm64le/libkernel.a" \ | |
| 118 | - "$CHEZ_ANDROID/lz4/lib/liblz4.a" \ | |
| 119 | - -lvidya -landroid -llog -lz -ldl -lm \ | |
| 120 | - -Wl,--no-undefined | |
| 121 | - | |
| 122 | -# --- the APK ---------------------------------------------------------------- | |
| 123 | -UNALIGNED="$BUILD/frq-unaligned.apk" | |
| 124 | -ALIGNED="$BUILD/frq-aligned.apk" | |
| 125 | -APK="$BUILD/frq.apk" | |
| 126 | -rm -f "$UNALIGNED" "$ALIGNED" "$APK" | |
| 127 | -"$TOOLS/aapt2" link \ | |
| 128 | - -o "$UNALIGNED" \ | |
| 129 | - -I "$ANDROID_HOME/platforms/android-36/android.jar" \ | |
| 130 | - --manifest "$ROOT/android/AndroidManifest.xml" \ | |
| 131 | - --min-sdk-version $API \ | |
| 132 | - --target-sdk-version 36 \ | |
| 133 | - --version-code 1 \ | |
| 134 | - --version-name 0.1.0 | |
| 135 | -# Stored, not deflated: the loader maps these straight out of the APK. | |
| 136 | -(cd "$STAGE" && zip -q -0 "$UNALIGNED" \ | |
| 137 | - lib/arm64-v8a/libvidya.so lib/arm64-v8a/libjoltapp.so \ | |
| 138 | - lib/arm64-v8a/libssl.so lib/arm64-v8a/libcrypto.so) | |
| 139 | -# The dex is read by the runtime rather than mapped, so it may as well deflate. | |
| 140 | -(cd "$STAGE" && zip -q "$UNALIGNED" classes.dex) | |
| 141 | -"$TOOLS/zipalign" -f -p 4 "$UNALIGNED" "$ALIGNED" | |
| 142 | - | |
| 143 | -KEYSTORE="$HOME/.android/debug.keystore" | |
| 144 | -if [[ ! -f "$KEYSTORE" ]]; then | |
| 145 | - mkdir -p "$(dirname "$KEYSTORE")" | |
| 146 | - keytool -genkeypair -v \ | |
| 147 | - -keystore "$KEYSTORE" -storepass android -keypass android \ | |
| 148 | - -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 \ | |
| 149 | - -dname "CN=Android Debug,O=Android,C=US" | |
| 150 | -fi | |
| 151 | -"$TOOLS/apksigner" sign \ | |
| 152 | - --ks "$KEYSTORE" --ks-key-alias androiddebugkey \ | |
| 153 | - --ks-pass pass:android --key-pass pass:android \ | |
| 154 | - --out "$APK" "$ALIGNED" | |
| 155 | -"$TOOLS/apksigner" verify "$APK" >/dev/null | |
| 156 | - | |
| 157 | -case "${1:-build}" in | |
| 158 | - build) printf '%s\n' "$APK" ;; | |
| 159 | - install) "$ADB" install -r "$APK" ;; | |
| 160 | - run) | |
| 161 | - "$ADB" install -r "$APK" | |
| 162 | - "$ADB" shell am force-stop "$PACKAGE" | |
| 163 | - "$ADB" shell am start -n "$ACTIVITY" | |
| 164 | - ;; | |
| 165 | - log) "$ADB" logcat -s VidyaJolt Vidya ;; | |
| 166 | - *) | |
| 167 | - echo "usage: $0 [build|install|run|log]" >&2 | |
| 168 | - exit 2 | |
| 169 | - ;; | |
| 170 | -esac | |
| deleted file mode 100755 | |||
| @@ -1,170 +0,0 @@ | |||
| 1 | -#!/usr/bin/env bash | ||
| 2 | -# Glue the two halves of the frq Android app into an APK. | ||
| 3 | -# | ||
| 4 | -# libvidya.so the C ABI on Rust/egui, cross-compiled by buck2, and the | ||
| 5 | -# NativeActivity's own library (it holds android-activity's | ||
| 6 | -# glue, so it owns the event loop) | ||
| 7 | -# libjoltapp.so jolt-native's android/jolt_main.c plus frq's Jolt boot | ||
| 8 | -# image, dlopened by the above | ||
| 9 | -# classes.dex one Java class, and only because a picture chooser answers | ||
| 10 | -# through onActivityResult and a NativeActivity has nowhere to | ||
| 11 | -# deliver that | ||
| 12 | -# libssl.so OpenSSL, because the platform's own is not ours to load: an | ||
| 13 | -# libcrypto.so app's linker namespace refuses /system/lib64/libssl.so, and | ||
| 14 | -# without one there is no TLS at all on the phone | ||
| 15 | -# | ||
| 16 | -# Neither half is built here beyond that last link: the UI library comes from | ||
| 17 | -# jolt-native's `just ffi-android` and the boot image from build-jolt-boot.sh. | ||
| 18 | -# Both native pieces are jolt-native's — only the boot image is frq's. | ||
| 19 | -set -euo pipefail | ||
| 20 | - | ||
| 21 | -ROOT="$(cd "$(dirname "$0")/.." && pwd)" | ||
| 22 | - | ||
| 23 | -# Where jolt-native is, answered the same way the justfile answers it: a | ||
| 24 | -# sibling checkout wins, and otherwise it is the clone `just lib` leaves under | ||
| 25 | -# .jolt-native. --git-common-dir rather than the working tree because this | ||
| 26 | -# script runs from a worktree as readily as from the checkout, and in one of | ||
| 27 | -# those "../jolt-native" is not a sibling of anything. | ||
| 28 | -CHECKOUT="$(dirname "$(git -C "$ROOT" rev-parse --path-format=absolute --git-common-dir)")" | ||
| 29 | -if [[ -z "${JOLT_NATIVE:-}" ]]; then | ||
| 30 | - if [[ -d "$CHECKOUT/../jolt-native" ]]; then | ||
| 31 | - JOLT_NATIVE="$(cd "$CHECKOUT/../jolt-native" && pwd)" | ||
| 32 | - else | ||
| 33 | - JOLT_NATIVE="$CHECKOUT/.jolt-native" | ||
| 34 | - fi | ||
| 35 | -fi | ||
| 36 | -[[ -d "$JOLT_NATIVE" ]] || { | ||
| 37 | - echo "no jolt-native at $JOLT_NATIVE — run \`just lib\` to clone it" >&2 | ||
| 38 | - exit 1 | ||
| 39 | -} | ||
| 40 | -ANDROID_HOME="${ANDROID_HOME:-$HOME/.local/share/android-sdk}" | ||
| 41 | -ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$HOME/.local/share/android-ndk-r29}" | ||
| 42 | -CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" | ||
| 43 | -OPENSSL_ANDROID="${OPENSSL_ANDROID:-$HOME/.cache/frq-openssl-android/lib}" | ||
| 44 | -BUILD="$ROOT/android/build" | ||
| 45 | -JOLT_BUILD="$BUILD/jolt" | ||
| 46 | -STAGE="$BUILD/stage" | ||
| 47 | -TOOLS="$ANDROID_HOME/build-tools/36.0.0" | ||
| 48 | -ADB="${ADB:-$ANDROID_HOME/platform-tools/adb}" | ||
| 49 | -NDK_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" | ||
| 50 | -PACKAGE="uk.nandi.frq" | ||
| 51 | -ACTIVITY="$PACKAGE/.FrqActivity" | ||
| 52 | -API=28 | ||
| 53 | - | ||
| 54 | -for path in \ | ||
| 55 | - "$NDK_BIN/aarch64-linux-android$API-clang" \ | ||
| 56 | - "$ANDROID_HOME/platforms/android-36/android.jar" \ | ||
| 57 | - "$TOOLS/aapt2" "$TOOLS/zipalign" "$TOOLS/apksigner" "$TOOLS/d8" \ | ||
| 58 | - "$OPENSSL_ANDROID/libssl.so" "$OPENSSL_ANDROID/libcrypto.so"; do | ||
| 59 | - [[ -e "$path" ]] || { echo "missing Android tool: $path" >&2; exit 1; } | ||
| 60 | -done | ||
| 61 | - | ||
| 62 | -# --- the UI half ------------------------------------------------------------ | ||
| 63 | -# buck2 fetches its own NDK for this, from the pin in jolt-native's | ||
| 64 | -# scripts/android-ndk.dotslash, so the toolchain below is the only one that has | ||
| 65 | -# to be installed by hand. | ||
| 66 | -( cd "$JOLT_NATIVE" && just ffi-android >&2 ) | ||
| 67 | -VIDYA_SO="$JOLT_NATIVE/build/android/arm64-v8a/libvidya.so" | ||
| 68 | -[[ -f "$VIDYA_SO" ]] || { echo "missing $VIDYA_SO" >&2; exit 1; } | ||
| 69 | - | ||
| 70 | -# --- the Jolt half ---------------------------------------------------------- | ||
| 71 | -"$ROOT/android/build-jolt-boot.sh" "$JOLT_BUILD" | ||
| 72 | -( | ||
| 73 | - cd "$JOLT_BUILD" | ||
| 74 | - # The boot image travels as a blob in the object file's data section; the | ||
| 75 | - # _binary_jolt_boot_{start,end} symbols jolt_main.c reads come from this. | ||
| 76 | - "$NDK_BIN/llvm-objcopy" \ | ||
| 77 | - --input-target=binary \ | ||
| 78 | - --output-target=elf64-littleaarch64 \ | ||
| 79 | - --binary-architecture=aarch64 \ | ||
| 80 | - jolt.boot jolt_boot.o | ||
| 81 | -) | ||
| 82 | - | ||
| 83 | -# --- the Java half ---------------------------------------------------------- | ||
| 84 | -# One class: the photo chooser's result has to land somewhere, and native code | ||
| 85 | -# is not somewhere. d8 turns it into the classes.dex the runtime loads. | ||
| 86 | -JAVA_BUILD="$BUILD/java" | ||
| 87 | -rm -rf "$JAVA_BUILD" | ||
| 88 | -mkdir -p "$JAVA_BUILD/classes" | ||
| 89 | -# android.jar on the class path is where every android.* type comes from; the | ||
| 90 | -# JDK's own java.* is what is left, and this class uses nothing of it that | ||
| 91 | -# Android does not have. (`-bootclasspath` would be the stricter way to say | ||
| 92 | -# that, and javac refuses it for a release this recent.) | ||
| 93 | -javac --release 17 \ | ||
| 94 | - --class-path "$ANDROID_HOME/platforms/android-36/android.jar" \ | ||
| 95 | - -d "$JAVA_BUILD/classes" \ | ||
| 96 | - "$ROOT/android/java/uk/nandi/frq/FrqActivity.java" | ||
| 97 | -"$TOOLS/d8" --min-api $API --output "$JAVA_BUILD" \ | ||
| 98 | - $(find "$JAVA_BUILD/classes" -name '*.class') | ||
| 99 | - | ||
| 100 | -rm -rf "$STAGE" | ||
| 101 | -mkdir -p "$STAGE/lib/arm64-v8a" | ||
| 102 | -cp "$JAVA_BUILD/classes.dex" "$STAGE/classes.dex" | ||
| 103 | -cp "$VIDYA_SO" "$STAGE/lib/arm64-v8a/libvidya.so" | ||
| 104 | -# jolt.mvn-http dlopens these by name at first use; beside the app's own | ||
| 105 | -# libraries is where an app's namespace will answer for that name. | ||
| 106 | -cp "$OPENSSL_ANDROID/libssl.so" "$OPENSSL_ANDROID/libcrypto.so" \ | ||
| 107 | - "$STAGE/lib/arm64-v8a/" | ||
| 108 | - | ||
| 109 | -"$NDK_BIN/aarch64-linux-android$API-clang" \ | ||
| 110 | - -shared -fPIC -O2 \ | ||
| 111 | - -o "$STAGE/lib/arm64-v8a/libjoltapp.so" \ | ||
| 112 | - "$JOLT_NATIVE/android/jolt_main.c" \ | ||
| 113 | - "$JOLT_BUILD/jolt_boot.o" \ | ||
| 114 | - -I"$JOLT_BUILD" \ | ||
| 115 | - -I"$JOLT_NATIVE/crates/jolt-vidya/include" \ | ||
| 116 | - -L"$STAGE/lib/arm64-v8a" \ | ||
| 117 | - "$CHEZ_ANDROID/tarm64le/boot/tarm64le/libkernel.a" \ | ||
| 118 | - "$CHEZ_ANDROID/lz4/lib/liblz4.a" \ | ||
| 119 | - -lvidya -landroid -llog -lz -ldl -lm \ | ||
| 120 | - -Wl,--no-undefined | ||
| 121 | - | ||
| 122 | -# --- the APK ---------------------------------------------------------------- | ||
| 123 | -UNALIGNED="$BUILD/frq-unaligned.apk" | ||
| 124 | -ALIGNED="$BUILD/frq-aligned.apk" | ||
| 125 | -APK="$BUILD/frq.apk" | ||
| 126 | -rm -f "$UNALIGNED" "$ALIGNED" "$APK" | ||
| 127 | -"$TOOLS/aapt2" link \ | ||
| 128 | - -o "$UNALIGNED" \ | ||
| 129 | - -I "$ANDROID_HOME/platforms/android-36/android.jar" \ | ||
| 130 | - --manifest "$ROOT/android/AndroidManifest.xml" \ | ||
| 131 | - --min-sdk-version $API \ | ||
| 132 | - --target-sdk-version 36 \ | ||
| 133 | - --version-code 1 \ | ||
| 134 | - --version-name 0.1.0 | ||
| 135 | -# Stored, not deflated: the loader maps these straight out of the APK. | ||
| 136 | -(cd "$STAGE" && zip -q -0 "$UNALIGNED" \ | ||
| 137 | - lib/arm64-v8a/libvidya.so lib/arm64-v8a/libjoltapp.so \ | ||
| 138 | - lib/arm64-v8a/libssl.so lib/arm64-v8a/libcrypto.so) | ||
| 139 | -# The dex is read by the runtime rather than mapped, so it may as well deflate. | ||
| 140 | -(cd "$STAGE" && zip -q "$UNALIGNED" classes.dex) | ||
| 141 | -"$TOOLS/zipalign" -f -p 4 "$UNALIGNED" "$ALIGNED" | ||
| 142 | - | ||
| 143 | -KEYSTORE="$HOME/.android/debug.keystore" | ||
| 144 | -if [[ ! -f "$KEYSTORE" ]]; then | ||
| 145 | - mkdir -p "$(dirname "$KEYSTORE")" | ||
| 146 | - keytool -genkeypair -v \ | ||
| 147 | - -keystore "$KEYSTORE" -storepass android -keypass android \ | ||
| 148 | - -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 \ | ||
| 149 | - -dname "CN=Android Debug,O=Android,C=US" | ||
| 150 | -fi | ||
| 151 | -"$TOOLS/apksigner" sign \ | ||
| 152 | - --ks "$KEYSTORE" --ks-key-alias androiddebugkey \ | ||
| 153 | - --ks-pass pass:android --key-pass pass:android \ | ||
| 154 | - --out "$APK" "$ALIGNED" | ||
| 155 | -"$TOOLS/apksigner" verify "$APK" >/dev/null | ||
| 156 | - | ||
| 157 | -case "${1:-build}" in | ||
| 158 | - build) printf '%s\n' "$APK" ;; | ||
| 159 | - install) "$ADB" install -r "$APK" ;; | ||
| 160 | - run) | ||
| 161 | - "$ADB" install -r "$APK" | ||
| 162 | - "$ADB" shell am force-stop "$PACKAGE" | ||
| 163 | - "$ADB" shell am start -n "$ACTIVITY" | ||
| 164 | - ;; | ||
| 165 | - log) "$ADB" logcat -s VidyaJolt Vidya ;; | ||
| 166 | - *) | ||
| 167 | - echo "usage: $0 [build|install|run|log]" >&2 | ||
| 168 | - exit 2 | ||
| 169 | - ;; | ||
| 170 | -esac | ||
added
android/build-jolt-boot.bb +169 -0 | new file mode 100755 | ||
| @@ -0,0 +1,169 @@ | ||
| 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")) | |
| new file mode 100755 | |||
| @@ -0,0 +1,169 @@ | |||
| 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")) | ||
deleted
android/build-jolt-boot.sh +0 -153 | deleted file mode 100755 | ||
| @@ -1,153 +0,0 @@ | ||
| 1 | -#!/usr/bin/env bash | |
| 2 | -set -euo pipefail | |
| 3 | - | |
| 4 | -ROOT="$(cd "$(dirname "$0")/.." && pwd)" | |
| 5 | - | |
| 6 | -# The boot image is built from :paths alone — there is no dependency | |
| 7 | -# resolution inside a cross compile — so every source root deps.edn would have | |
| 8 | -# resolved has to be named here instead. Two of them are git dependencies, | |
| 9 | -# which means the jolt cache rather than a checkout; the shas come out of | |
| 10 | -# deps.edn so there is one place to bump them. | |
| 11 | -CHECKOUT="$(dirname "$(git -C "$ROOT" rev-parse --path-format=absolute --git-common-dir)")" | |
| 12 | -if [[ -z "${JOLT_NATIVE:-}" ]]; then | |
| 13 | - if [[ -d "$CHECKOUT/../jolt-native" ]]; then | |
| 14 | - JOLT_NATIVE="$(cd "$CHECKOUT/../jolt-native" && pwd)" | |
| 15 | - else | |
| 16 | - JOLT_NATIVE="$CHECKOUT/.jolt-native" | |
| 17 | - fi | |
| 18 | -fi | |
| 19 | - | |
| 20 | -# The sha deps.edn pins for a given git url, so a bump there reaches this. | |
| 21 | -dep_sha() { | |
| 22 | - awk -v url="$1" ' | |
| 23 | - index($0, url) { found = 1 } | |
| 24 | - found && $1 == ":git/sha" { gsub(/[^0-9a-f]/, "", $2); print $2; exit } | |
| 25 | - ' "$ROOT/deps.edn" | |
| 26 | -} | |
| 27 | -GLIMMER_SHA="$(dep_sha https://gitlab.com/nandithebull/glimmer)" | |
| 28 | -GLIMMER="${GLIMMER:-$HOME/.jolt/gitlibs/https___gitlab.com_nandithebull_glimmer/$GLIMMER_SHA/src}" | |
| 29 | - | |
| 30 | -# glimmer-vidya lives inside jolt-native, so a sibling checkout answers for it | |
| 31 | -# the way it answers for libvidya; the cache is the fallback, at the sha | |
| 32 | -# deps.edn pins. | |
| 33 | -GLIMMER_VIDYA_SHA="$(dep_sha https://gitlab.com/nandithebull/jolt-native)" | |
| 34 | -if [[ -z "${GLIMMER_VIDYA:-}" ]]; then | |
| 35 | - if [[ -d "$JOLT_NATIVE/jolt/glimmer-vidya/src" ]]; then | |
| 36 | - GLIMMER_VIDYA="$JOLT_NATIVE/jolt/glimmer-vidya/src" | |
| 37 | - else | |
| 38 | - GLIMMER_VIDYA="$HOME/.jolt/gitlibs/https___gitlab.com_nandithebull_jolt-native/$GLIMMER_VIDYA_SHA/jolt/glimmer-vidya/src" | |
| 39 | - fi | |
| 40 | -fi | |
| 41 | - | |
| 42 | -for path in "$GLIMMER" "$GLIMMER_VIDYA"; do | |
| 43 | - [[ -d "$path" ]] || { | |
| 44 | - echo "missing Jolt source root: $path" >&2 | |
| 45 | - echo "run \`jolt -M:frq --help\` once to populate the git cache" >&2 | |
| 46 | - exit 1 | |
| 47 | - } | |
| 48 | -done | |
| 49 | -OUT="${1:?usage: build-jolt-boot.sh OUTPUT_DIRECTORY | --stamp}" | |
| 50 | -# The DotSlash-pinned jolt, not whatever is on PATH: an upstream jolt cannot | |
| 51 | -# open a TLS connection on Android — it reads the socket address out of | |
| 52 | -# `struct addrinfo` at glibc's offset, which is Bionic's `ai_canonname` — so a | |
| 53 | -# build made with one produces an APK that cannot sign in or send a picture. | |
| 54 | -# Override with JOLT= to use another. | |
| 55 | -# | |
| 56 | -# DOTSLASH and JOLT_MANIFEST are set when buck runs this: the manifest and the | |
| 57 | -# fetcher are inputs to that action, so the machine running it needs neither | |
| 58 | -# jolt nor DotSlash installed, and a remote worker resolves the same pin | |
| 59 | -# against the same digest. Without them the shim beside this script answers, | |
| 60 | -# which is what a person at a terminal gets. | |
| 61 | -if [[ -z "${JOLT:-}" ]]; then | |
| 62 | - if [[ -n "${DOTSLASH:-}" && -n "${JOLT_MANIFEST:-}" ]]; then | |
| 63 | - JOLT="$("$DOTSLASH" -- fetch "$JOLT_MANIFEST")" | |
| 64 | - else | |
| 65 | - JOLT="$ROOT/scripts/jolt" | |
| 66 | - fi | |
| 67 | -fi | |
| 68 | -MODULE="${MODULE:-frq.app}" | |
| 69 | -CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" | |
| 70 | -HOST_SCHEME="$CHEZ_ANDROID/ta6le/bin/ta6le/scheme" | |
| 71 | -TARGET_BOOT="$CHEZ_ANDROID/boot/tarm64le" | |
| 72 | -XPATCH="$CHEZ_ANDROID/xc-tarm64le/s/xpatch" | |
| 73 | - | |
| 74 | -for path in "$HOST_SCHEME" "$TARGET_BOOT/petite.boot" \ | |
| 75 | - "$TARGET_BOOT/scheme.boot" "$TARGET_BOOT/scheme.h" "$XPATCH"; do | |
| 76 | - [[ -e "$path" ]] || { | |
| 77 | - echo "missing Android Chez artifact: $path" >&2 | |
| 78 | - echo "Build Chez's tarm64le cross target first." >&2 | |
| 79 | - exit 1 | |
| 80 | - } | |
| 81 | -done | |
| 82 | -[[ -x "$JOLT" ]] || command -v "$JOLT" >/dev/null || { | |
| 83 | - echo "Jolt executable not found: $JOLT" >&2 | |
| 84 | - exit 1 | |
| 85 | -} | |
| 86 | - | |
| 87 | -# The boot image is a pure function of the Scheme sources, the module name, the | |
| 88 | -# flat-split flag and Chez's own boot files — all static. Hash them, and skip | |
| 89 | -# the whole thing when the stamp still matches: a Rust-only APK rebuild has no | |
| 90 | -# reason to spend fifteen single-threaded seconds recompiling Scheme. | |
| 91 | -# | |
| 92 | -# The flag is part of the stamp on purpose. JOLT_NO_FLAT_SPLIT changes the shape | |
| 93 | -# of what `jolt build` emits, so an app.build/ left by an ordinary build is not | |
| 94 | -# reusable here; a stamp miss wipes the tree below, which is what the | |
| 95 | -# unconditional `rm -rf` used to be defending against. | |
| 96 | -STAMP="$OUT/jolt.boot.stamp" | |
| 97 | -stamp_now() { | |
| 98 | - { | |
| 99 | - printf '%s\n' "$MODULE" "JOLT_NO_FLAT_SPLIT=1" | |
| 100 | - "$JOLT" --version 2>/dev/null || true | |
| 101 | - find "$ROOT/src" "$GLIMMER" "$GLIMMER_VIDYA" -type f \ | |
| 102 | - \( -name '*.jolt' -o -name '*.edn' \) -print0 | sort -z | xargs -0 sha256sum | |
| 103 | - sha256sum "$TARGET_BOOT/petite.boot" "$TARGET_BOOT/scheme.boot" "$XPATCH" | |
| 104 | - } | sha256sum | cut -d' ' -f1 | |
| 105 | -} | |
| 106 | - | |
| 107 | -# buck needs this before the work rather than after: the sources it hashes live | |
| 108 | -# in the jolt cache and in jolt-native, outside this cell, so nothing else makes | |
| 109 | -# them reach an action's digest. See the `buck` recipe in the justfile. | |
| 110 | -if [[ "${1:-}" == "--stamp" ]]; then | |
| 111 | - stamp_now | |
| 112 | - exit 0 | |
| 113 | -fi | |
| 114 | - | |
| 115 | -WANT="$(stamp_now)" | |
| 116 | -if [[ -f "$OUT/jolt.boot" && -f "$OUT/scheme.h" && -f "$STAMP" ]] && | |
| 117 | - [[ "$(<"$STAMP")" == "$WANT" ]]; then | |
| 118 | - echo "jolt boot image up to date" >&2 | |
| 119 | - exit 0 | |
| 120 | -fi | |
| 121 | - | |
| 122 | -rm -f "$STAMP" | |
| 123 | -rm -rf "$OUT/project" "$OUT/cross" | |
| 124 | -mkdir -p "$OUT/project" "$OUT/cross" | |
| 125 | -cat > "$OUT/project/deps.edn" <<EOF | |
| 126 | -{:paths ["$ROOT/src" "$GLIMMER" "$GLIMMER_VIDYA"]} | |
| 127 | -EOF | |
| 128 | - | |
| 129 | -( | |
| 130 | - cd "$OUT/project" | |
| 131 | - JOLT_NO_FLAT_SPLIT=1 "$JOLT" build \ | |
| 132 | - -m "$MODULE" -o app | |
| 133 | -) | |
| 134 | - | |
| 135 | -cat > "$OUT/cross/compile.ss" <<EOF | |
| 136 | -(import (chezscheme)) | |
| 137 | -(load "$XPATCH") | |
| 138 | -(optimize-level 2) | |
| 139 | -(generate-inspector-information #f) | |
| 140 | -(compile-file "$OUT/project/app.build/flat.ss" "$OUT/cross/flat.so") | |
| 141 | -(make-boot-file "$OUT/jolt.boot" '() | |
| 142 | - "$TARGET_BOOT/petite.boot" | |
| 143 | - "$TARGET_BOOT/scheme.boot" | |
| 144 | - "$OUT/cross/flat.so") | |
| 145 | -EOF | |
| 146 | - | |
| 147 | -SCHEMEHEAPDIRS="$CHEZ_ANDROID/ta6le/boot/ta6le" \ | |
| 148 | - "$HOST_SCHEME" --script "$OUT/cross/compile.ss" | |
| 149 | - | |
| 150 | -cp "$TARGET_BOOT/scheme.h" "$OUT/scheme.h" | |
| 151 | - | |
| 152 | -# Last, so an interrupted build leaves no stamp and the next run redoes it. | |
| 153 | -printf '%s\n' "$WANT" > "$STAMP" | |
| deleted file mode 100755 | |||
| @@ -1,153 +0,0 @@ | |||
| 1 | -#!/usr/bin/env bash | ||
| 2 | -set -euo pipefail | ||
| 3 | - | ||
| 4 | -ROOT="$(cd "$(dirname "$0")/.." && pwd)" | ||
| 5 | - | ||
| 6 | -# The boot image is built from :paths alone — there is no dependency | ||
| 7 | -# resolution inside a cross compile — so every source root deps.edn would have | ||
| 8 | -# resolved has to be named here instead. Two of them are git dependencies, | ||
| 9 | -# which means the jolt cache rather than a checkout; the shas come out of | ||
| 10 | -# deps.edn so there is one place to bump them. | ||
| 11 | -CHECKOUT="$(dirname "$(git -C "$ROOT" rev-parse --path-format=absolute --git-common-dir)")" | ||
| 12 | -if [[ -z "${JOLT_NATIVE:-}" ]]; then | ||
| 13 | - if [[ -d "$CHECKOUT/../jolt-native" ]]; then | ||
| 14 | - JOLT_NATIVE="$(cd "$CHECKOUT/../jolt-native" && pwd)" | ||
| 15 | - else | ||
| 16 | - JOLT_NATIVE="$CHECKOUT/.jolt-native" | ||
| 17 | - fi | ||
| 18 | -fi | ||
| 19 | - | ||
| 20 | -# The sha deps.edn pins for a given git url, so a bump there reaches this. | ||
| 21 | -dep_sha() { | ||
| 22 | - awk -v url="$1" ' | ||
| 23 | - index($0, url) { found = 1 } | ||
| 24 | - found && $1 == ":git/sha" { gsub(/[^0-9a-f]/, "", $2); print $2; exit } | ||
| 25 | - ' "$ROOT/deps.edn" | ||
| 26 | -} | ||
| 27 | -GLIMMER_SHA="$(dep_sha https://gitlab.com/nandithebull/glimmer)" | ||
| 28 | -GLIMMER="${GLIMMER:-$HOME/.jolt/gitlibs/https___gitlab.com_nandithebull_glimmer/$GLIMMER_SHA/src}" | ||
| 29 | - | ||
| 30 | -# glimmer-vidya lives inside jolt-native, so a sibling checkout answers for it | ||
| 31 | -# the way it answers for libvidya; the cache is the fallback, at the sha | ||
| 32 | -# deps.edn pins. | ||
| 33 | -GLIMMER_VIDYA_SHA="$(dep_sha https://gitlab.com/nandithebull/jolt-native)" | ||
| 34 | -if [[ -z "${GLIMMER_VIDYA:-}" ]]; then | ||
| 35 | - if [[ -d "$JOLT_NATIVE/jolt/glimmer-vidya/src" ]]; then | ||
| 36 | - GLIMMER_VIDYA="$JOLT_NATIVE/jolt/glimmer-vidya/src" | ||
| 37 | - else | ||
| 38 | - GLIMMER_VIDYA="$HOME/.jolt/gitlibs/https___gitlab.com_nandithebull_jolt-native/$GLIMMER_VIDYA_SHA/jolt/glimmer-vidya/src" | ||
| 39 | - fi | ||
| 40 | -fi | ||
| 41 | - | ||
| 42 | -for path in "$GLIMMER" "$GLIMMER_VIDYA"; do | ||
| 43 | - [[ -d "$path" ]] || { | ||
| 44 | - echo "missing Jolt source root: $path" >&2 | ||
| 45 | - echo "run \`jolt -M:frq --help\` once to populate the git cache" >&2 | ||
| 46 | - exit 1 | ||
| 47 | - } | ||
| 48 | -done | ||
| 49 | -OUT="${1:?usage: build-jolt-boot.sh OUTPUT_DIRECTORY | --stamp}" | ||
| 50 | -# The DotSlash-pinned jolt, not whatever is on PATH: an upstream jolt cannot | ||
| 51 | -# open a TLS connection on Android — it reads the socket address out of | ||
| 52 | -# `struct addrinfo` at glibc's offset, which is Bionic's `ai_canonname` — so a | ||
| 53 | -# build made with one produces an APK that cannot sign in or send a picture. | ||
| 54 | -# Override with JOLT= to use another. | ||
| 55 | -# | ||
| 56 | -# DOTSLASH and JOLT_MANIFEST are set when buck runs this: the manifest and the | ||
| 57 | -# fetcher are inputs to that action, so the machine running it needs neither | ||
| 58 | -# jolt nor DotSlash installed, and a remote worker resolves the same pin | ||
| 59 | -# against the same digest. Without them the shim beside this script answers, | ||
| 60 | -# which is what a person at a terminal gets. | ||
| 61 | -if [[ -z "${JOLT:-}" ]]; then | ||
| 62 | - if [[ -n "${DOTSLASH:-}" && -n "${JOLT_MANIFEST:-}" ]]; then | ||
| 63 | - JOLT="$("$DOTSLASH" -- fetch "$JOLT_MANIFEST")" | ||
| 64 | - else | ||
| 65 | - JOLT="$ROOT/scripts/jolt" | ||
| 66 | - fi | ||
| 67 | -fi | ||
| 68 | -MODULE="${MODULE:-frq.app}" | ||
| 69 | -CHEZ_ANDROID="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" | ||
| 70 | -HOST_SCHEME="$CHEZ_ANDROID/ta6le/bin/ta6le/scheme" | ||
| 71 | -TARGET_BOOT="$CHEZ_ANDROID/boot/tarm64le" | ||
| 72 | -XPATCH="$CHEZ_ANDROID/xc-tarm64le/s/xpatch" | ||
| 73 | - | ||
| 74 | -for path in "$HOST_SCHEME" "$TARGET_BOOT/petite.boot" \ | ||
| 75 | - "$TARGET_BOOT/scheme.boot" "$TARGET_BOOT/scheme.h" "$XPATCH"; do | ||
| 76 | - [[ -e "$path" ]] || { | ||
| 77 | - echo "missing Android Chez artifact: $path" >&2 | ||
| 78 | - echo "Build Chez's tarm64le cross target first." >&2 | ||
| 79 | - exit 1 | ||
| 80 | - } | ||
| 81 | -done | ||
| 82 | -[[ -x "$JOLT" ]] || command -v "$JOLT" >/dev/null || { | ||
| 83 | - echo "Jolt executable not found: $JOLT" >&2 | ||
| 84 | - exit 1 | ||
| 85 | -} | ||
| 86 | - | ||
| 87 | -# The boot image is a pure function of the Scheme sources, the module name, the | ||
| 88 | -# flat-split flag and Chez's own boot files — all static. Hash them, and skip | ||
| 89 | -# the whole thing when the stamp still matches: a Rust-only APK rebuild has no | ||
| 90 | -# reason to spend fifteen single-threaded seconds recompiling Scheme. | ||
| 91 | -# | ||
| 92 | -# The flag is part of the stamp on purpose. JOLT_NO_FLAT_SPLIT changes the shape | ||
| 93 | -# of what `jolt build` emits, so an app.build/ left by an ordinary build is not | ||
| 94 | -# reusable here; a stamp miss wipes the tree below, which is what the | ||
| 95 | -# unconditional `rm -rf` used to be defending against. | ||
| 96 | -STAMP="$OUT/jolt.boot.stamp" | ||
| 97 | -stamp_now() { | ||
| 98 | - { | ||
| 99 | - printf '%s\n' "$MODULE" "JOLT_NO_FLAT_SPLIT=1" | ||
| 100 | - "$JOLT" --version 2>/dev/null || true | ||
| 101 | - find "$ROOT/src" "$GLIMMER" "$GLIMMER_VIDYA" -type f \ | ||
| 102 | - \( -name '*.jolt' -o -name '*.edn' \) -print0 | sort -z | xargs -0 sha256sum | ||
| 103 | - sha256sum "$TARGET_BOOT/petite.boot" "$TARGET_BOOT/scheme.boot" "$XPATCH" | ||
| 104 | - } | sha256sum | cut -d' ' -f1 | ||
| 105 | -} | ||
| 106 | - | ||
| 107 | -# buck needs this before the work rather than after: the sources it hashes live | ||
| 108 | -# in the jolt cache and in jolt-native, outside this cell, so nothing else makes | ||
| 109 | -# them reach an action's digest. See the `buck` recipe in the justfile. | ||
| 110 | -if [[ "${1:-}" == "--stamp" ]]; then | ||
| 111 | - stamp_now | ||
| 112 | - exit 0 | ||
| 113 | -fi | ||
| 114 | - | ||
| 115 | -WANT="$(stamp_now)" | ||
| 116 | -if [[ -f "$OUT/jolt.boot" && -f "$OUT/scheme.h" && -f "$STAMP" ]] && | ||
| 117 | - [[ "$(<"$STAMP")" == "$WANT" ]]; then | ||
| 118 | - echo "jolt boot image up to date" >&2 | ||
| 119 | - exit 0 | ||
| 120 | -fi | ||
| 121 | - | ||
| 122 | -rm -f "$STAMP" | ||
| 123 | -rm -rf "$OUT/project" "$OUT/cross" | ||
| 124 | -mkdir -p "$OUT/project" "$OUT/cross" | ||
| 125 | -cat > "$OUT/project/deps.edn" <<EOF | ||
| 126 | -{:paths ["$ROOT/src" "$GLIMMER" "$GLIMMER_VIDYA"]} | ||
| 127 | -EOF | ||
| 128 | - | ||
| 129 | -( | ||
| 130 | - cd "$OUT/project" | ||
| 131 | - JOLT_NO_FLAT_SPLIT=1 "$JOLT" build \ | ||
| 132 | - -m "$MODULE" -o app | ||
| 133 | -) | ||
| 134 | - | ||
| 135 | -cat > "$OUT/cross/compile.ss" <<EOF | ||
| 136 | -(import (chezscheme)) | ||
| 137 | -(load "$XPATCH") | ||
| 138 | -(optimize-level 2) | ||
| 139 | -(generate-inspector-information #f) | ||
| 140 | -(compile-file "$OUT/project/app.build/flat.ss" "$OUT/cross/flat.so") | ||
| 141 | -(make-boot-file "$OUT/jolt.boot" '() | ||
| 142 | - "$TARGET_BOOT/petite.boot" | ||
| 143 | - "$TARGET_BOOT/scheme.boot" | ||
| 144 | - "$OUT/cross/flat.so") | ||
| 145 | -EOF | ||
| 146 | - | ||
| 147 | -SCHEMEHEAPDIRS="$CHEZ_ANDROID/ta6le/boot/ta6le" \ | ||
| 148 | - "$HOST_SCHEME" --script "$OUT/cross/compile.ss" | ||
| 149 | - | ||
| 150 | -cp "$TARGET_BOOT/scheme.h" "$OUT/scheme.h" | ||
| 151 | - | ||
| 152 | -# Last, so an interrupted build leaves no stamp and the next run redoes it. | ||
| 153 | -printf '%s\n' "$WANT" > "$STAMP" | ||
modified
justfile +16 -94 | @@ -1,107 +1,29 @@ | ||
| 1 | -set shell := ["bash", "-euo", "pipefail", "-c"] | |
| 2 | - | |
| 3 | -# Sibling checkouts, found relative to the *main* checkout rather than to this | |
| 4 | -# directory. In a git worktree they are not the same place: the justfile sits | |
| 5 | -# at .claude/worktrees/<name>, so "../jolt-native" from here would be | |
| 6 | -# .claude/worktrees/jolt-native, which is nothing. `--git-common-dir` is the one | |
| 7 | -# thing that answers the same in a worktree as it does in the checkout it came | |
| 8 | -# from. | |
| 9 | -checkout := parent_directory(`git rev-parse --path-format=absolute --git-common-dir`) | |
| 1 | +# Every recipe here is one line, because the work is in scripts/ — babashka | |
| 2 | +# scripts, run through the pin in scripts/bb. A recipe body is a shell script | |
| 3 | +# nobody can run on its own; a script in scripts/ is a script, and buck runs | |
| 4 | +# two of them as actions. | |
| 10 | 5 | |
| 11 | -# jolt-native holds both shared objects. A sibling checkout wins, so anyone | |
| 12 | -# working on the two repos together builds what they are editing; everyone else | |
| 13 | -# gets a clone of the gitlab repo under .jolt-native, pinned to the same commit | |
| 14 | -# deps.edn takes glimmer-vidya from. | |
| 15 | -jolt_native_url := "https://gitlab.com/nandithebull/jolt-native.git" | |
| 16 | -jolt_native_sha := "c71fc595f031aaa5cb461735af1865146f7b42ec" | |
| 17 | -jolt_native := ` | |
| 18 | - checkout="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")" | |
| 19 | - if [ -d "$checkout/../jolt-native" ]; then | |
| 20 | - cd "$checkout/../jolt-native" && pwd | |
| 21 | - else | |
| 22 | - echo "$checkout/.jolt-native" | |
| 23 | - fi | |
| 24 | -` | |
| 6 | +set shell := ["bash", "-euo", "pipefail", "-c"] | |
| 25 | 7 | |
| 26 | 8 | default: |
| 27 | 9 | @just --list |
| 28 | 10 | |
| 29 | -# Both native libraries: libvidya (the tree ABI glimmer-vidya binds) and | |
| 30 | -# libjoltmoq (the AV media plane). One workspace, one target directory. | |
| 31 | -lib: fetch | |
| 32 | - cd {{jolt_native}} && just build | |
| 33 | - | |
| 34 | -# Clone jolt-native at the pinned commit, unless it is already here — which it | |
| 35 | -# is whenever the sibling checkout exists, since that is what jolt_native then | |
| 36 | -# points at. | |
| 37 | -[private] | |
| 38 | -fetch: | |
| 39 | - if [ ! -d {{quote(jolt_native)}} ]; then \ | |
| 40 | - git clone {{jolt_native_url}} {{quote(jolt_native)}}; \ | |
| 41 | - cd {{quote(jolt_native)}} && git checkout --detach {{jolt_native_sha}}; \ | |
| 42 | - fi | |
| 11 | +# Both native libraries, cloning jolt-native at the pinned commit if needed. | |
| 12 | +lib: | |
| 13 | + scripts/lib.bb | |
| 43 | 14 | |
| 44 | 15 | # buck2, with the machine's paths written where the BUCK files can read them. |
| 45 | -# | |
| 46 | -# The same arrangement jolt-native uses, and for the same reason: a BUCK file | |
| 47 | -# may not look around the machine, and these five answers differ on every one. | |
| 48 | -# Generated rather than committed, so no checkout carries another's paths. | |
| 49 | 16 | buck *args: |
| 50 | - #!/usr/bin/env bash | |
| 51 | - set -euo pipefail | |
| 52 | - android_home="${ANDROID_HOME:-$HOME/.local/share/android-sdk}" | |
| 53 | - chez="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" | |
| 54 | - openssl="${OPENSSL_ANDROID:-$HOME/.cache/frq-openssl-android/lib}" | |
| 55 | - for path in "$android_home/build-tools/36.0.0/aapt2" \ | |
| 56 | - "$android_home/platforms/android-36/android.jar" \ | |
| 57 | - "$chez/boot/tarm64le/scheme.boot" \ | |
| 58 | - "$openssl/libssl.so"; do | |
| 59 | - [[ -e "$path" ]] || { echo "missing: $path" >&2; exit 1; } | |
| 60 | - done | |
| 61 | - # A jolt-native checkout wins over the pinned release, so that anyone | |
| 62 | - # working on both repos at once builds what they are editing. It is staged | |
| 63 | - # into this tree because a buck2 cell cannot reach outside its own root, | |
| 64 | - # and an action that shelled out to the other project would have nothing to | |
| 65 | - # invalidate on. With no checkout the release answers instead, fetched by | |
| 66 | - # digest — see toolchains/dist and scripts/libvidya-android.dotslash. | |
| 67 | - libvidya=pinned | |
| 68 | - if [[ -d "{{jolt_native}}/crates" ]]; then | |
| 69 | - libvidya=checkout | |
| 70 | - ( cd "{{jolt_native}}" && just ffi-android >/dev/null ) | |
| 71 | - mkdir -p android/prebuilt/arm64-v8a | |
| 72 | - cp "{{jolt_native}}/build/android/arm64-v8a/libvidya.so" \ | |
| 73 | - android/prebuilt/arm64-v8a/libvidya.so | |
| 74 | - # The glue travels with it, for the same reason: editing jolt_main.c | |
| 75 | - # should relink libjoltapp, and it cannot if buck only knows a path. | |
| 76 | - mkdir -p android/prebuilt/glue/android android/prebuilt/glue/include | |
| 77 | - cp "{{jolt_native}}/android/jolt_main.c" android/prebuilt/glue/android/ | |
| 78 | - cp "{{jolt_native}}"/crates/jolt-vidya/include/*.h android/prebuilt/glue/include/ | |
| 79 | - fi | |
| 80 | - # The boot image's other source roots are outside this cell too; hash them | |
| 81 | - # here so the digest reaches the action. See android/BUCK. | |
| 82 | - printf '[frq]\n jolt_native = %s\n android_home = %s\n chez_android = %s\n openssl_android = %s\n libvidya = %s\n boot_stamp = %s\n' \ | |
| 83 | - "{{jolt_native}}" "$android_home" "$chez" "$openssl" "$libvidya" \ | |
| 84 | - "$(android/build-jolt-boot.sh --stamp)" > .buckconfig.local | |
| 85 | - scripts/buck2 {{args}} | |
| 17 | + scripts/buck.bb {{args}} | |
| 86 | 18 | |
| 87 | -# The APK, built as a graph. `just apk install` puts it on the device. | |
| 19 | +# The APK. `just apk install` puts it on the device. | |
| 88 | 20 | apk action="build": |
| 89 | - #!/usr/bin/env bash | |
| 90 | - set -euo pipefail | |
| 91 | - apk="$(just buck build --show-output //:apk | awk '/frq.apk/{print $2}')" | |
| 92 | - case "{{action}}" in | |
| 93 | - build) printf '%s\n' "$apk" ;; | |
| 94 | - install) "${ADB:-$HOME/.local/share/android-sdk/platform-tools/adb}" install -r "$apk" ;; | |
| 95 | - run) | |
| 96 | - adb="${ADB:-$HOME/.local/share/android-sdk/platform-tools/adb}" | |
| 97 | - "$adb" install -r "$apk" | |
| 98 | - "$adb" shell am force-stop uk.nandi.frq | |
| 99 | - "$adb" shell am start -n uk.nandi.frq/.FrqActivity | |
| 100 | - ;; | |
| 101 | - log) "${ADB:-$HOME/.local/share/android-sdk/platform-tools/adb}" logcat -s VidyaJolt Vidya ;; | |
| 102 | - *) echo "usage: just apk [build|install|run|log]" >&2; exit 2 ;; | |
| 103 | - esac | |
| 21 | + scripts/apk.bb {{action}} | |
| 22 | + | |
| 23 | +# The archives scripts/*.dotslash pins, as the table buck reads. | |
| 24 | +sync-dist: | |
| 25 | + scripts/dotslash-to-buck | |
| 104 | 26 | |
| 105 | 27 | # The app. |
| 106 | 28 | run *args: |
| 107 | - LD_LIBRARY_PATH="{{jolt_native}}/target/release" jolt -M:frq {{args}} | |
| 29 | + scripts/run.bb {{args}} | |
| @@ -1,107 +1,29 @@ | |||
| 1 | -set shell := ["bash", "-euo", "pipefail", "-c"] | 1 | +# Every recipe here is one line, because the work is in scripts/ — babashka |
| 2 | - | 2 | +# scripts, run through the pin in scripts/bb. A recipe body is a shell script |
| 3 | -# Sibling checkouts, found relative to the *main* checkout rather than to this | 3 | +# nobody can run on its own; a script in scripts/ is a script, and buck runs |
| 4 | -# directory. In a git worktree they are not the same place: the justfile sits | 4 | +# two of them as actions. |
| 5 | -# at .claude/worktrees/<name>, so "../jolt-native" from here would be | ||
| 6 | -# .claude/worktrees/jolt-native, which is nothing. `--git-common-dir` is the one | ||
| 7 | -# thing that answers the same in a worktree as it does in the checkout it came | ||
| 8 | -# from. | ||
| 9 | -checkout := parent_directory(`git rev-parse --path-format=absolute --git-common-dir`) | ||
| 10 | 5 | ||
| 11 | -# jolt-native holds both shared objects. A sibling checkout wins, so anyone | 6 | +set shell := ["bash", "-euo", "pipefail", "-c"] |
| 12 | -# working on the two repos together builds what they are editing; everyone else | ||
| 13 | -# gets a clone of the gitlab repo under .jolt-native, pinned to the same commit | ||
| 14 | -# deps.edn takes glimmer-vidya from. | ||
| 15 | -jolt_native_url := "https://gitlab.com/nandithebull/jolt-native.git" | ||
| 16 | -jolt_native_sha := "c71fc595f031aaa5cb461735af1865146f7b42ec" | ||
| 17 | -jolt_native := ` | ||
| 18 | - checkout="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")" | ||
| 19 | - if [ -d "$checkout/../jolt-native" ]; then | ||
| 20 | - cd "$checkout/../jolt-native" && pwd | ||
| 21 | - else | ||
| 22 | - echo "$checkout/.jolt-native" | ||
| 23 | - fi | ||
| 24 | -` | ||
| 25 | 7 | ||
| 26 | default: | 8 | default: |
| 27 | @just --list | 9 | @just --list |
| 28 | 10 | ||
| 29 | -# Both native libraries: libvidya (the tree ABI glimmer-vidya binds) and | 11 | +# Both native libraries, cloning jolt-native at the pinned commit if needed. |
| 30 | -# libjoltmoq (the AV media plane). One workspace, one target directory. | 12 | +lib: |
| 31 | -lib: fetch | 13 | + scripts/lib.bb |
| 32 | - cd {{jolt_native}} && just build | ||
| 33 | - | ||
| 34 | -# Clone jolt-native at the pinned commit, unless it is already here — which it | ||
| 35 | -# is whenever the sibling checkout exists, since that is what jolt_native then | ||
| 36 | -# points at. | ||
| 37 | -[private] | ||
| 38 | -fetch: | ||
| 39 | - if [ ! -d {{quote(jolt_native)}} ]; then \ | ||
| 40 | - git clone {{jolt_native_url}} {{quote(jolt_native)}}; \ | ||
| 41 | - cd {{quote(jolt_native)}} && git checkout --detach {{jolt_native_sha}}; \ | ||
| 42 | - fi | ||
| 43 | 14 | ||
| 44 | # buck2, with the machine's paths written where the BUCK files can read them. | 15 | # buck2, with the machine's paths written where the BUCK files can read them. |
| 45 | -# | ||
| 46 | -# The same arrangement jolt-native uses, and for the same reason: a BUCK file | ||
| 47 | -# may not look around the machine, and these five answers differ on every one. | ||
| 48 | -# Generated rather than committed, so no checkout carries another's paths. | ||
| 49 | buck *args: | 16 | buck *args: |
| 50 | - #!/usr/bin/env bash | 17 | + scripts/buck.bb {{args}} |
| 51 | - set -euo pipefail | ||
| 52 | - android_home="${ANDROID_HOME:-$HOME/.local/share/android-sdk}" | ||
| 53 | - chez="${CHEZ_ANDROID:-$HOME/.cache/vidya-chez-android}" | ||
| 54 | - openssl="${OPENSSL_ANDROID:-$HOME/.cache/frq-openssl-android/lib}" | ||
| 55 | - for path in "$android_home/build-tools/36.0.0/aapt2" \ | ||
| 56 | - "$android_home/platforms/android-36/android.jar" \ | ||
| 57 | - "$chez/boot/tarm64le/scheme.boot" \ | ||
| 58 | - "$openssl/libssl.so"; do | ||
| 59 | - [[ -e "$path" ]] || { echo "missing: $path" >&2; exit 1; } | ||
| 60 | - done | ||
| 61 | - # A jolt-native checkout wins over the pinned release, so that anyone | ||
| 62 | - # working on both repos at once builds what they are editing. It is staged | ||
| 63 | - # into this tree because a buck2 cell cannot reach outside its own root, | ||
| 64 | - # and an action that shelled out to the other project would have nothing to | ||
| 65 | - # invalidate on. With no checkout the release answers instead, fetched by | ||
| 66 | - # digest — see toolchains/dist and scripts/libvidya-android.dotslash. | ||
| 67 | - libvidya=pinned | ||
| 68 | - if [[ -d "{{jolt_native}}/crates" ]]; then | ||
| 69 | - libvidya=checkout | ||
| 70 | - ( cd "{{jolt_native}}" && just ffi-android >/dev/null ) | ||
| 71 | - mkdir -p android/prebuilt/arm64-v8a | ||
| 72 | - cp "{{jolt_native}}/build/android/arm64-v8a/libvidya.so" \ | ||
| 73 | - android/prebuilt/arm64-v8a/libvidya.so | ||
| 74 | - # The glue travels with it, for the same reason: editing jolt_main.c | ||
| 75 | - # should relink libjoltapp, and it cannot if buck only knows a path. | ||
| 76 | - mkdir -p android/prebuilt/glue/android android/prebuilt/glue/include | ||
| 77 | - cp "{{jolt_native}}/android/jolt_main.c" android/prebuilt/glue/android/ | ||
| 78 | - cp "{{jolt_native}}"/crates/jolt-vidya/include/*.h android/prebuilt/glue/include/ | ||
| 79 | - fi | ||
| 80 | - # The boot image's other source roots are outside this cell too; hash them | ||
| 81 | - # here so the digest reaches the action. See android/BUCK. | ||
| 82 | - printf '[frq]\n jolt_native = %s\n android_home = %s\n chez_android = %s\n openssl_android = %s\n libvidya = %s\n boot_stamp = %s\n' \ | ||
| 83 | - "{{jolt_native}}" "$android_home" "$chez" "$openssl" "$libvidya" \ | ||
| 84 | - "$(android/build-jolt-boot.sh --stamp)" > .buckconfig.local | ||
| 85 | - scripts/buck2 {{args}} | ||
| 86 | 18 | ||
| 87 | -# The APK, built as a graph. `just apk install` puts it on the device. | 19 | +# The APK. `just apk install` puts it on the device. |
| 88 | apk action="build": | 20 | apk action="build": |
| 89 | - #!/usr/bin/env bash | 21 | + scripts/apk.bb {{action}} |
| 90 | - set -euo pipefail | 22 | + |
| 91 | - apk="$(just buck build --show-output //:apk | awk '/frq.apk/{print $2}')" | 23 | +# The archives scripts/*.dotslash pins, as the table buck reads. |
| 92 | - case "{{action}}" in | 24 | +sync-dist: |
| 93 | - build) printf '%s\n' "$apk" ;; | 25 | + scripts/dotslash-to-buck |
| 94 | - install) "${ADB:-$HOME/.local/share/android-sdk/platform-tools/adb}" install -r "$apk" ;; | ||
| 95 | - run) | ||
| 96 | - adb="${ADB:-$HOME/.local/share/android-sdk/platform-tools/adb}" | ||
| 97 | - "$adb" install -r "$apk" | ||
| 98 | - "$adb" shell am force-stop uk.nandi.frq | ||
| 99 | - "$adb" shell am start -n uk.nandi.frq/.FrqActivity | ||
| 100 | - ;; | ||
| 101 | - log) "${ADB:-$HOME/.local/share/android-sdk/platform-tools/adb}" logcat -s VidyaJolt Vidya ;; | ||
| 102 | - *) echo "usage: just apk [build|install|run|log]" >&2; exit 2 ;; | ||
| 103 | - esac | ||
| 104 | 26 | ||
| 105 | # The app. | 27 | # The app. |
| 106 | run *args: | 28 | run *args: |
| 107 | - LD_LIBRARY_PATH="{{jolt_native}}/target/release" jolt -M:frq {{args}} | 29 | + scripts/run.bb {{args}} |
modified
scripts/android-cc +30 -18 | @@ -1,20 +1,32 @@ | ||
| 1 | 1 | #!/bin/sh |
| 2 | -# The NDK's clang, as the linker rustc runs for an Android target. | |
| 3 | -# | |
| 4 | -# The buck-side twin of this is toolchains/ndk.sh, which the C toolchain gets | |
| 5 | -# as a dependency; both resolve the same archive, pinned in one place — | |
| 6 | -# android-ndk.dotslash beside this script. rustc reaches it through a shim | |
| 7 | -# instead because -Clinker takes a plain string: rustc_flags cannot carry an | |
| 8 | -# artifact path, so the linker has to be something with a stable name. The cfg | |
| 9 | -# in toolchains/BUCK is what keeps two NDKs from sharing a cache entry. | |
| 10 | -# | |
| 11 | -# ANDROID_NDK_HOME wins if it is set, so a machine that already has an NDK | |
| 12 | -# skips the download. | |
| 13 | -set -e | |
| 14 | -dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) | |
| 15 | -api=${JOLT_ANDROID_API:-28} | |
| 16 | -target=${JOLT_ANDROID_TARGET:-aarch64-linux-android} | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 17 | 5 | |
| 18 | -# android-ndk-bin is where the lookup lives, because an APK build wants the | |
| 19 | -# other binaries out of the same archive. | |
| 20 | -exec "$("$dir/android-ndk-bin")/clang" --target="$target$api" "$@" | |
| 6 | +;; The NDK's clang, as the linker rustc runs for an Android target. | |
| 7 | +;; | |
| 8 | +;; The buck-side twin of this is toolchains/ndk.sh, which the C toolchain gets | |
| 9 | +;; as a dependency; both resolve the same archive, pinned in one place — | |
| 10 | +;; android-ndk.dotslash beside this script. rustc reaches it through a shim | |
| 11 | +;; instead because -Clinker takes a plain string: rustc_flags cannot carry an | |
| 12 | +;; artifact path, so the linker has to be something with a stable name. The cfg | |
| 13 | +;; in toolchains/BUCK is what keeps two NDKs from sharing a cache entry. | |
| 14 | +;; | |
| 15 | +;; ANDROID_NDK_HOME wins if it is set, so a machine that already has an NDK | |
| 16 | +;; skips the download. | |
| 17 | +(require '[babashka.fs :as fs] | |
| 18 | + '[babashka.process :as p] | |
| 19 | + '[clojure.string :as str]) | |
| 20 | + | |
| 21 | +(def here (fs/parent (fs/canonicalize *file*))) | |
| 22 | +(def api (or (not-empty (str (System/getenv "JOLT_ANDROID_API"))) "28")) | |
| 23 | +(def target (or (not-empty (str (System/getenv "JOLT_ANDROID_TARGET"))) "aarch64-linux-android")) | |
| 24 | + | |
| 25 | +;; android-ndk-bin is where the lookup lives, because an APK build wants the | |
| 26 | +;; other binaries out of the same archive. | |
| 27 | +(def bin (str/trim (:out (p/shell {:out :string} (str (fs/path here "android-ndk-bin")))))) | |
| 28 | + | |
| 29 | +(System/exit | |
| 30 | + (:exit @(apply p/process {:inherit true} | |
| 31 | + (str (fs/path bin "clang")) (str "--target=" target api) | |
| 32 | + *command-line-args*))) | |
| @@ -1,20 +1,32 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | -# The NDK's clang, as the linker rustc runs for an Android target. | 2 | +#_( |
| 3 | -# | 3 | +exec "$(dirname "$0")/bb" "$0" "$@" |
| 4 | -# The buck-side twin of this is toolchains/ndk.sh, which the C toolchain gets | 4 | +) |
| 5 | -# as a dependency; both resolve the same archive, pinned in one place — | ||
| 6 | -# android-ndk.dotslash beside this script. rustc reaches it through a shim | ||
| 7 | -# instead because -Clinker takes a plain string: rustc_flags cannot carry an | ||
| 8 | -# artifact path, so the linker has to be something with a stable name. The cfg | ||
| 9 | -# in toolchains/BUCK is what keeps two NDKs from sharing a cache entry. | ||
| 10 | -# | ||
| 11 | -# ANDROID_NDK_HOME wins if it is set, so a machine that already has an NDK | ||
| 12 | -# skips the download. | ||
| 13 | -set -e | ||
| 14 | -dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) | ||
| 15 | -api=${JOLT_ANDROID_API:-28} | ||
| 16 | -target=${JOLT_ANDROID_TARGET:-aarch64-linux-android} | ||
| 17 | 5 | ||
| 18 | -# android-ndk-bin is where the lookup lives, because an APK build wants the | 6 | +;; The NDK's clang, as the linker rustc runs for an Android target. |
| 19 | -# other binaries out of the same archive. | 7 | +;; |
| 20 | -exec "$("$dir/android-ndk-bin")/clang" --target="$target$api" "$@" | 8 | +;; The buck-side twin of this is toolchains/ndk.sh, which the C toolchain gets |
| 9 | +;; as a dependency; both resolve the same archive, pinned in one place — | ||
| 10 | +;; android-ndk.dotslash beside this script. rustc reaches it through a shim | ||
| 11 | +;; instead because -Clinker takes a plain string: rustc_flags cannot carry an | ||
| 12 | +;; artifact path, so the linker has to be something with a stable name. The cfg | ||
| 13 | +;; in toolchains/BUCK is what keeps two NDKs from sharing a cache entry. | ||
| 14 | +;; | ||
| 15 | +;; ANDROID_NDK_HOME wins if it is set, so a machine that already has an NDK | ||
| 16 | +;; skips the download. | ||
| 17 | +(require '[babashka.fs :as fs] | ||
| 18 | + '[babashka.process :as p] | ||
| 19 | + '[clojure.string :as str]) | ||
| 20 | + | ||
| 21 | +(def here (fs/parent (fs/canonicalize *file*))) | ||
| 22 | +(def api (or (not-empty (str (System/getenv "JOLT_ANDROID_API"))) "28")) | ||
| 23 | +(def target (or (not-empty (str (System/getenv "JOLT_ANDROID_TARGET"))) "aarch64-linux-android")) | ||
| 24 | + | ||
| 25 | +;; android-ndk-bin is where the lookup lives, because an APK build wants the | ||
| 26 | +;; other binaries out of the same archive. | ||
| 27 | +(def bin (str/trim (:out (p/shell {:out :string} (str (fs/path here "android-ndk-bin")))))) | ||
| 28 | + | ||
| 29 | +(System/exit | ||
| 30 | + (:exit @(apply p/process {:inherit true} | ||
| 31 | + (str (fs/path bin "clang")) (str "--target=" target api) | ||
| 32 | + *command-line-args*))) | ||
modified
scripts/android-ndk-bin +29 -20 | @@ -1,23 +1,32 @@ | ||
| 1 | 1 | #!/bin/sh |
| 2 | -# Print the directory holding the NDK's clang and LLVM binutils. | |
| 3 | -# | |
| 4 | -# android-cc needs one of those binaries and an APK build needs others — | |
| 5 | -# llvm-objcopy, for the boot image blob — so the lookup is here rather than | |
| 6 | -# duplicated at each caller. ANDROID_NDK_HOME wins if it is set; otherwise the | |
| 7 | -# pin in android-ndk.dotslash beside this script answers, downloading once. | |
| 8 | -set -e | |
| 9 | -dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 10 | 5 | |
| 11 | -ndk=${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}} | |
| 12 | -if [ -n "$ndk" ]; then | |
| 13 | - for host in linux-x86_64 darwin-x86_64; do | |
| 14 | - if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then | |
| 15 | - echo "$ndk/toolchains/llvm/prebuilt/$host/bin" | |
| 16 | - exit 0 | |
| 17 | - fi | |
| 18 | - done | |
| 19 | - echo "scripts/android-ndk-bin: no llvm prebuilt under $ndk" >&2 | |
| 20 | - exit 1 | |
| 21 | -fi | |
| 6 | +;; Print the directory holding the NDK's clang and LLVM binutils. | |
| 7 | +;; | |
| 8 | +;; android-cc needs one of those binaries and an APK build needs others — | |
| 9 | +;; llvm-objcopy, for the boot image blob — so the lookup is here rather than | |
| 10 | +;; duplicated at each caller. ANDROID_NDK_HOME wins if it is set; otherwise the | |
| 11 | +;; pin in android-ndk.dotslash beside this script answers, downloading once. | |
| 12 | +(require '[babashka.fs :as fs] | |
| 13 | + '[babashka.process :as p] | |
| 14 | + '[clojure.string :as str]) | |
| 22 | 15 | |
| 23 | -dirname -- "$(dotslash -- fetch "$dir/android-ndk.dotslash")" | |
| 16 | +(def here (fs/parent (fs/canonicalize *file*))) | |
| 17 | +(def ndk (or (not-empty (str (System/getenv "ANDROID_NDK_HOME"))) | |
| 18 | + (not-empty (str (System/getenv "ANDROID_NDK_ROOT"))))) | |
| 19 | + | |
| 20 | +(if ndk | |
| 21 | + (if-let [bin (->> ["linux-x86_64" "darwin-x86_64"] | |
| 22 | + (map #(fs/path ndk "toolchains" "llvm" "prebuilt" % "bin")) | |
| 23 | + (filter fs/directory?) | |
| 24 | + first)] | |
| 25 | + (println (str bin)) | |
| 26 | + (binding [*out* *err*] | |
| 27 | + (println (str "scripts/android-ndk-bin: no llvm prebuilt under " ndk)) | |
| 28 | + (System/exit 1))) | |
| 29 | + (println (str (fs/parent | |
| 30 | + (str/trim (:out (p/shell {:out :string} | |
| 31 | + "dotslash" "--" "fetch" | |
| 32 | + (str (fs/path here "android-ndk.dotslash"))))))))) | |
| @@ -1,23 +1,32 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | -# Print the directory holding the NDK's clang and LLVM binutils. | 2 | +#_( |
| 3 | -# | 3 | +exec "$(dirname "$0")/bb" "$0" "$@" |
| 4 | -# android-cc needs one of those binaries and an APK build needs others — | 4 | +) |
| 5 | -# llvm-objcopy, for the boot image blob — so the lookup is here rather than | ||
| 6 | -# duplicated at each caller. ANDROID_NDK_HOME wins if it is set; otherwise the | ||
| 7 | -# pin in android-ndk.dotslash beside this script answers, downloading once. | ||
| 8 | -set -e | ||
| 9 | -dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) | ||
| 10 | 5 | ||
| 11 | -ndk=${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}} | 6 | +;; Print the directory holding the NDK's clang and LLVM binutils. |
| 12 | -if [ -n "$ndk" ]; then | 7 | +;; |
| 13 | - for host in linux-x86_64 darwin-x86_64; do | 8 | +;; android-cc needs one of those binaries and an APK build needs others — |
| 14 | - if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then | 9 | +;; llvm-objcopy, for the boot image blob — so the lookup is here rather than |
| 15 | - echo "$ndk/toolchains/llvm/prebuilt/$host/bin" | 10 | +;; duplicated at each caller. ANDROID_NDK_HOME wins if it is set; otherwise the |
| 16 | - exit 0 | 11 | +;; pin in android-ndk.dotslash beside this script answers, downloading once. |
| 17 | - fi | 12 | +(require '[babashka.fs :as fs] |
| 18 | - done | 13 | + '[babashka.process :as p] |
| 19 | - echo "scripts/android-ndk-bin: no llvm prebuilt under $ndk" >&2 | 14 | + '[clojure.string :as str]) |
| 20 | - exit 1 | ||
| 21 | -fi | ||
| 22 | 15 | ||
| 23 | -dirname -- "$(dotslash -- fetch "$dir/android-ndk.dotslash")" | 16 | +(def here (fs/parent (fs/canonicalize *file*))) |
| 17 | +(def ndk (or (not-empty (str (System/getenv "ANDROID_NDK_HOME"))) | ||
| 18 | + (not-empty (str (System/getenv "ANDROID_NDK_ROOT"))))) | ||
| 19 | + | ||
| 20 | +(if ndk | ||
| 21 | + (if-let [bin (->> ["linux-x86_64" "darwin-x86_64"] | ||
| 22 | + (map #(fs/path ndk "toolchains" "llvm" "prebuilt" % "bin")) | ||
| 23 | + (filter fs/directory?) | ||
| 24 | + first)] | ||
| 25 | + (println (str bin)) | ||
| 26 | + (binding [*out* *err*] | ||
| 27 | + (println (str "scripts/android-ndk-bin: no llvm prebuilt under " ndk)) | ||
| 28 | + (System/exit 1))) | ||
| 29 | + (println (str (fs/parent | ||
| 30 | + (str/trim (:out (p/shell {:out :string} | ||
| 31 | + "dotslash" "--" "fetch" | ||
| 32 | + (str (fs/path here "android-ndk.dotslash"))))))))) | ||
added
scripts/apk.bb +41 -0 | new file mode 100755 | ||
| @@ -0,0 +1,41 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 5 | + | |
| 6 | +;; The APK, built as a graph. | |
| 7 | +;; | |
| 8 | +;; apk.bb [build|install|run|log] | |
| 9 | +;; | |
| 10 | +;; The build itself is android/BUCK; this only asks buck for the file and then | |
| 11 | +;; does what was asked with it. android/build-apk.bb is the same APK built step | |
| 12 | +;; by step instead, for when the graph is what is being doubted. | |
| 13 | +(require '[babashka.classpath :as cp]) | |
| 14 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | |
| 15 | +(require '[frq.paths :as paths] | |
| 16 | + '[babashka.fs :as fs] | |
| 17 | + '[babashka.process :as p] | |
| 18 | + '[clojure.string :as str]) | |
| 19 | + | |
| 20 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | |
| 21 | +(def action (or (first *command-line-args*) "build")) | |
| 22 | +(def adb (paths/env "ADB" (str (fs/path (fs/home) ".local" "share" "android-sdk" | |
| 23 | + "platform-tools" "adb")))) | |
| 24 | +(def package "uk.nandi.frq") | |
| 25 | + | |
| 26 | +(defn apk [] | |
| 27 | + (->> (paths/out (str (fs/path root "scripts" "buck.bb")) | |
| 28 | + "build" "--show-output" "//:apk") | |
| 29 | + str/split-lines | |
| 30 | + (some #(when (str/includes? % "frq.apk") (second (str/split % #"\s+")))) | |
| 31 | + (str root "/"))) | |
| 32 | + | |
| 33 | +(case action | |
| 34 | + "build" (println (apk)) | |
| 35 | + "install" (p/shell adb "install" "-r" (apk)) | |
| 36 | + "run" (let [file (apk)] | |
| 37 | + (p/shell adb "install" "-r" file) | |
| 38 | + (p/shell adb "shell" "am" "force-stop" package) | |
| 39 | + (p/shell adb "shell" "am" "start" "-n" (str package "/.FrqActivity"))) | |
| 40 | + "log" (p/shell adb "logcat" "-s" "VidyaJolt" "Vidya") | |
| 41 | + (paths/die "usage: apk.bb [build|install|run|log]")) | |
| new file mode 100755 | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +#_( | ||
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | ||
| 4 | +) | ||
| 5 | + | ||
| 6 | +;; The APK, built as a graph. | ||
| 7 | +;; | ||
| 8 | +;; apk.bb [build|install|run|log] | ||
| 9 | +;; | ||
| 10 | +;; The build itself is android/BUCK; this only asks buck for the file and then | ||
| 11 | +;; does what was asked with it. android/build-apk.bb is the same APK built step | ||
| 12 | +;; by step instead, for when the graph is what is being doubted. | ||
| 13 | +(require '[babashka.classpath :as cp]) | ||
| 14 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | ||
| 15 | +(require '[frq.paths :as paths] | ||
| 16 | + '[babashka.fs :as fs] | ||
| 17 | + '[babashka.process :as p] | ||
| 18 | + '[clojure.string :as str]) | ||
| 19 | + | ||
| 20 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | ||
| 21 | +(def action (or (first *command-line-args*) "build")) | ||
| 22 | +(def adb (paths/env "ADB" (str (fs/path (fs/home) ".local" "share" "android-sdk" | ||
| 23 | + "platform-tools" "adb")))) | ||
| 24 | +(def package "uk.nandi.frq") | ||
| 25 | + | ||
| 26 | +(defn apk [] | ||
| 27 | + (->> (paths/out (str (fs/path root "scripts" "buck.bb")) | ||
| 28 | + "build" "--show-output" "//:apk") | ||
| 29 | + str/split-lines | ||
| 30 | + (some #(when (str/includes? % "frq.apk") (second (str/split % #"\s+")))) | ||
| 31 | + (str root "/"))) | ||
| 32 | + | ||
| 33 | +(case action | ||
| 34 | + "build" (println (apk)) | ||
| 35 | + "install" (p/shell adb "install" "-r" (apk)) | ||
| 36 | + "run" (let [file (apk)] | ||
| 37 | + (p/shell adb "install" "-r" file) | ||
| 38 | + (p/shell adb "shell" "am" "force-stop" package) | ||
| 39 | + (p/shell adb "shell" "am" "start" "-n" (str package "/.FrqActivity"))) | ||
| 40 | + "log" (p/shell adb "logcat" "-s" "VidyaJolt" "Vidya") | ||
| 41 | + (paths/die "usage: apk.bb [build|install|run|log]")) | ||
added
scripts/bb +65 -0 | new file mode 100755 | ||
| @@ -0,0 +1,65 @@ | ||
| 1 | +#!/usr/bin/env dotslash | |
| 2 | + | |
| 3 | +// babashka, which the scripts here are written in. | |
| 4 | +// | |
| 5 | +// A build script wants the things a shell is bad at — reading deps.edn, | |
| 6 | +// hashing a file set, holding a path in a variable without quoting it three | |
| 7 | +// times — and Clojure is already the language this repo is written in. Pinned | |
| 8 | +// like every other tool, so the script a person runs at a terminal is the one | |
| 9 | +// buck runs in an action. | |
| 10 | +// | |
| 11 | +// The static Linux builds, because a build machine's glibc is not ours to | |
| 12 | +// assume; macOS has no static option and does not need one. | |
| 13 | +{ | |
| 14 | + "name": "bb", | |
| 15 | + "platforms": { | |
| 16 | + "linux-x86_64": { | |
| 17 | + "size": 26177709, | |
| 18 | + "hash": "sha256", | |
| 19 | + "digest": "eb3edd128276f0b6fbdefcb18dc7d42652a95ea409a81b34f08e36b8ac3cbc3c", | |
| 20 | + "format": "tar.gz", | |
| 21 | + "path": "bb", | |
| 22 | + "providers": [ | |
| 23 | + { | |
| 24 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-linux-amd64-static.tar.gz" | |
| 25 | + } | |
| 26 | + ] | |
| 27 | + }, | |
| 28 | + "linux-aarch64": { | |
| 29 | + "size": 28271754, | |
| 30 | + "hash": "sha256", | |
| 31 | + "digest": "0efd6ef36b93ea2f0ae6ebf9d1bcd1a66167c2f41c17e990659aa067654437e8", | |
| 32 | + "format": "tar.gz", | |
| 33 | + "path": "bb", | |
| 34 | + "providers": [ | |
| 35 | + { | |
| 36 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-linux-aarch64-static.tar.gz" | |
| 37 | + } | |
| 38 | + ] | |
| 39 | + }, | |
| 40 | + "macos-x86_64": { | |
| 41 | + "size": 26050398, | |
| 42 | + "hash": "sha256", | |
| 43 | + "digest": "ebbfdf159e8d5ee8b7015b15c6558b2039fe76478de893cf317c8c50805340de", | |
| 44 | + "format": "tar.gz", | |
| 45 | + "path": "bb", | |
| 46 | + "providers": [ | |
| 47 | + { | |
| 48 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-macos-amd64.tar.gz" | |
| 49 | + } | |
| 50 | + ] | |
| 51 | + }, | |
| 52 | + "macos-aarch64": { | |
| 53 | + "size": 26548984, | |
| 54 | + "hash": "sha256", | |
| 55 | + "digest": "f7d18c3ab11bb4ad0e32a45c1ae40ae25911d5fa06ba4ded07cb90b8af158077", | |
| 56 | + "format": "tar.gz", | |
| 57 | + "path": "bb", | |
| 58 | + "providers": [ | |
| 59 | + { | |
| 60 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-macos-aarch64.tar.gz" | |
| 61 | + } | |
| 62 | + ] | |
| 63 | + } | |
| 64 | + } | |
| 65 | +} | |
| new file mode 100755 | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | +#!/usr/bin/env dotslash | ||
| 2 | + | ||
| 3 | +// babashka, which the scripts here are written in. | ||
| 4 | +// | ||
| 5 | +// A build script wants the things a shell is bad at — reading deps.edn, | ||
| 6 | +// hashing a file set, holding a path in a variable without quoting it three | ||
| 7 | +// times — and Clojure is already the language this repo is written in. Pinned | ||
| 8 | +// like every other tool, so the script a person runs at a terminal is the one | ||
| 9 | +// buck runs in an action. | ||
| 10 | +// | ||
| 11 | +// The static Linux builds, because a build machine's glibc is not ours to | ||
| 12 | +// assume; macOS has no static option and does not need one. | ||
| 13 | +{ | ||
| 14 | + "name": "bb", | ||
| 15 | + "platforms": { | ||
| 16 | + "linux-x86_64": { | ||
| 17 | + "size": 26177709, | ||
| 18 | + "hash": "sha256", | ||
| 19 | + "digest": "eb3edd128276f0b6fbdefcb18dc7d42652a95ea409a81b34f08e36b8ac3cbc3c", | ||
| 20 | + "format": "tar.gz", | ||
| 21 | + "path": "bb", | ||
| 22 | + "providers": [ | ||
| 23 | + { | ||
| 24 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-linux-amd64-static.tar.gz" | ||
| 25 | + } | ||
| 26 | + ] | ||
| 27 | + }, | ||
| 28 | + "linux-aarch64": { | ||
| 29 | + "size": 28271754, | ||
| 30 | + "hash": "sha256", | ||
| 31 | + "digest": "0efd6ef36b93ea2f0ae6ebf9d1bcd1a66167c2f41c17e990659aa067654437e8", | ||
| 32 | + "format": "tar.gz", | ||
| 33 | + "path": "bb", | ||
| 34 | + "providers": [ | ||
| 35 | + { | ||
| 36 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-linux-aarch64-static.tar.gz" | ||
| 37 | + } | ||
| 38 | + ] | ||
| 39 | + }, | ||
| 40 | + "macos-x86_64": { | ||
| 41 | + "size": 26050398, | ||
| 42 | + "hash": "sha256", | ||
| 43 | + "digest": "ebbfdf159e8d5ee8b7015b15c6558b2039fe76478de893cf317c8c50805340de", | ||
| 44 | + "format": "tar.gz", | ||
| 45 | + "path": "bb", | ||
| 46 | + "providers": [ | ||
| 47 | + { | ||
| 48 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-macos-amd64.tar.gz" | ||
| 49 | + } | ||
| 50 | + ] | ||
| 51 | + }, | ||
| 52 | + "macos-aarch64": { | ||
| 53 | + "size": 26548984, | ||
| 54 | + "hash": "sha256", | ||
| 55 | + "digest": "f7d18c3ab11bb4ad0e32a45c1ae40ae25911d5fa06ba4ded07cb90b8af158077", | ||
| 56 | + "format": "tar.gz", | ||
| 57 | + "path": "bb", | ||
| 58 | + "providers": [ | ||
| 59 | + { | ||
| 60 | + "url": "https://github.com/babashka/babashka/releases/download/v1.13.220/babashka-1.13.220-macos-aarch64.tar.gz" | ||
| 61 | + } | ||
| 62 | + ] | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | +} | ||
added
scripts/buck.bb +72 -0 | new file mode 100755 | ||
| @@ -0,0 +1,72 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 5 | + | |
| 6 | +;; buck2, with the machine's paths written where the BUCK files can read them. | |
| 7 | +;; | |
| 8 | +;; The same arrangement jolt-native uses, and for the same reason: a BUCK file | |
| 9 | +;; may not look around the machine, and these answers differ on every one. | |
| 10 | +;; Generated rather than committed, so no checkout carries another's paths. | |
| 11 | +(require '[babashka.classpath :as cp]) | |
| 12 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | |
| 13 | +(require '[frq.paths :as paths] | |
| 14 | + '[babashka.fs :as fs] | |
| 15 | + '[babashka.process :as p]) | |
| 16 | + | |
| 17 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | |
| 18 | +(def jolt-native (paths/jolt-native root)) | |
| 19 | +(def android-home (paths/env "ANDROID_HOME" (str (fs/path (fs/home) ".local" "share" "android-sdk")))) | |
| 20 | +(def chez (paths/env "CHEZ_ANDROID" (str (fs/path (fs/home) ".cache" "vidya-chez-android")))) | |
| 21 | +(def openssl (paths/env "OPENSSL_ANDROID" (str (fs/path (fs/home) ".cache" "frq-openssl-android" "lib")))) | |
| 22 | + | |
| 23 | +(paths/require-paths! "path" | |
| 24 | + [(fs/path android-home "build-tools" "36.0.0" "aapt2") | |
| 25 | + (fs/path android-home "platforms" "android-36" "android.jar") | |
| 26 | + (fs/path chez "boot" "tarm64le" "scheme.boot") | |
| 27 | + (fs/path openssl "libssl.so")]) | |
| 28 | + | |
| 29 | +;; A jolt-native checkout wins over the pinned release, so that anyone working | |
| 30 | +;; on both repos at once builds what they are editing. It is staged into this | |
| 31 | +;; tree because a buck2 cell cannot reach outside its own root, and an action | |
| 32 | +;; that shelled out to the other project would have nothing to invalidate on. | |
| 33 | +;; With no checkout the release answers instead, fetched by digest — see | |
| 34 | +;; toolchains/dist and scripts/libvidya-android.dotslash. | |
| 35 | +(def libvidya | |
| 36 | + (if (fs/directory? (fs/path jolt-native "crates")) | |
| 37 | + (let [prebuilt (fs/path root "android" "prebuilt")] | |
| 38 | + (p/shell {:dir jolt-native :out :string} "just" "ffi-android") | |
| 39 | + (fs/create-dirs (fs/path prebuilt "arm64-v8a")) | |
| 40 | + (fs/copy (fs/path jolt-native "build" "android" "arm64-v8a" "libvidya.so") | |
| 41 | + (fs/path prebuilt "arm64-v8a" "libvidya.so") | |
| 42 | + {:replace-existing true}) | |
| 43 | + ;; The glue travels with it, for the same reason: editing jolt_main.c | |
| 44 | + ;; should relink libjoltapp, and it cannot if buck only knows a path. | |
| 45 | + (fs/create-dirs (fs/path prebuilt "glue" "android")) | |
| 46 | + (fs/create-dirs (fs/path prebuilt "glue" "include")) | |
| 47 | + (fs/copy (fs/path jolt-native "android" "jolt_main.c") | |
| 48 | + (fs/path prebuilt "glue" "android" "jolt_main.c") | |
| 49 | + {:replace-existing true}) | |
| 50 | + (doseq [h (fs/glob (fs/path jolt-native "crates" "jolt-vidya" "include") "*.h")] | |
| 51 | + (fs/copy h (fs/path prebuilt "glue" "include" (fs/file-name h)) | |
| 52 | + {:replace-existing true})) | |
| 53 | + "checkout") | |
| 54 | + "pinned")) | |
| 55 | + | |
| 56 | +;; The boot image's other source roots are outside this cell too; hash them | |
| 57 | +;; here so the digest reaches the action. See android/BUCK. | |
| 58 | +(def boot-stamp | |
| 59 | + (paths/out (str (fs/path root "android" "build-jolt-boot.bb")) "--stamp")) | |
| 60 | + | |
| 61 | +(spit (str (fs/path root ".buckconfig.local")) | |
| 62 | + (str "[frq]\n" | |
| 63 | + " jolt_native = " jolt-native "\n" | |
| 64 | + " android_home = " android-home "\n" | |
| 65 | + " chez_android = " chez "\n" | |
| 66 | + " openssl_android = " openssl "\n" | |
| 67 | + " libvidya = " libvidya "\n" | |
| 68 | + " boot_stamp = " boot-stamp "\n")) | |
| 69 | + | |
| 70 | +(System/exit | |
| 71 | + (:exit @(apply p/process {:inherit true :dir root} | |
| 72 | + (str (fs/path root "scripts" "buck2")) *command-line-args*))) | |
| new file mode 100755 | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +#_( | ||
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | ||
| 4 | +) | ||
| 5 | + | ||
| 6 | +;; buck2, with the machine's paths written where the BUCK files can read them. | ||
| 7 | +;; | ||
| 8 | +;; The same arrangement jolt-native uses, and for the same reason: a BUCK file | ||
| 9 | +;; may not look around the machine, and these answers differ on every one. | ||
| 10 | +;; Generated rather than committed, so no checkout carries another's paths. | ||
| 11 | +(require '[babashka.classpath :as cp]) | ||
| 12 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | ||
| 13 | +(require '[frq.paths :as paths] | ||
| 14 | + '[babashka.fs :as fs] | ||
| 15 | + '[babashka.process :as p]) | ||
| 16 | + | ||
| 17 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | ||
| 18 | +(def jolt-native (paths/jolt-native root)) | ||
| 19 | +(def android-home (paths/env "ANDROID_HOME" (str (fs/path (fs/home) ".local" "share" "android-sdk")))) | ||
| 20 | +(def chez (paths/env "CHEZ_ANDROID" (str (fs/path (fs/home) ".cache" "vidya-chez-android")))) | ||
| 21 | +(def openssl (paths/env "OPENSSL_ANDROID" (str (fs/path (fs/home) ".cache" "frq-openssl-android" "lib")))) | ||
| 22 | + | ||
| 23 | +(paths/require-paths! "path" | ||
| 24 | + [(fs/path android-home "build-tools" "36.0.0" "aapt2") | ||
| 25 | + (fs/path android-home "platforms" "android-36" "android.jar") | ||
| 26 | + (fs/path chez "boot" "tarm64le" "scheme.boot") | ||
| 27 | + (fs/path openssl "libssl.so")]) | ||
| 28 | + | ||
| 29 | +;; A jolt-native checkout wins over the pinned release, so that anyone working | ||
| 30 | +;; on both repos at once builds what they are editing. It is staged into this | ||
| 31 | +;; tree because a buck2 cell cannot reach outside its own root, and an action | ||
| 32 | +;; that shelled out to the other project would have nothing to invalidate on. | ||
| 33 | +;; With no checkout the release answers instead, fetched by digest — see | ||
| 34 | +;; toolchains/dist and scripts/libvidya-android.dotslash. | ||
| 35 | +(def libvidya | ||
| 36 | + (if (fs/directory? (fs/path jolt-native "crates")) | ||
| 37 | + (let [prebuilt (fs/path root "android" "prebuilt")] | ||
| 38 | + (p/shell {:dir jolt-native :out :string} "just" "ffi-android") | ||
| 39 | + (fs/create-dirs (fs/path prebuilt "arm64-v8a")) | ||
| 40 | + (fs/copy (fs/path jolt-native "build" "android" "arm64-v8a" "libvidya.so") | ||
| 41 | + (fs/path prebuilt "arm64-v8a" "libvidya.so") | ||
| 42 | + {:replace-existing true}) | ||
| 43 | + ;; The glue travels with it, for the same reason: editing jolt_main.c | ||
| 44 | + ;; should relink libjoltapp, and it cannot if buck only knows a path. | ||
| 45 | + (fs/create-dirs (fs/path prebuilt "glue" "android")) | ||
| 46 | + (fs/create-dirs (fs/path prebuilt "glue" "include")) | ||
| 47 | + (fs/copy (fs/path jolt-native "android" "jolt_main.c") | ||
| 48 | + (fs/path prebuilt "glue" "android" "jolt_main.c") | ||
| 49 | + {:replace-existing true}) | ||
| 50 | + (doseq [h (fs/glob (fs/path jolt-native "crates" "jolt-vidya" "include") "*.h")] | ||
| 51 | + (fs/copy h (fs/path prebuilt "glue" "include" (fs/file-name h)) | ||
| 52 | + {:replace-existing true})) | ||
| 53 | + "checkout") | ||
| 54 | + "pinned")) | ||
| 55 | + | ||
| 56 | +;; The boot image's other source roots are outside this cell too; hash them | ||
| 57 | +;; here so the digest reaches the action. See android/BUCK. | ||
| 58 | +(def boot-stamp | ||
| 59 | + (paths/out (str (fs/path root "android" "build-jolt-boot.bb")) "--stamp")) | ||
| 60 | + | ||
| 61 | +(spit (str (fs/path root ".buckconfig.local")) | ||
| 62 | + (str "[frq]\n" | ||
| 63 | + " jolt_native = " jolt-native "\n" | ||
| 64 | + " android_home = " android-home "\n" | ||
| 65 | + " chez_android = " chez "\n" | ||
| 66 | + " openssl_android = " openssl "\n" | ||
| 67 | + " libvidya = " libvidya "\n" | ||
| 68 | + " boot_stamp = " boot-stamp "\n")) | ||
| 69 | + | ||
| 70 | +(System/exit | ||
| 71 | + (:exit @(apply p/process {:inherit true :dir root} | ||
| 72 | + (str (fs/path root "scripts" "buck2")) *command-line-args*))) | ||
modified
scripts/dotslash-to-buck +81 -82 | @@ -1,90 +1,89 @@ | ||
| 1 | -#!/usr/bin/env python3 | |
| 2 | -"""Generate buck2 http_archive data from the DotSlash files beside this script. | |
| 1 | +#!/bin/sh | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 3 | 5 | |
| 4 | -DotSlash is where a tool's version, URL and digest are written down. buck needs | |
| 5 | -the same three facts to fetch that tool itself — it cannot use the shim, which | |
| 6 | -resolves on the machine at run time and so is neither an action input nor | |
| 7 | -anything a remote worker could be given. Rather than write them twice and let | |
| 8 | -them drift, this reads the manifests and emits the table buck reads. | |
| 6 | +;; Generate buck2 http_archive data from the DotSlash files beside this script. | |
| 7 | +;; | |
| 8 | +;; DotSlash is where a tool's version, URL and digest are written down. buck | |
| 9 | +;; needs the same three facts to fetch that tool itself — it cannot use the | |
| 10 | +;; shim, which resolves on the machine at run time and so is neither an action | |
| 11 | +;; input nor anything a remote worker could be given. Rather than write them | |
| 12 | +;; twice and let them drift, this reads the manifests and emits the table buck | |
| 13 | +;; reads. | |
| 14 | +;; | |
| 15 | +;; just sync-dist # after editing any scripts/*.dotslash | |
| 16 | +;; | |
| 17 | +;; The DotSlash format is JSON with a `//` shebang line on top: one entry per | |
| 18 | +;; platform, each with a url, a size, a digest and the path of the binary | |
| 19 | +;; inside the archive. Only the archive itself is wanted here; the path inside | |
| 20 | +;; it is the shim's business, and the toolchain rules name what they need. | |
| 21 | +(require '[babashka.fs :as fs] | |
| 22 | + '[cheshire.core :as json] | |
| 23 | + '[clojure.string :as str]) | |
| 9 | 24 | |
| 10 | - just sync-dist # after editing any scripts/*.dotslash | |
| 25 | +(def here (fs/parent (fs/canonicalize *file*))) | |
| 26 | +(def out-file (fs/path (fs/parent here) "toolchains" "dist" "generated.bzl")) | |
| 11 | 27 | |
| 12 | -The DotSlash format is JSON with a `//` shebang line on top: one entry per | |
| 13 | -platform, each with a url, a size, a digest and the path of the binary inside | |
| 14 | -the archive. Only the archive itself is wanted here; the path inside it is the | |
| 15 | -shim's business, and the toolchain rules name what they need. | |
| 16 | -""" | |
| 28 | +;; The tools buck fetches for itself. buck2 is not among them: it is the thing | |
| 29 | +;; doing the fetching. jolt is not either — it is fetched by DotSlash on the | |
| 30 | +;; machine that runs the boot image compile, which may not be this one. | |
| 31 | +(def tools | |
| 32 | + [;; DotSlash itself, so an action can fetch what it needs where it runs | |
| 33 | + ;; rather than being handed it from here. See android/BUCK. | |
| 34 | + "dotslash" | |
| 35 | + ;; jolt-native's release, so the APK's UI half and the Jolt side of the | |
| 36 | + ;; tree ABI are fetched rather than built out of a second checkout. | |
| 37 | + "libvidya-android" | |
| 38 | + "glimmer-vidya" | |
| 39 | + "android-glue"]) | |
| 17 | 40 | |
| 18 | -import json | |
| 19 | -import pathlib | |
| 20 | -import sys | |
| 41 | +(defn load-manifest [stem] | |
| 42 | + (let [candidate (->> [(fs/path here (str stem ".dotslash")) (fs/path here stem)] | |
| 43 | + (filter fs/exists?) | |
| 44 | + first)] | |
| 45 | + (when-not candidate | |
| 46 | + (binding [*out* *err*] (println (str "no DotSlash manifest for " stem))) | |
| 47 | + (System/exit 1)) | |
| 48 | + (let [text (slurp (str candidate))] | |
| 49 | + (json/parse-string (subs text (str/index-of text "{")))))) | |
| 21 | 50 | |
| 22 | -HERE = pathlib.Path(__file__).resolve().parent | |
| 23 | -OUT = HERE.parent / "toolchains" / "dist" / "generated.bzl" | |
| 51 | +(defn platform-entry [entry] | |
| 52 | + (let [;; The path of the binary inside the archive starts with the directory | |
| 53 | + ;; the tarball unpacks into, which is what to strip. A flat archive — | |
| 54 | + ;; one binary, no directory around it — has nothing to strip, and | |
| 55 | + ;; naming the binary would strip the whole payload. | |
| 56 | + [head rest] (str/split (get entry "path") #"/" 2)] | |
| 57 | + (array-map "url" (get-in entry ["providers" 0 "url"]) | |
| 58 | + "sha256" (get entry "digest") | |
| 59 | + "strip_prefix" (if rest head "") | |
| 60 | + "type" (get entry "format")))) | |
| 24 | 61 | |
| 25 | -# The tools buck fetches for itself. buck2 is not among them: it is the thing | |
| 26 | -# doing the fetching. jolt is not either — it is fetched by DotSlash on the | |
| 27 | -# machine that runs the boot image compile, which may not be this one. | |
| 28 | -TOOLS = { | |
| 29 | - # DotSlash itself, so an action can fetch what it needs where it runs | |
| 30 | - # rather than being handed it from here. See android/BUCK. | |
| 31 | - "dotslash": "dotslash", | |
| 32 | - # jolt-native's release, so the APK's UI half and the Jolt side of the | |
| 33 | - # tree ABI are fetched rather than built out of a second checkout. | |
| 34 | - "libvidya-android": "libvidya-android", | |
| 35 | - "glimmer-vidya": "glimmer-vidya", | |
| 36 | - "android-glue": "android-glue", | |
| 37 | -} | |
| 62 | +(def dist | |
| 63 | + (into (array-map) | |
| 64 | + (for [stem (sort tools)] | |
| 65 | + [stem (into (array-map) | |
| 66 | + (for [[platform entry] (sort-by key (get (load-manifest stem) "platforms")) | |
| 67 | + ;; http_archive takes sha256 and nothing else. | |
| 68 | + :when (= "sha256" (get entry "hash"))] | |
| 69 | + [platform (platform-entry entry)]))]))) | |
| 38 | 70 | |
| 71 | +;; Four-space indentation, like the Starlark around it: this file is read by | |
| 72 | +;; people as often as by buck. | |
| 73 | +(def pretty | |
| 74 | + (json/create-pretty-printer | |
| 75 | + (assoc json/default-pretty-print-options | |
| 76 | + :indentation " " | |
| 77 | + :object-field-value-separator ": "))) | |
| 39 | 78 | |
| 40 | -def load(path): | |
| 41 | - text = path.read_text() | |
| 42 | - return json.loads(text[text.index("{") :]) | |
| 43 | - | |
| 44 | - | |
| 45 | -def main(): | |
| 46 | - entries = {} | |
| 47 | - for stem, name in sorted(TOOLS.items()): | |
| 48 | - for candidate in (HERE / f"{stem}.dotslash", HERE / stem): | |
| 49 | - if candidate.exists(): | |
| 50 | - manifest = load(candidate) | |
| 51 | - break | |
| 52 | - else: | |
| 53 | - sys.exit(f"no DotSlash manifest for {stem}") | |
| 54 | - | |
| 55 | - platforms = {} | |
| 56 | - for platform, entry in sorted(manifest["platforms"].items()): | |
| 57 | - if entry["hash"] != "sha256": | |
| 58 | - # http_archive takes sha256 and nothing else. | |
| 59 | - continue | |
| 60 | - url = entry["providers"][0]["url"] | |
| 61 | - # The path of the binary inside the archive starts with the | |
| 62 | - # directory the tarball unpacks into, which is what to strip. | |
| 63 | - # A flat archive — one binary, no directory around it — has | |
| 64 | - # nothing to strip, and naming the binary would strip the whole | |
| 65 | - # payload. | |
| 66 | - head, _, rest = entry["path"].partition("/") | |
| 67 | - platforms[platform] = { | |
| 68 | - "url": url, | |
| 69 | - "sha256": entry["digest"], | |
| 70 | - "strip_prefix": head if rest else "", | |
| 71 | - "type": entry["format"], | |
| 72 | - } | |
| 73 | - entries[name] = platforms | |
| 74 | - | |
| 75 | - lines = [ | |
| 76 | - "# @generated by scripts/dotslash-to-buck — do not edit.", | |
| 77 | - "#", | |
| 78 | - "# The same archives scripts/*.dotslash pins, as facts buck can act on.", | |
| 79 | - "# Bump the DotSlash file and run `just sync-dist`.", | |
| 80 | - "", | |
| 81 | - "DIST = " + json.dumps(entries, indent=4).replace(": true", ": True"), | |
| 82 | - "", | |
| 83 | - ] | |
| 84 | - OUT.parent.mkdir(parents=True, exist_ok=True) | |
| 85 | - OUT.write_text("\n".join(lines)) | |
| 86 | - print(f"wrote {OUT.relative_to(HERE.parent)}") | |
| 87 | - | |
| 88 | - | |
| 89 | -if __name__ == "__main__": | |
| 90 | - main() | |
| 79 | +(fs/create-dirs (fs/parent out-file)) | |
| 80 | +(spit (str out-file) | |
| 81 | + (str/join "\n" | |
| 82 | + ["# @generated by scripts/dotslash-to-buck — do not edit." | |
| 83 | + "#" | |
| 84 | + "# The same archives scripts/*.dotslash pins, as facts buck can act on." | |
| 85 | + "# Bump the DotSlash file and run `just sync-dist`." | |
| 86 | + "" | |
| 87 | + (str "DIST = " (json/generate-string dist {:pretty pretty})) | |
| 88 | + ""])) | |
| 89 | +(println (str "wrote " (fs/relativize (fs/parent here) out-file))) | |
| @@ -1,90 +1,89 @@ | |||
| 1 | -#!/usr/bin/env python3 | 1 | +#!/bin/sh |
| 2 | -"""Generate buck2 http_archive data from the DotSlash files beside this script. | 2 | +#_( |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | ||
| 4 | +) | ||
| 3 | 5 | ||
| 4 | -DotSlash is where a tool's version, URL and digest are written down. buck needs | 6 | +;; Generate buck2 http_archive data from the DotSlash files beside this script. |
| 5 | -the same three facts to fetch that tool itself — it cannot use the shim, which | 7 | +;; |
| 6 | -resolves on the machine at run time and so is neither an action input nor | 8 | +;; DotSlash is where a tool's version, URL and digest are written down. buck |
| 7 | -anything a remote worker could be given. Rather than write them twice and let | 9 | +;; needs the same three facts to fetch that tool itself — it cannot use the |
| 8 | -them drift, this reads the manifests and emits the table buck reads. | 10 | +;; shim, which resolves on the machine at run time and so is neither an action |
| 11 | +;; input nor anything a remote worker could be given. Rather than write them | ||
| 12 | +;; twice and let them drift, this reads the manifests and emits the table buck | ||
| 13 | +;; reads. | ||
| 14 | +;; | ||
| 15 | +;; just sync-dist # after editing any scripts/*.dotslash | ||
| 16 | +;; | ||
| 17 | +;; The DotSlash format is JSON with a `//` shebang line on top: one entry per | ||
| 18 | +;; platform, each with a url, a size, a digest and the path of the binary | ||
| 19 | +;; inside the archive. Only the archive itself is wanted here; the path inside | ||
| 20 | +;; it is the shim's business, and the toolchain rules name what they need. | ||
| 21 | +(require '[babashka.fs :as fs] | ||
| 22 | + '[cheshire.core :as json] | ||
| 23 | + '[clojure.string :as str]) | ||
| 9 | 24 | ||
| 10 | - just sync-dist # after editing any scripts/*.dotslash | 25 | +(def here (fs/parent (fs/canonicalize *file*))) |
| 26 | +(def out-file (fs/path (fs/parent here) "toolchains" "dist" "generated.bzl")) | ||
| 11 | 27 | ||
| 12 | -The DotSlash format is JSON with a `//` shebang line on top: one entry per | 28 | +;; The tools buck fetches for itself. buck2 is not among them: it is the thing |
| 13 | -platform, each with a url, a size, a digest and the path of the binary inside | 29 | +;; doing the fetching. jolt is not either — it is fetched by DotSlash on the |
| 14 | -the archive. Only the archive itself is wanted here; the path inside it is the | 30 | +;; machine that runs the boot image compile, which may not be this one. |
| 15 | -shim's business, and the toolchain rules name what they need. | 31 | +(def tools |
| 16 | -""" | 32 | + [;; DotSlash itself, so an action can fetch what it needs where it runs |
| 33 | + ;; rather than being handed it from here. See android/BUCK. | ||
| 34 | + "dotslash" | ||
| 35 | + ;; jolt-native's release, so the APK's UI half and the Jolt side of the | ||
| 36 | + ;; tree ABI are fetched rather than built out of a second checkout. | ||
| 37 | + "libvidya-android" | ||
| 38 | + "glimmer-vidya" | ||
| 39 | + "android-glue"]) | ||
| 17 | 40 | ||
| 18 | -import json | 41 | +(defn load-manifest [stem] |
| 19 | -import pathlib | 42 | + (let [candidate (->> [(fs/path here (str stem ".dotslash")) (fs/path here stem)] |
| 20 | -import sys | 43 | + (filter fs/exists?) |
| 44 | + first)] | ||
| 45 | + (when-not candidate | ||
| 46 | + (binding [*out* *err*] (println (str "no DotSlash manifest for " stem))) | ||
| 47 | + (System/exit 1)) | ||
| 48 | + (let [text (slurp (str candidate))] | ||
| 49 | + (json/parse-string (subs text (str/index-of text "{")))))) | ||
| 21 | 50 | ||
| 22 | -HERE = pathlib.Path(__file__).resolve().parent | 51 | +(defn platform-entry [entry] |
| 23 | -OUT = HERE.parent / "toolchains" / "dist" / "generated.bzl" | 52 | + (let [;; The path of the binary inside the archive starts with the directory |
| 53 | + ;; the tarball unpacks into, which is what to strip. A flat archive — | ||
| 54 | + ;; one binary, no directory around it — has nothing to strip, and | ||
| 55 | + ;; naming the binary would strip the whole payload. | ||
| 56 | + [head rest] (str/split (get entry "path") #"/" 2)] | ||
| 57 | + (array-map "url" (get-in entry ["providers" 0 "url"]) | ||
| 58 | + "sha256" (get entry "digest") | ||
| 59 | + "strip_prefix" (if rest head "") | ||
| 60 | + "type" (get entry "format")))) | ||
| 24 | 61 | ||
| 25 | -# The tools buck fetches for itself. buck2 is not among them: it is the thing | 62 | +(def dist |
| 26 | -# doing the fetching. jolt is not either — it is fetched by DotSlash on the | 63 | + (into (array-map) |
| 27 | -# machine that runs the boot image compile, which may not be this one. | 64 | + (for [stem (sort tools)] |
| 28 | -TOOLS = { | 65 | + [stem (into (array-map) |
| 29 | - # DotSlash itself, so an action can fetch what it needs where it runs | 66 | + (for [[platform entry] (sort-by key (get (load-manifest stem) "platforms")) |
| 30 | - # rather than being handed it from here. See android/BUCK. | 67 | + ;; http_archive takes sha256 and nothing else. |
| 31 | - "dotslash": "dotslash", | 68 | + :when (= "sha256" (get entry "hash"))] |
| 32 | - # jolt-native's release, so the APK's UI half and the Jolt side of the | 69 | + [platform (platform-entry entry)]))]))) |
| 33 | - # tree ABI are fetched rather than built out of a second checkout. | ||
| 34 | - "libvidya-android": "libvidya-android", | ||
| 35 | - "glimmer-vidya": "glimmer-vidya", | ||
| 36 | - "android-glue": "android-glue", | ||
| 37 | -} | ||
| 38 | 70 | ||
| 71 | +;; Four-space indentation, like the Starlark around it: this file is read by | ||
| 72 | +;; people as often as by buck. | ||
| 73 | +(def pretty | ||
| 74 | + (json/create-pretty-printer | ||
| 75 | + (assoc json/default-pretty-print-options | ||
| 76 | + :indentation " " | ||
| 77 | + :object-field-value-separator ": "))) | ||
| 39 | 78 | ||
| 40 | -def load(path): | 79 | +(fs/create-dirs (fs/parent out-file)) |
| 41 | - text = path.read_text() | 80 | +(spit (str out-file) |
| 42 | - return json.loads(text[text.index("{") :]) | 81 | + (str/join "\n" |
| 43 | - | 82 | + ["# @generated by scripts/dotslash-to-buck — do not edit." |
| 44 | - | 83 | + "#" |
| 45 | -def main(): | 84 | + "# The same archives scripts/*.dotslash pins, as facts buck can act on." |
| 46 | - entries = {} | 85 | + "# Bump the DotSlash file and run `just sync-dist`." |
| 47 | - for stem, name in sorted(TOOLS.items()): | 86 | + "" |
| 48 | - for candidate in (HERE / f"{stem}.dotslash", HERE / stem): | 87 | + (str "DIST = " (json/generate-string dist {:pretty pretty})) |
| 49 | - if candidate.exists(): | 88 | + ""])) |
| 50 | - manifest = load(candidate) | 89 | +(println (str "wrote " (fs/relativize (fs/parent here) out-file))) |
| 51 | - break | ||
| 52 | - else: | ||
| 53 | - sys.exit(f"no DotSlash manifest for {stem}") | ||
| 54 | - | ||
| 55 | - platforms = {} | ||
| 56 | - for platform, entry in sorted(manifest["platforms"].items()): | ||
| 57 | - if entry["hash"] != "sha256": | ||
| 58 | - # http_archive takes sha256 and nothing else. | ||
| 59 | - continue | ||
| 60 | - url = entry["providers"][0]["url"] | ||
| 61 | - # The path of the binary inside the archive starts with the | ||
| 62 | - # directory the tarball unpacks into, which is what to strip. | ||
| 63 | - # A flat archive — one binary, no directory around it — has | ||
| 64 | - # nothing to strip, and naming the binary would strip the whole | ||
| 65 | - # payload. | ||
| 66 | - head, _, rest = entry["path"].partition("/") | ||
| 67 | - platforms[platform] = { | ||
| 68 | - "url": url, | ||
| 69 | - "sha256": entry["digest"], | ||
| 70 | - "strip_prefix": head if rest else "", | ||
| 71 | - "type": entry["format"], | ||
| 72 | - } | ||
| 73 | - entries[name] = platforms | ||
| 74 | - | ||
| 75 | - lines = [ | ||
| 76 | - "# @generated by scripts/dotslash-to-buck — do not edit.", | ||
| 77 | - "#", | ||
| 78 | - "# The same archives scripts/*.dotslash pins, as facts buck can act on.", | ||
| 79 | - "# Bump the DotSlash file and run `just sync-dist`.", | ||
| 80 | - "", | ||
| 81 | - "DIST = " + json.dumps(entries, indent=4).replace(": true", ": True"), | ||
| 82 | - "", | ||
| 83 | - ] | ||
| 84 | - OUT.parent.mkdir(parents=True, exist_ok=True) | ||
| 85 | - OUT.write_text("\n".join(lines)) | ||
| 86 | - print(f"wrote {OUT.relative_to(HERE.parent)}") | ||
| 87 | - | ||
| 88 | - | ||
| 89 | -if __name__ == "__main__": | ||
| 90 | - main() | ||
added
scripts/frq/paths.clj +55 -0 | new file mode 100644 | ||
| @@ -0,0 +1,55 @@ | ||
| 1 | +;; Where the other halves of this build are, and how to ask a program a | |
| 2 | +;; question without writing the same three lines of shell each time. | |
| 3 | +;; | |
| 4 | +;; Every script here loads this; nothing else does. The answers match the | |
| 5 | +;; justfile's, deliberately — a person who runs `just apk` and a person who | |
| 6 | +;; runs android/build-apk.bb should be building the same thing. | |
| 7 | +(ns frq.paths | |
| 8 | + (:require [babashka.fs :as fs] | |
| 9 | + [babashka.process :as p] | |
| 10 | + [clojure.edn :as edn] | |
| 11 | + [clojure.string :as str])) | |
| 12 | + | |
| 13 | +(defn die [& lines] | |
| 14 | + (binding [*out* *err*] (run! println lines)) | |
| 15 | + (System/exit 1)) | |
| 16 | + | |
| 17 | +(defn out | |
| 18 | + "Run a command and return its standard output, trimmed. Throws if it fails." | |
| 19 | + [& args] | |
| 20 | + (str/trim (:out (apply p/shell {:out :string} (map str args))))) | |
| 21 | + | |
| 22 | +(defn env [k default] (or (not-empty (System/getenv k)) default)) | |
| 23 | + | |
| 24 | +;; The checkout this tree belongs to, which in a git worktree is not this tree: | |
| 25 | +;; the scripts sit under .claude/worktrees/<name>, so "../jolt-native" from | |
| 26 | +;; here is nothing at all. --git-common-dir is the one thing that answers the | |
| 27 | +;; same in a worktree as in the checkout it came from. | |
| 28 | +(defn checkout [root] | |
| 29 | + (str (fs/parent (out "git" "-C" (str root) | |
| 30 | + "rev-parse" "--path-format=absolute" "--git-common-dir")))) | |
| 31 | + | |
| 32 | +;; jolt-native holds both native halves. A sibling checkout wins, so anyone | |
| 33 | +;; working on the two repos together builds what they are editing; everyone | |
| 34 | +;; else gets the clone `just lib` leaves under .jolt-native. | |
| 35 | +(defn jolt-native [root] | |
| 36 | + (or (env "JOLT_NATIVE" nil) | |
| 37 | + (let [c (checkout root) | |
| 38 | + sibling (fs/path (fs/parent c) "jolt-native")] | |
| 39 | + (if (fs/directory? sibling) | |
| 40 | + (str (fs/canonicalize sibling)) | |
| 41 | + (str (fs/path c ".jolt-native")))))) | |
| 42 | + | |
| 43 | +;; The sha deps.edn pins for a git url, so a bump there reaches the boot image. | |
| 44 | +;; Read as data rather than grepped: it is Clojure, and so is this. | |
| 45 | +(defn dep-sha [root url] | |
| 46 | + (->> (:deps (edn/read-string (slurp (str (fs/path root "deps.edn"))))) | |
| 47 | + vals | |
| 48 | + (some #(when (= url (:git/url %)) (:git/sha %))))) | |
| 49 | + | |
| 50 | +(defn require-paths! | |
| 51 | + "Every path must exist, or say which one did not and stop." | |
| 52 | + [label paths & hint] | |
| 53 | + (doseq [p paths] | |
| 54 | + (when-not (fs/exists? (str p)) | |
| 55 | + (apply die (str "missing " label ": " p) hint)))) | |
| new file mode 100644 | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | +;; Where the other halves of this build are, and how to ask a program a | ||
| 2 | +;; question without writing the same three lines of shell each time. | ||
| 3 | +;; | ||
| 4 | +;; Every script here loads this; nothing else does. The answers match the | ||
| 5 | +;; justfile's, deliberately — a person who runs `just apk` and a person who | ||
| 6 | +;; runs android/build-apk.bb should be building the same thing. | ||
| 7 | +(ns frq.paths | ||
| 8 | + (:require [babashka.fs :as fs] | ||
| 9 | + [babashka.process :as p] | ||
| 10 | + [clojure.edn :as edn] | ||
| 11 | + [clojure.string :as str])) | ||
| 12 | + | ||
| 13 | +(defn die [& lines] | ||
| 14 | + (binding [*out* *err*] (run! println lines)) | ||
| 15 | + (System/exit 1)) | ||
| 16 | + | ||
| 17 | +(defn out | ||
| 18 | + "Run a command and return its standard output, trimmed. Throws if it fails." | ||
| 19 | + [& args] | ||
| 20 | + (str/trim (:out (apply p/shell {:out :string} (map str args))))) | ||
| 21 | + | ||
| 22 | +(defn env [k default] (or (not-empty (System/getenv k)) default)) | ||
| 23 | + | ||
| 24 | +;; The checkout this tree belongs to, which in a git worktree is not this tree: | ||
| 25 | +;; the scripts sit under .claude/worktrees/<name>, so "../jolt-native" from | ||
| 26 | +;; here is nothing at all. --git-common-dir is the one thing that answers the | ||
| 27 | +;; same in a worktree as in the checkout it came from. | ||
| 28 | +(defn checkout [root] | ||
| 29 | + (str (fs/parent (out "git" "-C" (str root) | ||
| 30 | + "rev-parse" "--path-format=absolute" "--git-common-dir")))) | ||
| 31 | + | ||
| 32 | +;; jolt-native holds both native halves. A sibling checkout wins, so anyone | ||
| 33 | +;; working on the two repos together builds what they are editing; everyone | ||
| 34 | +;; else gets the clone `just lib` leaves under .jolt-native. | ||
| 35 | +(defn jolt-native [root] | ||
| 36 | + (or (env "JOLT_NATIVE" nil) | ||
| 37 | + (let [c (checkout root) | ||
| 38 | + sibling (fs/path (fs/parent c) "jolt-native")] | ||
| 39 | + (if (fs/directory? sibling) | ||
| 40 | + (str (fs/canonicalize sibling)) | ||
| 41 | + (str (fs/path c ".jolt-native")))))) | ||
| 42 | + | ||
| 43 | +;; The sha deps.edn pins for a git url, so a bump there reaches the boot image. | ||
| 44 | +;; Read as data rather than grepped: it is Clojure, and so is this. | ||
| 45 | +(defn dep-sha [root url] | ||
| 46 | + (->> (:deps (edn/read-string (slurp (str (fs/path root "deps.edn"))))) | ||
| 47 | + vals | ||
| 48 | + (some #(when (= url (:git/url %)) (:git/sha %))))) | ||
| 49 | + | ||
| 50 | +(defn require-paths! | ||
| 51 | + "Every path must exist, or say which one did not and stop." | ||
| 52 | + [label paths & hint] | ||
| 53 | + (doseq [p paths] | ||
| 54 | + (when-not (fs/exists? (str p)) | ||
| 55 | + (apply die (str "missing " label ": " p) hint)))) | ||
added
scripts/lib.bb +30 -0 | new file mode 100755 | ||
| @@ -0,0 +1,30 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 5 | + | |
| 6 | +;; Both native libraries: libvidya (the tree ABI glimmer-vidya binds) and | |
| 7 | +;; libjoltmoq (the AV media plane). One workspace, one target directory. | |
| 8 | +;; | |
| 9 | +;; A sibling jolt-native checkout wins, so anyone working on the two repos | |
| 10 | +;; together builds what they are editing; everyone else gets a clone of the | |
| 11 | +;; gitlab repo under .jolt-native, pinned to the commit deps.edn takes | |
| 12 | +;; glimmer-vidya from — one place to bump, rather than a second sha here. | |
| 13 | +(require '[babashka.classpath :as cp]) | |
| 14 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | |
| 15 | +(require '[frq.paths :as paths] | |
| 16 | + '[babashka.fs :as fs] | |
| 17 | + '[babashka.process :as p]) | |
| 18 | + | |
| 19 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | |
| 20 | +(def url "https://gitlab.com/nandithebull/jolt-native.git") | |
| 21 | +(def sha (paths/dep-sha root "https://gitlab.com/nandithebull/jolt-native")) | |
| 22 | +(def jolt-native (paths/jolt-native root)) | |
| 23 | + | |
| 24 | +;; Already here whenever the sibling checkout exists, since that is what | |
| 25 | +;; jolt-native then points at. | |
| 26 | +(when-not (fs/directory? jolt-native) | |
| 27 | + (p/shell "git" "clone" url jolt-native) | |
| 28 | + (p/shell {:dir jolt-native} "git" "checkout" "--detach" sha)) | |
| 29 | + | |
| 30 | +(p/shell {:dir jolt-native} "just" "build") | |
| new file mode 100755 | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +#_( | ||
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | ||
| 4 | +) | ||
| 5 | + | ||
| 6 | +;; Both native libraries: libvidya (the tree ABI glimmer-vidya binds) and | ||
| 7 | +;; libjoltmoq (the AV media plane). One workspace, one target directory. | ||
| 8 | +;; | ||
| 9 | +;; A sibling jolt-native checkout wins, so anyone working on the two repos | ||
| 10 | +;; together builds what they are editing; everyone else gets a clone of the | ||
| 11 | +;; gitlab repo under .jolt-native, pinned to the commit deps.edn takes | ||
| 12 | +;; glimmer-vidya from — one place to bump, rather than a second sha here. | ||
| 13 | +(require '[babashka.classpath :as cp]) | ||
| 14 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | ||
| 15 | +(require '[frq.paths :as paths] | ||
| 16 | + '[babashka.fs :as fs] | ||
| 17 | + '[babashka.process :as p]) | ||
| 18 | + | ||
| 19 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | ||
| 20 | +(def url "https://gitlab.com/nandithebull/jolt-native.git") | ||
| 21 | +(def sha (paths/dep-sha root "https://gitlab.com/nandithebull/jolt-native")) | ||
| 22 | +(def jolt-native (paths/jolt-native root)) | ||
| 23 | + | ||
| 24 | +;; Already here whenever the sibling checkout exists, since that is what | ||
| 25 | +;; jolt-native then points at. | ||
| 26 | +(when-not (fs/directory? jolt-native) | ||
| 27 | + (p/shell "git" "clone" url jolt-native) | ||
| 28 | + (p/shell {:dir jolt-native} "git" "checkout" "--detach" sha)) | ||
| 29 | + | ||
| 30 | +(p/shell {:dir jolt-native} "just" "build") | ||
added
scripts/run.bb +21 -0 | new file mode 100755 | ||
| @@ -0,0 +1,21 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +#_( | |
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | |
| 4 | +) | |
| 5 | + | |
| 6 | +;; The app. The native libraries are wherever jolt-native is — a sibling | |
| 7 | +;; checkout, or the clone `just lib` leaves under .jolt-native. | |
| 8 | +(require '[babashka.classpath :as cp]) | |
| 9 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | |
| 10 | +(require '[frq.paths :as paths] | |
| 11 | + '[babashka.fs :as fs] | |
| 12 | + '[babashka.process :as p]) | |
| 13 | + | |
| 14 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | |
| 15 | + | |
| 16 | +(System/exit | |
| 17 | + (:exit @(apply p/process | |
| 18 | + {:inherit true | |
| 19 | + :extra-env {"LD_LIBRARY_PATH" | |
| 20 | + (str (fs/path (paths/jolt-native root) "target" "release"))}} | |
| 21 | + "jolt" "-M:frq" *command-line-args*))) | |
| new file mode 100755 | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +#_( | ||
| 3 | +exec "$(dirname "$0")/bb" "$0" "$@" | ||
| 4 | +) | ||
| 5 | + | ||
| 6 | +;; The app. The native libraries are wherever jolt-native is — a sibling | ||
| 7 | +;; checkout, or the clone `just lib` leaves under .jolt-native. | ||
| 8 | +(require '[babashka.classpath :as cp]) | ||
| 9 | +(cp/add-classpath (str (babashka.fs/parent *file*))) | ||
| 10 | +(require '[frq.paths :as paths] | ||
| 11 | + '[babashka.fs :as fs] | ||
| 12 | + '[babashka.process :as p]) | ||
| 13 | + | ||
| 14 | +(def root (str (fs/canonicalize (fs/path (fs/parent *file*) "..")))) | ||
| 15 | + | ||
| 16 | +(System/exit | ||
| 17 | + (:exit @(apply p/process | ||
| 18 | + {:inherit true | ||
| 19 | + :extra-env {"LD_LIBRARY_PATH" | ||
| 20 | + (str (fs/path (paths/jolt-native root) "target" "release"))}} | ||
| 21 | + "jolt" "-M:frq" *command-line-args*))) | ||