nandi/frqpublic Fork 0
8a86eb395d5f8ac215916c6f849042aa1b481c03
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 · 254 lines · 12.3 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
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago5# Rust nor the Java. The two native halves and the glue come out of jolt-native's
6# release, fetched by digest through scripts/*.dotslash; the rest is this repo's.
7# Nothing here is built out of a jolt-native checkout, and none is needed.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago8#
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.
12_ANDROID_HOME = read_root_config("frq", "android_home", "")
13_CHEZ = read_root_config("frq", "chez_android", "")
14_OPENSSL = read_root_config("frq", "openssl_android", "")
15
16_API = "28"
17_TOOLS = _ANDROID_HOME + "/build-tools/36.0.0"
18_ANDROID_JAR = _ANDROID_HOME + "/platforms/android-36/android.jar"
19# `$(...)` in a genrule cmd is a buck macro, not the shell's substitution, so
20# everything this file runs at command time uses backticks.
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago21# This repo's own: an APK build needs a clang and an llvm-objcopy, and neither
22# is jolt-native's to supply. Both resolve the NDK through
23# scripts/android-ndk.dotslash, downloading it once.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago24_NDK_BIN = "ndk=`$(location //scripts:android-ndk-bin)`"
25_CC = "$(location //scripts:android-cc)"
26
27# An absolute path for $OUT, so a command may cd without losing it. buck has
28# already made the parent directory. Spelled out step by step because backticks
29# do not nest.
30# The archive's top directory is what strip_prefix took off, so `src` is
31# directly inside it. Absolute, because the script is handed this and does its
32# own directory changing.
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago33_GLIMMER_VIDYA = "GLIMMER_VIDYA=`realpath $(location toolchains//dist:glimmer-vidya)/src` "
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago34
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago35# jolt_main.c and the ABI's headers, as targets rather than bare paths — see
36# the rules below.
Make the worktree its own buck root, and the glue an input ae207b8 nandi 18d ago37_GLUE_C = "`realpath $(location :glue-c)`"
38_GLUE_INCLUDE = "`realpath $(location :glue-include)`"
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago39
40_ABS_OUT = 'd=`dirname "$OUT"` && b=`basename "$OUT"` && d=`cd "$d" && pwd` && out="$d/$b"'
41
42# Somewhere to assemble in. Removed by the command that made it, on success;
43# buck's own scratch space is not this.
44_TMP = 'tmp=`mktemp -d`'
45
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago46# A babashka script, run in place: it loads scripts/frq/paths.clj beside it and
47# resolves its own interpreter through scripts/bb, the DotSlash pin. Referenced
48# 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 ago49export_file(
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago50 name = "build-jolt-boot.bb",
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago51 mode = "reference",
52)
53
54# --- the UI half ------------------------------------------------------------
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago55# The .so out of jolt-native's release, fetched by digest — which is what makes
56# an APK buildable with no jolt-native checkout and no NDK anywhere on the
57# machine. A genrule rather than a path, so the library's bytes are an input to
58# what reads them: an action that shelled out to build it would have nothing to
59# invalidate on and would serve the same stale object forever.
60genrule(
61 name = "libvidya",
62 out = "libvidya.so",
63 cmd = "cp $(location toolchains//dist:libvidya-android)/libvidya.so \"$OUT\"",
64)
65
66# The media plane, out of the same archive. Necessarily the same release:
67# libjoltapp links both, and pairing one release's media plane with another's
68# UI is a combination nothing has tested.
69genrule(
70 name = "libjoltmoq",
71 out = "libjoltmoq.so",
72 cmd = "cp $(location toolchains//dist:libjoltmoq-android)/libjoltmoq.so \"$OUT\"",
73)
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago74
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago75# jolt_main.c and the ABI's headers, from the glue archive of the same release.
76# Targets rather than paths for the reason above — an input is the only thing
77# buck can notice a change in.
78genrule(
79 name = "glue-c",
80 out = "jolt_main.c",
81 cmd = "cp $(location toolchains//dist:android-glue)/android/jolt_main.c \"$OUT\"",
82)
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago83
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago84# A directory of headers rather than a filegroup: a filegroup keeps each file
85# at its own path inside the output, so `-I` would have to name the staging
86# directory again.
87genrule(
88 name = "glue-include",
89 out = "include",
90 cmd = "cp -r $(location toolchains//dist:android-glue)/include \"$OUT\"",
91)
Make the worktree its own buck root, and the glue an input ae207b8 nandi 18d ago92
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago93# The C++ runtime, out of the same NDK the glue is compiled with.
94#
95# openh264 is C++, and its build script asks to be linked against
96# `libc++_shared.so` by name — so libjoltmoq carries that as a DT_NEEDED. An
97# app's linker namespace will not hand out the platform's own copy (there is no
98# stable one to hand out), so the APK has to carry it, exactly as it carries
99# OpenSSL below and for the same reason.
100#
101# Only when the media plane is packaged: nothing else here is C++.
102genrule(
103 name = "libcxx",
104 out = "libc++_shared.so",
105 cmd = _ABS_OUT + " && " + _NDK_BIN + " && " +
106 "cp \"$ndk\"/../sysroot/usr/lib/aarch64-linux-android/libc++_shared.so \"$out\"",
107)
108
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago109# --- the Jolt half ----------------------------------------------------------
110# The boot image: frq's Scheme, cross-compiled to arm64 by Chez. The sources
111# are an input so that editing one rebuilds this; the compile itself reads them
112# through the deps.edn the script writes.
113# The sources here are frq's own. The other two roots the image is compiled
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago114# from — glimmer and glimmer-vidya, both out of the jolt cache — are outside
115# this cell, and so are Chez's cross boot files and the pinned
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago116# jolt itself. None of them can be an input, so the `buck` recipe hashes them
117# and writes the digest below; naming it in the command is what puts them in
118# this action's identity.
119# jolt reaches this action as a manifest and a fetcher rather than as a
120# program: both are inputs, so nothing here depends on what is installed where
121# the compile runs, and a remote worker resolves the same pin — by the same
122# digest — from the same place this machine would.
123genrule(
124 name = "jolt-boot",
125 out = "boot",
126 srcs = ["//:jolt-sources"],
127 cmd = "# sources outside this cell: " + read_root_config("frq", "boot_stamp", "unknown") + "\n" +
128 "DOTSLASH=$(location toolchains//dist:dotslash)/dotslash " +
129 "JOLT_MANIFEST=$(location //scripts:jolt) " +
Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago130 # The one source root the script cannot find for itself: the jolt
131 # cache is not an action input, and this is. A new release recompiles
132 # the image because of it.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago133 _GLIMMER_VIDYA +
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago134 "$(location :build-jolt-boot.bb) \"$OUT\" >&2",
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago135)
136
137# The image travels as a blob in an object file's data section; the
138# _binary_jolt_boot_{start,end} symbols jolt_main.c reads are named after the
139# input *path*, which is why this copies the file somewhere it can be called
140# exactly `jolt.boot` before converting it.
141genrule(
142 name = "jolt-boot-obj",
143 out = "jolt_boot.o",
144 cmd = _ABS_OUT + " && " +
145 _TMP + " && " + _NDK_BIN + " && " +
146 "cp $(location :jolt-boot)/jolt.boot \"$tmp/jolt.boot\" && " +
147 "( cd \"$tmp\" && \"$ndk\"/llvm-objcopy " +
148 "--input-target=binary --output-target=elf64-littleaarch64 " +
149 "--binary-architecture=aarch64 jolt.boot jolt_boot.o ) && " +
150 "cp \"$tmp/jolt_boot.o\" \"$out\" && rm -rf \"$tmp\"",
151)
152
153# The glue: jolt-native's android/jolt_main.c over the boot image, linked
154# against libvidya by name. --no-undefined is what makes a symbol the Scheme
155# side registers but the ABI no longer exports a build failure here rather than
156# a crash on the phone.
157genrule(
158 name = "libjoltapp",
159 out = "libjoltapp.so",
160 cmd = _ABS_OUT + " && lib=`mktemp -d` && " +
161 "cp $(location :libvidya) \"$lib/libvidya.so\" && " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago162 "cp $(location :libjoltmoq) \"$lib/libjoltmoq.so\" && " +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago163 _CC + " -shared -fPIC -O2 -o \"$out\" " +
164 _GLUE_C + " " +
165 "$(location :jolt-boot-obj) " +
166 "-I$(location :jolt-boot) " +
167 "-I" + _GLUE_INCLUDE + " " +
168 "-L\"$lib\" " +
169 _CHEZ + "/tarm64le/boot/tarm64le/libkernel.a " +
170 _CHEZ + "/lz4/lib/liblz4.a " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago171 "-lvidya -ljoltmoq -landroid -llog -lz -ldl -lm -Wl,--no-undefined && " +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago172 "rm -rf \"$lib\"",
173)
174
175# --- the Java half ----------------------------------------------------------
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago176# Two classes: the photo chooser's result has to land somewhere and native code
177# is not somewhere, and Camera2 has no C API worth the name — CameraCapture
178# opens the camera in Java and pushes NV12 planes down to libjoltmoq over JNI.
179# android.jar on the class path is where every android.* type comes from; the
180# JDK's own java.* is what is left.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago181genrule(
182 name = "classes-dex",
183 out = "classes.dex",
184 srcs = glob(["java/**/*.java"]),
185 cmd = _ABS_OUT + " && " + _TMP + " && " +
186 "javac --release 17 --class-path " + _ANDROID_JAR + " -d \"$tmp/classes\" $SRCS && " +
187 "classes=`find \"$tmp/classes\" -name '*.class'` && " +
188 _TOOLS + "/d8 --min-api " + _API + " --output \"$tmp\" $classes && " +
189 "cp \"$tmp/classes.dex\" \"$out\" && rm -rf \"$tmp\"",
190)
191
192# --- the package ------------------------------------------------------------
193# The libraries are stored rather than deflated: the loader maps them straight
194# out of the APK. The dex is read rather than mapped, so it may as well
195# compress. OpenSSL travels with the app because the platform's own is not ours
196# to load — an app's linker namespace refuses /system/lib64/libssl.so, and
197# without one there is no TLS on the phone at all.
198genrule(
199 name = "apk-unsigned",
200 out = "frq-unsigned.apk",
201 srcs = ["AndroidManifest.xml"],
202 cmd = _ABS_OUT + " && stage=`mktemp -d` && " +
203 "mkdir -p \"$stage/lib/arm64-v8a\" && " +
204 "cp $(location :libvidya) \"$stage/lib/arm64-v8a/libvidya.so\" && " +
205 "cp $(location :libjoltapp) \"$stage/lib/arm64-v8a/libjoltapp.so\" && " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago206 "cp $(location :libjoltmoq) \"$stage/lib/arm64-v8a/libjoltmoq.so\" && " +
207 "cp $(location :libcxx) \"$stage/lib/arm64-v8a/libc++_shared.so\" && " +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago208 "cp " + _OPENSSL + "/libssl.so " + _OPENSSL + "/libcrypto.so \"$stage/lib/arm64-v8a/\" && " +
209 "cp $(location :classes-dex) \"$stage/classes.dex\" && " +
210 _TOOLS + "/aapt2 link -o \"$out\" -I " + _ANDROID_JAR + " " +
211 "--manifest $SRCS --min-sdk-version " + _API + " --target-sdk-version 36 " +
212 "--version-code 1 --version-name 0.1.0 >&2 && " +
213 "( cd \"$stage\" && zip -q -0 \"$out\" lib/arm64-v8a/libvidya.so " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago214 "lib/arm64-v8a/libjoltmoq.so lib/arm64-v8a/libc++_shared.so " +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 19d ago215 "lib/arm64-v8a/libjoltapp.so lib/arm64-v8a/libssl.so lib/arm64-v8a/libcrypto.so && " +
216 "zip -q \"$out\" classes.dex ) && rm -rf \"$stage\"",
217)
218
219# Aligned and signed with the debug key, which is generated on first use the
220# way the SDK's own tools generate it.
221genrule(
222 name = "apk",
223 out = "frq.apk",
224 cmd = _ABS_OUT + " && " + _TMP + " && " +
225 "ks=\"$HOME/.android/debug.keystore\" && " +
226 "if [ ! -f \"$ks\" ]; then mkdir -p \"$HOME/.android\" && " +
227 "keytool -genkeypair -v -keystore \"$ks\" -storepass android -keypass android " +
228 "-alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 " +
229 "-dname 'CN=Android Debug,O=Android,C=US' >&2; fi && " +
230 _TOOLS + "/zipalign -f -p 4 $(location :apk-unsigned) \"$tmp/aligned.apk\" && " +
231 _TOOLS + "/apksigner sign --ks \"$ks\" --ks-key-alias androiddebugkey " +
232 "--ks-pass pass:android --key-pass pass:android --out \"$out\" \"$tmp/aligned.apk\" && " +
233 _TOOLS + "/apksigner verify \"$out\" >/dev/null && rm -rf \"$tmp\"",
234 visibility = ["PUBLIC"],
235)
236
237# Does the pin resolve where the action runs, and does what it resolves to run
238# there? Nothing depends on this; it is here to be asked by hand.
239#
240# buck2 build //android:jolt-fetch-check --remote-only --materializations=all \
241# -c build.execution_platforms=root//platforms:remote -c "parser...->root//platforms:remote"
242#
243# It is what found the glibc floor: jolt needs 2.38, and the rbe-ubuntu22-04
244# image this platform used to name ships 2.35, so the fetch succeeded and the
245# binary would not start. The answer was the newer image, not a different pin.
246genrule(
247 name = "jolt-fetch-check",
248 out = "report",
249 cmd = "jolt=`$(location toolchains//dist:dotslash)/dotslash -- fetch $(location //scripts:jolt)`; " +
250 "{ echo \"fetched: $jolt\"; " +
251 "cat /etc/os-release 2>/dev/null | head -1 || true; " +
252 "ldd --version 2>&1 | head -1 || true; " +
253 "echo '--- run ---'; \"$jolt\" --version; echo \"rc=$?\"; } > \"$OUT\" 2>&1 || true",
254)