nandi/frqpublic Fork 0
43ada0baeebdb0aaa22e7659498a9422d3642725
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.

BUCK · 246 lines · 12.4 KBPython Blame HistoryRaw
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago1# The APK, as a graph rather than a script.
2#
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago3# Every step the old build-apk script ran in sequence is a target here, so a change
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago4# to one screen rebuilds the boot image and repackages, and touches neither the
5# Rust nor the Java. The two native halves come from jolt-native — libvidya out
6# of its buck2 graph, the glue compiled by the NDK its scripts/ pin — and the
7# rest is this repo's.
8#
9# The machine-specific paths are read from .buckconfig.local, which the `buck`
10# recipe in the justfile writes. Nothing here is found by looking around the
11# machine; if a path is missing the recipe says which.
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago12load(":defs.bzl", "glue", "libjoltmoq", "libvidya")
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago13
14_JOLT_NATIVE = read_root_config("frq", "jolt_native", "")
15_ANDROID_HOME = read_root_config("frq", "android_home", "")
16_CHEZ = read_root_config("frq", "chez_android", "")
17_OPENSSL = read_root_config("frq", "openssl_android", "")
18
19_API = "28"
20_TOOLS = _ANDROID_HOME + "/build-tools/36.0.0"
21_ANDROID_JAR = _ANDROID_HOME + "/platforms/android-36/android.jar"
22# `$(...)` in a genrule cmd is a buck macro, not the shell's substitution, so
23# everything this file runs at command time uses backticks.
24# This repo's own, not the other checkout's: an APK build needs a clang and an
25# llvm-objcopy whether or not jolt-native is on the machine. Both resolve the
26# NDK through scripts/android-ndk.dotslash, downloading it once.
27_NDK_BIN = "ndk=`$(location //scripts:android-ndk-bin)`"
28_CC = "$(location //scripts:android-cc)"
29
30# An absolute path for $OUT, so a command may cd without losing it. buck has
31# already made the parent directory. Spelled out step by step because backticks
32# do not nest.
33# Empty when a checkout answers for it, so the script's own lookup wins.
34# The archive's top directory is what strip_prefix took off, so `src` is
35# directly inside it. Absolute, because the script is handed this and does its
36# own directory changing.
37_GLIMMER_VIDYA = "" if read_root_config("frq", "libvidya", "pinned") == "checkout" else \
38 "GLIMMER_VIDYA=`realpath $(location toolchains//dist:glimmer-vidya)/src` "
39
Make the worktree its own buck root, and the glue an input ae207b8 nandi 18d ago40# jolt_main.c and the ABI's headers, as targets — see defs.bzl for which of
41# the two sources answers, and why neither may be a bare path.
42_GLUE_C = "`realpath $(location :glue-c)`"
43_GLUE_INCLUDE = "`realpath $(location :glue-include)`"
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago44
45_ABS_OUT = 'd=`dirname "$OUT"` && b=`basename "$OUT"` && d=`cd "$d" && pwd` && out="$d/$b"'
46
47# Somewhere to assemble in. Removed by the command that made it, on success;
48# buck's own scratch space is not this.
49_TMP = 'tmp=`mktemp -d`'
50
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago51# 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.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago54export_file(
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago55 name = "build-jolt-boot.bb",
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago56 mode = "reference",
57)
58
59# --- the UI half ------------------------------------------------------------
60# Two ways in, and both make the library's bytes an input rather than a
61# command buck would cache forever:
62#
63# pinned the .so out of jolt-native's release, fetched by digest. What a
64# build gets by default, and what makes an APK buildable with no
65# jolt-native checkout and no NDK anywhere on the machine.
66# checkout the .so a sibling jolt-native just built, staged into this cell
67# by the `buck` recipe. Anyone working on both repos at once
68# builds what they are editing.
69#
70# The recipe decides which, by whether that checkout exists, and says so here.
71libvidya(name = "libvidya")
72
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago73# The media plane, on the same terms — see the macro for why it can be absent.
74# `_MOQ` is False for a pinned build, and every use of it below falls back to
75# the APK this repo shipped before the media plane crossed.
76_MOQ = libjoltmoq(name = "libjoltmoq")
77
Make the worktree its own buck root, and the glue an input ae207b8 nandi 18d ago78glue(c_name = "glue-c", include_name = "glue-include")
79
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago80# The C++ runtime, out of the same NDK the glue is compiled with.
81#
82# openh264 is C++, and its build script asks to be linked against
83# `libc++_shared.so` by name — so libjoltmoq carries that as a DT_NEEDED. An
84# app's linker namespace will not hand out the platform's own copy (there is no
85# stable one to hand out), so the APK has to carry it, exactly as it carries
86# OpenSSL below and for the same reason.
87#
88# Only when the media plane is packaged: nothing else here is C++.
89genrule(
90 name = "libcxx",
91 out = "libc++_shared.so",
92 cmd = _ABS_OUT + " && " + _NDK_BIN + " && " +
93 "cp \"$ndk\"/../sysroot/usr/lib/aarch64-linux-android/libc++_shared.so \"$out\"",
94)
95
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago96# --- the Jolt half ----------------------------------------------------------
97# The boot image: frq's Scheme, cross-compiled to arm64 by Chez. The sources
98# are an input so that editing one rebuilds this; the compile itself reads them
99# through the deps.edn the script writes.
100# The sources here are frq's own. The other two roots the image is compiled
101# from — glimmer, out of the jolt cache, and glimmer-vidya, out of jolt-native
102# — are outside this cell, and so are Chez's cross boot files and the pinned
103# jolt itself. None of them can be an input, so the `buck` recipe hashes them
104# and writes the digest below; naming it in the command is what puts them in
105# this action's identity.
106# jolt reaches this action as a manifest and a fetcher rather than as a
107# program: both are inputs, so nothing here depends on what is installed where
108# the compile runs, and a remote worker resolves the same pin — by the same
109# digest — from the same place this machine would.
110genrule(
111 name = "jolt-boot",
112 out = "boot",
113 srcs = ["//:jolt-sources"],
114 cmd = "# sources outside this cell: " + read_root_config("frq", "boot_stamp", "unknown") + "\n" +
115 "DOTSLASH=$(location toolchains//dist:dotslash)/dotslash " +
116 "JOLT_MANIFEST=$(location //scripts:jolt) " +
117 # The one source root the script cannot find for itself when there
118 # is no jolt-native checkout. An input, so a new release recompiles
119 # the image.
120 _GLIMMER_VIDYA +
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago121 "$(location :build-jolt-boot.bb) \"$OUT\" >&2",
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago122)
123
124# The image travels as a blob in an object file's data section; the
125# _binary_jolt_boot_{start,end} symbols jolt_main.c reads are named after the
126# input *path*, which is why this copies the file somewhere it can be called
127# exactly `jolt.boot` before converting it.
128genrule(
129 name = "jolt-boot-obj",
130 out = "jolt_boot.o",
131 cmd = _ABS_OUT + " && " +
132 _TMP + " && " + _NDK_BIN + " && " +
133 "cp $(location :jolt-boot)/jolt.boot \"$tmp/jolt.boot\" && " +
134 "( cd \"$tmp\" && \"$ndk\"/llvm-objcopy " +
135 "--input-target=binary --output-target=elf64-littleaarch64 " +
136 "--binary-architecture=aarch64 jolt.boot jolt_boot.o ) && " +
137 "cp \"$tmp/jolt_boot.o\" \"$out\" && rm -rf \"$tmp\"",
138)
139
140# The glue: jolt-native's android/jolt_main.c over the boot image, linked
141# against libvidya by name. --no-undefined is what makes a symbol the Scheme
142# side registers but the ABI no longer exports a build failure here rather than
143# a crash on the phone.
144genrule(
145 name = "libjoltapp",
146 out = "libjoltapp.so",
147 cmd = _ABS_OUT + " && lib=`mktemp -d` && " +
148 "cp $(location :libvidya) \"$lib/libvidya.so\" && " +
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago149 ("cp $(location :libjoltmoq) \"$lib/libjoltmoq.so\" && " if _MOQ else "") +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago150 _CC + " -shared -fPIC -O2 -o \"$out\" " +
151 _GLUE_C + " " +
152 "$(location :jolt-boot-obj) " +
153 "-I$(location :jolt-boot) " +
154 "-I" + _GLUE_INCLUDE + " " +
155 "-L\"$lib\" " +
156 _CHEZ + "/tarm64le/boot/tarm64le/libkernel.a " +
157 _CHEZ + "/lz4/lib/liblz4.a " +
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago158 "-lvidya " + ("-ljoltmoq " if _MOQ else "") +
159 "-landroid -llog -lz -ldl -lm -Wl,--no-undefined && " +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago160 "rm -rf \"$lib\"",
161)
162
163# --- the Java half ----------------------------------------------------------
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago164# Two classes: the photo chooser's result has to land somewhere and native code
165# is not somewhere, and Camera2 has no C API worth the name — CameraCapture
166# opens the camera in Java and pushes NV12 planes down to libjoltmoq over JNI.
167# android.jar on the class path is where every android.* type comes from; the
168# JDK's own java.* is what is left.
169#
170# CameraCapture is compiled whether or not the media plane is packaged. It is a
171# few kilobytes of dex, and a class nothing loads costs nothing; keeping it out
172# of the `_MOQ` branch keeps the branch to the two things that actually differ.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago173genrule(
174 name = "classes-dex",
175 out = "classes.dex",
176 srcs = glob(["java/**/*.java"]),
177 cmd = _ABS_OUT + " && " + _TMP + " && " +
178 "javac --release 17 --class-path " + _ANDROID_JAR + " -d \"$tmp/classes\" $SRCS && " +
179 "classes=`find \"$tmp/classes\" -name '*.class'` && " +
180 _TOOLS + "/d8 --min-api " + _API + " --output \"$tmp\" $classes && " +
181 "cp \"$tmp/classes.dex\" \"$out\" && rm -rf \"$tmp\"",
182)
183
184# --- the package ------------------------------------------------------------
185# The libraries are stored rather than deflated: the loader maps them straight
186# out of the APK. The dex is read rather than mapped, so it may as well
187# compress. OpenSSL travels with the app because the platform's own is not ours
188# to load — an app's linker namespace refuses /system/lib64/libssl.so, and
189# without one there is no TLS on the phone at all.
190genrule(
191 name = "apk-unsigned",
192 out = "frq-unsigned.apk",
193 srcs = ["AndroidManifest.xml"],
194 cmd = _ABS_OUT + " && stage=`mktemp -d` && " +
195 "mkdir -p \"$stage/lib/arm64-v8a\" && " +
196 "cp $(location :libvidya) \"$stage/lib/arm64-v8a/libvidya.so\" && " +
197 "cp $(location :libjoltapp) \"$stage/lib/arm64-v8a/libjoltapp.so\" && " +
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago198 ("cp $(location :libjoltmoq) \"$stage/lib/arm64-v8a/libjoltmoq.so\" && " if _MOQ else "") +
199 ("cp $(location :libcxx) \"$stage/lib/arm64-v8a/libc++_shared.so\" && " if _MOQ else "") +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago200 "cp " + _OPENSSL + "/libssl.so " + _OPENSSL + "/libcrypto.so \"$stage/lib/arm64-v8a/\" && " +
201 "cp $(location :classes-dex) \"$stage/classes.dex\" && " +
202 _TOOLS + "/aapt2 link -o \"$out\" -I " + _ANDROID_JAR + " " +
203 "--manifest $SRCS --min-sdk-version " + _API + " --target-sdk-version 36 " +
204 "--version-code 1 --version-name 0.1.0 >&2 && " +
205 "( cd \"$stage\" && zip -q -0 \"$out\" lib/arm64-v8a/libvidya.so " +
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago206 ("lib/arm64-v8a/libjoltmoq.so lib/arm64-v8a/libc++_shared.so " if _MOQ else "") +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago207 "lib/arm64-v8a/libjoltapp.so lib/arm64-v8a/libssl.so lib/arm64-v8a/libcrypto.so && " +
208 "zip -q \"$out\" classes.dex ) && rm -rf \"$stage\"",
209)
210
211# Aligned and signed with the debug key, which is generated on first use the
212# way the SDK's own tools generate it.
213genrule(
214 name = "apk",
215 out = "frq.apk",
216 cmd = _ABS_OUT + " && " + _TMP + " && " +
217 "ks=\"$HOME/.android/debug.keystore\" && " +
218 "if [ ! -f \"$ks\" ]; then mkdir -p \"$HOME/.android\" && " +
219 "keytool -genkeypair -v -keystore \"$ks\" -storepass android -keypass android " +
220 "-alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 " +
221 "-dname 'CN=Android Debug,O=Android,C=US' >&2; fi && " +
222 _TOOLS + "/zipalign -f -p 4 $(location :apk-unsigned) \"$tmp/aligned.apk\" && " +
223 _TOOLS + "/apksigner sign --ks \"$ks\" --ks-key-alias androiddebugkey " +
224 "--ks-pass pass:android --key-pass pass:android --out \"$out\" \"$tmp/aligned.apk\" && " +
225 _TOOLS + "/apksigner verify \"$out\" >/dev/null && rm -rf \"$tmp\"",
226 visibility = ["PUBLIC"],
227)
228
229# Does the pin resolve where the action runs, and does what it resolves to run
230# there? Nothing depends on this; it is here to be asked by hand.
231#
232# buck2 build //android:jolt-fetch-check --remote-only --materializations=all \
233# -c build.execution_platforms=root//platforms:remote -c "parser...->root//platforms:remote"
234#
235# It is what found the glibc floor: jolt needs 2.38, and the rbe-ubuntu22-04
236# image this platform used to name ships 2.35, so the fetch succeeded and the
237# binary would not start. The answer was the newer image, not a different pin.
238genrule(
239 name = "jolt-fetch-check",
240 out = "report",
241 cmd = "jolt=`$(location toolchains//dist:dotslash)/dotslash -- fetch $(location //scripts:jolt)`; " +
242 "{ echo \"fetched: $jolt\"; " +
243 "cat /etc/os-release 2>/dev/null | head -1 || true; " +
244 "ldd --version 2>&1 | head -1 || true; " +
245 "echo '--- run ---'; \"$jolt\" --version; echo \"rc=$?\"; } > \"$OUT\" 2>&1 || true",
246)