0.3.3-0-pre.3
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: Android/README.md -->
|
<!-- file: Android/README.md -->
|
||||||
<!-- version: 9 -->
|
<!-- version: 11 -->
|
||||||
|
|
||||||
# Android
|
# Android
|
||||||
|
|
||||||
Le frontend Android est un projet Gradle multi-module séparé du workspace Cargo.
|
Le frontend Android natif est un projet Gradle multi-module séparé du workspace Cargo.
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
@@ -20,24 +20,43 @@ Chaque module `game-*` est une application Android indépendante ; il dépend du
|
|||||||
|
|
||||||
## Toolchain de référence
|
## Toolchain de référence
|
||||||
|
|
||||||
La baseline stable `0.3.1` conserve :
|
Le pipeline Android natif `0.3.3` utilise :
|
||||||
|
|
||||||
- Android Gradle Plugin `9.4.0` ;
|
- Android Gradle Plugin `9.4.0` ;
|
||||||
- Gradle `9.6.0` attendu par AGP 9.4 ;
|
- Gradle `>= 9.6.0`, résolu depuis l'environnement de développement ;
|
||||||
- JDK `17` ;
|
- JDK `>= 17` pour exécuter Gradle/AGP, sans `JAVA_HOME` imposé par le projet ;
|
||||||
|
- niveau Java source/target `17` pour la glue Android ;
|
||||||
- `compileSdk 36` ;
|
- `compileSdk 36` ;
|
||||||
- `targetSdk 36` ;
|
- `targetSdk 36` ;
|
||||||
- `minSdk 21` ;
|
- `minSdk 21` ;
|
||||||
- NDK `28.2.13676358` (`r28c`) ;
|
- NDK `28.2.13676358` (`r28c`) ;
|
||||||
- SDL3 Android AAR `3.4.16`.
|
- SDL3 Android AAR `3.4.16`.
|
||||||
|
|
||||||
Le chemin historique compile déjà `game-android-entrypoint` via `cargo-ndk` et `scripts/build_android_rust.py`. `0.3.3` réactive ce produit Android natif afin de transférer cette orchestration dans Gradle, sans changer les responsabilités Rust/SDL3/Java/JNI.
|
`Android/settings.gradle` déclare `minimumGradleVersion = 9.6.0` et refuse explicitement une version plus ancienne. Une version système plus récente reste autorisée et devient la version réellement utilisée pour le build.
|
||||||
|
|
||||||
|
Le JDK n'est pas épinglé par le projet Android natif. Le shell/IDE fournit le JDK courant ; la gate doit afficher `java -version` et `gradle --version` afin de consigner la combinaison réellement validée. Le POC Tauri Android historique conserve ses propres contraintes de JDK et de wrapper sous sa crate ; elles ne pilotent pas le chemin SDL3 natif.
|
||||||
|
|
||||||
|
## Commandes Gradle
|
||||||
|
|
||||||
|
Depuis la racine du dépôt :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -version
|
||||||
|
(cd Android && gradle --version)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le build natif normal utilise directement le Gradle de l'environnement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||||
|
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le dépôt ne versionne pas de Gradle Wrapper pour le projet Android natif. Le minimum est un contrat de compatibilité, pas une version exacte imposée à toutes les machines.
|
||||||
|
|
||||||
## SDL3 AAR
|
## SDL3 AAR
|
||||||
|
|
||||||
L'archive n'est pas vendorizée dans le dépôt.
|
L'archive n'est pas vendorizée dans le dépôt. Placer :
|
||||||
|
|
||||||
Placer :
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SDL3-3.4.16.aar
|
SDL3-3.4.16.aar
|
||||||
@@ -49,32 +68,34 @@ dans :
|
|||||||
Android/libs/
|
Android/libs/
|
||||||
```
|
```
|
||||||
|
|
||||||
Le module `common` consomme cet artefact et `SaseGameActivity` étend désormais `org.libsdl.app.SDLActivity`.
|
Le module `common` compile contre cet artefact et `SaseGameActivity` étend `org.libsdl.app.SDLActivity`.
|
||||||
|
|
||||||
## Limite volontaire de ce jalon
|
|
||||||
|
|
||||||
Les APK peuvent être compilés côté Java/SDL3 une fois l'AAR disponible, mais ne disposent pas encore du `cdylib` Rust contenant le point d'entrée de jeu.
|
|
||||||
|
|
||||||
Le lancement fonctionnel sur appareil/émulateur appartient donc au delta suivant.
|
|
||||||
|
|
||||||
## Rust Android natif
|
## Rust Android natif
|
||||||
|
|
||||||
Le crate `game-android-entrypoint` produit `libgame_android_entrypoint.so`. Il est compilé séparément pour chaque jeu avec une feature Cargo et copié dans le `jniLibs` du module Android concerné.
|
Le crate `game-android-entrypoint` produit `libgame_android_entrypoint.so`. `Android/gradle/sasedev-rust-android.gradle` transfère au graphe Gradle les responsabilités historiques d'extraction SDL3, de résolution NDK, de `cargo ndk`, de sélection de feature et de staging `jniLibs`.
|
||||||
|
|
||||||
Pour `arm64-v8a` :
|
À `0.3.3-0-pre.3`, Snake et Reflex utilisent tous deux le même pipeline commun avec deux ABI :
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
rustup target add aarch64-linux-android
|
arm64-v8a
|
||||||
cargo install cargo-ndk
|
x86_64
|
||||||
python3 ../scripts/build_android_rust.py reflex
|
|
||||||
python3 ../scripts/build_android_rust.py snake
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Le script extrait temporairement `libSDL3.so` de l'AAR uniquement pour fournir le chemin de linkage à `rustc`; l'AAR reste responsable du packaging SDL3 dans l'APK.
|
Chaque `assembleDebug` doit déclencher, pour chaque ABI, un build `cargo ndk` avec exactement la feature du module concerné. Les outputs sont placés sous :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/<game>/build/generated/sasedevNative/debug/<abi>/jniLibs/<abi>/
|
||||||
|
├── libSDL3.so
|
||||||
|
└── libgame_android_entrypoint.so
|
||||||
|
```
|
||||||
|
|
||||||
|
AGP fusionne ces sources `jniLibs` générées dans un APK Debug universal contenant les deux architectures. Aucune copie durable n'est faite dans `src/main/jniLibs`.
|
||||||
|
|
||||||
|
`scripts/build_android_rust.py` reste temporairement comme chemin historique jusqu'au jalon de fermeture `0-pre.4`; il ne fait plus partie du chemin normal `assembleDebug` de Snake ou Reflex.
|
||||||
|
|
||||||
## Bridge JNI et tactile
|
## Bridge JNI et tactile
|
||||||
|
|
||||||
À partir de `0.1.0-0-pre.9`, `SaseGameActivity` vérifie au démarrage la version du bridge Java/JNI chargé depuis la bibliothèque Rust.
|
`SaseGameActivity` vérifie au démarrage la version du bridge Java/JNI chargé depuis la bibliothèque Rust.
|
||||||
|
|
||||||
Les touch events standards restent traités via SDL3 et ne transitent pas par JNI.
|
Les touch events standards restent traités via SDL3 et ne transitent pas par JNI.
|
||||||
|
|
||||||
@@ -97,6 +118,6 @@ Aucune ressource source n'est copiée durablement dans un module Android ou une
|
|||||||
|
|
||||||
`Android/gradle.properties` définit `androidNdkVersion=28.2.13676358`.
|
`Android/gradle.properties` définit `androidNdkVersion=28.2.13676358`.
|
||||||
|
|
||||||
Gradle utilise cette valeur comme `ndkVersion`. Le script Rust Android résout la même version sous `${ANDROID_HOME}/ndk/` et définit `ANDROID_NDK_HOME` uniquement pour le sous-processus `cargo ndk`.
|
Gradle utilise cette valeur comme `ndkVersion`. La tâche Rust Android résout la même version sous `${ANDROID_HOME}/ndk/` ou `${ANDROID_SDK_ROOT}/ndk/` et définit `ANDROID_NDK_HOME` uniquement pour le sous-processus `cargo ndk`.
|
||||||
|
|
||||||
Aucun `ANDROID_NDK_HOME` global n'est requis ; plusieurs NDK peuvent rester installés côte à côte.
|
Aucun `ANDROID_NDK_HOME` global n'est requis ; plusieurs NDK peuvent rester installés côte à côte.
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
// file: Android/game-reflex-poc/build.gradle
|
// file: Android/game-reflex-poc/build.gradle
|
||||||
// version: 45
|
// version: 46
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext.sasedevRustGameFeature = "reflex"
|
||||||
|
ext.sasedevRustAndroidAbis = ["arm64-v8a", "x86_64"]
|
||||||
|
ext.sasedevRustAndroidApi = 21
|
||||||
|
|
||||||
def sdl3AarName = providers.gradleProperty("sdl3AarName").get()
|
def sdl3AarName = providers.gradleProperty("sdl3AarName").get()
|
||||||
def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
||||||
|
|
||||||
@@ -19,6 +23,10 @@ android {
|
|||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 2
|
versionCode 2
|
||||||
versionName '0.2.0'
|
versionName '0.2.0'
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -34,3 +42,4 @@ dependencies {
|
|||||||
|
|
||||||
ext.sasedevGameAssetDirectory = "game-reflex-poc"
|
ext.sasedevGameAssetDirectory = "game-reflex-poc"
|
||||||
apply from: rootProject.file("gradle/sasedev-assets.gradle")
|
apply from: rootProject.file("gradle/sasedev-assets.gradle")
|
||||||
|
apply from: rootProject.file("gradle/sasedev-rust-android.gradle")
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
// file: Android/game-snake-poc/build.gradle
|
// file: Android/game-snake-poc/build.gradle
|
||||||
// version: 45
|
// version: 47
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext.sasedevRustGameFeature = "snake"
|
||||||
|
ext.sasedevRustAndroidAbis = ["arm64-v8a", "x86_64"]
|
||||||
|
ext.sasedevRustAndroidApi = 21
|
||||||
|
|
||||||
def sdl3AarName = providers.gradleProperty("sdl3AarName").get()
|
def sdl3AarName = providers.gradleProperty("sdl3AarName").get()
|
||||||
def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
||||||
|
|
||||||
@@ -19,6 +23,10 @@ android {
|
|||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 2
|
versionCode 2
|
||||||
versionName '0.2.0'
|
versionName '0.2.0'
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -34,3 +42,4 @@ dependencies {
|
|||||||
|
|
||||||
ext.sasedevGameAssetDirectory = "game-snake-poc"
|
ext.sasedevGameAssetDirectory = "game-snake-poc"
|
||||||
apply from: rootProject.file("gradle/sasedev-assets.gradle")
|
apply from: rootProject.file("gradle/sasedev-assets.gradle")
|
||||||
|
apply from: rootProject.file("gradle/sasedev-rust-android.gradle")
|
||||||
|
|||||||
192
Android/gradle/sasedev-rust-android.gradle
Normal file
192
Android/gradle/sasedev-rust-android.gradle
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
// file: Android/gradle/sasedev-rust-android.gradle
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import javax.inject.Inject
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
|
||||||
|
abstract class BuildSasedevRustAndroidTask extends DefaultTask {
|
||||||
|
@InputFile
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getSdl3Aar()
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract ConfigurableFileCollection getRustInputs()
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract Property<String> getGameFeature()
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract Property<String> getAbi()
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract Property<Integer> getAndroidApi()
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract Property<String> getNdkVersion()
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
abstract DirectoryProperty getRepositoryRoot()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getOutputDirectory()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ExecOperations getExecOperations()
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void buildNative() {
|
||||||
|
def selectedAbi = abi.get()
|
||||||
|
def selectedFeature = gameFeature.get()
|
||||||
|
def aar = sdl3Aar.get().asFile
|
||||||
|
def repository = repositoryRoot.get().asFile
|
||||||
|
def output = outputDirectory.get().asFile
|
||||||
|
def runtimeDirectory = new File(output, selectedAbi)
|
||||||
|
def linkDirectory = new File(temporaryDir, "link")
|
||||||
|
|
||||||
|
project.delete(output)
|
||||||
|
project.delete(linkDirectory)
|
||||||
|
runtimeDirectory.mkdirs()
|
||||||
|
linkDirectory.mkdirs()
|
||||||
|
|
||||||
|
byte[] sdl3Payload
|
||||||
|
new ZipFile(aar).withCloseable { archive ->
|
||||||
|
def candidates = [
|
||||||
|
"prefab/modules/SDL3/libs/android.${selectedAbi}/libSDL3.so",
|
||||||
|
"jni/${selectedAbi}/libSDL3.so",
|
||||||
|
]
|
||||||
|
def entry = candidates.collect { candidate -> archive.getEntry(candidate) }.find { candidate -> candidate != null }
|
||||||
|
if (entry == null) {
|
||||||
|
def suffix = "/libs/android.${selectedAbi}/libSDL3.so"
|
||||||
|
def fallback = Collections.list(archive.entries()).findAll { candidate ->
|
||||||
|
!candidate.directory && candidate.name.endsWith(suffix)
|
||||||
|
}
|
||||||
|
if (fallback.size() != 1) {
|
||||||
|
throw new GradleException(
|
||||||
|
"SDL3 shared library for ABI ${selectedAbi} is missing from ${aar}; " +
|
||||||
|
"expected Prefab member ${candidates[0]}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
entry = fallback[0]
|
||||||
|
}
|
||||||
|
sdl3Payload = archive.getInputStream(entry).withCloseable { stream -> stream.bytes }
|
||||||
|
}
|
||||||
|
|
||||||
|
def linkLibrary = new File(linkDirectory, "libSDL3.so")
|
||||||
|
def runtimeLibrary = new File(runtimeDirectory, "libSDL3.so")
|
||||||
|
linkLibrary.bytes = sdl3Payload
|
||||||
|
runtimeLibrary.bytes = sdl3Payload
|
||||||
|
|
||||||
|
def androidHome = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
|
||||||
|
if (!androidHome) {
|
||||||
|
throw new GradleException("ANDROID_HOME or ANDROID_SDK_ROOT is required to resolve the project NDK")
|
||||||
|
}
|
||||||
|
def configuredNdk = new File(androidHome, "ndk/${ndkVersion.get()}")
|
||||||
|
if (!configuredNdk.isDirectory()) {
|
||||||
|
throw new GradleException("configured Android NDK is missing: ${configuredNdk}")
|
||||||
|
}
|
||||||
|
|
||||||
|
def inheritedRustFlags = System.getenv("RUSTFLAGS")?.trim()
|
||||||
|
def linkFlag = "-Lnative=${linkDirectory}"
|
||||||
|
def rustFlags = inheritedRustFlags ? "${inheritedRustFlags} ${linkFlag}" : linkFlag
|
||||||
|
|
||||||
|
execOperations.exec { spec ->
|
||||||
|
spec.workingDir(repository)
|
||||||
|
spec.environment("ANDROID_NDK_HOME", configuredNdk.absolutePath)
|
||||||
|
spec.environment("CARGO_NDK_PLATFORM", androidApi.get().toString())
|
||||||
|
spec.environment("RUSTFLAGS", rustFlags)
|
||||||
|
spec.commandLine(
|
||||||
|
"cargo",
|
||||||
|
"ndk",
|
||||||
|
"-t",
|
||||||
|
selectedAbi,
|
||||||
|
"-o",
|
||||||
|
output.absolutePath,
|
||||||
|
"build",
|
||||||
|
"-p",
|
||||||
|
"game-android-entrypoint",
|
||||||
|
"--no-default-features",
|
||||||
|
"--features",
|
||||||
|
selectedFeature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def rustLibrary = new File(runtimeDirectory, "libgame_android_entrypoint.so")
|
||||||
|
if (!runtimeLibrary.isFile()) {
|
||||||
|
throw new GradleException("missing staged SDL3 Android library: ${runtimeLibrary}")
|
||||||
|
}
|
||||||
|
if (!rustLibrary.isFile()) {
|
||||||
|
throw new GradleException("missing Rust Android library after cargo-ndk build: ${rustLibrary}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!project.ext.has("sasedevRustGameFeature")) {
|
||||||
|
throw new GradleException("sasedevRustGameFeature must select exactly one Cargo game feature")
|
||||||
|
}
|
||||||
|
if (!project.ext.has("sasedevRustAndroidAbis")) {
|
||||||
|
throw new GradleException("sasedevRustAndroidAbis must declare at least one Android ABI")
|
||||||
|
}
|
||||||
|
if (!project.ext.has("sasedevRustAndroidApi")) {
|
||||||
|
throw new GradleException("sasedevRustAndroidApi must declare the Rust Android API level")
|
||||||
|
}
|
||||||
|
|
||||||
|
def selectedGameFeature = project.ext.sasedevRustGameFeature as String
|
||||||
|
def selectedAbis = (project.ext.sasedevRustAndroidAbis as List).collect { configuredAbi -> configuredAbi as String }
|
||||||
|
def selectedAndroidApi = project.ext.sasedevRustAndroidApi as Integer
|
||||||
|
def configuredSdl3AarName = providers.gradleProperty("sdl3AarName").get()
|
||||||
|
def configuredNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
||||||
|
def repositoryDirectory = rootProject.projectDir.parentFile
|
||||||
|
|
||||||
|
def supportedAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"] as Set
|
||||||
|
if (selectedAbis.isEmpty()) {
|
||||||
|
throw new GradleException("sasedevRustAndroidAbis must not be empty")
|
||||||
|
}
|
||||||
|
if (selectedAbis.toSet().size() != selectedAbis.size()) {
|
||||||
|
throw new GradleException("sasedevRustAndroidAbis must not contain duplicates: ${selectedAbis}")
|
||||||
|
}
|
||||||
|
def unsupportedAbis = selectedAbis.findAll { configuredAbi -> !supportedAbis.contains(configuredAbi) }
|
||||||
|
if (!unsupportedAbis.isEmpty()) {
|
||||||
|
throw new GradleException("unsupported Rust Android ABI(s): ${unsupportedAbis.join(', ')}")
|
||||||
|
}
|
||||||
|
|
||||||
|
androidComponents {
|
||||||
|
onVariants(selector().withBuildType("debug")) { variant ->
|
||||||
|
selectedAbis.each { selectedAbi ->
|
||||||
|
def variantSuffix = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1)
|
||||||
|
def abiSuffix = selectedAbi
|
||||||
|
.split(/[^A-Za-z0-9]+/)
|
||||||
|
.findAll { segment -> !segment.isEmpty() }
|
||||||
|
.collect { segment -> segment.substring(0, 1).toUpperCase() + segment.substring(1) }
|
||||||
|
.join("")
|
||||||
|
def nativeTask = tasks.register(
|
||||||
|
"build${variantSuffix}SasedevRust${abiSuffix}",
|
||||||
|
BuildSasedevRustAndroidTask,
|
||||||
|
) {
|
||||||
|
sdl3Aar.set(rootProject.layout.projectDirectory.file("libs/${configuredSdl3AarName}"))
|
||||||
|
rustInputs.from(rootProject.file("../Cargo.toml"))
|
||||||
|
rustInputs.from(rootProject.file("../.cargo/config.toml"))
|
||||||
|
rustInputs.from(rootProject.fileTree("../crates") {
|
||||||
|
include "**/*.rs"
|
||||||
|
include "**/Cargo.toml"
|
||||||
|
include "**/build.rs"
|
||||||
|
})
|
||||||
|
gameFeature.set(selectedGameFeature)
|
||||||
|
abi.set(selectedAbi)
|
||||||
|
androidApi.set(selectedAndroidApi)
|
||||||
|
ndkVersion.set(configuredNdkVersion)
|
||||||
|
repositoryRoot.set(repositoryDirectory)
|
||||||
|
outputDirectory.set(layout.buildDirectory.dir("generated/sasedevNative/${variant.name}/${selectedAbi}/jniLibs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant.sources.jniLibs == null) {
|
||||||
|
throw new GradleException("AGP variant ${variant.name} does not expose jniLibs sources")
|
||||||
|
}
|
||||||
|
variant.sources.jniLibs.addGeneratedSourceDirectory(nativeTask) { task ->
|
||||||
|
task.outputDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
// file: Android/settings.gradle
|
// file: Android/settings.gradle
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
|
import org.gradle.util.GradleVersion
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
@@ -9,6 +11,14 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def minimumGradleVersion = GradleVersion.version("9.6.0")
|
||||||
|
def currentGradleVersion = GradleVersion.current()
|
||||||
|
if (currentGradleVersion < minimumGradleVersion) {
|
||||||
|
throw new GradleException(
|
||||||
|
"Gradle ${minimumGradleVersion.version} or newer is required; current version is ${currentGradleVersion.version}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 81
|
# version: 82
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
@@ -21,7 +21,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.3-0-pre.1"
|
version = "0.3.3-0-pre.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/games"
|
repository = "https://git.sasedev.com/Sasedev/games"
|
||||||
|
|||||||
180
deltas/0.3.3/0-pre.2.md
Normal file
180
deltas/0.3.3/0-pre.2.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<!-- file: deltas/0.3.3/0-pre.2.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.3-0-pre.2
|
||||||
|
|
||||||
|
## Base et statut précédent
|
||||||
|
|
||||||
|
Cette tranche part exclusivement de `0.3.3-0-pre.1` validé par l'utilisateur le 2026-09-21. `history/0.3.3/0-pre.1.md` enregistre la gate propre et l'inventaire Android réellement observé avant ouverture du développement productif.
|
||||||
|
|
||||||
|
Aucun fix de `0-pre.1` n'est requis.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Prouver sur Snake Debug et une seule ABI que Gradle possède désormais la chaîne native complète :
|
||||||
|
|
||||||
|
```text
|
||||||
|
:game-snake-poc:assembleDebug
|
||||||
|
-> source jniLibs générée par AGP Variant API
|
||||||
|
-> extraction de libSDL3.so depuis l'AAR
|
||||||
|
-> résolution du NDK projet r28c
|
||||||
|
-> cargo ndk arm64-v8a
|
||||||
|
-> feature snake uniquement
|
||||||
|
-> libSDL3.so + libgame_android_entrypoint.so
|
||||||
|
-> APK Debug ARM64
|
||||||
|
```
|
||||||
|
|
||||||
|
La commande normale ne dépend plus d'un appel préalable à `scripts/build_android_rust.py snake`.
|
||||||
|
|
||||||
|
## Gradle Wrapper reproductible
|
||||||
|
|
||||||
|
Le projet Android reçoit le wrapper Gradle et l'épingle à `9.6.0`, version attendue par AGP `9.4.0` dans le cadrage `0-pre.1`.
|
||||||
|
|
||||||
|
Contrats de chaîne d'approvisionnement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gradle 9.6.0 binary ZIP SHA-256
|
||||||
|
bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
|
||||||
|
|
||||||
|
Gradle Wrapper JAR SHA-256
|
||||||
|
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
|
||||||
|
```
|
||||||
|
|
||||||
|
`validateDistributionUrl=true` reste activé. L'audit de distribution vérifie statiquement ces contrats.
|
||||||
|
|
||||||
|
Le wrapper respecte `JAVA_HOME`; l'environnement utilisateur validé expose JDK 17 via cette variable même si `java` dans le `PATH` pointe vers JDK 25.
|
||||||
|
|
||||||
|
## Ownership Gradle/Cargo
|
||||||
|
|
||||||
|
`Android/gradle/sasedev-rust-android.gradle` introduit une tâche native incrémentale par ABI. Ses inputs comprennent l'AAR SDL3, les manifests/configurations Cargo utiles, le code Rust, la feature, l'ABI, l'API Android et la version NDK. Son output est un répertoire `jniLibs` généré sous `build/generated/sasedevNative/...`.
|
||||||
|
|
||||||
|
La tâche :
|
||||||
|
|
||||||
|
1. extrait `libSDL3.so` depuis Prefab ou le fallback AAR compatible ;
|
||||||
|
2. résout `${ANDROID_HOME}/ndk/28.2.13676358` ou l'équivalent `ANDROID_SDK_ROOT` ;
|
||||||
|
3. définit `ANDROID_NDK_HOME`, `CARGO_NDK_PLATFORM=21` et le chemin de linkage SDL3 uniquement pour le sous-processus ;
|
||||||
|
4. lance `cargo ndk` sans defaults avec la seule feature Snake ;
|
||||||
|
5. vérifie les deux `.so` attendus ;
|
||||||
|
6. remet l'output à `variant.sources.jniLibs.addGeneratedSourceDirectory`, qui crée la dépendance de build AGP.
|
||||||
|
|
||||||
|
Aucun nouvel orchestrateur Python ou shell spécifique au projet n'est introduit.
|
||||||
|
|
||||||
|
## Borne mono-ABI
|
||||||
|
|
||||||
|
`game-snake-poc` fixe volontairement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature snake
|
||||||
|
ABI arm64-v8a
|
||||||
|
API Rust 21
|
||||||
|
```
|
||||||
|
|
||||||
|
et `ndk.abiFilters 'arm64-v8a'` empêche ce jalon de masquer une erreur de task wiring derrière le packaging d'autres ABI. `x86_64` reste pour `0-pre.3` après validation réelle de cette preuve ARM64.
|
||||||
|
|
||||||
|
Reflex n'est pas modifié dans cette tranche.
|
||||||
|
|
||||||
|
## Audit de distribution
|
||||||
|
|
||||||
|
`scripts/audit_distribution_layout.py` exige désormais les cinq nouveaux éléments durables du pipeline :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/gradlew
|
||||||
|
Android/gradlew.bat
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.properties
|
||||||
|
Android/gradle/sasedev-rust-android.gradle
|
||||||
|
```
|
||||||
|
|
||||||
|
Il contrôle également le checksum du JAR wrapper, la distribution 9.6.0, l'absence de délégation Python dans le nouveau build natif et le contrat mono-ABI Snake.
|
||||||
|
|
||||||
|
## Fichiers
|
||||||
|
|
||||||
|
Ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/gradlew
|
||||||
|
Android/gradlew.bat
|
||||||
|
Android/gradle/sasedev-rust-android.gradle
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.properties
|
||||||
|
deltas/0.3.3/0-pre.2.md
|
||||||
|
history/0.3.3/0-pre.1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifiés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/README.md
|
||||||
|
Android/game-snake-poc/build.gradle
|
||||||
|
Cargo.toml
|
||||||
|
docs/development/006-ANDROID_RUST_NATIVE_BUILD.md
|
||||||
|
scripts/audit_distribution_layout.py
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/build_android_rust.py`, Reflex, les sources Rust, `CHANGELOG.md` et `ROADMAP.md` restent inchangés.
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
Le générateur exécute uniquement les audits statiques autorisés après constitution du delta :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
|
||||||
|
python3 scripts/audit_distribution_layout.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun build Cargo/Gradle, test ou smoke final n'est attribué au générateur.
|
||||||
|
|
||||||
|
## Validation utilisateur demandée
|
||||||
|
|
||||||
|
Depuis la racine :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
|
||||||
|
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
|
||||||
|
python3 scripts/audit_distribution_layout.py
|
||||||
|
|
||||||
|
cargo fmt --all
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
sha256sum Android/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
grep -E '^(distributionUrl|distributionSha256Sum|validateDistributionUrl)=' Android/gradle/wrapper/gradle-wrapper.properties
|
||||||
|
|
||||||
|
(cd Android && ./gradlew --version)
|
||||||
|
(cd Android && ./gradlew :game-snake-poc:assembleDebug)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le build Snake doit être lancé directement : ne pas appeler `scripts/build_android_rust.py snake` avant Gradle.
|
||||||
|
|
||||||
|
Puis inspecter l'APK produit :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
APK="$(find Android/game-snake-poc/build/outputs/apk/debug -maxdepth 1 -type f -name '*.apk' -print -quit)"
|
||||||
|
test -n "$APK"
|
||||||
|
printf 'APK=%s\n' "$APK"
|
||||||
|
unzip -Z1 "$APK" | grep '^lib/' | sort
|
||||||
|
unzip -Z1 "$APK" | grep -Fx 'lib/arm64-v8a/libSDL3.so'
|
||||||
|
unzip -Z1 "$APK" | grep -Fx 'lib/arm64-v8a/libgame_android_entrypoint.so'
|
||||||
|
if unzip -Z1 "$APK" | grep -Eq '^lib/(x86_64|armeabi-v7a|x86)/'; then
|
||||||
|
printf '%s\n' 'unexpected non-ARM64 native library in mono-ABI proof' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Le checksum attendu du wrapper JAR est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun smoke appareil n'est requis dans cette tranche ; il reste planifié après établissement du packaging multi-ABI.
|
||||||
|
|
||||||
|
## Suite après validation
|
||||||
|
|
||||||
|
Si cette gate est propre, `0-pre.3` crée `history/0.3.3/0-pre.2.md`, étend le même ownership à `arm64-v8a + x86_64`, exige un APK Debug universal contenant les deux couples de bibliothèques et migre Reflex uniquement si la factorisation commune reste naturelle.
|
||||||
|
|
||||||
|
Une erreur de wrapper, de task wiring, de linkage SDL3, de feature Cargo ou de contenu APK produit d'abord `0-pre.2.fix.N`.
|
||||||
4
deltas/0.3.3/0-pre.3.delete.txt
Normal file
4
deltas/0.3.3/0-pre.3.delete.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Android/gradlew
|
||||||
|
Android/gradlew.bat
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.properties
|
||||||
253
deltas/0.3.3/0-pre.3.md
Normal file
253
deltas/0.3.3/0-pre.3.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
<!-- file: deltas/0.3.3/0-pre.3.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.3-0-pre.3
|
||||||
|
|
||||||
|
## Base et statut précédent
|
||||||
|
|
||||||
|
Cette tranche part exclusivement de `0.3.3-0-pre.2`, validé par l'utilisateur le 2026-09-21. `history/0.3.3/0-pre.2.md` enregistre la gate complète, le build Gradle réussi et l'APK Snake ARM64 inspecté.
|
||||||
|
|
||||||
|
Aucun fix de `0-pre.2` n'est requis.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Cette tranche traite ensemble les deux décisions prévues avant l'AAB :
|
||||||
|
|
||||||
|
1. passer d'une version Gradle/JDK imposée pour la preuve à un contrat de minimum compatible avec l'environnement ;
|
||||||
|
2. élargir le pipeline Gradle/Cargo natif à `arm64-v8a + x86_64` et produire des APK Debug universal.
|
||||||
|
|
||||||
|
Snake reste le jeu-sonde principal. Reflex devient le second consommateur de la logique commune puisque sa migration ne demande qu'un mapping de feature et d'ABI, sans nouvelle architecture.
|
||||||
|
|
||||||
|
## Politique Gradle minimale
|
||||||
|
|
||||||
|
AGP `9.4.0` conserve Gradle `9.6.0` comme minimum. Le projet déclare désormais dans `Android/settings.gradle` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
minimumGradleVersion = 9.6.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script compare ce minimum à `GradleVersion.current()` et échoue explicitement si le Gradle système est trop ancien.
|
||||||
|
|
||||||
|
Le projet Android natif ne versionne plus de Gradle Wrapper. Une machine avec Gradle `9.6.0`, `9.7.1` ou une version ultérieure compatible utilise directement sa propre version. La version réellement employée est consignée par la gate avec :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd Android && gradle --version)
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette décision concerne uniquement `Android/`. Les wrappers générés ou contraintes historiques propres au POC Tauri restent locaux à la crate Tauri et ne sont pas réinterprétés.
|
||||||
|
|
||||||
|
## Politique JDK
|
||||||
|
|
||||||
|
Aucun `export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` n'est imposé au chemin SDL3 natif.
|
||||||
|
|
||||||
|
Gradle/AGP utilisent le JDK fourni normalement par le shell ou l'IDE. Le minimum de la chaîne reste Java 17, tandis que la gate doit vérifier le JDK réellement sélectionné. Le JDK 25 installé dans l'environnement utilisateur est donc un candidat normal pour cette validation.
|
||||||
|
|
||||||
|
La glue Java Android conserve volontairement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
sourceCompatibility = 17
|
||||||
|
targetCompatibility = 17
|
||||||
|
```
|
||||||
|
|
||||||
|
Le JDK de build et le niveau Java de l'application restent deux contrats distincts.
|
||||||
|
|
||||||
|
## Multi-ABI Snake
|
||||||
|
|
||||||
|
`game-snake-poc` déclare maintenant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature snake
|
||||||
|
ABI arm64-v8a + x86_64
|
||||||
|
API Rust 21
|
||||||
|
```
|
||||||
|
|
||||||
|
et :
|
||||||
|
|
||||||
|
```text
|
||||||
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
```
|
||||||
|
|
||||||
|
La logique commune `sasedev-rust-android.gradle`, déjà prouvée en mono-ABI, enregistre une tâche Rust par ABI et remet les deux répertoires `jniLibs` générés à AGP. L'APK Debug attendu est universal, sans split ABI par défaut.
|
||||||
|
|
||||||
|
## Reflex devient second consommateur
|
||||||
|
|
||||||
|
`game-reflex-poc` reçoit uniquement la configuration symétrique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature reflex
|
||||||
|
ABI arm64-v8a + x86_64
|
||||||
|
API Rust 21
|
||||||
|
```
|
||||||
|
|
||||||
|
puis applique la même logique Gradle commune. Aucun nouveau script, type de tâche, bridge JNI ou chemin d'output n'est introduit.
|
||||||
|
|
||||||
|
Cette symétrie confirme que la factorisation appartient bien à `Android/gradle/sasedev-rust-android.gradle` et non aux modules de jeu.
|
||||||
|
|
||||||
|
## Suppression du wrapper natif
|
||||||
|
|
||||||
|
Un ZIP delta n'efface pas les fichiers d'un jalon précédent. Le manifest :
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.3/0-pre.3.delete.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
supprime :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/gradlew
|
||||||
|
Android/gradlew.bat
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.properties
|
||||||
|
```
|
||||||
|
|
||||||
|
Après extraction du delta à la racine du dépôt, appliquer :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
while IFS= read -r path; do
|
||||||
|
rm -f -- "$path"
|
||||||
|
done < deltas/0.3.3/0-pre.3.delete.txt
|
||||||
|
rmdir --ignore-fail-on-non-empty Android/gradle/wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
L'audit de distribution traite désormais ces quatre fichiers comme interdits dans le projet Android natif afin d'éviter un retour involontaire à une version Gradle épinglée.
|
||||||
|
|
||||||
|
## Règles et documentation
|
||||||
|
|
||||||
|
Les règles de commandes Android utilisent désormais `gradle` depuis `Android/` et exigent le minimum déclaré par le projet. `CMD-051` devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(cd Android && gradle :<app>:assembleDebug)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plan `0.3.3`, la documentation Android et le document de build natif distinguent explicitement :
|
||||||
|
|
||||||
|
- Gradle système `>= 9.6.0` ;
|
||||||
|
- JDK runtime fourni par l'environnement ;
|
||||||
|
- Java source/target 17 ;
|
||||||
|
- contraintes Tauri historiques séparées ;
|
||||||
|
- deux ABI natives productives de cette tranche.
|
||||||
|
|
||||||
|
## Fichiers
|
||||||
|
|
||||||
|
Ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.3/0-pre.3.md
|
||||||
|
deltas/0.3.3/0-pre.3.delete.txt
|
||||||
|
history/0.3.3/0-pre.2.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifiés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/README.md
|
||||||
|
Android/game-reflex-poc/build.gradle
|
||||||
|
Android/game-snake-poc/build.gradle
|
||||||
|
Android/settings.gradle
|
||||||
|
Cargo.toml
|
||||||
|
docs/development/006-ANDROID_RUST_NATIVE_BUILD.md
|
||||||
|
docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md
|
||||||
|
docs/rules/RULES_COMMANDS.md
|
||||||
|
docs/rules/RULES_VALIDATION_MATRIX.md
|
||||||
|
scripts/audit_distribution_layout.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Supprimés par manifest :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/gradlew
|
||||||
|
Android/gradlew.bat
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
Android/gradle/wrapper/gradle-wrapper.properties
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/build_android_rust.py`, le code Rust, les manifests Android, `CHANGELOG.md` et `ROADMAP.md` restent inchangés.
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
Le générateur exécute uniquement les audits statiques autorisés après constitution du delta :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
|
||||||
|
python3 scripts/audit_distribution_layout.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun build Cargo/Gradle, test ou smoke final n'est attribué au générateur.
|
||||||
|
|
||||||
|
## Validation utilisateur demandée
|
||||||
|
|
||||||
|
Exécuter la gate depuis un shell normal, sans ajouter l'ancien `export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -version
|
||||||
|
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
|
||||||
|
(cd Android && gradle --version)
|
||||||
|
|
||||||
|
cargo fmt --all
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
|
||||||
|
python3 scripts/audit_distribution_layout.py
|
||||||
|
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||||
|
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||||
|
```
|
||||||
|
|
||||||
|
La sortie `gradle --version` doit montrer une version `>= 9.6.0` et le JDK effectivement fourni par votre environnement. Une version supérieure au minimum n'est pas une divergence.
|
||||||
|
|
||||||
|
## Inspection des APK universal
|
||||||
|
|
||||||
|
Pour chaque jeu :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for game in game-snake-poc game-reflex-poc; do
|
||||||
|
APK="$(find "Android/${game}/build/outputs/apk/debug" -maxdepth 1 -type f -name '*.apk' -print -quit)"
|
||||||
|
test -n "$APK"
|
||||||
|
printf 'APK=%s\n' "$APK"
|
||||||
|
unzip -Z1 "$APK" | grep '^lib/' | sort
|
||||||
|
|
||||||
|
for abi in arm64-v8a x86_64; do
|
||||||
|
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libSDL3.so"
|
||||||
|
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libgame_android_entrypoint.so"
|
||||||
|
done
|
||||||
|
|
||||||
|
if unzip -Z1 "$APK" | grep -Eq '^lib/(armeabi-v7a|x86)/'; then
|
||||||
|
printf '%s\n' "unexpected 32-bit native library in ${game} universal APK" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
L'absence de split est démontrée par un seul APK Debug par module contenant simultanément les bibliothèques ARM64 et x86_64.
|
||||||
|
|
||||||
|
## Smoke multi-architecture Snake
|
||||||
|
|
||||||
|
Après inspection mécanique, installer le même APK Snake sur l'AVD x86_64 puis sur l'appareil ARM64 réel.
|
||||||
|
|
||||||
|
AVD connu :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
APK="Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk"
|
||||||
|
adb -s emulator-5554 install -r "$APK"
|
||||||
|
adb -s emulator-5554 shell am start -n com.sasedev.games.snake/.SnakeActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis sur l'appareil réel en remplaçant `<arm64-serial>` par le serial affiché par `adb devices -l` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb -s <arm64-serial> install -r "$APK"
|
||||||
|
adb -s <arm64-serial> shell am start -n com.sasedev.games.snake/.SnakeActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
Le smoke attendu est borné : lancement sans erreur de chargement JNI/SDL3 et affichage du jeu. La validation gameplay exhaustive reste hors de ce jalon.
|
||||||
|
|
||||||
|
## Suite après validation
|
||||||
|
|
||||||
|
Si cette gate est propre, `0-pre.4` ouvre le chemin AAB, la vérification 16 KB, la validation du plancher Android disponible et la fermeture de `scripts/build_android_rust.py` si aucune responsabilité n'y subsiste.
|
||||||
|
|
||||||
|
Toute erreur de minimum Gradle, exécution JDK, task wiring x86_64, linkage SDL3, packaging universal ou activation Reflex produit d'abord `0-pre.3.fix.1`.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/development/006-ANDROID_RUST_NATIVE_BUILD.md -->
|
<!-- file: docs/development/006-ANDROID_RUST_NATIVE_BUILD.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 4 -->
|
||||||
|
|
||||||
# Build Rust Android natif
|
# Build Rust Android natif
|
||||||
|
|
||||||
@@ -12,95 +12,95 @@ Le crate Rust `game-android-entrypoint` est un `cdylib`. Il est compilé avec ex
|
|||||||
- `reflex` ;
|
- `reflex` ;
|
||||||
- `snake`.
|
- `snake`.
|
||||||
|
|
||||||
Le même nom de bibliothèque native peut être utilisé dans les deux APK puisque chaque module Android possède son propre répertoire `jniLibs`.
|
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
|
## SDL3 AAR et Prefab
|
||||||
|
|
||||||
L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab.
|
L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab. Pour chaque ABI, la logique Gradle commune cherche d'abord :
|
||||||
|
|
||||||
Pour `arm64-v8a`, la structure attendue est notamment :
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
prefab/modules/SDL3/libs/android.arm64-v8a/libSDL3.so
|
prefab/modules/SDL3/libs/android.<abi>/libSDL3.so
|
||||||
```
|
```
|
||||||
|
|
||||||
Le script `scripts/build_android_rust.py` accepte cette structure officielle et conserve un fallback `jni/<abi>/libSDL3.so` pour compatibilité.
|
Elle accepte aussi `jni/<abi>/libSDL3.so`, puis un fallback Prefab univoque. Pour chaque ABI elle :
|
||||||
|
|
||||||
Pour chaque application, le script :
|
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.
|
||||||
|
|
||||||
1. extrait `libSDL3.so` depuis l'AAR ;
|
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.
|
||||||
2. l'utilise comme bibliothèque de liaison pour le build Rust ;
|
|
||||||
3. copie `libSDL3.so` dans le `jniLibs/<abi>/` du module Android ;
|
|
||||||
4. lance `cargo ndk` ;
|
|
||||||
5. vérifie la présence de `libgame_android_entrypoint.so`.
|
|
||||||
|
|
||||||
Ainsi, `SDLActivity` dispose au runtime de `libSDL3.so` et de la bibliothèque Rust applicative.
|
|
||||||
|
|
||||||
## Toolchain
|
## Toolchain
|
||||||
|
|
||||||
SDL Android requiert actuellement SDK 35 ou ultérieur, NDK r28c ou ultérieur et API minimale 21. La baseline projet conserve `compileSdk 36`, `minSdk 21` et l'AAR SDL3 3.4.16.
|
Le contrat projet utilise :
|
||||||
|
|
||||||
Installer au minimum :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rustup target add aarch64-linux-android
|
|
||||||
cargo install cargo-ndk
|
|
||||||
```
|
|
||||||
|
|
||||||
NDK de référence :
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
r28c = 28.2.13676358
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
Avec le SDK manager :
|
`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.
|
||||||
|
|
||||||
```bash
|
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.
|
||||||
"${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "ndk;28.2.13676358" "platform-tools"
|
|
||||||
|
`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.3
|
||||||
|
|
||||||
|
La preuve mono-ABI de `0-pre.2` a validé que `assembleDebug` déclenche correctement le build Rust/SDL3 ARM64 sans orchestrateur Python préalable.
|
||||||
|
|
||||||
|
`0-pre.3` élargit ce même graphe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
arm64-v8a
|
||||||
|
x86_64
|
||||||
```
|
```
|
||||||
|
|
||||||
`cargo-ndk` détecte un NDK installé par Android Studio ou utilise `ANDROID_NDK_HOME` lorsqu'il est défini.
|
pour Snake et Reflex :
|
||||||
|
|
||||||
Pour utiliser directement `adb` :
|
```text
|
||||||
|
:<game>:assembleDebug
|
||||||
```bash
|
-> buildDebugSasedevRustArm64V8a
|
||||||
export PATH="${ANDROID_HOME}/platform-tools:${PATH}"
|
-> buildDebugSasedevRustX8664
|
||||||
|
-> deux sources jniLibs générées
|
||||||
|
-> APK Debug universal
|
||||||
```
|
```
|
||||||
|
|
||||||
Cet export peut être ajouté au fichier de configuration shell local de la machine.
|
Chaque arbre ABI contient :
|
||||||
|
|
||||||
## Build
|
```text
|
||||||
|
build/generated/sasedevNative/debug/<abi>/jniLibs/<abi>/
|
||||||
|
├── libSDL3.so
|
||||||
|
└── libgame_android_entrypoint.so
|
||||||
|
```
|
||||||
|
|
||||||
Depuis la racine :
|
Depuis la racine :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 scripts/build_android_rust.py reflex
|
java -version
|
||||||
python3 scripts/build_android_rust.py snake
|
(cd Android && gradle --version)
|
||||||
|
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||||
|
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||||
```
|
```
|
||||||
|
|
||||||
Puis :
|
Il ne faut pas appeler `scripts/build_android_rust.py` avant ces tâches. Le script Python historique reste temporairement versionné jusqu'à la fermeture complète du chemin natif en `0-pre.4`.
|
||||||
|
|
||||||
```bash
|
Les outputs natifs restent sous `Android/<module>/build/` et ne sont jamais versionnés.
|
||||||
cd Android
|
|
||||||
gradle :game-reflex-poc:assembleDebug
|
|
||||||
gradle :game-snake-poc:assembleDebug
|
|
||||||
cd ..
|
|
||||||
```
|
|
||||||
|
|
||||||
Les bibliothèques générées sous `Android/*/src/main/jniLibs/` sont des artefacts de build et ne sont pas versionnées.
|
|
||||||
|
|
||||||
## Smoke appareil
|
## Smoke appareil
|
||||||
|
|
||||||
Préflight :
|
Le premier APK universal doit être inspecté mécaniquement avant installation. Il doit contenir exactement les couples attendus pour `arm64-v8a` et `x86_64`, sans dépendre de `armeabi-v7a` ou `x86`.
|
||||||
|
|
||||||
```bash
|
Le plan prévoit ensuite un smoke sur l'AVD x86_64 disponible et sur l'appareil ARM64 réel. Ces smokes attestent le packaging multi-ABI ; ils ne changent pas le `minSdk`, qui reste un axe séparé et sera consolidé avec l'AAB/compatibilité dans `0-pre.4`.
|
||||||
adb version
|
|
||||||
adb devices
|
|
||||||
```
|
|
||||||
|
|
||||||
Puis installation/lancement des APK si un appareil ou émulateur est disponible.
|
|
||||||
|
|
||||||
L'absence de `adb` ou d'appareil n'est pas un défaut fonctionnel du projet ; elle bloque uniquement le smoke appareil.
|
|
||||||
|
|
||||||
## Exception FFI Rust
|
## Exception FFI Rust
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md -->
|
<!-- file: docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Plan 0.3.3 — Android SDL3 natif multi-ABI et packaging Gradle
|
# Plan 0.3.3 — Android SDL3 natif multi-ABI et packaging Gradle
|
||||||
|
|
||||||
@@ -36,8 +36,8 @@ Les décisions de cadrage `0-pre.1` sont :
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
AGP 9.4.0
|
AGP 9.4.0
|
||||||
Gradle 9.6.0 à épingler par wrapper lors de l'implémentation
|
Gradle >= 9.6.0, version système acceptée si supérieure
|
||||||
JDK 17
|
JDK >= 17, fourni par l'environnement ; JDK 25 candidat validable
|
||||||
compileSdk 36
|
compileSdk 36
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
minSdk candidat 21
|
minSdk candidat 21
|
||||||
@@ -52,6 +52,16 @@ splits ABI hors chemin par défaut
|
|||||||
|
|
||||||
Le `minSdk 21` est fondé sur le plancher Android actuellement documenté par SDL3, pas sur la seule valeur historique du dépôt. Le `targetSdk 36` est maintenu pour la politique Google Play actuelle.
|
Le `minSdk 21` est fondé sur le plancher Android actuellement documenté par SDL3, pas sur la seule valeur historique du dépôt. Le `targetSdk 36` est maintenu pour la politique Google Play actuelle.
|
||||||
|
|
||||||
|
### Révision de toolchain après `0-pre.2`
|
||||||
|
|
||||||
|
La preuve mono-ABI a réussi avec le wrapper Gradle `9.6.0` et JDK 17. Après cette validation, la décision projet est de ne pas transformer ces valeurs de preuve en versions exactes imposées au chemin SDL3 natif :
|
||||||
|
|
||||||
|
- AGP `9.4.0` conserve Gradle `9.6.0` comme minimum ;
|
||||||
|
- un Gradle système plus récent est autorisé et doit être affiché dans la gate ;
|
||||||
|
- le JDK runtime doit satisfaire Gradle/AGP (`>= 17`) mais provient de l'environnement ;
|
||||||
|
- le niveau Java de la glue Android reste `sourceCompatibility/targetCompatibility = 17` ;
|
||||||
|
- les contraintes JDK/Gradle du POC Tauri restent locales à ce POC et ne pilotent pas Android SDL3 natif.
|
||||||
|
|
||||||
Le pipeline devra aussi vérifier la compatibilité 16 KB des bibliothèques natives finales. AGP 9.4 et NDK r28c fournissent le socle requis, mais le prébuild SDL3 doit être contrôlé dans l'APK/AAB produit.
|
Le pipeline devra aussi vérifier la compatibilité 16 KB des bibliothèques natives finales. AGP 9.4 et NDK r28c fournissent le socle requis, mais le prébuild SDL3 doit être contrôlé dans l'APK/AAB produit.
|
||||||
|
|
||||||
## Ownership cible
|
## Ownership cible
|
||||||
@@ -85,7 +95,7 @@ La future logique commune doit couvrir au minimum :
|
|||||||
11. diagnostics explicites si outil, target, AAR, NDK ou output manque ;
|
11. diagnostics explicites si outil, target, AAR, NDK ou output manque ;
|
||||||
12. absence de secret de signature dans le dépôt.
|
12. absence de secret de signature dans le dépôt.
|
||||||
|
|
||||||
Le projet Android natif doit acquérir un Gradle Wrapper épinglé à la version compatible AGP afin que `CMD-051` devienne réellement reproductible sans dépendre d'un Gradle global.
|
Le projet Android natif déclare `minimumGradleVersion=9.6.0` et refuse au chargement une version inférieure. Le Gradle réellement utilisé est celui de l'environnement ; une version plus récente que le minimum est autorisée. Le projet n'épingle pas de wrapper natif.
|
||||||
|
|
||||||
## ABI et compatibilité OS
|
## ABI et compatibilité OS
|
||||||
|
|
||||||
@@ -134,15 +144,15 @@ Aucune réécriture Gradle productive dans cette tranche.
|
|||||||
|
|
||||||
### `0-pre.2` — ownership Gradle/Cargo sur Snake mono-ABI
|
### `0-pre.2` — ownership Gradle/Cargo sur Snake mono-ABI
|
||||||
|
|
||||||
Introduire le Gradle Wrapper compatible AGP 9.4 et la logique Gradle commune minimale. Faire de `:game-snake-poc:assembleDebug` le propriétaire du build Rust `arm64-v8a`, de l'extraction/linkage SDL3 et des `jniLibs` générés, sans appel préalable au script Python.
|
Introduire la logique Gradle commune minimale et prouver que `:game-snake-poc:assembleDebug` possède le build Rust `arm64-v8a`, l'extraction/linkage SDL3 et les `jniLibs` générés, sans appel préalable au script Python. Cette preuve a initialement utilisé un wrapper Gradle 9.6.0 afin d'isoler le task wiring.
|
||||||
|
|
||||||
Le delta doit rester borné à une ABI pour valider l'ordre des tâches, les inputs/outputs, le mapping de feature et les diagnostics avant de multiplier les dimensions.
|
La gate utilisateur du 2026-09-21 a validé ce chemin. Après cette preuve, la politique toolchain a été révisée : le projet Android natif utilise désormais un Gradle système `>= 9.6.0` et le JDK courant de l'environnement au lieu d'épinger Gradle/JDK pour ce chemin.
|
||||||
|
|
||||||
### `0-pre.3` — multi-ABI et APK universal
|
### `0-pre.3` — toolchain minimale, multi-ABI et APK universal
|
||||||
|
|
||||||
Étendre la logique commune à `arm64-v8a + x86_64`, produire un APK Debug universal Snake et vérifier mécaniquement les deux couples `libSDL3.so`/`libgame_android_entrypoint.so`.
|
Retirer le wrapper natif introduit pour la preuve `0-pre.2`, déclarer et contrôler `Gradle >= 9.6.0`, ne plus imposer `JAVA_HOME`, puis étendre la logique commune à `arm64-v8a + x86_64`. Produire un APK Debug universal Snake et vérifier mécaniquement les deux couples `libSDL3.so`/`libgame_android_entrypoint.so`.
|
||||||
|
|
||||||
Ajouter Reflex comme second consommateur dans cette tranche si, et seulement si, la factorisation commune réduit son activation à une configuration de module sans nouvelle architecture.
|
La factorisation commune étant réduite au mapping `module -> feature`, ajouter Reflex comme second consommateur avec les mêmes deux ABI, sans nouvelle architecture.
|
||||||
|
|
||||||
### `0-pre.4` — AAB, minSdk et fermeture du chemin historique
|
### `0-pre.4` — AAB, minSdk et fermeture du chemin historique
|
||||||
|
|
||||||
@@ -185,12 +195,11 @@ adb version
|
|||||||
adb devices -l
|
adb devices -l
|
||||||
emulator -list-avds
|
emulator -list-avds
|
||||||
(cd Android && gradle --version)
|
(cd Android && gradle --version)
|
||||||
printf 'configured NDK: '; sed -n 's/^androidNdkVersion=//p' Android/gradle.properties
|
printf 'configured NDK: '; sed -n 's/^androidNdkVersion=//p' ls -ld "$ANDROID_HOME/ndk/28.2.13676358"
|
||||||
ls -ld "$ANDROID_HOME/ndk/28.2.13676358"
|
|
||||||
test -f Android/libs/SDL3-3.4.16.aar && sha256sum Android/libs/SDL3-3.4.16.aar
|
test -f Android/libs/SDL3-3.4.16.aar && sha256sum Android/libs/SDL3-3.4.16.aar
|
||||||
```
|
```
|
||||||
|
|
||||||
L'absence du Gradle global n'est pas destinée à devenir une dépendance durable : `0-pre.2` doit introduire le wrapper. En revanche, l'inventaire demandé par le prompt `004` doit relever l'état actuel avant cette transition.
|
La politique a été révisée après validation de `0-pre.2` : le projet Android natif dépend volontairement d'un Gradle fourni par l'environnement, avec minimum `9.6.0` déclaré et contrôlé par `settings.gradle`. Il n'impose plus de wrapper ni de `JAVA_HOME`; les gates consignent les versions réellement utilisées.
|
||||||
|
|
||||||
## Critères d'entrée en RC
|
## Critères d'entrée en RC
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_COMMANDS.md -->
|
<!-- file: docs/rules/RULES_COMMANDS.md -->
|
||||||
<!-- version: 14 -->
|
<!-- version: 15 -->
|
||||||
|
|
||||||
# Règles d'exécution des commandes
|
# Règles d'exécution des commandes
|
||||||
|
|
||||||
@@ -48,11 +48,11 @@
|
|||||||
|
|
||||||
## Android et Gradle
|
## Android et Gradle
|
||||||
|
|
||||||
- **CMD-ANDROID-001** — Les commandes Gradle Android sont exécutées depuis `Android/` ou avec un chemin explicite vers le wrapper du projet.
|
- **CMD-ANDROID-001** — Les commandes Gradle du projet Android natif sont exécutées depuis `Android/` avec le `gradle` résolu par l’environnement ; sa version doit être au moins égale à `minimumGradleVersion` déclaré par le projet.
|
||||||
- **CMD-ANDROID-002** — Les tâches ciblées par application sont préférées, par exemple `./gradlew :game-reflex-poc:assembleDebug`, lorsqu'elles existent.
|
- **CMD-ANDROID-002** — Les tâches ciblées par application sont préférées, par exemple `gradle :game-reflex-poc:assembleDebug`, lorsqu'elles existent.
|
||||||
- **CMD-ANDROID-003** — Un build Android global n'est pas exécuté si la tranche ne touche ni Android ni le contrat natif utilisé par Android.
|
- **CMD-ANDROID-003** — Un build Android global n'est pas exécuté si la tranche ne touche ni Android ni le contrat natif utilisé par Android.
|
||||||
- **CMD-ANDROID-004** — `gradle clean` ou `./gradlew clean` reste un nettoyage Android ciblé ; il n'est pas rendu obligatoire uniquement parce qu'un `cargo clean` est planifié.
|
- **CMD-ANDROID-004** — `gradle clean` reste un nettoyage Android ciblé ; il n'est pas rendu obligatoire uniquement parce qu'un `cargo clean` est planifié.
|
||||||
- **CMD-ANDROID-005** — Les commandes Android réelles ne deviennent des gates qu'après introduction du wrapper Gradle, de l'AGP, du NDK, de SDL3 et des modules exécutables correspondants.
|
- **CMD-ANDROID-005** — Les commandes Android réelles ne deviennent des gates qu'après déclaration d'un minimum Gradle contrôlé, de l'AGP, du NDK, de SDL3 et des modules exécutables correspondants.
|
||||||
|
|
||||||
## Web
|
## Web
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_VALIDATION_MATRIX.md -->
|
<!-- file: docs/rules/RULES_VALIDATION_MATRIX.md -->
|
||||||
<!-- version: 5 -->
|
<!-- version: 6 -->
|
||||||
|
|
||||||
# Matrice normative des commandes et validations
|
# Matrice normative des commandes et validations
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ Les commandes ciblées restent la norme pendant l'implémentation ; les gates wo
|
|||||||
| `CMD-046` | `(cd <tauri-app> && cargo tauri android dev)` | gates Rust/frontend applicables | smoke interactif Tauri Android | `pre`/beta |
|
| `CMD-046` | `(cd <tauri-app> && cargo tauri android dev)` | gates Rust/frontend applicables | smoke interactif Tauri Android | `pre`/beta |
|
||||||
| `CMD-047` | `(cd <tauri-app> && cargo tauri android build)` | gates Rust/frontend applicables | packaging Tauri Android final/prefinal | beta/RC |
|
| `CMD-047` | `(cd <tauri-app> && cargo tauri android build)` | gates Rust/frontend applicables | packaging Tauri Android final/prefinal | beta/RC |
|
||||||
| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta |
|
| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta |
|
||||||
| `CMD-051` | `(cd Android && ./gradlew :<app>:assembleDebug)` | `CMD-050` si Rust natif change | Android/app/manifest/Java touché | pre/beta |
|
| `CMD-051` | `(cd Android && gradle :<app>:assembleDebug)` | `CMD-050` si Rust natif change | Android/app/manifest/Java touché | pre/beta |
|
||||||
| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta |
|
| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta |
|
||||||
| `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC |
|
| `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC |
|
||||||
| `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance |
|
| `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance |
|
||||||
|
|||||||
51
history/0.3.3/0-pre.1.md
Normal file
51
history/0.3.3/0-pre.1.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!-- file: history/0.3.3/0-pre.1.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Historique 0.3.3-0-pre.1
|
||||||
|
|
||||||
|
## Statut
|
||||||
|
|
||||||
|
Le cadrage `0.3.3-0-pre.1` a été validé par l'utilisateur le 2026-09-21. Les audits, le formatage, le check workspace et Clippy strict passent ; aucun `0-pre.1.fix.N` n'est requis avant l'ouverture de `0-pre.2`.
|
||||||
|
|
||||||
|
## Gate utilisateur
|
||||||
|
|
||||||
|
La gate demandée dans le delta a été exécutée avec succès :
|
||||||
|
|
||||||
|
```text
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
games.sasedev workspace audit: clean
|
||||||
|
Markdown table audit: clean (5 table(s), 233 file(s))
|
||||||
|
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
|
||||||
|
cargo fmt --all: clean
|
||||||
|
cargo fmt --all -- --check: clean
|
||||||
|
cargo check --workspace: clean
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Le compte Markdown local est supérieur à celui du ZIP taggé audité pendant `0-pre.1`; l'audit reste propre et aucune divergence de source n'est déduite de ce seul compteur.
|
||||||
|
|
||||||
|
## Environnement Android confirmé
|
||||||
|
|
||||||
|
L'inventaire frais transmis pour ouvrir `0-pre.2` confirme :
|
||||||
|
|
||||||
|
- Rust et Cargo `1.94.1` ;
|
||||||
|
- `cargo-ndk 4.1.2` ;
|
||||||
|
- targets Rust Android installées : `aarch64-linux-android`, `armv7-linux-androideabi`, `i686-linux-android`, `x86_64-linux-android` ;
|
||||||
|
- target WASM `wasm32-unknown-unknown` et host `x86_64-unknown-linux-gnu` installés ;
|
||||||
|
- `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` ;
|
||||||
|
- la commande `java` du `PATH` expose Temurin `25.0.4.1`, tandis que Gradle utilise bien le JDK `17.0.20.1` via `JAVA_HOME` pour son launcher et son daemon ;
|
||||||
|
- `ANDROID_HOME=/home/sinus/DEV/AndroidSDK` ;
|
||||||
|
- ADB `37.0.1` ;
|
||||||
|
- Samsung `SM_G965F` réel connecté ;
|
||||||
|
- émulateur x86_64 `emulator-5554` connecté ;
|
||||||
|
- AVD `Medium_Phone`, `Medium_Phone_API_36.0` et `sasedev_games_api36` disponibles ;
|
||||||
|
- Gradle système `9.7.1` ;
|
||||||
|
- NDK projet `28.2.13676358` présent ;
|
||||||
|
- `Android/libs/SDL3-3.4.16.aar` présent avec SHA-256 `03710fc7b49cc070551446841a843840fabf2aaaaa3043cd5739292a55e4e61c`.
|
||||||
|
|
||||||
|
La différence entre `java -version` et `JAVA_HOME` n'est pas un défaut de la gate : le pipeline Gradle doit utiliser le wrapper et le JDK désigné par `JAVA_HOME`, pas dépendre implicitement du premier `java` du `PATH`.
|
||||||
|
|
||||||
|
## Transmission vers 0-pre.2
|
||||||
|
|
||||||
|
Tous les prérequis prévus par le plan sont disponibles pour la preuve mono-ABI Snake : wrapper Gradle épinglable, JDK 17 effectif pour Gradle, r28c, AAR SDL3 attendu, target Rust ARM64 et appareil ARM64 disponible pour une validation ultérieure.
|
||||||
87
history/0.3.3/0-pre.2.md
Normal file
87
history/0.3.3/0-pre.2.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<!-- file: history/0.3.3/0-pre.2.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Historique 0.3.3-0-pre.2
|
||||||
|
|
||||||
|
## Statut
|
||||||
|
|
||||||
|
`0.3.3-0-pre.2` a été validé par l'utilisateur le 2026-09-21. La preuve Snake ARM64 confirme que `assembleDebug` possède désormais le build Rust/SDL3 sans exécution préalable de `scripts/build_android_rust.py`.
|
||||||
|
|
||||||
|
Aucun `0-pre.2.fix.N` n'est requis ; la suite peut ouvrir `0-pre.3`.
|
||||||
|
|
||||||
|
## Gate statique et Rust
|
||||||
|
|
||||||
|
La gate fournie est propre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
games.sasedev workspace audit: clean
|
||||||
|
Markdown table audit: clean (5 table(s), 235 file(s))
|
||||||
|
Distribution layout audit: clean (53 required path(s), 1 forbidden path(s) absent)
|
||||||
|
cargo fmt --all: clean
|
||||||
|
cargo fmt --all -- --check: clean
|
||||||
|
cargo check --workspace: clean
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Le workspace compilé porte bien `0.3.3-0-pre.2` sur les crates concernées.
|
||||||
|
|
||||||
|
## Preuve Gradle/JDK du jalon
|
||||||
|
|
||||||
|
Le wrapper introduit pour cette preuve a été contrôlé avant build :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gradle Wrapper JAR SHA-256
|
||||||
|
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
|
||||||
|
|
||||||
|
Gradle distribution
|
||||||
|
9.6.0
|
||||||
|
|
||||||
|
Gradle binary ZIP SHA-256
|
||||||
|
bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
|
||||||
|
```
|
||||||
|
|
||||||
|
`./gradlew --version` a téléchargé puis exécuté Gradle `9.6.0`. Pour cette gate, le launcher et le daemon utilisaient Temurin `17.0.20.1` via `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64`.
|
||||||
|
|
||||||
|
Cette combinaison décrit la preuve `0-pre.2`; elle n'est pas conservée comme contrainte exacte du pipeline natif après la décision de toolchain prise pour `0-pre.3`.
|
||||||
|
|
||||||
|
## Build Snake ARM64
|
||||||
|
|
||||||
|
La commande :
|
||||||
|
|
||||||
|
```text
|
||||||
|
:game-snake-poc:assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
a déclenché `buildDebugSasedevRustArm64V8a`, compilé `game-android-entrypoint` avec `cargo ndk` pour `aarch64-linux-android`, copié les bibliothèques sous `build/generated/sasedevNative/...` et terminé avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
BUILD SUCCESSFUL
|
||||||
|
54 actionable tasks: 54 executed
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune commande Python de build natif n'a été exécutée avant Gradle.
|
||||||
|
|
||||||
|
## Inspection APK
|
||||||
|
|
||||||
|
L'artefact produit est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
L'inspection confirme les deux bibliothèques ARM64 attendues :
|
||||||
|
|
||||||
|
```text
|
||||||
|
lib/arm64-v8a/libSDL3.so
|
||||||
|
lib/arm64-v8a/libgame_android_entrypoint.so
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune bibliothèque `x86_64`, `armeabi-v7a` ou `x86` n'est présente dans cette preuve mono-ABI. Les lignes répétées dans la sortie utilisateur provenaient des deux `grep -Fx` exécutés après le listing initial, pas de doublons internes à l'APK.
|
||||||
|
|
||||||
|
## Transmission vers 0-pre.3
|
||||||
|
|
||||||
|
Le task wiring, l'extraction SDL3, le linkage Rust, le staging `jniLibs` généré et le packaging ARM64 sont donc prouvés. `0-pre.3` peut élargir ce même contrat à `arm64-v8a + x86_64`.
|
||||||
|
|
||||||
|
Après cette validation, la politique toolchain a été révisée explicitement : le projet Android SDL3 natif doit accepter le Gradle système lorsqu'il est `>= 9.6.0` et utiliser le JDK normal de l'environnement, sans `export JAVA_HOME` spécifique au projet. Les contraintes historiques du POC Tauri restent séparées.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# file: scripts/audit_distribution_layout.py
|
# file: scripts/audit_distribution_layout.py
|
||||||
# version: 14
|
# version: 16
|
||||||
|
|
||||||
"""Audit the static distribution layout expected by supported POCs."""
|
"""Audit the static distribution layout expected by supported POCs."""
|
||||||
|
|
||||||
@@ -13,6 +13,10 @@ import sys
|
|||||||
|
|
||||||
FORBIDDEN_PATHS = (
|
FORBIDDEN_PATHS = (
|
||||||
"builds",
|
"builds",
|
||||||
|
"Android/gradlew",
|
||||||
|
"Android/gradlew.bat",
|
||||||
|
"Android/gradle/wrapper/gradle-wrapper.jar",
|
||||||
|
"Android/gradle/wrapper/gradle-wrapper.properties",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +64,8 @@ REQUIRED_PATHS = (
|
|||||||
"Web/game-snake-poc/frontend/ts/provenance.ts",
|
"Web/game-snake-poc/frontend/ts/provenance.ts",
|
||||||
"Android/game-reflex-poc/build.gradle",
|
"Android/game-reflex-poc/build.gradle",
|
||||||
"Android/game-snake-poc/build.gradle",
|
"Android/game-snake-poc/build.gradle",
|
||||||
|
"Android/settings.gradle",
|
||||||
|
"Android/gradle/sasedev-rust-android.gradle",
|
||||||
"scripts/build_android_rust.py",
|
"scripts/build_android_rust.py",
|
||||||
"scripts/build_reflex_tauri_wasm.py",
|
"scripts/build_reflex_tauri_wasm.py",
|
||||||
"assets/common",
|
"assets/common",
|
||||||
@@ -302,6 +308,71 @@ def audit_snake_tauri_generated_android(root: pathlib.Path) -> list[str]:
|
|||||||
return violations
|
return violations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def audit_android_native_gradle(root: pathlib.Path) -> list[str]:
|
||||||
|
"""Validate the minimum Gradle contract and multi-ABI native Rust consumers."""
|
||||||
|
|
||||||
|
violations: list[str] = []
|
||||||
|
settings_path = root / "Android/settings.gradle"
|
||||||
|
native_script_path = root / "Android/gradle/sasedev-rust-android.gradle"
|
||||||
|
snake_build_path = root / "Android/game-snake-poc/build.gradle"
|
||||||
|
reflex_build_path = root / "Android/game-reflex-poc/build.gradle"
|
||||||
|
required = (settings_path, native_script_path, snake_build_path, reflex_build_path)
|
||||||
|
if not all(path.exists() for path in required):
|
||||||
|
return violations
|
||||||
|
|
||||||
|
settings = settings_path.read_text(encoding="utf-8")
|
||||||
|
settings_fragments = (
|
||||||
|
'import org.gradle.util.GradleVersion',
|
||||||
|
'GradleVersion.version("9.6.0")',
|
||||||
|
'GradleVersion.current()',
|
||||||
|
'currentGradleVersion < minimumGradleVersion',
|
||||||
|
)
|
||||||
|
for fragment in settings_fragments:
|
||||||
|
if fragment not in settings:
|
||||||
|
violations.append(f"DIST-LAYOUT-072: Android settings missing minimum Gradle gate: {fragment}")
|
||||||
|
|
||||||
|
native_script = native_script_path.read_text(encoding="utf-8")
|
||||||
|
native_fragments = (
|
||||||
|
'spec.commandLine(',
|
||||||
|
'"cargo",',
|
||||||
|
'"ndk",',
|
||||||
|
'"--no-default-features",',
|
||||||
|
'variant.sources.jniLibs.addGeneratedSourceDirectory',
|
||||||
|
'generated/sasedevNative/',
|
||||||
|
'libgame_android_entrypoint.so',
|
||||||
|
'libSDL3.so',
|
||||||
|
)
|
||||||
|
for fragment in native_fragments:
|
||||||
|
if fragment not in native_script:
|
||||||
|
violations.append(f"DIST-LAYOUT-074: Gradle-owned Rust Android build missing contract: {fragment}")
|
||||||
|
if "build_android_rust.py" in native_script or "python" in native_script.lower():
|
||||||
|
violations.append("DIST-LAYOUT-075: Gradle-owned Rust Android build must not delegate orchestration to Python")
|
||||||
|
|
||||||
|
consumers = (
|
||||||
|
(
|
||||||
|
"Snake",
|
||||||
|
snake_build_path.read_text(encoding="utf-8"),
|
||||||
|
'ext.sasedevRustGameFeature = "snake"',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Reflex",
|
||||||
|
reflex_build_path.read_text(encoding="utf-8"),
|
||||||
|
'ext.sasedevRustGameFeature = "reflex"',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
shared_fragments = (
|
||||||
|
'ext.sasedevRustAndroidAbis = ["arm64-v8a", "x86_64"]',
|
||||||
|
"abiFilters 'arm64-v8a', 'x86_64'",
|
||||||
|
'apply from: rootProject.file("gradle/sasedev-rust-android.gradle")',
|
||||||
|
)
|
||||||
|
for label, content, feature_fragment in consumers:
|
||||||
|
for fragment in (feature_fragment, *shared_fragments):
|
||||||
|
if fragment not in content:
|
||||||
|
violations.append(f"DIST-LAYOUT-076: {label} Android multi-ABI consumer missing contract: {fragment}")
|
||||||
|
return violations
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
"""Validate that every static distribution boundary exists."""
|
"""Validate that every static distribution boundary exists."""
|
||||||
|
|
||||||
@@ -322,6 +393,7 @@ def main() -> int:
|
|||||||
contract_violations.extend(audit_snake_web_integration(root))
|
contract_violations.extend(audit_snake_web_integration(root))
|
||||||
contract_violations.extend(audit_snake_tauri_android_integration(root))
|
contract_violations.extend(audit_snake_tauri_android_integration(root))
|
||||||
contract_violations.extend(audit_snake_tauri_generated_android(root))
|
contract_violations.extend(audit_snake_tauri_generated_android(root))
|
||||||
|
contract_violations.extend(audit_android_native_gradle(root))
|
||||||
if missing:
|
if missing:
|
||||||
for relative in missing:
|
for relative in missing:
|
||||||
print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr)
|
print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr)
|
||||||
|
|||||||
Reference in New Issue
Block a user