nandi/frqpublic Fork 0
43cc3f0376f08659dea357cd994b3b465d00d9aa
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 34832a8 · on 43cc3f0376f08659dea357cd994b3b465d00d9aa · nandi · 18d ago
build-apk.bb · 163 lines · 8.2 KBBlitzBasic Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/bin/sh
#_(
exec "$(dirname "$0")/../scripts/bb" "$0" "$@"
)

;; Glue the two halves of the frq Android app into an APK.
;;
;;   libvidya.so    the C ABI on Rust/egui, cross-compiled by buck2, and the
;;                  NativeActivity's own library (it holds android-activity's
;;                  glue, so it owns the event loop)
;;   libjoltapp.so  jolt-native's android/jolt_main.c plus frq's Jolt boot
;;                  image, dlopened by the above
;;   classes.dex    one Java class, and only because a picture chooser answers
;;                  through onActivityResult and a NativeActivity has nowhere
;;                  to deliver that
;;   libssl.so      OpenSSL, because the platform's own is not ours to load: an
;;   libcrypto.so   app's linker namespace refuses /system/lib64/libssl.so, and
;;                  without one there is no TLS at all on the phone
;;
;; Neither half is built here beyond that last link: the UI library comes from
;; jolt-native's `just ffi-android` and the boot image from build-jolt-boot.bb.
;; Both native pieces are jolt-native's — only the boot image is frq's.
;;
;;   build-apk.bb [build|install|run|log]
(require '[babashka.classpath :as cp])
(cp/add-classpath (str (babashka.fs/path (babashka.fs/parent *file*) ".." "scripts")))
(require '[frq.paths :as paths]
         '[babashka.fs :as fs]
         '[babashka.process :as p])

(def root (str (fs/canonicalize (fs/path (fs/parent *file*) ".."))))
(def action (or (first *command-line-args*) "build"))

;; Where jolt-native is, answered the way the justfile answers it: a sibling
;; checkout wins, and otherwise it is the clone `just lib` leaves under
;; .jolt-native.
(def jolt-native (paths/jolt-native root))
(when-not (fs/directory? jolt-native)
  (paths/die (str "no jolt-native at " jolt-native " — run `just lib` to clone it")))

(def android-home (paths/env "ANDROID_HOME" (str (fs/path (fs/home) ".local" "share" "android-sdk"))))
(def ndk-home (paths/env "ANDROID_NDK_HOME" (str (fs/path (fs/home) ".local" "share" "android-ndk-r29"))))
(def chez (paths/env "CHEZ_ANDROID" (str (fs/path (fs/home) ".cache" "vidya-chez-android"))))
(def openssl (paths/env "OPENSSL_ANDROID" (str (fs/path (fs/home) ".cache" "frq-openssl-android" "lib"))))
(def build (fs/path root "android" "build"))
(def jolt-build (fs/path build "jolt"))
(def stage (fs/path build "stage"))
(def tools (fs/path android-home "build-tools" "36.0.0"))
(def android-jar (fs/path android-home "platforms" "android-36" "android.jar"))
(def adb (paths/env "ADB" (str (fs/path android-home "platform-tools" "adb"))))
(def ndk-bin (fs/path ndk-home "toolchains" "llvm" "prebuilt" "linux-x86_64" "bin"))
(def package "uk.nandi.frq")
(def activity (str package "/.FrqActivity"))
(def api "28")
(def clang (str (fs/path ndk-bin (str "aarch64-linux-android" api "-clang"))))
(def arm-lib (fs/path stage "lib" "arm64-v8a"))

(paths/require-paths! "Android tool"
                      [clang android-jar
                       (fs/path tools "aapt2") (fs/path tools "zipalign")
                       (fs/path tools "apksigner") (fs/path tools "d8")
                       (fs/path openssl "libssl.so") (fs/path openssl "libcrypto.so")])

;; --- the UI half -----------------------------------------------------------
;; buck2 fetches its own NDK for this, from the pin in jolt-native's
;; scripts/android-ndk.dotslash, so the toolchain above is the only one that
;; has to be installed by hand.
(p/shell {:dir jolt-native :out *err*} "just" "ffi-android")
(def vidya-so (fs/path jolt-native "build" "android" "arm64-v8a" "libvidya.so"))
(paths/require-paths! "library" [vidya-so])

;; --- the Jolt half ---------------------------------------------------------
(p/shell (str (fs/path root "android" "build-jolt-boot.bb")) (str jolt-build))
;; The boot image travels as a blob in the object file's data section; the
;; _binary_jolt_boot_{start,end} symbols jolt_main.c reads come from this.
(p/shell {:dir (str jolt-build)}
         (str (fs/path ndk-bin "llvm-objcopy"))
         "--input-target=binary"
         "--output-target=elf64-littleaarch64"
         "--binary-architecture=aarch64"
         "jolt.boot" "jolt_boot.o")

;; --- the Java half ---------------------------------------------------------
;; One class: the photo chooser's result has to land somewhere, and native code
;; is not somewhere. d8 turns it into the classes.dex the runtime loads.
(def java-build (fs/path build "java"))
(fs/delete-tree java-build)
(fs/create-dirs (fs/path java-build "classes"))
;; android.jar on the class path is where every android.* type comes from; the
;; JDK's own java.* is what is left, and this class uses nothing of it that
;; Android does not have. (`-bootclasspath` would be the stricter way to say
;; that, and javac refuses it for a release this recent.)
(p/shell "javac" "--release" "17"
         "--class-path" (str android-jar)
         "-d" (str (fs/path java-build "classes"))
         (str (fs/path root "android" "java" "uk" "nandi" "frq" "FrqActivity.java")))
(apply p/shell (str (fs/path tools "d8")) "--min-api" api "--output" (str java-build)
       (map str (fs/glob (fs/path java-build "classes") "**.class")))

(fs/delete-tree stage)
(fs/create-dirs arm-lib)
(fs/copy (fs/path java-build "classes.dex") (fs/path stage "classes.dex"))
(fs/copy vidya-so (fs/path arm-lib "libvidya.so"))
;; jolt.mvn-http dlopens these by name at first use; beside the app's own
;; libraries is where an app's namespace will answer for that name.
(doseq [lib ["libssl.so" "libcrypto.so"]]
  (fs/copy (fs/path openssl lib) (fs/path arm-lib lib)))

(p/shell clang "-shared" "-fPIC" "-O2"
         "-o" (str (fs/path arm-lib "libjoltapp.so"))
         (str (fs/path jolt-native "android" "jolt_main.c"))
         (str (fs/path jolt-build "jolt_boot.o"))
         (str "-I" jolt-build)
         (str "-I" (fs/path jolt-native "crates" "jolt-vidya" "include"))
         (str "-L" arm-lib)
         (str (fs/path chez "tarm64le" "boot" "tarm64le" "libkernel.a"))
         (str (fs/path chez "lz4" "lib" "liblz4.a"))
         "-lvidya" "-landroid" "-llog" "-lz" "-ldl" "-lm"
         "-Wl,--no-undefined")

;; --- the APK ---------------------------------------------------------------
(def unaligned (fs/path build "frq-unaligned.apk"))
(def aligned (fs/path build "frq-aligned.apk"))
(def apk (fs/path build "frq.apk"))
(run! fs/delete-if-exists [unaligned aligned apk])
(p/shell (str (fs/path tools "aapt2")) "link"
         "-o" (str unaligned)
         "-I" (str android-jar)
         "--manifest" (str (fs/path root "android" "AndroidManifest.xml"))
         "--min-sdk-version" api
         "--target-sdk-version" "36"
         "--version-code" "1"
         "--version-name" "0.1.0")
;; Stored, not deflated: the loader maps these straight out of the APK.
(p/shell {:dir (str stage)} "zip" "-q" "-0" (str unaligned)
         "lib/arm64-v8a/libvidya.so" "lib/arm64-v8a/libjoltapp.so"
         "lib/arm64-v8a/libssl.so" "lib/arm64-v8a/libcrypto.so")
;; The dex is read by the runtime rather than mapped, so it may as well deflate.
(p/shell {:dir (str stage)} "zip" "-q" (str unaligned) "classes.dex")
(p/shell (str (fs/path tools "zipalign")) "-f" "-p" "4" (str unaligned) (str aligned))

(def keystore (fs/path (fs/home) ".android" "debug.keystore"))
(when-not (fs/exists? keystore)
  (fs/create-dirs (fs/parent keystore))
  (p/shell "keytool" "-genkeypair" "-v"
           "-keystore" (str keystore) "-storepass" "android" "-keypass" "android"
           "-alias" "androiddebugkey" "-keyalg" "RSA" "-keysize" "2048"
           "-validity" "10000"
           "-dname" "CN=Android Debug,O=Android,C=US"))
(p/shell (str (fs/path tools "apksigner")) "sign"
         "--ks" (str keystore) "--ks-key-alias" "androiddebugkey"
         "--ks-pass" "pass:android" "--key-pass" "pass:android"
         "--out" (str apk) (str aligned))
(p/shell {:out :string} (str (fs/path tools "apksigner")) "verify" (str apk))

(case action
  "build" (println (str apk))
  "install" (p/shell adb "install" "-r" (str apk))
  "run" (do (p/shell adb "install" "-r" (str apk))
            (p/shell adb "shell" "am" "force-stop" package)
            (p/shell adb "shell" "am" "start" "-n" activity))
  "log" (p/shell adb "logcat" "-s" "VidyaJolt" "Vidya")
  (paths/die "usage: build-apk.bb [build|install|run|log]"))