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

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

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>
nandi committed 2026-08-31T18:32:40-07:00 Browse files
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
9797 from the vidya checkout; only the boot image is frq's.
9898
9999 ```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
102102 ```
103103
104104 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 ```bash99 ```bash
100-./android/build-apk.sh run # build, install, launch on a connected device100+./android/build-apk.bb run # build, install, launch on a connected device
101-./android/build-apk.sh log # logcat, filtered101+./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 Chez104 Needs what vidya's Android build needs — SDK, NDK r29, and a cross-built Chez
modified android/BUCK +6 -3
@@ -1,6 +1,6 @@
11 # The APK, as a graph rather than a script.
22 #
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
44 # to one screen rebuilds the boot image and repackages, and touches neither the
55 # Rust nor the Java. The two native halves come from jolt-native — libvidya out
66 # 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
4848 # buck's own scratch space is not this.
4949 _TMP = 'tmp=`mktemp -d`'
5050
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.
5154 export_file(
52- name = "build-jolt-boot.sh",
55+ name = "build-jolt-boot.bb",
5356 mode = "reference",
5457 )
5558
@@ -94,7 +97,7 @@ genrule(
9497 # is no jolt-native checkout. An input, so a new release recompiles
9598 # the image.
9699 _GLIMMER_VIDYA +
97- "$(location :build-jolt-boot.sh) \"$OUT\" >&2",
100+ "$(location :build-jolt-boot.bb) \"$OUT\" >&2",
98101 )
99102
100103 # 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 change3+# 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 the4 # 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 out5 # 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 the6 # 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 recompiles97 # 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; the103 # 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.
105
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"]
257
268 default:
279 @just --list
2810
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
4314
4415 # 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.
4916 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}}
8618
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.
8820 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
10426
10527 # The app.
10628 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 this3+# 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 sits4+# 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 anyone6+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 --list9 @just --list
28 10
29-# Both native libraries: libvidya (the tree ABI glimmer-vidya binds) and11+# 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: fetch13+ 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 bash17+ 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 bash21+ scripts/apk.bb {{action}}
90- set -euo pipefail22+
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}}" in24+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 @@
11 #!/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+)
175
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/sh1 #!/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 gets4+)
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 the6+;; 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 @@
11 #!/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+)
105
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])
2215
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/sh1 #!/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" ]; then7+;;
13- for host in linux-x86_64 darwin-x86_64; do8+;; android-cc needs one of those binaries and an APK build needs others
14- if [ -d "$ndk/toolchains/llvm/prebuilt/$host/bin" ]; then9+;; 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 011+;; pin in android-ndk.dotslash beside this script answers, downloading once.
17- fi12+(require '[babashka.fs :as fs]
18- done13+ '[babashka.process :as p]
19- echo "scripts/android-ndk-bin: no llvm prebuilt under $ndk" >&214+ '[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+)
35
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])
924
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"))
1127
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"])
1740
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 "{"))))))
2150
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"))))
2461
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)]))])))
3870
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 ": ")))
3978
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 python31+#!/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 needs6+;; 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, which7+;;
6-resolves on the machine at run time and so is neither an action input nor8+;; 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 let9+;; 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/*.dotslash25+(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 per28+;; 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 inside29+;; 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 the30+;; 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 json41+(defn load-manifest [stem]
19-import pathlib42+ (let [candidate (->> [(fs/path here (str stem ".dotslash")) (fs/path here stem)]
20-import sys43+ (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().parent51+(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 thing62+(def dist
26-# doing the fetching. jolt is not either — it is fetched by DotSlash on the63+ (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 runs66+ (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 the69+ [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*)))