nandi/frqpublic Fork 0
6aa2a6b
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.

Build the APK from the flake, and from nothing

android/BUCK stays the build for a person at a terminal: it is incremental,
and it lets a sibling jolt-native checkout win over the pinned release. What
this adds is the other build — on a machine with no Android SDK, no NDK, no
hand-built Chez cross target and no ~/.cache at all:

  nix build .#apk

The five steps are the same five, in the same order; every path
.buckconfig.local answers for is answered by the store instead. The SDK and
NDK come from androidenv (r29, the version scripts/android-ndk.dotslash
pins), libvidya and the glue by the digests in scripts/*.dotslash, and the
boot image from the addrinfo fork rather than upstream — an APK built with
upstream cannot open a TLS connection on Bionic at all.

The one piece with no nixpkgs equivalent is Chez's tarm64le workarea, so it
is built here. ~/.cache/vidya-chez-android was the reference for the flags,
but only a partial one: its Mf-config records LIBS with -lrt and -lpthread,
which Bionic has no separate libraries for, so that make died once
libkernel.a existed and was left there. buck only ever reads the artifacts
that got made; a build that has to finish needs --disable-auto-flags, and
--disable-curses because Bionic has no curses.h.

OpenSSL is built by the NDK rather than by pkgsCross.aarch64-android, whose
compiler-rt does not compile on this nixpkgs.

.#apk signs with a keystore generated in the derivation, so it is
installable and not reproducible; .#apk-unsigned is the pure output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-08-31T20:58:14-07:00 Browse files
6aa2a6b parent: 9cc5d99
modified flake.lock +40 -0
@@ -1,5 +1,24 @@
11 {
22 "nodes": {
3+ "chez-src": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1778520543,
7+ "narHash": "sha256-/AQbCbjfIpZjgQJB2E1QXSOfcTGF24xfBDOyZDUWtO0=",
8+ "ref": "refs/tags/v10.4.1",
9+ "rev": "e95a7efbafa2cf3bd5343ea542e6bc909a7ab2c4",
10+ "revCount": 2525,
11+ "submodules": true,
12+ "type": "git",
13+ "url": "https://github.com/cisco/ChezScheme"
14+ },
15+ "original": {
16+ "ref": "refs/tags/v10.4.1",
17+ "submodules": true,
18+ "type": "git",
19+ "url": "https://github.com/cisco/ChezScheme"
20+ }
21+ },
322 "flake-utils": {
423 "inputs": {
524 "systems": "systems"
@@ -35,6 +54,25 @@
3554 "url": "https://gitlab.com/nandithebull/glimmer"
3655 }
3756 },
57+ "jolt-android-src": {
58+ "flake": false,
59+ "locked": {
60+ "lastModified": 1788137223,
61+ "narHash": "sha256-VeHi864PEtZoHRNA7ms9uaJb6+7nHvDyRXqLLPQ1tao=",
62+ "ref": "refs/heads/main",
63+ "rev": "2b80d68d1f7a31ba92b208b3957e5fb555617ada",
64+ "revCount": 2742,
65+ "submodules": true,
66+ "type": "git",
67+ "url": "https://gitlab.com/nandithebull/jolt"
68+ },
69+ "original": {
70+ "rev": "2b80d68d1f7a31ba92b208b3957e5fb555617ada",
71+ "submodules": true,
72+ "type": "git",
73+ "url": "https://gitlab.com/nandithebull/jolt"
74+ }
75+ },
3876 "jolt-native": {
3977 "flake": false,
4078 "locked": {
@@ -108,7 +146,9 @@
108146 },
109147 "root": {
110148 "inputs": {
149+ "chez-src": "chez-src",
111150 "glimmer": "glimmer",
151+ "jolt-android-src": "jolt-android-src",
112152 "jolt-native": "jolt-native",
113153 "jolt-src": "jolt-src",
114154 "nixgl": "nixgl",
@@ -1,5 +1,24 @@
1 {1 {
2 "nodes": {2 "nodes": {
3+ "chez-src": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1778520543,
7+ "narHash": "sha256-/AQbCbjfIpZjgQJB2E1QXSOfcTGF24xfBDOyZDUWtO0=",
8+ "ref": "refs/tags/v10.4.1",
9+ "rev": "e95a7efbafa2cf3bd5343ea542e6bc909a7ab2c4",
10+ "revCount": 2525,
11+ "submodules": true,
12+ "type": "git",
13+ "url": "https://github.com/cisco/ChezScheme"
14+ },
15+ "original": {
16+ "ref": "refs/tags/v10.4.1",
17+ "submodules": true,
18+ "type": "git",
19+ "url": "https://github.com/cisco/ChezScheme"
20+ }
21+ },
3 "flake-utils": {22 "flake-utils": {
4 "inputs": {23 "inputs": {
5 "systems": "systems"24 "systems": "systems"
@@ -35,6 +54,25 @@
35 "url": "https://gitlab.com/nandithebull/glimmer"54 "url": "https://gitlab.com/nandithebull/glimmer"
36 }55 }
37 },56 },
57+ "jolt-android-src": {
58+ "flake": false,
59+ "locked": {
60+ "lastModified": 1788137223,
61+ "narHash": "sha256-VeHi864PEtZoHRNA7ms9uaJb6+7nHvDyRXqLLPQ1tao=",
62+ "ref": "refs/heads/main",
63+ "rev": "2b80d68d1f7a31ba92b208b3957e5fb555617ada",
64+ "revCount": 2742,
65+ "submodules": true,
66+ "type": "git",
67+ "url": "https://gitlab.com/nandithebull/jolt"
68+ },
69+ "original": {
70+ "rev": "2b80d68d1f7a31ba92b208b3957e5fb555617ada",
71+ "submodules": true,
72+ "type": "git",
73+ "url": "https://gitlab.com/nandithebull/jolt"
74+ }
75+ },
38 "jolt-native": {76 "jolt-native": {
39 "flake": false,77 "flake": false,
40 "locked": {78 "locked": {
@@ -108,7 +146,9 @@
108 },146 },
109 "root": {147 "root": {
110 "inputs": {148 "inputs": {
149+ "chez-src": "chez-src",
111 "glimmer": "glimmer",150 "glimmer": "glimmer",
151+ "jolt-android-src": "jolt-android-src",
112 "jolt-native": "jolt-native",152 "jolt-native": "jolt-native",
113 "jolt-src": "jolt-src",153 "jolt-src": "jolt-src",
114 "nixgl": "nixgl",154 "nixgl": "nixgl",
modified flake.nix +64 -3
@@ -34,6 +34,26 @@
3434 flake = false;
3535 };
3636
37+ # Chez itself, because the APK needs a cross target nixpkgs does not
38+ # build: frq's Scheme is compiled to an arm64 boot image, and that wants
39+ # Chez's own `tarm64le` workarea — boot files, xpatch and libkernel.a.
40+ # The version is the one the hand-built tree under ~/.cache used, and the
41+ # submodules are not optional (zuo builds it, lz4 and zlib link into it).
42+ # The fork jolt's own Android pin names, built here rather than fetched as
43+ # a release binary: upstream reads the socket address out of `struct
44+ # addrinfo` at glibc's offset, which on Bionic is `ai_canonname`, so an APK
45+ # built with upstream cannot open a TLS connection at all. Only the boot
46+ # image uses it; the desktop package still builds jolt-src.
47+ jolt-android-src = {
48+ url = "git+https://gitlab.com/nandithebull/jolt?rev=2b80d68d1f7a31ba92b208b3957e5fb555617ada&submodules=1";
49+ flake = false;
50+ };
51+
52+ chez-src = {
53+ url = "git+https://github.com/cisco/ChezScheme?ref=refs/tags/v10.4.1&submodules=1";
54+ flake = false;
55+ };
56+
3757 # The sha deps.edn pins, on the fork with the reconciler fixes.
3858 glimmer = {
3959 url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";
@@ -47,7 +67,7 @@
4767 };
4868 };
4969
50- outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer, nixgl }:
70+ outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer, chez-src, jolt-android-src, nixgl }:
5171 let
5272 systems = [ "x86_64-linux" "aarch64-linux" ];
5373 forEachSystem = f:
@@ -115,10 +135,13 @@
115135 };
116136
117137 # Jolt itself: Clojure on Chez, built the way its own flake builds it.
118- joltRuntime = pkgs.stdenv.mkDerivation {
138+ # A function, because there are two of them — upstream for the
139+ # desktop, and the Bionic-addrinfo fork for the boot image the APK
140+ # carries. Nothing else about the build differs.
141+ joltFrom = src: pkgs.stdenv.mkDerivation {
119142 pname = "jolt";
120143 version = "dev";
121- src = jolt-src;
144+ inherit src;
122145
123146 strictDeps = true;
124147 nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];
@@ -150,6 +173,9 @@
150173 '';
151174 };
152175
176+ joltRuntime = joltFrom jolt-src;
177+ joltAndroid = joltFrom jolt-android-src;
178+
153179 # glimmer-vidya lives inside the jolt-native checkout, and its own
154180 # deps.edn asks for glimmer by git the top-level override below
155181 # answers for both.
@@ -208,11 +234,46 @@
208234 mkdir -p "$out/bin"
209235 ln -s ${frqScript} "$out/bin/frq"
210236 '';
237+ # --- Android ------------------------------------------------------
238+ # The SDK and the NDK are Google's, which means unfree and a licence
239+ # to accept — so this is its own import of nixpkgs rather than the
240+ # `legacyPackages` everything above uses. Confined to the Android
241+ # outputs: `nix build` of frq itself never evaluates it.
242+ #
243+ # The NDK here is r29, which is the version scripts/android-ndk.dotslash
244+ # pins and the one the pinned libvidya was built with.
245+ androidPkgs = import nixpkgs {
246+ inherit (pkgs.stdenv.hostPlatform) system;
247+ config = {
248+ allowUnfree = true;
249+ android_sdk.accept_license = true;
250+ };
251+ };
252+
253+ androidComposition = androidPkgs.androidenv.composeAndroidPackages {
254+ buildToolsVersions = [ "36.0.0" ];
255+ platformVersions = [ "36" ];
256+ includeNDK = true;
257+ };
258+
259+ android = import ./nix/android.nix {
260+ inherit pkgs self chez-src jolt-native glimmer joltAndroid;
261+ inherit (pkgs) lib;
262+ androidSdk = androidComposition.androidsdk;
263+ ndk = androidComposition.ndk-bundle;
264+ };
211265 in
212266 {
213267 inherit native frq;
214268 jolt = joltRuntime;
215269 default = frq;
270+ }
271+ # An APK is built by a linux-x86_64 NDK and a linux-x86_64 jolt, and
272+ # Google ships no other; on aarch64 the Android outputs are simply
273+ # absent rather than present and broken.
274+ // lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
275+ inherit (android) apk chezAndroid joltBoot libjoltapp;
276+ apk-unsigned = android.apk-unsigned;
216277 });
217278
218279 apps = forEachSystem (pkgs: {
@@ -34,6 +34,26 @@
34 flake = false;34 flake = false;
35 };35 };
36 36
37+ # Chez itself, because the APK needs a cross target nixpkgs does not
38+ # build: frq's Scheme is compiled to an arm64 boot image, and that wants
39+ # Chez's own `tarm64le` workarea — boot files, xpatch and libkernel.a.
40+ # The version is the one the hand-built tree under ~/.cache used, and the
41+ # submodules are not optional (zuo builds it, lz4 and zlib link into it).
42+ # The fork jolt's own Android pin names, built here rather than fetched as
43+ # a release binary: upstream reads the socket address out of `struct
44+ # addrinfo` at glibc's offset, which on Bionic is `ai_canonname`, so an APK
45+ # built with upstream cannot open a TLS connection at all. Only the boot
46+ # image uses it; the desktop package still builds jolt-src.
47+ jolt-android-src = {
48+ url = "git+https://gitlab.com/nandithebull/jolt?rev=2b80d68d1f7a31ba92b208b3957e5fb555617ada&submodules=1";
49+ flake = false;
50+ };
51+
52+ chez-src = {
53+ url = "git+https://github.com/cisco/ChezScheme?ref=refs/tags/v10.4.1&submodules=1";
54+ flake = false;
55+ };
56+
37 # The sha deps.edn pins, on the fork with the reconciler fixes.57 # The sha deps.edn pins, on the fork with the reconciler fixes.
38 glimmer = {58 glimmer = {
39 url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";59 url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";
@@ -47,7 +67,7 @@
47 };67 };
48 };68 };
49 69
50- outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer, nixgl }:70+ outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer, chez-src, jolt-android-src, nixgl }:
51 let71 let
52 systems = [ "x86_64-linux" "aarch64-linux" ];72 systems = [ "x86_64-linux" "aarch64-linux" ];
53 forEachSystem = f:73 forEachSystem = f:
@@ -115,10 +135,13 @@
115 };135 };
116 136
117 # Jolt itself: Clojure on Chez, built the way its own flake builds it.137 # Jolt itself: Clojure on Chez, built the way its own flake builds it.
118- joltRuntime = pkgs.stdenv.mkDerivation {138+ # A function, because there are two of them — upstream for the
139+ # desktop, and the Bionic-addrinfo fork for the boot image the APK
140+ # carries. Nothing else about the build differs.
141+ joltFrom = src: pkgs.stdenv.mkDerivation {
119 pname = "jolt";142 pname = "jolt";
120 version = "dev";143 version = "dev";
121- src = jolt-src;144+ inherit src;
122 145
123 strictDeps = true;146 strictDeps = true;
124 nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];147 nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];
@@ -150,6 +173,9 @@
150 '';173 '';
151 };174 };
152 175
176+ joltRuntime = joltFrom jolt-src;
177+ joltAndroid = joltFrom jolt-android-src;
178+
153 # glimmer-vidya lives inside the jolt-native checkout, and its own179 # glimmer-vidya lives inside the jolt-native checkout, and its own
154 # deps.edn asks for glimmer by git the top-level override below180 # deps.edn asks for glimmer by git the top-level override below
155 # answers for both.181 # answers for both.
@@ -208,11 +234,46 @@
208 mkdir -p "$out/bin"234 mkdir -p "$out/bin"
209 ln -s ${frqScript} "$out/bin/frq"235 ln -s ${frqScript} "$out/bin/frq"
210 '';236 '';
237+ # --- Android ------------------------------------------------------
238+ # The SDK and the NDK are Google's, which means unfree and a licence
239+ # to accept — so this is its own import of nixpkgs rather than the
240+ # `legacyPackages` everything above uses. Confined to the Android
241+ # outputs: `nix build` of frq itself never evaluates it.
242+ #
243+ # The NDK here is r29, which is the version scripts/android-ndk.dotslash
244+ # pins and the one the pinned libvidya was built with.
245+ androidPkgs = import nixpkgs {
246+ inherit (pkgs.stdenv.hostPlatform) system;
247+ config = {
248+ allowUnfree = true;
249+ android_sdk.accept_license = true;
250+ };
251+ };
252+
253+ androidComposition = androidPkgs.androidenv.composeAndroidPackages {
254+ buildToolsVersions = [ "36.0.0" ];
255+ platformVersions = [ "36" ];
256+ includeNDK = true;
257+ };
258+
259+ android = import ./nix/android.nix {
260+ inherit pkgs self chez-src jolt-native glimmer joltAndroid;
261+ inherit (pkgs) lib;
262+ androidSdk = androidComposition.androidsdk;
263+ ndk = androidComposition.ndk-bundle;
264+ };
211 in265 in
212 {266 {
213 inherit native frq;267 inherit native frq;
214 jolt = joltRuntime;268 jolt = joltRuntime;
215 default = frq;269 default = frq;
270+ }
271+ # An APK is built by a linux-x86_64 NDK and a linux-x86_64 jolt, and
272+ # Google ships no other; on aarch64 the Android outputs are simply
273+ # absent rather than present and broken.
274+ // lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
275+ inherit (android) apk chezAndroid joltBoot libjoltapp;
276+ apk-unsigned = android.apk-unsigned;
216 });277 });
217 278
218 apps = forEachSystem (pkgs: {279 apps = forEachSystem (pkgs: {
added nix/android.nix +364 -0
new file mode 100644
@@ -0,0 +1,364 @@
1+# The APK, as derivations rather than as a buck2 graph.
2+#
3+# android/BUCK builds the same five things, and does it better for a person at
4+# a terminal: it is incremental, and it lets a sibling jolt-native checkout win
5+# over the pinned release so that editing the Rust rebuilds the APK. Nothing
6+# here replaces that. What this adds is the other build — from nothing, on a
7+# machine with no Android SDK, no NDK, no hand-built Chez cross target and no
8+# ~/.cache at all:
9+#
10+# nix build .#apk
11+#
12+# On a machine with a remote builder configured, prefer
13+#
14+# nix build .#apk --store ssh-ng://eu.nixbuild.net --eval-store auto
15+#
16+# rather than letting `builders` do it. With `builders`, nix copies the output
17+# of every remotely-built derivation back, and androidenv's NDK is both
18+# `preferLocalBuild` and absent from cache.nixos.org — so the 3.1 GB unpacked
19+# toolchain is built here and uploaded. With the remote as the *store* the
20+# whole graph stays there, only .drv files go up, and the builder fetches
21+# Google's zip over its own link.
22+#
23+# Every path .buckconfig.local answers for is answered here by the store
24+# instead. The steps are in the same order and do the same work; where a
25+# genrule read `read_root_config`, a derivation takes an argument.
26+{ pkgs, lib, self, chez-src, jolt-native, glimmer, joltAndroid, androidSdk, ndk }:
27+
28+let
29+ # What the APK targets, in the three spellings the tools want it in.
30+ apiLevel = "28";
31+ targetSdk = "36";
32+ abi = "arm64-v8a";
33+ package = "uk.nandi.frq";
34+ version = "0.1.0";
35+
36+ sdk = "${androidSdk}/libexec/android-sdk";
37+ buildTools = "${sdk}/build-tools/36.0.0";
38+ androidJar = "${sdk}/platforms/android-${targetSdk}/android.jar";
39+
40+ # The NDK's clang finds its sysroot, resource directory and the rest of LLVM
41+ # relative to itself, so it is named by path rather than copied anywhere.
42+ # androidenv installs the tree under libexec/android-sdk and leaves
43+ # `ndk-bundle` pointing at the versioned directory beside it.
44+ ndkRoot = "${ndk}/libexec/android-sdk/ndk-bundle";
45+ ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
46+ cc = "${ndkBin}/aarch64-linux-android${apiLevel}-clang";
47+
48+ # The two halves that come out of jolt-native's releases, by the digests
49+ # scripts/*.dotslash pins. Same bytes buck fetches; DotSlash's `digest` is
50+ # over the archive, which is what fetchurl hashes too.
51+ libvidya = pkgs.fetchurl {
52+ url = "https://gitlab.com/-/project/85910092/uploads/a7264f20582e6d42d45b17fe626ba471/jolt-native-android-arm64-v0.1.1.tar.gz";
53+ sha256 = "bb85c57ea263b9bfe113927c6f6b0c8c6414b97056bcb0281b8444187616a233";
54+ };
55+
56+ glue = pkgs.fetchurl {
57+ url = "https://gitlab.com/-/project/85910092/uploads/16b1a3ea32dac6737fc21aec701d7b0c/jolt-native-android-glue-v0.1.1.tar.gz";
58+ sha256 = "7f4c179d72a3660ce8e80c3cf52a788ea33d7ef97d6f624310e61e7f4f98851b";
59+ };
60+
61+ # Unpacked once, so the three consumers below name files rather than repeat
62+ # the tar.
63+ vidyaLib = pkgs.runCommand "libvidya-android" { } ''
64+ mkdir -p "$out"
65+ tar -xzf ${libvidya} -C "$out"
66+ '';
67+
68+ glueSrc = pkgs.runCommand "jolt-android-glue" { } ''
69+ mkdir -p "$out"
70+ tar -xzf ${glue} -C "$out" --strip-components=1
71+ '';
72+
73+ # --- Chez's arm64 cross target ------------------------------------------
74+ # The one piece with no nixpkgs equivalent: `pkgs.chez` builds a Scheme for
75+ # this machine, and what the boot image needs is Chez's `tarm64le` workarea —
76+ # the target boot files, the cross compiler's xpatch, and the arm64
77+ # libkernel.a that libjoltapp links.
78+ #
79+ # Three builds in one derivation, because each needs the one before it:
80+ #
81+ # ta6le a host Scheme, which is what cross-compiles anything
82+ # boot XM=... the target's boot files and xpatch, made by that host
83+ # tarm64le the target kernel, compiled by the NDK
84+ #
85+ # The flags are the ones the hand-built tree under ~/.cache/vidya-chez-android
86+ # was configured with, read back out of its Mf-config. zlib is Android's own
87+ # (`-lz`, which Bionic has); lz4 is the in-tree submodule, built for arm64
88+ # here because a cross configure links it rather than building it.
89+ chezAndroid = pkgs.stdenv.mkDerivation {
90+ pname = "chez-scheme-android";
91+ version = "10.4.1";
92+ src = chez-src;
93+
94+ strictDeps = true;
95+ nativeBuildInputs = with pkgs; [ gnumake which ];
96+
97+ dontConfigure = true;
98+
99+ buildPhase = ''
100+ runHook preBuild
101+
102+ # Both workareas turn off the expression editor's two dependencies, which
103+ # is what ~/.cache/vidya-chez-android was configured with (its Mf-config
104+ # has empty cursesLib/ncursesLib, and disablex11=yes on the target). The
105+ # host Scheme here is only ever a cross compiler, and Bionic has no
106+ # curses.h at all so on the target it is not a preference but a
107+ # requirement.
108+ ./configure -m=ta6le --disable-x11 --disable-curses CC_FOR_BUILD="$CC"
109+ make -j"$NIX_BUILD_CORES"
110+ make boot XM=tarm64le -j"$NIX_BUILD_CORES"
111+
112+ # lz4 for the phone, not for this machine: the host build above left an
113+ # x86_64 liblz4.a in the same place, and the cross link needs it gone.
114+ make -C lz4/lib clean
115+ make -C lz4/lib liblz4.a -j"$NIX_BUILD_CORES" \
116+ CC=${cc} AR=${ndkBin}/llvm-ar
117+
118+ # --disable-auto-flags stops configure appending -lrt and -lpthread, which
119+ # is what its unix branch does for a glibc host and what Bionic has no
120+ # separate libraries for both live in libc there. Everything it would
121+ # otherwise add is passed explicitly below, matching the Mf-config of
122+ # the tree this was reconstructed from.
123+ ./configure -m=tarm64le --cross --disable-x11 --disable-curses \
124+ --disable-auto-flags \
125+ LIBS="-ldl -lm" \
126+ CC=${cc} \
127+ AR=${ndkBin}/llvm-ar \
128+ CC_FOR_BUILD="$CC" \
129+ ZLIB=-lz \
130+ LZ4="$PWD/lz4/lib/liblz4.a" \
131+ CPPFLAGS="-I$PWD/lz4/lib" \
132+ CFLAGS="-O2 -D_REENTRANT -pthread -fPIC"
133+ make -j"$NIX_BUILD_CORES"
134+
135+ runHook postBuild
136+ '';
137+
138+ # The whole workarea, at the paths CHEZ_ANDROID means: the host Scheme
139+ # loads xpatch out of xc-tarm64le/s, and the link below reads two archives
140+ # from elsewhere in the tree. Pruning it would only be guessing at which
141+ # of those the cross compiler still opens.
142+ installPhase = ''
143+ runHook preInstall
144+ mkdir -p "$out"
145+ cp -r . "$out/"
146+ runHook postInstall
147+ '';
148+
149+ # A Scheme built for this machine and a kernel built for another one; the
150+ # usual fixups have an opinion about both, and neither wants it.
151+ dontStrip = true;
152+ dontPatchELF = true;
153+ };
154+
155+ hostScheme = "${chezAndroid}/ta6le/bin/ta6le/scheme";
156+ targetBoot = "${chezAndroid}/boot/tarm64le";
157+ xpatch = "${chezAndroid}/xc-tarm64le/s/xpatch";
158+
159+ # --- the Jolt half --------------------------------------------------------
160+ # frq's Scheme, cross-compiled to an arm64 boot image. android/build-jolt-boot.bb
161+ # does this by hand-writing a deps.edn of :paths and then driving Chez; so
162+ # does this, for the same reason — there is no dependency resolution inside a
163+ # cross compile, so every source root deps.edn would have resolved is named.
164+ #
165+ # The jolt that runs it is the fork, not upstream and not the one the desktop
166+ # package builds: upstream reads the socket address out of `struct addrinfo`
167+ # at glibc's offset, which on Bionic is `ai_canonname`, and an APK built with
168+ # it cannot open a TLS connection at all.
169+ joltBoot = pkgs.stdenv.mkDerivation {
170+ pname = "frq-jolt-boot";
171+ inherit version;
172+
173+ dontUnpack = true;
174+ strictDeps = true;
175+ nativeBuildInputs = [ joltAndroid ];
176+
177+ buildPhase = ''
178+ runHook preBuild
179+
180+ export HOME="$TMPDIR"
181+ mkdir -p project cross
182+
183+ cat > project/deps.edn <<EOF
184+ {:paths ["${self}/src" "${glimmer}/src" "${jolt-native}/jolt/glimmer-vidya/src"]}
185+ EOF
186+
187+ # The flat build, which is the one shape make-boot-file can take.
188+ ( cd project && JOLT_NO_FLAT_SPLIT=1 jolt build -m frq.app -o app )
189+
190+ cat > cross/compile.ss <<EOF
191+ (import (chezscheme))
192+ (load "${xpatch}")
193+ (optimize-level 2)
194+ (generate-inspector-information #f)
195+ (compile-file "$PWD/project/app.build/flat.ss" "$PWD/cross/flat.so")
196+ (make-boot-file "$PWD/jolt.boot" '()
197+ "${targetBoot}/petite.boot"
198+ "${targetBoot}/scheme.boot"
199+ "$PWD/cross/flat.so")
200+ EOF
201+
202+ SCHEMEHEAPDIRS="${chezAndroid}/ta6le/boot/ta6le" \
203+ ${hostScheme} --script cross/compile.ss
204+
205+ runHook postBuild
206+ '';
207+
208+ # scheme.h travels with the image because jolt_main.c includes it.
209+ installPhase = ''
210+ runHook preInstall
211+ mkdir -p "$out"
212+ cp jolt.boot "$out/jolt.boot"
213+ cp ${targetBoot}/scheme.h "$out/scheme.h"
214+ runHook postInstall
215+ '';
216+ };
217+
218+ # The image travels as a blob in an object file's data section. The
219+ # _binary_jolt_boot_{start,end} symbols jolt_main.c reads are named after the
220+ # input *path*, so this copies the file somewhere it is called exactly
221+ # `jolt.boot` before converting it.
222+ joltBootObj = pkgs.runCommand "jolt-boot-obj" { } ''
223+ cp ${joltBoot}/jolt.boot jolt.boot
224+ ${ndkBin}/llvm-objcopy \
225+ --input-target=binary --output-target=elf64-littleaarch64 \
226+ --binary-architecture=aarch64 jolt.boot "$out"
227+ '';
228+
229+ # The glue: jolt-native's jolt_main.c over the boot image, linked against
230+ # libvidya by name. --no-undefined is what makes a symbol the Scheme side
231+ # registers but the ABI no longer exports a build failure here rather than a
232+ # crash on the phone.
233+ libjoltapp = pkgs.runCommand "libjoltapp.so" { } ''
234+ mkdir -p lib
235+ cp ${vidyaLib}/libvidya.so lib/libvidya.so
236+
237+ ${cc} -shared -fPIC -O2 -o "$out" \
238+ ${glueSrc}/android/jolt_main.c \
239+ ${joltBootObj} \
240+ -I${joltBoot} \
241+ -I${glueSrc}/include \
242+ -Llib \
243+ ${chezAndroid}/tarm64le/boot/tarm64le/libkernel.a \
244+ ${chezAndroid}/lz4/lib/liblz4.a \
245+ -lvidya -landroid -llog -lz -ldl -lm -Wl,--no-undefined
246+ '';
247+
248+ # --- the Java half --------------------------------------------------------
249+ # One class: the photo chooser's result has to land somewhere, and native
250+ # code is not somewhere.
251+ classesDex = pkgs.runCommand "classes.dex"
252+ {
253+ nativeBuildInputs = [ pkgs.jdk17 ];
254+ } ''
255+ mkdir -p classes out
256+ # -encoding, because a build sandbox has no locale and javac then reads
257+ # the source as US-ASCII on which the comments' em dashes are errors.
258+ javac --release 17 -encoding UTF-8 --class-path ${androidJar} -d classes \
259+ $(find ${self}/android/java -name '*.java')
260+ ${buildTools}/d8 --min-api ${apiLevel} --output out $(find classes -name '*.class')
261+ cp out/classes.dex "$out"
262+ '';
263+
264+ # --- the package ----------------------------------------------------------
265+ # OpenSSL travels with the app because the platform's own is not ours to
266+ # load: an app's linker namespace refuses /system/lib64/libssl.so, and
267+ # without one there is no TLS on the phone at all.
268+ # Built by the NDK rather than by pkgsCross.aarch64-android: that cross
269+ # stdenv cannot build its own compiler-rt on this nixpkgs — os_version_check.c
270+ # includes <pthread.h> and the sysroot it is handed has no such header — and
271+ # an APK has no use for a second toolchain anyway. OpenSSL's own android-arm64
272+ # target wants the NDK's llvm on PATH and takes the API level from the flag.
273+ # The version is the one ~/.cache/frq-openssl-android was built from.
274+ opensslAndroid = pkgs.stdenv.mkDerivation {
275+ pname = "openssl-android";
276+ version = "3.5.4";
277+
278+ src = pkgs.fetchurl {
279+ url = "https://github.com/openssl/openssl/releases/download/openssl-3.5.4/openssl-3.5.4.tar.gz";
280+ sha256 = "16ay6ppxsky3qhg6573370iz93kihfwx9n5ipmlnjcam97w12wwn";
281+ };
282+
283+ strictDeps = true;
284+ nativeBuildInputs = with pkgs; [ perl ];
285+
286+ configurePhase = ''
287+ runHook preConfigure
288+ export ANDROID_NDK_ROOT="${ndkRoot}"
289+ export PATH="${ndkBin}:$PATH"
290+ # Through perl rather than as a program: its shebang is /usr/bin/env,
291+ # which a build sandbox does not have.
292+ perl ./Configure android-arm64 -D__ANDROID_API__=${apiLevel} \
293+ shared no-tests no-docs \
294+ --prefix="$out" --openssldir="$out/etc/ssl"
295+ runHook postConfigure
296+ '';
297+
298+ # install_sw, not install: the rest of an OpenSSL install is for a machine
299+ # that runs it, and this one only ships two .so files into an APK.
300+ installTargets = [ "install_sw" ];
301+
302+ dontStrip = true;
303+ dontPatchELF = true;
304+ };
305+
306+ # The libraries are stored rather than deflated: the loader maps them
307+ # straight out of the APK. The dex is read rather than mapped, so it may as
308+ # well compress.
309+ apkUnsigned = pkgs.runCommand "frq-unsigned.apk"
310+ {
311+ nativeBuildInputs = [ pkgs.zip ];
312+ } ''
313+ mkdir -p stage/lib/${abi}
314+ cp ${vidyaLib}/libvidya.so stage/lib/${abi}/libvidya.so
315+ cp ${libjoltapp} stage/lib/${abi}/libjoltapp.so
316+ cp ${opensslAndroid.out}/lib/libssl.so stage/lib/${abi}/libssl.so
317+ cp ${opensslAndroid.out}/lib/libcrypto.so stage/lib/${abi}/libcrypto.so
318+ cp ${classesDex} stage/classes.dex
319+ chmod -R u+w stage
320+
321+ ${buildTools}/aapt2 link -o "$out" -I ${androidJar} \
322+ --manifest ${self}/android/AndroidManifest.xml \
323+ --min-sdk-version ${apiLevel} --target-sdk-version ${targetSdk} \
324+ --version-code 1 --version-name ${version}
325+
326+ ( cd stage && \
327+ zip -q -0 "$out" lib/${abi}/libvidya.so lib/${abi}/libjoltapp.so \
328+ lib/${abi}/libssl.so lib/${abi}/libcrypto.so && \
329+ zip -q "$out" classes.dex )
330+ '';
331+
332+ # Aligned and signed with a debug key. The key is generated here rather than
333+ # read from ~/.android, which is the one place this build is deliberately
334+ # not the buck one: a keystore outside the store would make the output
335+ # depend on the machine, and a release key has no business in the store at
336+ # all. So this output is installable and not reproducible — keytool stamps
337+ # the certificate with the time — and anything meant for a store should be
338+ # signed from .#apk-unsigned instead.
339+ apk = pkgs.runCommand "frq-${version}.apk"
340+ {
341+ nativeBuildInputs = [ pkgs.jdk17 ];
342+ meta = {
343+ description = "frq for Android, debug-signed";
344+ platforms = [ "x86_64-linux" ];
345+ };
346+ } ''
347+ export HOME="$TMPDIR"
348+ keytool -genkeypair -keystore debug.keystore \
349+ -storepass android -keypass android -alias androiddebugkey \
350+ -keyalg RSA -keysize 2048 -validity 10000 \
351+ -dname 'CN=Android Debug,O=Android,C=US'
352+
353+ ${buildTools}/zipalign -f -p 4 ${apkUnsigned} aligned.apk
354+ ${buildTools}/apksigner sign --ks debug.keystore \
355+ --ks-key-alias androiddebugkey \
356+ --ks-pass pass:android --key-pass pass:android \
357+ --out "$out" aligned.apk
358+ ${buildTools}/apksigner verify "$out"
359+ '';
360+in
361+{
362+ inherit chezAndroid joltBoot libjoltapp classesDex apk;
363+ apk-unsigned = apkUnsigned;
364+}
new file mode 100644
@@ -0,0 +1,364 @@
1+# The APK, as derivations rather than as a buck2 graph.
2+#
3+# android/BUCK builds the same five things, and does it better for a person at
4+# a terminal: it is incremental, and it lets a sibling jolt-native checkout win
5+# over the pinned release so that editing the Rust rebuilds the APK. Nothing
6+# here replaces that. What this adds is the other build — from nothing, on a
7+# machine with no Android SDK, no NDK, no hand-built Chez cross target and no
8+# ~/.cache at all:
9+#
10+# nix build .#apk
11+#
12+# On a machine with a remote builder configured, prefer
13+#
14+# nix build .#apk --store ssh-ng://eu.nixbuild.net --eval-store auto
15+#
16+# rather than letting `builders` do it. With `builders`, nix copies the output
17+# of every remotely-built derivation back, and androidenv's NDK is both
18+# `preferLocalBuild` and absent from cache.nixos.org — so the 3.1 GB unpacked
19+# toolchain is built here and uploaded. With the remote as the *store* the
20+# whole graph stays there, only .drv files go up, and the builder fetches
21+# Google's zip over its own link.
22+#
23+# Every path .buckconfig.local answers for is answered here by the store
24+# instead. The steps are in the same order and do the same work; where a
25+# genrule read `read_root_config`, a derivation takes an argument.
26+{ pkgs, lib, self, chez-src, jolt-native, glimmer, joltAndroid, androidSdk, ndk }:
27+
28+let
29+ # What the APK targets, in the three spellings the tools want it in.
30+ apiLevel = "28";
31+ targetSdk = "36";
32+ abi = "arm64-v8a";
33+ package = "uk.nandi.frq";
34+ version = "0.1.0";
35+
36+ sdk = "${androidSdk}/libexec/android-sdk";
37+ buildTools = "${sdk}/build-tools/36.0.0";
38+ androidJar = "${sdk}/platforms/android-${targetSdk}/android.jar";
39+
40+ # The NDK's clang finds its sysroot, resource directory and the rest of LLVM
41+ # relative to itself, so it is named by path rather than copied anywhere.
42+ # androidenv installs the tree under libexec/android-sdk and leaves
43+ # `ndk-bundle` pointing at the versioned directory beside it.
44+ ndkRoot = "${ndk}/libexec/android-sdk/ndk-bundle";
45+ ndkBin = "${ndkRoot}/toolchains/llvm/prebuilt/linux-x86_64/bin";
46+ cc = "${ndkBin}/aarch64-linux-android${apiLevel}-clang";
47+
48+ # The two halves that come out of jolt-native's releases, by the digests
49+ # scripts/*.dotslash pins. Same bytes buck fetches; DotSlash's `digest` is
50+ # over the archive, which is what fetchurl hashes too.
51+ libvidya = pkgs.fetchurl {
52+ url = "https://gitlab.com/-/project/85910092/uploads/a7264f20582e6d42d45b17fe626ba471/jolt-native-android-arm64-v0.1.1.tar.gz";
53+ sha256 = "bb85c57ea263b9bfe113927c6f6b0c8c6414b97056bcb0281b8444187616a233";
54+ };
55+
56+ glue = pkgs.fetchurl {
57+ url = "https://gitlab.com/-/project/85910092/uploads/16b1a3ea32dac6737fc21aec701d7b0c/jolt-native-android-glue-v0.1.1.tar.gz";
58+ sha256 = "7f4c179d72a3660ce8e80c3cf52a788ea33d7ef97d6f624310e61e7f4f98851b";
59+ };
60+
61+ # Unpacked once, so the three consumers below name files rather than repeat
62+ # the tar.
63+ vidyaLib = pkgs.runCommand "libvidya-android" { } ''
64+ mkdir -p "$out"
65+ tar -xzf ${libvidya} -C "$out"
66+ '';
67+
68+ glueSrc = pkgs.runCommand "jolt-android-glue" { } ''
69+ mkdir -p "$out"
70+ tar -xzf ${glue} -C "$out" --strip-components=1
71+ '';
72+
73+ # --- Chez's arm64 cross target ------------------------------------------
74+ # The one piece with no nixpkgs equivalent: `pkgs.chez` builds a Scheme for
75+ # this machine, and what the boot image needs is Chez's `tarm64le` workarea —
76+ # the target boot files, the cross compiler's xpatch, and the arm64
77+ # libkernel.a that libjoltapp links.
78+ #
79+ # Three builds in one derivation, because each needs the one before it:
80+ #
81+ # ta6le a host Scheme, which is what cross-compiles anything
82+ # boot XM=... the target's boot files and xpatch, made by that host
83+ # tarm64le the target kernel, compiled by the NDK
84+ #
85+ # The flags are the ones the hand-built tree under ~/.cache/vidya-chez-android
86+ # was configured with, read back out of its Mf-config. zlib is Android's own
87+ # (`-lz`, which Bionic has); lz4 is the in-tree submodule, built for arm64
88+ # here because a cross configure links it rather than building it.
89+ chezAndroid = pkgs.stdenv.mkDerivation {
90+ pname = "chez-scheme-android";
91+ version = "10.4.1";
92+ src = chez-src;
93+
94+ strictDeps = true;
95+ nativeBuildInputs = with pkgs; [ gnumake which ];
96+
97+ dontConfigure = true;
98+
99+ buildPhase = ''
100+ runHook preBuild
101+
102+ # Both workareas turn off the expression editor's two dependencies, which
103+ # is what ~/.cache/vidya-chez-android was configured with (its Mf-config
104+ # has empty cursesLib/ncursesLib, and disablex11=yes on the target). The
105+ # host Scheme here is only ever a cross compiler, and Bionic has no
106+ # curses.h at all so on the target it is not a preference but a
107+ # requirement.
108+ ./configure -m=ta6le --disable-x11 --disable-curses CC_FOR_BUILD="$CC"
109+ make -j"$NIX_BUILD_CORES"
110+ make boot XM=tarm64le -j"$NIX_BUILD_CORES"
111+
112+ # lz4 for the phone, not for this machine: the host build above left an
113+ # x86_64 liblz4.a in the same place, and the cross link needs it gone.
114+ make -C lz4/lib clean
115+ make -C lz4/lib liblz4.a -j"$NIX_BUILD_CORES" \
116+ CC=${cc} AR=${ndkBin}/llvm-ar
117+
118+ # --disable-auto-flags stops configure appending -lrt and -lpthread, which
119+ # is what its unix branch does for a glibc host and what Bionic has no
120+ # separate libraries for both live in libc there. Everything it would
121+ # otherwise add is passed explicitly below, matching the Mf-config of
122+ # the tree this was reconstructed from.
123+ ./configure -m=tarm64le --cross --disable-x11 --disable-curses \
124+ --disable-auto-flags \
125+ LIBS="-ldl -lm" \
126+ CC=${cc} \
127+ AR=${ndkBin}/llvm-ar \
128+ CC_FOR_BUILD="$CC" \
129+ ZLIB=-lz \
130+ LZ4="$PWD/lz4/lib/liblz4.a" \
131+ CPPFLAGS="-I$PWD/lz4/lib" \
132+ CFLAGS="-O2 -D_REENTRANT -pthread -fPIC"
133+ make -j"$NIX_BUILD_CORES"
134+
135+ runHook postBuild
136+ '';
137+
138+ # The whole workarea, at the paths CHEZ_ANDROID means: the host Scheme
139+ # loads xpatch out of xc-tarm64le/s, and the link below reads two archives
140+ # from elsewhere in the tree. Pruning it would only be guessing at which
141+ # of those the cross compiler still opens.
142+ installPhase = ''
143+ runHook preInstall
144+ mkdir -p "$out"
145+ cp -r . "$out/"
146+ runHook postInstall
147+ '';
148+
149+ # A Scheme built for this machine and a kernel built for another one; the
150+ # usual fixups have an opinion about both, and neither wants it.
151+ dontStrip = true;
152+ dontPatchELF = true;
153+ };
154+
155+ hostScheme = "${chezAndroid}/ta6le/bin/ta6le/scheme";
156+ targetBoot = "${chezAndroid}/boot/tarm64le";
157+ xpatch = "${chezAndroid}/xc-tarm64le/s/xpatch";
158+
159+ # --- the Jolt half --------------------------------------------------------
160+ # frq's Scheme, cross-compiled to an arm64 boot image. android/build-jolt-boot.bb
161+ # does this by hand-writing a deps.edn of :paths and then driving Chez; so
162+ # does this, for the same reason — there is no dependency resolution inside a
163+ # cross compile, so every source root deps.edn would have resolved is named.
164+ #
165+ # The jolt that runs it is the fork, not upstream and not the one the desktop
166+ # package builds: upstream reads the socket address out of `struct addrinfo`
167+ # at glibc's offset, which on Bionic is `ai_canonname`, and an APK built with
168+ # it cannot open a TLS connection at all.
169+ joltBoot = pkgs.stdenv.mkDerivation {
170+ pname = "frq-jolt-boot";
171+ inherit version;
172+
173+ dontUnpack = true;
174+ strictDeps = true;
175+ nativeBuildInputs = [ joltAndroid ];
176+
177+ buildPhase = ''
178+ runHook preBuild
179+
180+ export HOME="$TMPDIR"
181+ mkdir -p project cross
182+
183+ cat > project/deps.edn <<EOF
184+ {:paths ["${self}/src" "${glimmer}/src" "${jolt-native}/jolt/glimmer-vidya/src"]}
185+ EOF
186+
187+ # The flat build, which is the one shape make-boot-file can take.
188+ ( cd project && JOLT_NO_FLAT_SPLIT=1 jolt build -m frq.app -o app )
189+
190+ cat > cross/compile.ss <<EOF
191+ (import (chezscheme))
192+ (load "${xpatch}")
193+ (optimize-level 2)
194+ (generate-inspector-information #f)
195+ (compile-file "$PWD/project/app.build/flat.ss" "$PWD/cross/flat.so")
196+ (make-boot-file "$PWD/jolt.boot" '()
197+ "${targetBoot}/petite.boot"
198+ "${targetBoot}/scheme.boot"
199+ "$PWD/cross/flat.so")
200+ EOF
201+
202+ SCHEMEHEAPDIRS="${chezAndroid}/ta6le/boot/ta6le" \
203+ ${hostScheme} --script cross/compile.ss
204+
205+ runHook postBuild
206+ '';
207+
208+ # scheme.h travels with the image because jolt_main.c includes it.
209+ installPhase = ''
210+ runHook preInstall
211+ mkdir -p "$out"
212+ cp jolt.boot "$out/jolt.boot"
213+ cp ${targetBoot}/scheme.h "$out/scheme.h"
214+ runHook postInstall
215+ '';
216+ };
217+
218+ # The image travels as a blob in an object file's data section. The
219+ # _binary_jolt_boot_{start,end} symbols jolt_main.c reads are named after the
220+ # input *path*, so this copies the file somewhere it is called exactly
221+ # `jolt.boot` before converting it.
222+ joltBootObj = pkgs.runCommand "jolt-boot-obj" { } ''
223+ cp ${joltBoot}/jolt.boot jolt.boot
224+ ${ndkBin}/llvm-objcopy \
225+ --input-target=binary --output-target=elf64-littleaarch64 \
226+ --binary-architecture=aarch64 jolt.boot "$out"
227+ '';
228+
229+ # The glue: jolt-native's jolt_main.c over the boot image, linked against
230+ # libvidya by name. --no-undefined is what makes a symbol the Scheme side
231+ # registers but the ABI no longer exports a build failure here rather than a
232+ # crash on the phone.
233+ libjoltapp = pkgs.runCommand "libjoltapp.so" { } ''
234+ mkdir -p lib
235+ cp ${vidyaLib}/libvidya.so lib/libvidya.so
236+
237+ ${cc} -shared -fPIC -O2 -o "$out" \
238+ ${glueSrc}/android/jolt_main.c \
239+ ${joltBootObj} \
240+ -I${joltBoot} \
241+ -I${glueSrc}/include \
242+ -Llib \
243+ ${chezAndroid}/tarm64le/boot/tarm64le/libkernel.a \
244+ ${chezAndroid}/lz4/lib/liblz4.a \
245+ -lvidya -landroid -llog -lz -ldl -lm -Wl,--no-undefined
246+ '';
247+
248+ # --- the Java half --------------------------------------------------------
249+ # One class: the photo chooser's result has to land somewhere, and native
250+ # code is not somewhere.
251+ classesDex = pkgs.runCommand "classes.dex"
252+ {
253+ nativeBuildInputs = [ pkgs.jdk17 ];
254+ } ''
255+ mkdir -p classes out
256+ # -encoding, because a build sandbox has no locale and javac then reads
257+ # the source as US-ASCII on which the comments' em dashes are errors.
258+ javac --release 17 -encoding UTF-8 --class-path ${androidJar} -d classes \
259+ $(find ${self}/android/java -name '*.java')
260+ ${buildTools}/d8 --min-api ${apiLevel} --output out $(find classes -name '*.class')
261+ cp out/classes.dex "$out"
262+ '';
263+
264+ # --- the package ----------------------------------------------------------
265+ # OpenSSL travels with the app because the platform's own is not ours to
266+ # load: an app's linker namespace refuses /system/lib64/libssl.so, and
267+ # without one there is no TLS on the phone at all.
268+ # Built by the NDK rather than by pkgsCross.aarch64-android: that cross
269+ # stdenv cannot build its own compiler-rt on this nixpkgs — os_version_check.c
270+ # includes <pthread.h> and the sysroot it is handed has no such header — and
271+ # an APK has no use for a second toolchain anyway. OpenSSL's own android-arm64
272+ # target wants the NDK's llvm on PATH and takes the API level from the flag.
273+ # The version is the one ~/.cache/frq-openssl-android was built from.
274+ opensslAndroid = pkgs.stdenv.mkDerivation {
275+ pname = "openssl-android";
276+ version = "3.5.4";
277+
278+ src = pkgs.fetchurl {
279+ url = "https://github.com/openssl/openssl/releases/download/openssl-3.5.4/openssl-3.5.4.tar.gz";
280+ sha256 = "16ay6ppxsky3qhg6573370iz93kihfwx9n5ipmlnjcam97w12wwn";
281+ };
282+
283+ strictDeps = true;
284+ nativeBuildInputs = with pkgs; [ perl ];
285+
286+ configurePhase = ''
287+ runHook preConfigure
288+ export ANDROID_NDK_ROOT="${ndkRoot}"
289+ export PATH="${ndkBin}:$PATH"
290+ # Through perl rather than as a program: its shebang is /usr/bin/env,
291+ # which a build sandbox does not have.
292+ perl ./Configure android-arm64 -D__ANDROID_API__=${apiLevel} \
293+ shared no-tests no-docs \
294+ --prefix="$out" --openssldir="$out/etc/ssl"
295+ runHook postConfigure
296+ '';
297+
298+ # install_sw, not install: the rest of an OpenSSL install is for a machine
299+ # that runs it, and this one only ships two .so files into an APK.
300+ installTargets = [ "install_sw" ];
301+
302+ dontStrip = true;
303+ dontPatchELF = true;
304+ };
305+
306+ # The libraries are stored rather than deflated: the loader maps them
307+ # straight out of the APK. The dex is read rather than mapped, so it may as
308+ # well compress.
309+ apkUnsigned = pkgs.runCommand "frq-unsigned.apk"
310+ {
311+ nativeBuildInputs = [ pkgs.zip ];
312+ } ''
313+ mkdir -p stage/lib/${abi}
314+ cp ${vidyaLib}/libvidya.so stage/lib/${abi}/libvidya.so
315+ cp ${libjoltapp} stage/lib/${abi}/libjoltapp.so
316+ cp ${opensslAndroid.out}/lib/libssl.so stage/lib/${abi}/libssl.so
317+ cp ${opensslAndroid.out}/lib/libcrypto.so stage/lib/${abi}/libcrypto.so
318+ cp ${classesDex} stage/classes.dex
319+ chmod -R u+w stage
320+
321+ ${buildTools}/aapt2 link -o "$out" -I ${androidJar} \
322+ --manifest ${self}/android/AndroidManifest.xml \
323+ --min-sdk-version ${apiLevel} --target-sdk-version ${targetSdk} \
324+ --version-code 1 --version-name ${version}
325+
326+ ( cd stage && \
327+ zip -q -0 "$out" lib/${abi}/libvidya.so lib/${abi}/libjoltapp.so \
328+ lib/${abi}/libssl.so lib/${abi}/libcrypto.so && \
329+ zip -q "$out" classes.dex )
330+ '';
331+
332+ # Aligned and signed with a debug key. The key is generated here rather than
333+ # read from ~/.android, which is the one place this build is deliberately
334+ # not the buck one: a keystore outside the store would make the output
335+ # depend on the machine, and a release key has no business in the store at
336+ # all. So this output is installable and not reproducible — keytool stamps
337+ # the certificate with the time — and anything meant for a store should be
338+ # signed from .#apk-unsigned instead.
339+ apk = pkgs.runCommand "frq-${version}.apk"
340+ {
341+ nativeBuildInputs = [ pkgs.jdk17 ];
342+ meta = {
343+ description = "frq for Android, debug-signed";
344+ platforms = [ "x86_64-linux" ];
345+ };
346+ } ''
347+ export HOME="$TMPDIR"
348+ keytool -genkeypair -keystore debug.keystore \
349+ -storepass android -keypass android -alias androiddebugkey \
350+ -keyalg RSA -keysize 2048 -validity 10000 \
351+ -dname 'CN=Android Debug,O=Android,C=US'
352+
353+ ${buildTools}/zipalign -f -p 4 ${apkUnsigned} aligned.apk
354+ ${buildTools}/apksigner sign --ks debug.keystore \
355+ --ks-key-alias androiddebugkey \
356+ --ks-pass pass:android --key-pass pass:android \
357+ --out "$out" aligned.apk
358+ ${buildTools}/apksigner verify "$out"
359+ '';
360+in
361+{
362+ inherit chezAndroid joltBoot libjoltapp classesDex apk;
363+ apk-unsigned = apkUnsigned;
364+}