Ship the APK smaller: strip the libraries, pack the image
69 MB for a debug sideload, and about a third of it was never going to be read on the phone. Two changes, both in the packaging rather than in what it packages. The libraries are stripped as they are staged. Everything the loader and jolt_main.c's dlsym registration need is in .dynsym; .symtab and the debug sections are for a debugger, and there is none on the phone. Worth 20 MB, nearly all of it libjoltmoq and libc++_shared — jolt-native's release does not strip, and the NDK's libc++ carries its whole symbol table. It happens here so the derivations keep handing whole objects to whoever builds `.#libjoltapp` to look at one. The boot image is written with gzip at maximum instead of lz4 at its fastest, which is another 2.3 MB. It was already compressed — the uncompressed-looking size was lz4 doing very little — so this is packing harder, not packing at last. The kernel that reads it back has zlib already, chezAndroid being configured ZLIB=-lz, so nothing extra ships. 47 MB now. What is left is two objects that will not strip further: the boot image is jolt, glimmer and frq compiled flat, and libjoltmoq is 10.7 MB of codec .text built in another repo, where opt-level and LTO live. Launched on a Pixel 6a: the image decompresses, Chez boots, the media plane comes up and it dials out. A call was not carried over it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7434fee parent: 5129987 modified
nix/android.nix +26 -0 | @@ -205,6 +205,18 @@ let | ||
| 205 | 205 | (load "${xpatch}") |
| 206 | 206 | (optimize-level 2) |
| 207 | 207 | (generate-inspector-information #f) |
| 208 | + ;; Packed harder, not packed for the first time: fasl output is | |
| 209 | + ;; compressed already, but with lz4 at its fastest setting, and on | |
| 210 | + ;; this image that leaves 2.3 MB on the table. What reads it back is | |
| 211 | + ;; the kernel linked into libjoltapp, which has zlib because | |
| 212 | + ;; chezAndroid is configured ZLIB=-lz — so nothing extra ships to | |
| 213 | + ;; decompress it. | |
| 214 | + ;; | |
| 215 | + ;; Less than the ratio of the whole file suggests (15.9 MB to 13.6): | |
| 216 | + ;; compression is per fasl entry rather than over the image. | |
| 217 | + (fasl-compressed #t) | |
| 218 | + (compress-format 'gzip) | |
| 219 | + (compress-level 'maximum) | |
| 208 | 220 | (compile-file "$PWD/project/app.build/flat.ss" "$PWD/cross/flat.so") |
| 209 | 221 | (make-boot-file "$PWD/jolt.boot" '() |
| 210 | 222 | "${targetBoot}/petite.boot" |
| @@ -334,6 +346,20 @@ let | ||
| 334 | 346 | cp ${classesDex} stage/classes.dex |
| 335 | 347 | chmod -R u+w stage |
| 336 | 348 | |
| 349 | + # Everything the loader needs is in .dynsym, and that is what --strip-all | |
| 350 | + # keeps: what goes is .symtab and the debug sections, which are read by a | |
| 351 | + # debugger and by nothing on the phone. Worth about a third of the package | |
| 352 | + # — libjoltmoq and libc++_shared are most of it, and the release libraries | |
| 353 | + # arrive unstripped because jolt-native's own build does not strip them. | |
| 354 | + # | |
| 355 | + # Here rather than in the derivations that produce them: the inputs stay | |
| 356 | + # whole (a stripped libjoltapp is a worse thing to hand a debugger, and | |
| 357 | + # `nix build .#libjoltapp` is how it is looked at), and this is the one | |
| 358 | + # place that knows the difference between an object and a shipped one. | |
| 359 | + # The NDK's, not nixpkgs' — the host strip has no opinion worth trusting | |
| 360 | + # about an arm64 object. | |
| 361 | + ${ndkBin}/llvm-strip --strip-all stage/lib/${abi}/*.so | |
| 362 | + | |
| 337 | 363 | ${buildTools}/aapt2 link -o "$out" -I ${androidJar} \ |
| 338 | 364 | --manifest ${self}/android/AndroidManifest.xml \ |
| 339 | 365 | --min-sdk-version ${apiLevel} --target-sdk-version ${targetSdk} \ |
| @@ -205,6 +205,18 @@ let | |||
| 205 | (load "${xpatch}") | 205 | (load "${xpatch}") |
| 206 | (optimize-level 2) | 206 | (optimize-level 2) |
| 207 | (generate-inspector-information #f) | 207 | (generate-inspector-information #f) |
| 208 | + ;; Packed harder, not packed for the first time: fasl output is | ||
| 209 | + ;; compressed already, but with lz4 at its fastest setting, and on | ||
| 210 | + ;; this image that leaves 2.3 MB on the table. What reads it back is | ||
| 211 | + ;; the kernel linked into libjoltapp, which has zlib because | ||
| 212 | + ;; chezAndroid is configured ZLIB=-lz — so nothing extra ships to | ||
| 213 | + ;; decompress it. | ||
| 214 | + ;; | ||
| 215 | + ;; Less than the ratio of the whole file suggests (15.9 MB to 13.6): | ||
| 216 | + ;; compression is per fasl entry rather than over the image. | ||
| 217 | + (fasl-compressed #t) | ||
| 218 | + (compress-format 'gzip) | ||
| 219 | + (compress-level 'maximum) | ||
| 208 | (compile-file "$PWD/project/app.build/flat.ss" "$PWD/cross/flat.so") | 220 | (compile-file "$PWD/project/app.build/flat.ss" "$PWD/cross/flat.so") |
| 209 | (make-boot-file "$PWD/jolt.boot" '() | 221 | (make-boot-file "$PWD/jolt.boot" '() |
| 210 | "${targetBoot}/petite.boot" | 222 | "${targetBoot}/petite.boot" |
| @@ -334,6 +346,20 @@ let | |||
| 334 | cp ${classesDex} stage/classes.dex | 346 | cp ${classesDex} stage/classes.dex |
| 335 | chmod -R u+w stage | 347 | chmod -R u+w stage |
| 336 | 348 | ||
| 349 | + # Everything the loader needs is in .dynsym, and that is what --strip-all | ||
| 350 | + # keeps: what goes is .symtab and the debug sections, which are read by a | ||
| 351 | + # debugger and by nothing on the phone. Worth about a third of the package | ||
| 352 | + # — libjoltmoq and libc++_shared are most of it, and the release libraries | ||
| 353 | + # arrive unstripped because jolt-native's own build does not strip them. | ||
| 354 | + # | ||
| 355 | + # Here rather than in the derivations that produce them: the inputs stay | ||
| 356 | + # whole (a stripped libjoltapp is a worse thing to hand a debugger, and | ||
| 357 | + # `nix build .#libjoltapp` is how it is looked at), and this is the one | ||
| 358 | + # place that knows the difference between an object and a shipped one. | ||
| 359 | + # The NDK's, not nixpkgs' — the host strip has no opinion worth trusting | ||
| 360 | + # about an arm64 object. | ||
| 361 | + ${ndkBin}/llvm-strip --strip-all stage/lib/${abi}/*.so | ||
| 362 | + | ||
| 337 | ${buildTools}/aapt2 link -o "$out" -I ${androidJar} \ | 363 | ${buildTools}/aapt2 link -o "$out" -I ${androidJar} \ |
| 338 | --manifest ${self}/android/AndroidManifest.xml \ | 364 | --manifest ${self}/android/AndroidManifest.xml \ |
| 339 | --min-sdk-version ${apiLevel} --target-sdk-version ${targetSdk} \ | 365 | --min-sdk-version ${apiLevel} --target-sdk-version ${targetSdk} \ |