nandi/frqpublic Fork 0
77d4bec
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, at the rev flake.lock pins

The APK already built; what it built with was not the flake's. The
toolchain was `nix shell nixpkgs#clojure nixpkgs#jdk17 nixpkgs#flutter`,
which reads the flake registry, and the SDK was a `nix build --impure
--expr` around getFlake of nixos-unstable. Two unlocked references, so
the Flutter compiling the APK and the nixpkgs under everything else were
free to drift apart without flake.lock changing a line.

So: androidPkgsFor, a second import of the locked input carrying the
allowUnfree and android_sdk.accept_license that cannot be set on a
legacyPackages attribute after the fact — which is why that config had
been stranded in an --impure --expr in the first place. Over it
packages.android-sdk, with the compose arguments the recipe had inline,
and devShells.flutter with clojure, jdk17 and flutter in it.

Out of the default shell on purpose: Flutter brings its own Dart and a
JDK's worth of closure, and a desktop build opens none of it.

Still impure, and still has to be. Gradle resolves its own dependencies
and installs build-tools into ANDROID_HOME as it goes, so the recipe
still copies the store SDK to flutter/.home and lets it finish there.
Pinning the toolchain does not make the build sandboxable; it makes the
toolchain reproducible.

Verified: nix flake check passes, and `just apk` builds app-debug.apk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-12T01:27:40-07:00 Browse files
77d4bec parent: 7dff3cf
modified CLAUDE.md +5 -2
@@ -39,8 +39,11 @@ Adding a host call means adding it to the seam in `common/frq/io.cljc` and to
3939 both implementations. Name it for the result rather than the mechanism — the
4040 seam has `write-private-file!` and not a chmod, because Dart has no chmod.
4141
42-`flutter/` does not build; there is no cljd toolchain or Flutter SDK in the
43-flake. See flutter/README.md. It is the only APK there is — the jolt APK,
42+`flutter/` builds with `just apk`, out of the flake's own `.#flutter` shell
43+(clojure, jdk17, flutter) and its `.#android-sdk` package. Impure on purpose:
44+Gradle fetches its own dependencies and writes into `ANDROID_HOME`, so the
45+recipe copies the store SDK to `flutter/.home` and lets it finish there. See
46+flutter/README.md. It is the only APK there is — the jolt APK,
4447 `nix/android.nix`, `android/` and the `.#apk` outputs are gone, because every
4548 backend that APK could paint with is retired. jvui and Vidya were experiments;
4649 libcosmic is the desktop window and does not cross to a phone.
@@ -39,8 +39,11 @@ Adding a host call means adding it to the seam in `common/frq/io.cljc` and to
39 both implementations. Name it for the result rather than the mechanism — the39 both implementations. Name it for the result rather than the mechanism — the
40 seam has `write-private-file!` and not a chmod, because Dart has no chmod.40 seam has `write-private-file!` and not a chmod, because Dart has no chmod.
41 41
42-`flutter/` does not build; there is no cljd toolchain or Flutter SDK in the42+`flutter/` builds with `just apk`, out of the flake's own `.#flutter` shell
43-flake. See flutter/README.md. It is the only APK there is — the jolt APK,43+(clojure, jdk17, flutter) and its `.#android-sdk` package. Impure on purpose:
44+Gradle fetches its own dependencies and writes into `ANDROID_HOME`, so the
45+recipe copies the store SDK to `flutter/.home` and lets it finish there. See
46+flutter/README.md. It is the only APK there is — the jolt APK,
44 `nix/android.nix`, `android/` and the `.#apk` outputs are gone, because every47 `nix/android.nix`, `android/` and the `.#apk` outputs are gone, because every
45 backend that APK could paint with is retired. jvui and Vidya were experiments;48 backend that APK could paint with is retired. jvui and Vidya were experiments;
46 libcosmic is the desktop window and does not cross to a phone.49 libcosmic is the desktop window and does not cross to a phone.
modified flake.nix +68 -0
@@ -101,6 +101,42 @@
101101 enable32bits = false;
102102 }).nixGLIntel;
103103
104+ # The Android SDK wants two things `nixpkgs.legacyPackages` cannot give:
105+ # `allowUnfree`, because the SDK's own licence is not free, and
106+ # `android_sdk.accept_license`, which is how you say so in a file rather
107+ # than at a prompt a build has no terminal for. Neither can be set on a
108+ # legacyPackages attribute after the fact, so this is a second import of
109+ # the same locked nixpkgs rather than a second nixpkgs.
110+ #
111+ # This used to live in `just apk` as a `nix build --impure --expr` with
112+ # `builtins.getFlake "github:NixOS/nixpkgs/nixos-unstable"` inside it —
113+ # which fetched whatever nixos-unstable was that morning, not what
114+ # flake.lock pins, so the SDK under the APK and the nixpkgs under
115+ # everything else were free to drift apart. Here they are the same rev.
116+ androidPkgsFor = system: import nixpkgs {
117+ inherit system;
118+ config = {
119+ allowUnfree = true;
120+ android_sdk.accept_license = true;
121+ };
122+ };
123+
124+ # Only the floor Gradle stands on. It installs build-tools and a platform
125+ # into ANDROID_HOME itself as it goes — see `just apk` for why that means
126+ # a writable copy — so composing more of them here buys nothing.
127+ #
128+ # includeNDK = false deliberately: the app is Dart and path_provider is
129+ # platform channels, so there is no native code to need one, and asking
130+ # for it is a few hundred megabytes and a Gradle fetch of that exact NDK.
131+ androidSdkFor = system:
132+ let android = androidPkgsFor system; in
133+ (android.androidenv.composeAndroidPackages {
134+ cmdLineToolsVersion = "13.0";
135+ buildToolsVersions = [ "34.0.0" ];
136+ platformVersions = [ "35" "34" ];
137+ includeNDK = false;
138+ }).androidsdk;
139+
104140 # egui reaches for these with dlopen rather than linking them, so being
105141 # in the cdylib's buildInputs is not enough — whatever starts frq has to
106142 # put them on the loader path itself. Without libx11 here, vidya reports
@@ -416,6 +452,12 @@
416452 jolt = joltRuntime;
417453 default = frq;
418454
455+ # The Android SDK `just apk` copies into flutter/.home. A package
456+ # rather than something the recipe evaluates inline, so that
457+ # `nix build .#android-sdk` is how you pre-warm it and `nix flake
458+ # show` admits it exists.
459+ android-sdk = androidSdkFor pkgs.stdenv.hostPlatform.system;
460+
419461 # frq and everything it loads, squashed into one runnable file for
420462 # hosts without Nix. The whole closure rides along — Mesa included,
421463 # which is not waste: off NixOS the launcher goes through nixGL, and
@@ -539,6 +581,32 @@
539581 unset frq_named frq_git frq_near
540582 '';
541583 };
584+
585+ # The APK toolchain, which the default shell deliberately does not
586+ # carry: Flutter brings its own Dart, Gradle and a JDK's worth of
587+ # closure, and a desktop build has no use for any of it.
588+ #
589+ # `just apk` used to name these as `nix shell nixpkgs#clojure
590+ # nixpkgs#jdk17 nixpkgs#flutter`, which is the flake registry's
591+ # nixpkgs and not this flake's so the Flutter under the APK
592+ # floated while everything else was locked. Same three packages,
593+ # from flake.lock now.
594+ #
595+ # JDK 17 and not newer on purpose: the Flutter template's Gradle
596+ # plugin pins a Gradle that rejects a JDK it was released before,
597+ # and the failure reads as an unsupported class file version rather
598+ # than as a version mismatch.
599+ flutter = pkgs.mkShellNoCC {
600+ name = "frq-flutter";
601+ packages = [ pkgs.clojure pkgs.jdk17 pkgs.flutter pkgs.just ];
602+
603+ # Where the recipe copies from. Naming it here is also what makes
604+ # entering the shell build it, so the first `just apk` does not
605+ # stop for a few hundred megabytes of SDK with nothing said about
606+ # why.
607+ FRQ_ANDROID_SDK =
608+ "${androidSdkFor pkgs.stdenv.hostPlatform.system}/libexec/android-sdk";
609+ };
542610 });
543611
544612 apps = forEachSystem (pkgs: {
@@ -101,6 +101,42 @@
101 enable32bits = false;101 enable32bits = false;
102 }).nixGLIntel;102 }).nixGLIntel;
103 103
104+ # The Android SDK wants two things `nixpkgs.legacyPackages` cannot give:
105+ # `allowUnfree`, because the SDK's own licence is not free, and
106+ # `android_sdk.accept_license`, which is how you say so in a file rather
107+ # than at a prompt a build has no terminal for. Neither can be set on a
108+ # legacyPackages attribute after the fact, so this is a second import of
109+ # the same locked nixpkgs rather than a second nixpkgs.
110+ #
111+ # This used to live in `just apk` as a `nix build --impure --expr` with
112+ # `builtins.getFlake "github:NixOS/nixpkgs/nixos-unstable"` inside it —
113+ # which fetched whatever nixos-unstable was that morning, not what
114+ # flake.lock pins, so the SDK under the APK and the nixpkgs under
115+ # everything else were free to drift apart. Here they are the same rev.
116+ androidPkgsFor = system: import nixpkgs {
117+ inherit system;
118+ config = {
119+ allowUnfree = true;
120+ android_sdk.accept_license = true;
121+ };
122+ };
123+
124+ # Only the floor Gradle stands on. It installs build-tools and a platform
125+ # into ANDROID_HOME itself as it goes — see `just apk` for why that means
126+ # a writable copy — so composing more of them here buys nothing.
127+ #
128+ # includeNDK = false deliberately: the app is Dart and path_provider is
129+ # platform channels, so there is no native code to need one, and asking
130+ # for it is a few hundred megabytes and a Gradle fetch of that exact NDK.
131+ androidSdkFor = system:
132+ let android = androidPkgsFor system; in
133+ (android.androidenv.composeAndroidPackages {
134+ cmdLineToolsVersion = "13.0";
135+ buildToolsVersions = [ "34.0.0" ];
136+ platformVersions = [ "35" "34" ];
137+ includeNDK = false;
138+ }).androidsdk;
139+
104 # egui reaches for these with dlopen rather than linking them, so being140 # egui reaches for these with dlopen rather than linking them, so being
105 # in the cdylib's buildInputs is not enough — whatever starts frq has to141 # in the cdylib's buildInputs is not enough — whatever starts frq has to
106 # put them on the loader path itself. Without libx11 here, vidya reports142 # put them on the loader path itself. Without libx11 here, vidya reports
@@ -416,6 +452,12 @@
416 jolt = joltRuntime;452 jolt = joltRuntime;
417 default = frq;453 default = frq;
418 454
455+ # The Android SDK `just apk` copies into flutter/.home. A package
456+ # rather than something the recipe evaluates inline, so that
457+ # `nix build .#android-sdk` is how you pre-warm it and `nix flake
458+ # show` admits it exists.
459+ android-sdk = androidSdkFor pkgs.stdenv.hostPlatform.system;
460+
419 # frq and everything it loads, squashed into one runnable file for461 # frq and everything it loads, squashed into one runnable file for
420 # hosts without Nix. The whole closure rides along — Mesa included,462 # hosts without Nix. The whole closure rides along — Mesa included,
421 # which is not waste: off NixOS the launcher goes through nixGL, and463 # which is not waste: off NixOS the launcher goes through nixGL, and
@@ -539,6 +581,32 @@
539 unset frq_named frq_git frq_near581 unset frq_named frq_git frq_near
540 '';582 '';
541 };583 };
584+
585+ # The APK toolchain, which the default shell deliberately does not
586+ # carry: Flutter brings its own Dart, Gradle and a JDK's worth of
587+ # closure, and a desktop build has no use for any of it.
588+ #
589+ # `just apk` used to name these as `nix shell nixpkgs#clojure
590+ # nixpkgs#jdk17 nixpkgs#flutter`, which is the flake registry's
591+ # nixpkgs and not this flake's so the Flutter under the APK
592+ # floated while everything else was locked. Same three packages,
593+ # from flake.lock now.
594+ #
595+ # JDK 17 and not newer on purpose: the Flutter template's Gradle
596+ # plugin pins a Gradle that rejects a JDK it was released before,
597+ # and the failure reads as an unsupported class file version rather
598+ # than as a version mismatch.
599+ flutter = pkgs.mkShellNoCC {
600+ name = "frq-flutter";
601+ packages = [ pkgs.clojure pkgs.jdk17 pkgs.flutter pkgs.just ];
602+
603+ # Where the recipe copies from. Naming it here is also what makes
604+ # entering the shell build it, so the first `just apk` does not
605+ # stop for a few hundred megabytes of SDK with nothing said about
606+ # why.
607+ FRQ_ANDROID_SDK =
608+ "${androidSdkFor pkgs.stdenv.hostPlatform.system}/libexec/android-sdk";
609+ };
542 });610 });
543 611
544 apps = forEachSystem (pkgs: {612 apps = forEachSystem (pkgs: {
modified flutter/README.md +20 -3
@@ -1,8 +1,8 @@
11 # The ClojureDart half
22
3-Nothing here builds yet. This is the boundary, drawn before the port rather
4-than after it, so that the question "can this file go on the phone?" has a
5-filesystem answer.
3+This is the boundary, drawn before the port rather than after it, so that the
4+question "can this file go on the phone?" has a filesystem answer. It builds:
5+see "Building it" below.
66
77 ## The three trees
88
@@ -90,6 +90,23 @@ toolchain — clojure, a JDK, Flutter, and an SDK composed by androidenv — and
9090 the recipe copies that SDK to `flutter/.home` for Gradle to finish off. That
9191 copy and everything Gradle leaves behind are gitignored.
9292
93+All of it is the flake's, which it did not used to be. The toolchain was
94+`nix shell nixpkgs#clojure nixpkgs#jdk17 nixpkgs#flutter` and the SDK was a
95+`nix build --impure --expr` around `builtins.getFlake
96+"github:NixOS/nixpkgs/nixos-unstable"` — two references to an *unlocked*
97+nixpkgs, so the Flutter that compiled the APK and the nixpkgs under everything
98+else could drift apart without flake.lock changing a line. They are
99+`devShells.<system>.flutter` and `packages.<system>.android-sdk` now, at the
100+pinned rev, and the recipe is `nix develop .#flutter --command` over
101+`nix build .#android-sdk`.
102+
103+The SDK needs `allowUnfree` and `android_sdk.accept_license`, which cannot be
104+set on a `legacyPackages` attribute after the fact — hence `androidPkgsFor` in
105+the flake, a second `import` of the same locked input rather than a second
106+nixpkgs. The Flutter toolchain is kept out of the default dev shell: it brings
107+its own Dart and a JDK's worth of closure, and a desktop build wants none of
108+it.
109+
93110 Two things the Flutter template wanted that are deliberately not here. There is
94111 no `ndkVersion` in `android/app/build.gradle.kts`: setting it makes Gradle
95112 fetch that exact NDK, and there is no native code to need one — the app is
@@ -1,8 +1,8 @@
1 # The ClojureDart half1 # The ClojureDart half
2 2
3-Nothing here builds yet. This is the boundary, drawn before the port rather3+This is the boundary, drawn before the port rather than after it, so that the
4-than after it, so that the question "can this file go on the phone?" has a4+question "can this file go on the phone?" has a filesystem answer. It builds:
5-filesystem answer.5+see "Building it" below.
6 6
7 ## The three trees7 ## The three trees
8 8
@@ -90,6 +90,23 @@ toolchain — clojure, a JDK, Flutter, and an SDK composed by androidenv — and
90 the recipe copies that SDK to `flutter/.home` for Gradle to finish off. That90 the recipe copies that SDK to `flutter/.home` for Gradle to finish off. That
91 copy and everything Gradle leaves behind are gitignored.91 copy and everything Gradle leaves behind are gitignored.
92 92
93+All of it is the flake's, which it did not used to be. The toolchain was
94+`nix shell nixpkgs#clojure nixpkgs#jdk17 nixpkgs#flutter` and the SDK was a
95+`nix build --impure --expr` around `builtins.getFlake
96+"github:NixOS/nixpkgs/nixos-unstable"` — two references to an *unlocked*
97+nixpkgs, so the Flutter that compiled the APK and the nixpkgs under everything
98+else could drift apart without flake.lock changing a line. They are
99+`devShells.<system>.flutter` and `packages.<system>.android-sdk` now, at the
100+pinned rev, and the recipe is `nix develop .#flutter --command` over
101+`nix build .#android-sdk`.
102+
103+The SDK needs `allowUnfree` and `android_sdk.accept_license`, which cannot be
104+set on a `legacyPackages` attribute after the fact — hence `androidPkgsFor` in
105+the flake, a second `import` of the same locked input rather than a second
106+nixpkgs. The Flutter toolchain is kept out of the default dev shell: it brings
107+its own Dart and a JDK's worth of closure, and a desktop build wants none of
108+it.
109+
93 Two things the Flutter template wanted that are deliberately not here. There is110 Two things the Flutter template wanted that are deliberately not here. There is
94 no `ndkVersion` in `android/app/build.gradle.kts`: setting it makes Gradle111 no `ndkVersion` in `android/app/build.gradle.kts`: setting it makes Gradle
95 fetch that exact NDK, and there is no native code to need one — the app is112 fetch that exact NDK, and there is no native code to need one — the app is
modified justfile +9 -3
@@ -72,8 +72,11 @@ apk action="build":
7272 set -euo pipefail
7373 cd "{{justfile_directory()}}/flutter"
7474
75- sdk="$(nix build --impure --no-link --print-out-paths \
76- --expr 'let pkgs = import (builtins.getFlake "github:NixOS/nixpkgs/nixos-unstable") { system = "x86_64-linux"; config = { allowUnfree = true; android_sdk.accept_license = true; }; }; in (pkgs.androidenv.composeAndroidPackages { cmdLineToolsVersion = "13.0"; buildToolsVersions = [ "34.0.0" ]; platformVersions = [ "35" "34" ]; includeNDK = false; }).androidsdk')/libexec/android-sdk"
75+ # The flake's, not an --impure --expr against whatever nixos-unstable is
76+ # today: the licence config the SDK needs lives in `androidPkgsFor` now,
77+ # so this is an ordinary output at the rev flake.lock pins.
78+ sdk="$(nix build --no-link --print-out-paths \
79+ "{{justfile_directory()}}#android-sdk")/libexec/android-sdk"
7780
7881 export HOME="$PWD/.home"
7982 export ANDROID_HOME="$HOME/android-sdk"
@@ -88,7 +91,10 @@ apk action="build":
8891 chmod -R u+w "$ANDROID_HOME"
8992 fi
9093
91- flutter="nix shell nixpkgs#clojure nixpkgs#jdk17 nixpkgs#flutter --command"
94+ # Also the flake's. `nix shell nixpkgs#...` read the registry, which is a
95+ # different and unlocked nixpkgs — the Flutter that built the APK could
96+ # move under it without flake.lock changing a line.
97+ flutter="nix develop {{justfile_directory()}}#flutter --command"
9298
9399 $flutter clojure -M:cljd compile
94100
@@ -72,8 +72,11 @@ apk action="build":
72 set -euo pipefail72 set -euo pipefail
73 cd "{{justfile_directory()}}/flutter"73 cd "{{justfile_directory()}}/flutter"
74 74
75- sdk="$(nix build --impure --no-link --print-out-paths \75+ # The flake's, not an --impure --expr against whatever nixos-unstable is
76- --expr 'let pkgs = import (builtins.getFlake "github:NixOS/nixpkgs/nixos-unstable") { system = "x86_64-linux"; config = { allowUnfree = true; android_sdk.accept_license = true; }; }; in (pkgs.androidenv.composeAndroidPackages { cmdLineToolsVersion = "13.0"; buildToolsVersions = [ "34.0.0" ]; platformVersions = [ "35" "34" ]; includeNDK = false; }).androidsdk')/libexec/android-sdk"76+ # today: the licence config the SDK needs lives in `androidPkgsFor` now,
77+ # so this is an ordinary output at the rev flake.lock pins.
78+ sdk="$(nix build --no-link --print-out-paths \
79+ "{{justfile_directory()}}#android-sdk")/libexec/android-sdk"
77 80
78 export HOME="$PWD/.home"81 export HOME="$PWD/.home"
79 export ANDROID_HOME="$HOME/android-sdk"82 export ANDROID_HOME="$HOME/android-sdk"
@@ -88,7 +91,10 @@ apk action="build":
88 chmod -R u+w "$ANDROID_HOME"91 chmod -R u+w "$ANDROID_HOME"
89 fi92 fi
90 93
91- flutter="nix shell nixpkgs#clojure nixpkgs#jdk17 nixpkgs#flutter --command"94+ # Also the flake's. `nix shell nixpkgs#...` read the registry, which is a
95+ # different and unlocked nixpkgs — the Flutter that built the APK could
96+ # move under it without flake.lock changing a line.
97+ flutter="nix develop {{justfile_directory()}}#flutter --command"
92 98
93 $flutter clojure -M:cljd compile99 $flutter clojure -M:cljd compile
94 100