Files
games/docs/development/006-ANDROID_RUST_NATIVE_BUILD.md
2026-09-21 14:10:30 +02:00

133 lines
6.4 KiB
Markdown

<!-- file: docs/development/006-ANDROID_RUST_NATIVE_BUILD.md -->
<!-- version: 7 -->
# Build Rust Android natif
## Contrat SDL
`SDLActivity` charge `SDL3`, puis `game_android_entrypoint`, et invoque le symbole `SDL_main`.
Le crate Rust `game-android-entrypoint` est un `cdylib`. Il est compilé avec exactement une feature métier lors du packaging d'une application :
- `reflex` ;
- `snake`.
Le même nom de bibliothèque native peut être utilisé dans les deux APK puisque chaque module Android possède son propre ensemble de `jniLibs` générés.
## SDL3 AAR et Prefab
L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab. Pour chaque ABI, la logique Gradle commune cherche d'abord :
```text
prefab/modules/SDL3/libs/android.<abi>/libSDL3.so
```
Elle accepte aussi `jni/<abi>/libSDL3.so`, puis un fallback Prefab univoque. Pour chaque ABI elle :
1. extrait `libSDL3.so` depuis l'AAR dans un répertoire temporaire de linkage ;
2. stage `libSDL3.so` dans les `jniLibs` générés du variant ;
3. lance `cargo ndk` avec exactement une feature jeu ;
4. stage `libgame_android_entrypoint.so` dans le même arbre généré ;
5. vérifie la présence des deux bibliothèques avant de rendre l'output au variant AGP.
Les `jniLibs` générés sont déclarés à la Variant API via `variant.sources.jniLibs.addGeneratedSourceDirectory`. Gradle possède ainsi la dépendance entre le packaging Android et le build natif au lieu d'exiger une commande préalable séparée.
## Toolchain
Le contrat projet utilise :
```text
AGP 9.4.0
Gradle minimum 9.6.0
JDK runtime >= 17, fourni par l'environnement
Java source/target 17
compileSdk 36
minSdk 21
NDK 28.2.13676358 / r28c
SDL3 AAR 3.4.16
cargo-ndk requis côté développeur
```
`Android/settings.gradle` compare `GradleVersion.current()` au minimum projet `9.6.0`. Le projet n'épingle donc pas une distribution Gradle : un Gradle local plus récent est accepté tant qu'il satisfait le minimum.
Le projet Android natif n'impose pas `JAVA_HOME`. Le JDK courant du shell/IDE exécute Gradle ; `java -version` et `gradle --version` sont relevés pendant les gates. Le niveau de bytecode/source Java de la glue reste explicitement `17`, indépendamment du JDK de build.
`cargo-ndk` détecte le NDK désigné pour le sous-processus via `ANDROID_NDK_HOME`. La tâche Gradle résout cette valeur à partir de `ANDROID_HOME` ou `ANDROID_SDK_ROOT` et de `androidNdkVersion`; aucun `ANDROID_NDK_HOME` global n'est requis.
## État 0.3.3-0-pre.5
`0-pre.4` a validé les quatre ABI sur Snake et Reflex : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`. Le même mécanisme Gradle est désormais enregistré pour toutes les variantes Android, et pas seulement Debug.
Pour Debug :
```text
:<game>:assembleDebug
-> buildDebugSasedevRust<Abi>
-> cargo ndk build
-> APK Debug universal
```
Pour Release/AAB :
```text
:<game>:bundleRelease
-> buildReleaseSasedevRust<Abi>
-> cargo ndk build --release
-> AAB Release multi-ABI
```
Chaque arbre généré contient :
```text
build/generated/sasedevNative/<variant>/<abi>/jniLibs/<abi>/
├── libSDL3.so
└── libgame_android_entrypoint.so
```
Depuis la racine :
```bash
java -version
(cd Android && gradle --version)
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
(cd Android && gradle :game-snake-poc:bundleRelease)
(cd Android && gradle :game-reflex-poc:bundleRelease)
```
Le builder historique `scripts/build_android_rust.py` est retiré : il n'existe plus de chemin natif Android qui stage des bibliothèques dans `src/main/jniLibs`. Les sorties natives sont exclusivement générées sous `build/`.
### Pages mémoire 16 KB
La validation porte sur les deux dimensions requises pour les appareils 64 bits concernés par la contrainte Google Play 16 KB :
1. segments ELF des `.so` `arm64-v8a` et `x86_64` avec un alignement `LOAD` au moins `2**14`, contrôlés par `llvm-objdump -p` depuis le NDK configuré ;
2. alignement ZIP de l'APK avec `zipalign -c -P 16 -v 4`.
AGP `9.4.0` est supérieur au plancher `8.5.1` documenté pour le packaging 16 KB et NDK `r28c` produit les nouvelles bibliothèques 64 bits avec alignement 16 KB par défaut. Cette propriété ne dispense pas de contrôler `libSDL3.so`, qui est précompilée dans l'AAR.
Les ABI 32 bits `armeabi-v7a` et `x86` restent dans le package pour compatibilité ancienne et peuvent conserver des segments ELF `2**12` ; cela ne constitue pas un échec de l'exigence Play 16 KB, explicitement portée sur les appareils 64 bits.
Pour l'AAB, les huit bibliothèques doivent être présentes sous `base/lib/<abi>/`. Lorsque `bundletool` est disponible, `bundletool dump config --bundle=<aab>` peut compléter la vérification du mode d'alignement du bundle.
La preuve runtime utilise une image Android 15/API 35 x86_64 `ps16k` et retient `adb shell getconf PAGE_SIZE` comme contrôle de la taille de page du device. `16384` est le résultat attendu ; une valeur `KernelPageSize` lue dans un VMA particulier de `/proc/<pid>/smaps` n'est pas utilisée comme substitut à ce contrôle.
### Plancher Android
Le contrat est consolidé à Android 5.0 / API 21 :
- `minSdk 21` dans chaque application ;
- `sasedevRustAndroidApi = 21` pour `cargo ndk` ;
- SDL3 documente API 21 comme minimum Android ;
- la gate `0-pre.5` a validé un smoke réel sur Android 5.0/API 21 x86 : installation de l'APK universal et démarrage de `SnakeActivity` réussis.
## Smoke appareil
L'APK universal doit être inspecté mécaniquement avant installation. Il doit contenir les deux bibliothèques natives pour chacune des quatre ABI : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
Le smoke réel reste effectué sur les architectures matériellement disponibles : AVD x86_64 et appareil ARM64. Les ABI 32 bits sont néanmoins exigées au build et dans l'APK ; leur exécution réelle dépend de la disponibilité d'un appareil/AVD 32 bits. Le `minSdk 21` est consolidé en `0-pre.5`; les smokes disponibles servent de preuve runtime complémentaire et ne redéfinissent pas ce plancher.
## Exception FFI Rust
Rust 2024 exige un attribut unsafe pour imposer un nom de symbole d'export. Le projet autorise donc exclusivement `#[unsafe(export_name = "SDL_main")]` dans `game-android-entrypoint`. Cette exception ne permet ni bloc `unsafe`, ni fonction `unsafe`, ni déréférencement des pointeurs `argc/argv`.