Repository navigation
Expand file tree
/
Copy pathflake.nix
More file actions
525 lines (516 loc) · 27.3 KB
/
Copy pathflake.nix
File metadata and controls
525 lines (516 loc) · 27.3 KB
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
{
inputs = {
# Fetched from the NixOS channel mirror (*.nixos.org) rather than
# `github:nixos/nixpkgs` so the flake resolves in sandboxes whose GitHub
# access is scoped to the session's own repositories (e.g. Claude Code on
# the web). Channel revisions are what cache.nixos.org has prebuilt, so
# binary-cache hit rates are as good as tracking the release branch head.
nixpkgs.url = "https://channels.nixos.org/nixos-26.05/nixexprs.tar.xz";
};
# `self.submodules = true` deliberately *not* set here. It made every `nix`
# call fetch all twelve submodules with `refs/*:refs/*` -- a full-history
# fetch per submodule, lvgl alone being ~760 MiB of refs -- and only
# `packages.build` ever reads those sources. The dev shell does not: every job
# that enters it (`build`, `ruby-checks`, `wasm`) compiles the checkout in
# $GITHUB_WORKSPACE, not nix's copy of it, so the walk was pure latency there.
# The one consumer that needs them asks per command instead:
#
# nix build '.?submodules=1#build'
#
# See the `flake` job in .github/workflows/build.yml.
outputs =
{ self, nixpkgs }:
let
# Stands in for flake-utils' `eachDefaultSystem`, dropped as an input for
# the same GitHub-scoping reason as nixpkgs above.
forAllSystems = nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
in
{
packages = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
build = pkgs.stdenv.mkDerivation {
name = "rpg-maker-clone";
srcs = [ ./. ];
# bison and gperf regenerate vendored mruby's own compiler
# (mrbgems/mruby-compiler/core/{parse.y,keywords} ->
# y.tab.c/lex.def) whenever a 3rd/mruby patch touches either
# source -- a no-op on an ordinary build, since the checked-in
# generated files are already newer, but load-bearing the moment
# one does (the pinned fork commit (mruby-defined-keyword, docs/patch-fork-tracking.md), the first to).
nativeBuildInputs =
with pkgs;
[
autoconf
automake
bison
ccache
cmake
cmake-format
emscripten
git
gperf
lhasa
libXi
mold
ninja
nixfmt
pre-commit
ruby_4_0
sccache
unar
wget
xvfb
xvfb-run
]
# Software-GL environment for the MZ WebGL backend's headless EGL
# (mruby-mvjs/src/mvgl.cxx). The setup hook exports LIBGL_* /
# __EGL_VENDOR_LIBRARY_FILENAMES / LD_LIBRARY_PATH pointing at
# mesa's llvmpipe + glvnd EGL vendor, so the surfaceless EGL
# context finds a driver on a plain (non-NixOS) CI runner and
# MV::GL.smoke_test runs. Linux-only, like the backend itself.
++ lib.optionals stdenv.hostPlatform.isLinux [ mesa.llvmpipeHook ];
# Vendored verbatim from unicode.org under data/unicode-mappings/
# (see its README.md for the source URLs and hashes).
cp932_table = ./data/unicode-mappings/bestfit932.txt;
jis0208_table = ./data/unicode-mappings/JIS0208.TXT;
buildInputs =
with pkgs;
[
SDL2
SDL2_mixer
]
# libglvnd supplies the <EGL/egl.h> / <GLES2/gl2.h> headers and
# the libEGL / libGLESv2 dispatch libraries the RPG Maker MZ
# renderer needs; the
# off-screen driver comes from mesa at run time via llvmpipeHook
# above. With the headers present mvgl.cxx's __has_include guard
# compiles the real backend instead of the inert stubs, so
# MV::GL.smoke_test renders in CI rather than skipping.
# Linux-only: mvgl stubs itself where the headers are absent.
++ lib.optionals stdenv.hostPlatform.isLinux [ libglvnd ];
# The package build only builds; tests run separately via CTest.
# Prevents nixpkgs' pytest check hook from hijacking the check phase
# (it collects no tests and fails with exit code 5).
doCheck = false;
dontUsePytestCheck = true;
CMAKE_BUILD_TYPE = "RelWithDebInfo";
CMAKE_GENERATOR = "Ninja";
CTEST_OUTPUT_ON_FAILURE = "1";
GLOG_logtostderr = "1";
RUBY_YJIT_ENABLE = "1";
LOCALE_ARCHIVE =
if system == "x86_64-linux" then "${pkgs.glibcLocales}/lib/locale/locale-archive" else null;
shellHook = ''
export CTEST_PARALLEL_LEVEL=$NIX_BUILD_CORES
'';
};
}
// nixpkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
# The pspdev cross-toolchain ($PSPDEV: psp-gcc/binutils/newlib/
# pspsdk headers, psp-cmake and the CreatePBP.cmake plumbing app/psp's
# CMakeLists.txt drives), packaged from upstream's own release tarball.
# nixpkgs has no pspsdk/psptoolchain package (checked the pinned
# channel), so this fills the gap rather than overriding one.
#
# Why the release tarball instead of building psptoolchain from
# source: the toolchain is a chain of autotools cross-builds
# (binutils -> gcc stage 1 -> newlib -> gcc stage 2) that takes hours
# and has never been packaged for nix; upstream attaches a ready-made
# x86_64-linux build to every GitHub release, and the toolchain turns
# out to be fully relocatable -- psp-gcc compiles PSP ELFs straight
# out of an extracted tarball wherever it sits, no baked-in
# /usr/local/pspdev. autoPatchelfHook repoints the host tools'
# interpreters/RPATHs at the nix closure (the bundled MIPS objects
# are left alone; they are not x86_64 ELF).
#
# Version pinning matters beyond reproducibility here: the release
# tag fixes which pspsdk commit ships, and scripts/'
# build_psp_fixup_imports.bash + patches/'
# psp-fixup-imports-jal-relocation-aware.patch pin the *same* commit
# (314b2083, v20260801's "Add AMCTRL_OBJS to libpspkernel_a_LIBADD")
# as the source the patched psp-fixup-imports below is compiled
# from. Bump all three together, and re-check that the stock tool
# the tarball ships still matches the patch's assumptions when you
# do (the patch's preamble says how to tell).
#
# That patched tool replaces the tarball's stock psp-fixup-imports in
# the same postInstall: app/psp/CMakeLists.txt refuses to configure
# against the stock one (it misorders imports so several genuine
# PSP module imports silently resolve wrong at runtime -- see the
# patch preamble), so a devshell whose
# SDK still carried it would fail loudly anyway. Building it inside
# this derivation (rather than asking the shell hook to run the
# script) keeps the sandbox honest: no network fetch of pspsdk at
# shell-entry time, the sources come from the fetchzip pin instead.
pspdev =
let
version = "20260801";
# The take-cheeze/pspsdk fork commit that pins the release
# tarball's pspsdk (314b2083) plus the JAL-relocation-aware
# psp-fixup-imports change (docs/patch-fork-tracking.md).
pspsdkFixupSrc = pkgs.fetchzip {
url = "https://github.com/take-cheeze/pspsdk/archive/723862d4859f5bbfeaab3acf051084a09c71cbfb.tar.gz";
hash = "sha256-7BSI7GR/Kv8gLu/PesE7lpM2nc7yEVjelpZPAPeNNBU=";
};
in
pkgs.stdenv.mkDerivation {
pname = "pspdev";
inherit version;
src = pkgs.fetchurl {
url = "https://github.com/pspdev/pspdev/releases/download/v${version}/pspdev-ubuntu-latest-x86_64.tar.gz";
hash = "sha256-emyy3iG/vB1KoG0Q0KRcVH3tyPbhyPx/+Me6bu5mfuY=";
};
sourceRoot = "pspdev";
nativeBuildInputs = [ pkgs.autoPatchelfHook ];
# Host-side shared objects the tarball's tools link against that
# Ubuntu ships and a nix closure does not. zstd is the big one --
# binutils and cc1/cc1plus are built with
# --enable-compressed-debug-sections, so they all want
# libzstd.so.1; gmp/mpfr/mpc are gcc's own arithmetic; xz/expat/
# ncurses/readline/libusb are psp-gdb and the download tools.
# (libarchive/curl/openssl/gpgme would only serve the bundled
# pacman, which installPhase drops.)
buildInputs = with pkgs; [
stdenv.cc.cc.lib
zlib
zstd
gmp
mpfr
libmpc
xz
expat
ncurses
readline
libusb1
];
dontConfigure = true;
dontBuild = true;
installPhase = ''
# The image bundles pacman (+ its libgpgme dependency) for
# installing psp-packages into the SDK at image build time;
# nothing here consumes it, and nixpkgs' gpgme has a different
# soname than Ubuntu's, so autoPatchelf can never satisfy it.
# Drop the tree rather than pin a library nothing calls.
rm -rf ./share/pacman
cp -r . "$out"
'';
postFixup = ''
# Compile the JAL-relocation-aware psp-fixup-imports from the
# pinned pspsdk fork commit, exactly the steps
# scripts/build/psp_fixup_imports.bash performs on a
# hand-installed SDK (its config.h note applies verbatim:
# tools/types.h includes config.h unguarded).
cp -r "${pspsdkFixupSrc}" ./fixup-src
chmod -R u+w ./fixup-src
cp ${./patches/psp-fixup-imports-config.h} ./fixup-src/config.h
cc -O2 -DHAVE_CONFIG_H \
-I./fixup-src -I./fixup-src/tools \
-o ./fixup-built \
./fixup-src/tools/psp-fixup-imports.c \
./fixup-src/tools/getopt_long.c \
./fixup-src/tools/sha1.c
install -m 755 ./fixup-built "$out/bin/psp-fixup-imports"
'';
};
# The emulator the `psp-smoke` CI job boots the EBOOT under. Based on
# nixpkgs' own package, but the local patch below changes its output
# hash, so it no longer matches what cache.nixos.org has prebuilt for
# the channel this flake pins -- every `nix build '.#ppsspp'` compiles
# PPSSPP from source instead of substituting a closure. That is a
# multi-minute C++ build, so the `psp-smoke` job caches the Nix store
# across runs (the same `cache-nix-action` step every other job in
# `.github/workflows/build.yml` already uses) to avoid paying it on
# every push.
#
# The default (non-Qt) build is the one that carries what the smoke
# test needs: it configures with -DHEADLESS=ON and installs the
# headless binary as `$out/bin/ppsspp-headless`, next to the SDL
# frontend. Its assets are found without a working directory dance --
# the real binary sits in $out/share/ppsspp/bin, and PPSSPP's headless
# entry point walks up from the executable looking for assets/flash0,
# which lands on $out/share/ppsspp/assets one level up.
#
# Linux-only, matching the package's own meta.platforms: forcing this
# attribute on darwin (e.g. `nix flake show`) would otherwise throw.
#
# Carries several local patches on top of nixpkgs' build, all found
# and root-caused while trying to read the PSP EBOOT's own boot log
# under PPSSPP-headless (see
# app/psp/README.md); none are upstreamed to hrydgard/ppsspp yet,
# so all of them are carried by the take-cheeze/ppsspp fork commit
# this flake's own `ppsspp`/`ppsspp-headless` build
# (docs/patch-fork-tracking.md). Each patch's own note on when it is
# safe to drop lives in the fork's commit message.
#
# - sceKernelCreateLwMutex (Core/HLE/sceKernelMutex.cpp)
# dereferences its caller-supplied workarea pointer without
# validating it first, unlike every sibling LwMutex function in
# the same file -- a guest passing workareaPtr=0 turns that
# into a null-pointer write that segfaults the *host* emulator
# process rather than raising a guest-catchable error.
# - The interpreter's mfic/mtic (Core/MIPS/MIPSInt.cpp) are
# no-ops instead of touching the interruptsEnabled flag PPSSPP
# already tracks for the equivalent syscalls, so pspsdk's own
# fast interrupt-disable/enable (built directly on those two
# instructions, used to guard its non-reentrant C-runtime
# state) provides no real protection under PPSSPP.
# - Common/x64Analyzer.cpp's crash-recovery disassembler (used by
# Core/MemFault.cpp when bIgnoreBadMemAccess lets a bad guest
# access be skipped instead of halting) has no case for the
# 0x88/0x8A 8-bit-register MOV opcodes, only their 32/64-bit
# counterparts -- so a bad access from an ordinary byte
# store/load hard-stops emulation instead of being skipped like
# every other access width already is.
# - Four SysclibForKernel imports (strtoul/strncat/memchr/
# tolower) this EBOOT pulls in have no HLE handler at all,
# silently no-opping and leaving register garbage instead of
# doing the real operation.
# - sysclib_memset/sysclib_memmove (also SysclibForKernel) return
# 0 instead of their destination pointer, unlike every sibling
# pointer-returning function in the same file (sysclib_memcpy,
# sysclib_strcat, ...) -- breaking any caller relying on real
# memset()/memmove()'s C-standard return value, including GCC's
# own "memset(p, ...); return p;" idiom optimization, which
# silently becomes "return memset(...)" and returns PPSSPP's
# wrong 0 instead of the real pointer.
# - sysclib_strchr/sysclib_strrchr (SysclibForKernel again) call
# std::string::find(str, c) -- "find this whole string inside
# itself starting at offset c" -- where they meant
# find((char)c). Every lookup therefore returns the string
# itself for c == 0 and NULL for everything else, whatever the
# string holds, and newlib's path splitting on '/' rides on
# exactly these two.
# - Core/HLE/sceIo.cpp's path-taking functions (sceIoDopen,
# sceIoChdir, sceIoRemove, sceIoRmdir) dereference the guest
# pointer WrapU_C hands them without checking it, so a bad
# path builds a std::string from nullptr and terminates the
# host process -- the same class as the LwMutex bug above.
ppsspp = pkgs.ppsspp.overrideAttrs (old: {
# take-cheeze/ppsspp at the v1.20.4 release plus the seven local
# patches (branch rpg-maker-clone/patches-pin; see
# docs/patch-fork-tracking.md). Submodules are needed by the build.
src = pkgs.fetchFromGitHub {
owner = "take-cheeze";
repo = "ppsspp";
rev = "70cb2fb6a951ca77f111ea1f4f346ddea015962b";
fetchSubmodules = true;
hash = "sha256-K05f9673sVb/cb+2W04I3ijTsDMualC7yqU1ZeviwL4=";
};
});
# The Android SDK + NDK the Gradle build (app/android) compiles
# against, assembled by nixpkgs' androidenv from its pinned,
# hash-checked copies of Google's repository XML -- the same
# components the CI android job installs by hand through
# sdkmanager ("ndk;27.2.12479018", "cmake;3.22.1" on the runner's
# preinstalled SDK) and the manual ~/android-sdk the README used to
# ask for. Versions mirror app/android/app/build.gradle's own pins:
# ndkVersion "27.2.12479018", compileSdk 34 (AGP 8.5.2's default
# build-tools 34.0.0) and externalNativeBuild's cmake "3.22.1".
# androidenv lays the NDK out at ndk/<version> and cmake at
# cmake/<version> under the SDK root, exactly where AGP's
# ndkVersion/cmake version lookups expect them, and the accepted
# licenses are baked in so Gradle never stops on an unaccepted
# agreement.
android-sdk =
let
# composeAndroidPackages takes license acceptance from the
# nixpkgs *config* (androidenv's license.nix), not from its own
# arguments, so this composition gets a pkgs instance of its own
# with android_sdk.accept_license set -- the licenses are baked
# into the SDK tree it produces, and Gradle never stops on an
# unaccepted agreement. The SDK components are themselves unfree
# (Google's SDK terms), so allowUnfree rides along. Both are
# scoped to this import: the flake's other outputs keep the
# stock config.
androidPkgs = import nixpkgs {
localSystem = system;
config = {
android_sdk.accept_license = true;
allowUnfree = true;
};
};
in
# composeAndroidPackages returns an attrset of components; the
# flake packages output wants one derivation, so take the
# assembled SDK root (the `androidsdk` member -- cmdline-tools.nix's
# symlink farm with platforms/, build-tools/, ndk/<version>/,
# cmake/<version>/ and the accepted licenses).
(androidPkgs.androidenv.composeAndroidPackages {
platformVersions = [ "34" ];
buildToolsVersions = [ "34.0.0" ];
includeNDK = true;
ndkVersions = [ "27.2.12479018" ];
cmakeVersions = [ "3.22.1" ];
includeEmulator = false;
includeSources = false;
includeSystemImages = false;
}).androidsdk;
}
);
# `devShells.default`, the current flake schema's spelling of what used to
# be the singular `devShell` output. The compiler launcher is dev-only:
# the sandboxed package build has no sccache to hit.
devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
sccache = "${pkgs.sccache}/bin/sccache";
in
{
default = self.packages.${system}.build.overrideAttrs (old: {
CMAKE_C_COMPILER_LAUNCHER = sccache;
CMAKE_CXX_COMPILER_LAUNCHER = sccache;
# wine is a shell-only dependency, kept out of `packages.build`
# above: nothing in the package build runs a Windows binary, so
# putting it there only made `nix build '.#build'` (the `flake` CI
# job) realise a 32-bit wine closure it never opens. It is still
# load-bearing for the shell — `scripts/rtp/install.bash` /
# `rtp_xp_install.bash` install the RPG Maker 2000/XP RTPs by
# running the vendors' own installers under wine, and the engine
# then resolves the RTP through that prefix's registry
# (`rtp_path()` / `xp_rtp_path()` in src/main.cxx) — so the `build`
# CI job, which enters the shell, keeps it. `winetricks` is gone
# entirely: nothing in the tree ever called it.
nativeBuildInputs =
old.nativeBuildInputs
++ pkgs.lib.optionals (system == "x86_64-linux") [
pkgs.winePackages.staging
pkgs.winePackages.fonts
];
# `.envrc`'s `layout python3` puts the project venv's bin/ ahead of
# everything else on PATH, so the venv's `python3` -- a symlink into
# this flake's nix store closure -- is what any `#!/usr/bin/env
# python3` script resolves to for the lifetime of the shell. That
# interpreter's loader is nix's own glibc (a hardcoded nix-store
# path, not /lib64/ld-linux-x86-64.so.2), which never reads the
# host distro's ld.so.cache or /usr/lib -- only its own RPATH
# closure and LD_LIBRARY_PATH. Binary wheels pulled into the venv
# with plain pip (numpy, which piper-tts depends on) expect the
# host's libstdc++/libz to be reachable the normal FHS way and
# fail with "cannot open shared object file" without them, as does
# any *other* `env python3` script picked up while cd'd into this
# directory (e.g. the system's /opt/rocm `amd-smi`, which needs
# libstdc++ the same way). Exporting these two nix-store lib dirs
# on LD_LIBRARY_PATH covers both cases without touching the host
# system.
shellHook = old.shellHook + ''
export LD_LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.zlib}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
'';
});
# Only host tools used by the closed-world bc2cpp gate.
bc2cpp = pkgs.mkShell {
packages = with pkgs; [
ruby_4_0
gcc
clang
bison
gperf
git
curl
python3
pkg-config
];
};
# libclang and its Python bindings, for scripts/native/binding_facts.py
# `nix develop .#clang -c ruby scripts/native/binding_split.rb check`.
# Kept out of the default shell so nothing else pays for the LLVM
# closure. The script also needs 3rd/ and a built mruby include
# directory (RGSS_MRUBY_BUILD_DIR, default build/mruby/host).
clang = pkgs.mkShell {
packages = [
pkgs.ruby_4_0
(pkgs.python3.withPackages (ps: [ ps.libclang ]))
];
LIBCLANG_FILE = "${pkgs.llvmPackages.libclang.lib}/lib/libclang.so";
RUBY_YJIT_ENABLE = "1";
};
# PSP development: everything `psp-cmake`/`cmake --build` needs to
# configure and build app/psp's EBOOT, plus this flake's patched
# PPSSPP headless build for running it (the same binary CI's
# psp-smoke job uses). The toolchain is the packaged upstream
# release above -- no hand-installed ~/dev/pspdev required; the
# patched psp-fixup-imports app/psp/CMakeLists.txt insists on ships
# inside it.
#
# The shell *is* packages.build, overrideAttrs'd like
# devShells.default above, rather than a fresh mkShell re-listing
# the host tools: every addition to the package build's
# environment (cmake/ninja/ruby/autoconf/automake/pre-commit, the
# gperf/bison pair mruby's lexer regeneration needs on a nix
# machine -- the trap PSP-HANDOFF.md's environment notes describe,
# where a stale direnv shell fails mid-regeneration and leaves a
# zero-byte lex.def behind) reaches PSP builds without a second
# copy of the list drifting out of sync. Only what PSP work adds
# beyond the native baseline lives here.
#
# Linux-only: packages.pspdev is an x86_64-linux tarball and
# packages.ppsspp is Linux-only anyway. On darwin, use the default
# devshell plus upstream's macOS release tarball by hand.
psp = self.packages.${system}.build.overrideAttrs (old: {
nativeBuildInputs =
old.nativeBuildInputs
++ [
self.packages.${system}.pspdev
pkgs.pkg-config
]
++ nixpkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [
self.packages.${system}.ppsspp
];
PSPDEV = "${self.packages.${system}.pspdev}";
CMAKE_BUILD_TYPE = "MinSizeRel";
shellHook = old.shellHook + ''
echo "psp shell: PSPDEV=$PSPDEV"
"$PSPDEV/bin/psp-gcc" --version | head -1
echo "build: psp-cmake -S app/psp -B build-psp && cmake --build build-psp"
echo "run: ppsspp-headless --log --graphics=software build-psp/EBOOT.PBP"
'';
});
# The Android build shell, to the `psp` shell what packages.pspdev is
# to the PSP toolchain: the base build environment (cmake/ninja/
# ruby/gperf/bison, the Unicode mapping tables) plus the SDK+NDK
# composition above and a JDK for Gradle, wired through the
# environment variables AGP and gradlew actually read -- ANDROID_HOME
# for the SDK root (AGP finds ndk/<version> and cmake/<version>
# inside it itself), ANDROID_NDK_HOME for anything that wants the NDK
# directly, JAVA_HOME so ./gradlew runs on this JDK instead of
# prompting a toolchain download. Gradle itself stays the wrapper
# (gradlew pins the distribution; first run fetches it).
#
# Linux-only, like the psp shell: CI builds the APK on ubuntu, and
# that is the configuration this composition is pinned and tested
# against. On darwin use the default devshell plus a hand-installed
# SDK (app/android/README.md keeps the manual recipe).
android = nixpkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux (
self.packages.${system}.build.overrideAttrs (old: {
nativeBuildInputs = old.nativeBuildInputs ++ [
self.packages.${system}.android-sdk
pkgs.jdk21
];
# The SDK root is the derivation's libexec/android-sdk tree
# (deployAndroidPackage's layout) -- that is where platforms/,
# build-tools/, ndk/<version>/ and cmake/<version>/ live, and
# what AGP walks for its ndkVersion / cmake version lookups.
ANDROID_HOME = "${self.packages.${system}.android-sdk}/libexec/android-sdk";
ANDROID_NDK_HOME = "${self.packages.${system}.android-sdk}/libexec/android-sdk/ndk/27.2.12479018";
JAVA_HOME = "${pkgs.jdk21.home}";
shellHook = old.shellHook + ''
echo "android shell: ANDROID_HOME=$ANDROID_HOME"
java -version 2>&1 | head -1
echo "build: (cd app/android && ./gradlew :app:assembleDebug)"
echo "install: adb install -r app/android/app/build/outputs/apk/debug/app-debug.apk"
'';
})
);
}
);
};
}