nandi/frqpublic Fork 0
6c745dfdacf9ce4b652034872fbebd67d16a66e5
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 · 239 lines · 11.9 KBPython Blame HistoryRaw
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 18d 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 18d 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 18d 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 18d 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 18d 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 18d 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
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago73# The media plane, on the same terms and from the same release.
74libjoltmoq(name = "libjoltmoq")
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago75
Make the worktree its own buck root, and the glue an input ae207b8 nandi 18d ago76glue(c_name = "glue-c", include_name = "glue-include")
77
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago78# The C++ runtime, out of the same NDK the glue is compiled with.
79#
80# openh264 is C++, and its build script asks to be linked against
81# `libc++_shared.so` by name — so libjoltmoq carries that as a DT_NEEDED. An
82# app's linker namespace will not hand out the platform's own copy (there is no
83# stable one to hand out), so the APK has to carry it, exactly as it carries
84# OpenSSL below and for the same reason.
85#
86# Only when the media plane is packaged: nothing else here is C++.
87genrule(
88 name = "libcxx",
89 out = "libc++_shared.so",
90 cmd = _ABS_OUT + " && " + _NDK_BIN + " && " +
91 "cp \"$ndk\"/../sysroot/usr/lib/aarch64-linux-android/libc++_shared.so \"$out\"",
92)
93
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 18d ago94# --- the Jolt half ----------------------------------------------------------
95# The boot image: frq's Scheme, cross-compiled to arm64 by Chez. The sources
96# are an input so that editing one rebuilds this; the compile itself reads them
97# through the deps.edn the script writes.
98# The sources here are frq's own. The other two roots the image is compiled
99# from — glimmer, out of the jolt cache, and glimmer-vidya, out of jolt-native
100# — are outside this cell, and so are Chez's cross boot files and the pinned
101# jolt itself. None of them can be an input, so the `buck` recipe hashes them
102# and writes the digest below; naming it in the command is what puts them in
103# this action's identity.
104# jolt reaches this action as a manifest and a fetcher rather than as a
105# program: both are inputs, so nothing here depends on what is installed where
106# the compile runs, and a remote worker resolves the same pin — by the same
107# digest — from the same place this machine would.
108genrule(
109 name = "jolt-boot",
110 out = "boot",
111 srcs = ["//:jolt-sources"],
112 cmd = "# sources outside this cell: " + read_root_config("frq", "boot_stamp", "unknown") + "\n" +
113 "DOTSLASH=$(location toolchains//dist:dotslash)/dotslash " +
114 "JOLT_MANIFEST=$(location //scripts:jolt) " +
115 # The one source root the script cannot find for itself when there
116 # is no jolt-native checkout. An input, so a new release recompiles
117 # the image.
118 _GLIMMER_VIDYA +
Write the build in babashka, and pin the babashka 34832a8 nandi 18d ago119 "$(location :build-jolt-boot.bb) \"$OUT\" >&2",
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 18d ago120)
121
122# The image travels as a blob in an object file's data section; the
123# _binary_jolt_boot_{start,end} symbols jolt_main.c reads are named after the
124# input *path*, which is why this copies the file somewhere it can be called
125# exactly `jolt.boot` before converting it.
126genrule(
127 name = "jolt-boot-obj",
128 out = "jolt_boot.o",
129 cmd = _ABS_OUT + " && " +
130 _TMP + " && " + _NDK_BIN + " && " +
131 "cp $(location :jolt-boot)/jolt.boot \"$tmp/jolt.boot\" && " +
132 "( cd \"$tmp\" && \"$ndk\"/llvm-objcopy " +
133 "--input-target=binary --output-target=elf64-littleaarch64 " +
134 "--binary-architecture=aarch64 jolt.boot jolt_boot.o ) && " +
135 "cp \"$tmp/jolt_boot.o\" \"$out\" && rm -rf \"$tmp\"",
136)
137
138# The glue: jolt-native's android/jolt_main.c over the boot image, linked
139# against libvidya by name. --no-undefined is what makes a symbol the Scheme
140# side registers but the ABI no longer exports a build failure here rather than
141# a crash on the phone.
142genrule(
143 name = "libjoltapp",
144 out = "libjoltapp.so",
145 cmd = _ABS_OUT + " && lib=`mktemp -d` && " +
146 "cp $(location :libvidya) \"$lib/libvidya.so\" && " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago147 "cp $(location :libjoltmoq) \"$lib/libjoltmoq.so\" && " +
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 18d ago148 _CC + " -shared -fPIC -O2 -o \"$out\" " +
149 _GLUE_C + " " +
150 "$(location :jolt-boot-obj) " +
151 "-I$(location :jolt-boot) " +
152 "-I" + _GLUE_INCLUDE + " " +
153 "-L\"$lib\" " +
154 _CHEZ + "/tarm64le/boot/tarm64le/libkernel.a " +
155 _CHEZ + "/lz4/lib/liblz4.a " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago156 "-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 18d ago157 "rm -rf \"$lib\"",
158)
159
160# --- the Java half ----------------------------------------------------------
Carry the media plane, and the class that opens the camera 43ada0b nandi 18d ago161# Two classes: the photo chooser's result has to land somewhere and native code
162# is not somewhere, and Camera2 has no C API worth the name — CameraCapture
163# opens the camera in Java and pushes NV12 planes down to libjoltmoq over JNI.
164# android.jar on the class path is where every android.* type comes from; the
165# JDK's own java.* is what is left.
Build the APK with buck2, from pins rather than from a second checkout a71ef8f nandi 18d ago166genrule(
167 name = "classes-dex",
168 out = "classes.dex",
169 srcs = glob(["java/**/*.java"]),
170 cmd = _ABS_OUT + " && " + _TMP + " && " +
171 "javac --release 17 --class-path " + _ANDROID_JAR + " -d \"$tmp/classes\" $SRCS && " +
172 "classes=`find \"$tmp/classes\" -name '*.class'` && " +
173 _TOOLS + "/d8 --min-api " + _API + " --output \"$tmp\" $classes && " +
174 "cp \"$tmp/classes.dex\" \"$out\" && rm -rf \"$tmp\"",
175)
176
177# --- the package ------------------------------------------------------------
178# The libraries are stored rather than deflated: the loader maps them straight
179# out of the APK. The dex is read rather than mapped, so it may as well
180# compress. OpenSSL travels with the app because the platform's own is not ours
181# to load — an app's linker namespace refuses /system/lib64/libssl.so, and
182# without one there is no TLS on the phone at all.
183genrule(
184 name = "apk-unsigned",
185 out = "frq-unsigned.apk",
186 srcs = ["AndroidManifest.xml"],
187 cmd = _ABS_OUT + " && stage=`mktemp -d` && " +
188 "mkdir -p \"$stage/lib/arm64-v8a\" && " +
189 "cp $(location :libvidya) \"$stage/lib/arm64-v8a/libvidya.so\" && " +
190 "cp $(location :libjoltapp) \"$stage/lib/arm64-v8a/libjoltapp.so\" && " +
Pin the media plane, now that there is a release to pin 03d5a6b nandi 18d ago191 "cp $(location :libjoltmoq) \"$stage/lib/arm64-v8a/libjoltmoq.so\" && " +
192 "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 18d ago193 "cp " + _OPENSSL + "/libssl.so " + _OPENSSL + "/libcrypto.so \"$stage/lib/arm64-v8a/\" && " +
194 "cp $(location :classes-dex) \"$stage/classes.dex\" && " +
195 _TOOLS + "/aapt2 link -o \"$out\" -I " + _ANDROID_JAR + " " +
196 "--manifest $SRCS --min-sdk-version " + _API + " --target-sdk-version 36 " +
197 "--version-code 1 --version-name 0.1.0 >&2 && " +
198 "( 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 ago199 "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 18d ago200 "lib/arm64-v8a/libjoltapp.so lib/arm64-v8a/libssl.so lib/arm64-v8a/libcrypto.so && " +
201 "zip -q \"$out\" classes.dex ) && rm -rf \"$stage\"",
202)
203
204# Aligned and signed with the debug key, which is generated on first use the
205# way the SDK's own tools generate it.
206genrule(
207 name = "apk",
208 out = "frq.apk",
209 cmd = _ABS_OUT + " && " + _TMP + " && " +
210 "ks=\"$HOME/.android/debug.keystore\" && " +
211 "if [ ! -f \"$ks\" ]; then mkdir -p \"$HOME/.android\" && " +
212 "keytool -genkeypair -v -keystore \"$ks\" -storepass android -keypass android " +
213 "-alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 " +
214 "-dname 'CN=Android Debug,O=Android,C=US' >&2; fi && " +
215 _TOOLS + "/zipalign -f -p 4 $(location :apk-unsigned) \"$tmp/aligned.apk\" && " +
216 _TOOLS + "/apksigner sign --ks \"$ks\" --ks-key-alias androiddebugkey " +
217 "--ks-pass pass:android --key-pass pass:android --out \"$out\" \"$tmp/aligned.apk\" && " +
218 _TOOLS + "/apksigner verify \"$out\" >/dev/null && rm -rf \"$tmp\"",
219 visibility = ["PUBLIC"],
220)
221
222# Does the pin resolve where the action runs, and does what it resolves to run
223# there? Nothing depends on this; it is here to be asked by hand.
224#
225# buck2 build //android:jolt-fetch-check --remote-only --materializations=all \
226# -c build.execution_platforms=root//platforms:remote -c "parser...->root//platforms:remote"
227#
228# It is what found the glibc floor: jolt needs 2.38, and the rbe-ubuntu22-04
229# image this platform used to name ships 2.35, so the fetch succeeded and the
230# binary would not start. The answer was the newer image, not a different pin.
231genrule(
232 name = "jolt-fetch-check",
233 out = "report",
234 cmd = "jolt=`$(location toolchains//dist:dotslash)/dotslash -- fetch $(location //scripts:jolt)`; " +
235 "{ echo \"fetched: $jolt\"; " +
236 "cat /etc/os-release 2>/dev/null | head -1 || true; " +
237 "ldd --version 2>&1 | head -1 || true; " +
238 "echo '--- run ---'; \"$jolt\" --version; echo \"rc=$?\"; } > \"$OUT\" 2>&1 || true",
239)