0.3.1-0-pre.2.fix.2

This commit is contained in:
2026-09-20 21:49:23 +02:00
parent bded27c944
commit 1835464bb3
13 changed files with 168 additions and 357 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 72
# version: 73
[workspace]
resolver = "3"
@@ -21,7 +21,7 @@ members = [
]
[workspace.package]
version = "0.3.1-0-pre.2.fix.1"
version = "0.3.1-0-pre.2.fix.2"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 43 -->
<!-- version: 44 -->
# games.sasedev
@@ -26,7 +26,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
Version stable de référence : `0.3.0`.
Version de développement courante : `0.3.1-0-pre.2.fix.1`.
Version de développement courante : `0.3.1-0-pre.2.fix.2`.
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/apps/game-snake-poc-tauri/README.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# game-snake-poc-tauri
@@ -22,6 +22,6 @@ Le frontend minimal de `0.3.1-0-pre.2` valide uniquement le démarrage Tauri, le
Les hooks `beforeDevCommand` et `beforeBuildCommand` de Tauri possèdent le build WASM et Vite. Aucun orchestrateur Python n'est utilisé. Les bindings WASM, la distribution Vite, les caches, `node_modules/` et le projet Android généré restent hors des sources livrées ou dans des chemins générés ignorés.
Pour Tauri CLI `2.11.x`, le projet Android généré utilise encore Gradle `8.14.3`. Le hook `scripts/prepare-android-gradle.mjs` applique avant `dev`/`build` la migration Android déjà retenue upstream par Tauri (`Gradle 9.6.1`, AGP `9.3.1`, Kotlin `2.2.10`) afin de rester exécutable avec Java 25. Cette préparation modifie uniquement `gen/android/`, reste idempotente et ne dépend pas du Gradle global installé sur la machine.
Le host ne modifie pas le projet Android généré par Tauri. La branche stable Tauri 2.11.x conserve son propre wrapper Gradle ; les commandes Android doivent donc s'exécuter avec un JDK compatible avec cette chaîne. Pour la machine de validation, `JAVA_HOME` doit pointer vers le JBR fourni par Android Studio (ou vers un autre JDK compatible), plutôt que vers le Java 25 global.
Voir [`USAGE.md`](USAGE.md) pour l'initialisation locale et les smokes Android.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/apps/game-snake-poc-tauri/USAGE.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# Utilisation — Snake Tauri Android
@@ -9,7 +9,16 @@ Le host utilise Cargo/Rust, `cargo tauri`, Node/npm, `wasm-bindgen`, Java et le
`NDK_HOME` n'est pas un prérequis utilisateur du projet. Tauri sélectionne la version NDK side-by-side appropriée sous `$ANDROID_HOME/ndk/<version>` et peut injecter lui-même `NDK_HOME` dans ses sous-processus Android. Une variable globale ne doit donc pas être ajoutée uniquement pour satisfaire la documentation générique de Tauri.
`JAVA_HOME` n'est pas imposé tant que la CLI Tauri résout correctement `java` depuis l'environnement. L'environnement de validation utilise Java 25 ; Tauri CLI `2.11.x` génère encore un wrapper Gradle `8.14.3`, qui ne peut pas exécuter ce JDK. Le host corrige ce scaffold généré avant les hooks frontend vers Gradle `9.6.1`, AGP `9.3.1` et Kotlin `2.2.10`, combinaison déjà adoptée par la migration Android upstream de Tauri. Le Gradle global installé sur la machine n'est pas utilisé par `gradlew` et n'est pas requis par ce correctif.
Pour Android, `JAVA_HOME` doit pointer vers un JDK compatible avec la chaîne Gradle générée par la version stable de Tauri 2 utilisée par le projet. La documentation Tauri recommande le JBR fourni par Android Studio. Le Java 25 global de la machine de validation n'est pas utilisé pour les commandes Tauri Android tant qu'il reste incompatible avec le wrapper Gradle stable. Le Gradle global installé sur la machine n'est pas utilisé par `gradlew` et n'est pas un prérequis du projet.
Exemple lorsque Android Studio est installé sous `/opt/android-studio` :
```bash
export JAVA_HOME=/opt/android-studio/jbr
"$JAVA_HOME/bin/java" -version
```
Si Android Studio se trouve ailleurs, utiliser le chemin réel de son répertoire `jbr`. Ne pas modifier `gen/android` pour contourner une incompatibilité de JDK.
Pour les cibles actuellement disponibles, `aarch64-linux-android` permet le smoke sur l'appareil ARM64 et `x86_64-linux-android` celui des AVD x86_64. Les autres ABI ne sont pas requises pour `0-pre.2`.
@@ -27,7 +36,7 @@ Initialiser une fois le projet Android généré par Tauri :
(cd crates/apps/game-snake-poc-tauri && cargo tauri android init)
```
Le répertoire `gen/` produit par Tauri est généré et ignoré ; il n'appartient pas aux deltas. Le correctif Gradle 9 est appliqué à ce répertoire par le hook Tauri au prochain `android dev` ou `android build`, y compris si `gen/android` existe déjà depuis `0-pre.2`. Il n'est donc pas nécessaire de rejouer `android init` pour appliquer le fix.
Le répertoire `gen/` produit par Tauri est généré et ignoré ; il n'appartient pas aux deltas et ne doit pas être patché par des scripts propres au jeu. Une initialisation déjà réalisée reste utilisable ; il n'est pas nécessaire de rejouer `android init` pour ce fix.
## Smoke Android de développement

View File

@@ -1,42 +1,9 @@
// file: crates/apps/game-snake-poc-tauri/build.rs
// version: 2
// version: 3
//! Build-time integration for the Snake Tauri host.
const GENERATED_ANDROID_TAURI_BUILD_GRADLE: &str = "gen/android/app/tauri.build.gradle.kts";
const GRADLE_8_IMPLEMENTATION_DECLARATION: &str = "val implementation by configurations";
const GRADLE_9_IMPLEMENTATION_DECLARATION: &str = "val implementation = configurations.getByName(\"implementation\")";
fn patch_generated_android_gradle() {
let manifest_dir = match std::env::var_os("CARGO_MANIFEST_DIR") {
Some(value) => std::path::PathBuf::from(value),
None => return,
};
let path = manifest_dir.join(GENERATED_ANDROID_TAURI_BUILD_GRADLE);
let content = match std::fs::read_to_string(&path) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => return,
std::result::Result::Err(error) => {
println!("cargo:warning=failed to inspect {}: {error}", path.display());
return;
},
};
if content.contains(GRADLE_9_IMPLEMENTATION_DECLARATION) {
return;
}
if !content.contains(GRADLE_8_IMPLEMENTATION_DECLARATION) {
println!("cargo:warning={} does not match the expected Tauri 2.11.x Gradle bridge", path.display());
return;
}
let patched = content.replace(GRADLE_8_IMPLEMENTATION_DECLARATION, GRADLE_9_IMPLEMENTATION_DECLARATION);
if let std::result::Result::Err(error) = std::fs::write(&path, patched) {
println!("cargo:warning=failed to prepare {} for Gradle 9: {error}", path.display());
}
return;
}
fn main() {
tauri_build::build();
patch_generated_android_gradle();
return;
}

View File

@@ -6,9 +6,8 @@
"scripts": {
"wasm:dev": "cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown && wasm-bindgen ../../../../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../../../../builds/sasedev-games/game-snake-poc-tauri/wasm --out-name game_snake_poc_wasm",
"wasm:build": "cargo build -p game-snake-poc-wasm --release --target wasm32-unknown-unknown && wasm-bindgen ../../../../builds/sasedev-games/target/wasm32-unknown-unknown/release/game_snake_poc_wasm.wasm --target web --out-dir ../../../../builds/sasedev-games/game-snake-poc-tauri/wasm --out-name game_snake_poc_wasm",
"dev": "npm run android:prepare && npm run wasm:dev && vite",
"build": "npm run android:prepare && npm run wasm:build && tsc && vite build",
"android:prepare": "node scripts/prepare-android-gradle.mjs"
"dev": "npm run wasm:dev && vite",
"build": "npm run wasm:build && tsc && vite build"
},
"dependencies": {
"@fltsci/tauri-plugin-tracing": "^0.3",

View File

@@ -1,219 +0,0 @@
// file: crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs
// version: 1
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import process from "node:process";
const GRADLE_VERSION = "9.6.1";
const AGP_VERSION = "9.3.1";
const KOTLIN_VERSION = "2.2.10";
function fail(message) {
process.stderr.write(`Snake Tauri Android Gradle preparation failed: ${message}\n`);
process.exitCode = 1;
throw new Error(message);
}
function replaceRequired(content, previous, next, label) {
if (content.includes(next)) {
return content;
}
if (!content.includes(previous)) {
return fail(`${label}: expected neither the Tauri 2.11.x form nor the prepared form`);
}
return content.replace(previous, next);
}
function removeLine(content, line) {
const withLf = `${line}\n`;
if (content.includes(withLf)) {
return content.replace(withLf, "");
}
if (content.endsWith(line)) {
return content.slice(0, -line.length);
}
return content;
}
function appendProperty(content, property) {
if (content.includes(`${property}\n`) || content.endsWith(property)) {
return content;
}
const separator = content.endsWith("\n") ? "" : "\n";
return `${content}${separator}${property}\n`;
}
function updateFile(path, transform) {
if (!existsSync(path)) {
return fail(`missing generated Android file: ${path}`);
}
const before = readFileSync(path, "utf8");
const after = transform(before);
if (after !== before) {
writeFileSync(path, after, "utf8");
}
}
function patchWrapper(androidRoot) {
const path = resolve(androidRoot, "gradle/wrapper/gradle-wrapper.properties");
updateFile(path, content => replaceRequired(
content,
"gradle-8.14.3-bin.zip",
`gradle-${GRADLE_VERSION}-bin.zip`,
"Gradle wrapper",
));
}
function patchRootBuild(androidRoot) {
const path = resolve(androidRoot, "build.gradle.kts");
updateFile(path, content => {
let result = replaceRequired(
content,
'classpath("com.android.tools.build:gradle:8.11.0")',
`classpath("com.android.tools.build:gradle:${AGP_VERSION}")`,
"Android Gradle Plugin",
);
result = replaceRequired(
result,
'classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")',
`classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN_VERSION}")`,
"Kotlin Gradle Plugin",
);
return result;
});
}
function patchBuildSrcManifest(androidRoot) {
const path = resolve(androidRoot, "buildSrc/build.gradle.kts");
updateFile(path, content => replaceRequired(
content,
'implementation("com.android.tools.build:gradle:8.11.0")',
`implementation("com.android.tools.build:gradle:${AGP_VERSION}")`,
"buildSrc Android Gradle Plugin",
));
}
function patchBuildTask(androidRoot) {
const path = resolve(androidRoot, "buildSrc/src/main/kotlin/BuildTask.kt");
updateFile(path, content => {
let result = content;
if (!result.includes("import javax.inject.Inject")) {
result = replaceRequired(
result,
"import org.gradle.api.tasks.TaskAction\n",
"import org.gradle.api.tasks.TaskAction\nimport javax.inject.Inject\nimport org.gradle.process.ExecOperations\n",
"BuildTask imports",
);
}
result = replaceRequired(
result,
"open class BuildTask : DefaultTask() {\n @Input\n var rootDirRel: String? = null\n",
"abstract class BuildTask : DefaultTask() {\n @get:Inject\n abstract val execOperations: ExecOperations\n\n @Input\n var rootDirRel: String? = null\n @Input\n var projectDir: String? = null\n",
"BuildTask ExecOperations declaration",
);
result = replaceRequired(
result,
" project.exec {\n workingDir(File(project.projectDir, rootDirRel))\n",
" execOperations.exec {\n workingDir(File(projectDir, rootDirRel))\n",
"BuildTask process execution",
);
result = result.replaceAll("project.logger.isEnabled", "logger.isEnabled");
return result;
});
}
function patchRustPlugin(androidRoot) {
const path = resolve(androidRoot, "buildSrc/src/main/kotlin/RustPlugin.kt");
updateFile(path, content => {
let result = replaceRequired(
content,
" val targetBuildTask = project.tasks.maybeCreate(\n \"rustBuild$targetArchCapitalized$profileCapitalized\",\n BuildTask::class.java\n ).apply {\n",
" val targetBuildTask = project.tasks.register(\n \"rustBuild$targetArchCapitalized$profileCapitalized\",\n BuildTask::class.java\n ) {\n",
"RustPlugin task registration",
);
if (!result.includes(" projectDir = project.projectDir.path\n")) {
result = replaceRequired(
result,
" rootDirRel = config.rootDirRel\n target = targetName\n",
" rootDirRel = config.rootDirRel\n projectDir = project.projectDir.path\n target = targetName\n",
"RustPlugin project directory",
);
}
return result;
});
}
function patchAppBuild(androidRoot) {
const path = resolve(androidRoot, "app/build.gradle.kts");
updateFile(path, content => {
let result = content;
if (!result.includes("import org.jetbrains.kotlin.gradle.dsl.JvmTarget")) {
result = `import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n${result}`;
}
result = replaceRequired(
result,
" isMinifyEnabled = true\n proguardFiles(\n *fileTree(\".\") { include(\"**/*.pro\") }\n .plus(getDefaultProguardFile(\"proguard-android-optimize.txt\"))\n .toList().toTypedArray()\n )\n",
" optimization {\n enable = true\n }\n proguardFiles(\n *fileTree(\".\") {\n include(\"**/*.pro\")\n exclude(\"build/**\")\n }.files.toTypedArray()\n )\n",
"Android release optimization DSL",
);
result = replaceRequired(
result,
" kotlinOptions {\n jvmTarget = \"1.8\"\n }\n buildFeatures {\n",
" compileOptions {\n sourceCompatibility = JavaVersion.VERSION_1_8\n targetCompatibility = JavaVersion.VERSION_1_8\n }\n buildFeatures {\n",
"Android compile options",
);
if (!result.includes("kotlin {\n compilerOptions")) {
result = replaceRequired(
result,
"}\n\nrust {\n",
"}\n\nkotlin {\n compilerOptions {\n jvmTarget = JvmTarget.JVM_1_8\n }\n}\n\nrust {\n",
"Kotlin compiler options",
);
}
result = result.replace('apply(from = "tauri.build.gradle.kts")', 'apply(from = file("tauri.build.gradle.kts"))');
return result;
});
}
function patchGradleProperties(androidRoot) {
const path = resolve(androidRoot, "gradle.properties");
updateFile(path, content => {
let result = content;
result = removeLine(result, "android.useAndroidX=true");
result = removeLine(result, "android.nonTransitiveRClass=true");
result = removeLine(result, "android.nonFinalResIds=false");
result = appendProperty(result, "org.gradle.configuration-cache=true");
result = appendProperty(result, "android.proguard.failOnMissingFiles=false");
result = appendProperty(result, "android.builtInKotlin=false");
result = appendProperty(result, "android.newDsl=false");
return result;
});
}
function resolveAndroidRoot() {
const marker = "--android-project";
const markerIndex = process.argv.indexOf(marker);
if (markerIndex >= 0) {
const value = process.argv[markerIndex + 1];
if (value === undefined || value.startsWith("--")) {
return fail(`${marker} requires a path`);
}
return resolve(process.cwd(), value);
}
return resolve(process.cwd(), "gen/android");
}
function main() {
const androidRoot = resolveAndroidRoot();
patchWrapper(androidRoot);
patchRootBuild(androidRoot);
patchBuildSrcManifest(androidRoot);
patchBuildTask(androidRoot);
patchRustPlugin(androidRoot);
patchAppBuild(androidRoot);
patchGradleProperties(androidRoot);
process.stdout.write(`Prepared Tauri Android Gradle ${GRADLE_VERSION} / AGP ${AGP_VERSION} / Kotlin ${KOTLIN_VERSION}.\n`);
}
main();

View File

@@ -0,0 +1,2 @@
crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs
history/0.3.1/0-pre.2.md

View File

@@ -0,0 +1,122 @@
<!-- file: deltas/0.3.1/0-pre.2.fix.2.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.2.fix.2
## Base
Base déclarée : `0.3.1-0-pre.2.fix.1`.
## Objet
Ce fix retire la migration Gradle 9 locale introduite par `fix.1`, revient au chemin Android officiellement généré par Tauri 2 stable et corrige l'audit workspace pour qu'il n'analyse pas le contenu généré sous `gen/` comme du code source du dépôt.
Il corrige également une violation documentaire de `fix.1` : `history/0.3.1/0-pre.2.md` avait été créé alors que `0-pre.2` n'était pas accepté. Aucune entrée `history/` n'est conservée pour ce jalon tant que sa gate n'est pas verte.
## Cause confirmée
Le smoke `0-pre.2` a établi trois faits distincts :
- Tauri sélectionne correctement le NDK side-by-side sous `$ANDROID_HOME/ndk/<version>` ; l'absence de `NDK_HOME` utilisateur n'est pas la cause du défaut ;
- le wrapper Android généré par Tauri CLI `2.11.3` utilise Gradle `8.14.3`, qui ne peut pas s'exécuter avec Java 25 ;
- `cargo tauri android init` crée légitimement `gen/android/app/src/main/assets`, mais l'audit `GAME-ASSET-001` parcourait jusque-là les répertoires générés.
La dernière CLI Tauri 2 stable vérifiée pendant ce fix est `2.11.5`. Son template Android conserve Gradle `8.14.3` et AGP `8.11.0`. La migration Gradle 9 existe upstream mais n'est pas encore publiée dans la branche stable Tauri 2 ; la reproduire par patch local ferait du projet games.sasedev le mainteneur d'un fork implicite du template Tauri.
## Correction Tauri Android
Le host Snake ne modifie plus `gen/android` :
- suppression de `scripts/prepare-android-gradle.mjs` ;
- suppression du hook `android:prepare` dans `package.json` ;
- restauration des hooks Tauri vers le seul pipeline WASM/Vite ;
- `build.rs` délègue uniquement à `tauri_build::build()` ;
- l'audit de distribution interdit désormais la réintroduction d'un patch `gen/android` dans ces deux surfaces.
Pour Tauri 2 stable, le contrat local devient celui recommandé par Tauri : `JAVA_HOME` doit pointer vers un JDK compatible avec le wrapper généré, de préférence le JBR livré avec Android Studio. Le Java 25 global peut rester installé ; il ne doit simplement pas être le JDK utilisé par les commandes Android Tauri tant que le wrapper stable ne le supporte pas.
Le Gradle global installé sous `/opt/gradle` n'est pas utilisé par `gen/android/gradlew` et n'entre pas dans le contrat de build.
## Correction de l'audit workspace
`gen/` est déjà un chemin généré et ignoré par `.gitignore`. `audit_project_workspace_rules.py` exclut désormais ces arbres pour les contrôles qui portent sur la propriété du code source :
- `GAME-ASSET-001` n'interdit plus les `assets` créés par le projet Android Tauri généré ;
- `GAME-ANDROID-001` n'interdit plus les éventuelles sources Java générées sous `gen/`.
Les assets et sources Java réellement versionnés hors des emplacements autorisés restent interdits.
## Historique
`history/0.3.1/0-pre.2.md` est supprimé. `DOC-HIST-002` et `DOC-HIST-003` imposent qu'une entrée d'historique ne soit créée qu'après acceptation effective du jalon. Le prochain delta ne recréera cette entrée que si la gate `0-pre.2.fix.2` est validée.
## Suppressions
Le manifest `deltas/0.3.1/0-pre.2.fix.2.delete.txt` supprime :
- `crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs` ;
- `history/0.3.1/0-pre.2.md`.
Après extraction du delta à la racine du dépôt, appliquer les suppressions avec :
```bash
while IFS= read -r path; do
rm -f -- "$path"
done < deltas/0.3.1/0-pre.2.fix.2.delete.txt
```
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `crates/apps/game-snake-poc-tauri/build.rs` ;
- `crates/apps/game-snake-poc-tauri/package.json` ;
- `crates/apps/game-snake-poc-tauri/README.md` ;
- `crates/apps/game-snake-poc-tauri/USAGE.md` ;
- `docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md` ;
- `scripts/audit_project_workspace_rules.py` ;
- `scripts/audit_distribution_layout.py`.
## Fichiers ajoutés
- `deltas/0.3.1/0-pre.2.fix.2.md` ;
- `deltas/0.3.1/0-pre.2.fix.2.delete.txt`.
## Validation statique du générateur
Le générateur peut exécuter les audits statiques et vérifier explicitement que les chemins générés Tauri ne déclenchent plus `GAME-ASSET-001` / `GAME-ANDROID-001`. Ces contrôles ne remplacent pas les gates build/runtime utilisateur.
## Gate utilisateur
Après application du delta et de son manifest de suppression :
```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
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Avant le smoke Android, sélectionner le JBR d'Android Studio dans le shell. Exemple si Android Studio est sous `/opt/android-studio` :
```bash
export JAVA_HOME=/opt/android-studio/jbr
"$JAVA_HOME/bin/java" -version
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Si Android Studio se trouve ailleurs, remplacer `/opt/android-studio/jbr` par son chemin réel. Ne pas exécuter `npm run dev` directement et ne pas rejouer `cargo tauri android init` pour ce fix lorsque `gen/android` existe déjà.
## Critère d'acceptation
Le fix est accepté lorsque :
- les trois audits Python sont propres malgré la présence du `gen/android` local ;
- Cargo/Clippy/tests ciblés sont propres ;
- `cargo tauri android dev` n'échoue plus sur `Unsupported class file major version 69` ;
- le host minimal atteint le smoke Android prévu par `0-pre.2`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Plan v0.3.1 — second host Snake Tauri Android
@@ -77,7 +77,7 @@ La tranche vise un premier démarrage Tauri Android minimal, pas encore toute la
Gate utilisateur : audits, Cargo workspace strict, test ciblé de l'adapter WASM, non-régression du build Web direct, `cargo tauri android init` puis `cargo tauri android dev` sur un AVD ou appareil disponible. `cargo tauri android build` reste reporté aux phases finales sauf besoin diagnostique.
Le premier smoke `0-pre.2` a révélé deux défauts bornés : `build.rs` échouait sous Clippy strict à cause de la rustdoc manquante, et Java 25 ne pouvait pas exécuter le wrapper Gradle `8.14.3` généré par Tauri CLI `2.11.3`. `0-pre.2.fix.1` conserve Tauri 2.x mais prépare idempotemment le projet généré vers la combinaison Android upstream `Gradle 9.6.1 / AGP 9.3.1 / Kotlin 2.2.10`, tout en adaptant les API Gradle 9 retirées du `buildSrc`. Le Gradle global de la machine ne fait pas partie du contrat reproductible.
Le premier smoke `0-pre.2` a révélé deux défauts bornés : `build.rs` échouait sous Clippy strict à cause de la rustdoc manquante, et Java 25 ne pouvait pas exécuter le wrapper Gradle `8.14.3` généré par Tauri CLI `2.11.3`. `0-pre.2.fix.1` a tenté de migrer localement le scaffold généré vers Gradle 9 ; cette approche est abandonnée par `0-pre.2.fix.2` car la branche stable Tauri 2.11.x n'a pas encore publié cette migration et le projet ne doit pas maintenir un fork implicite des templates Android Tauri. Le fix utilise donc la chaîne Android générée officiellement par Tauri stable et impose pour les commandes Android un `JAVA_HOME` compatible, de préférence le JBR fourni par Android Studio. Le Gradle global de la machine ne fait pas partie du contrat reproductible.
### `0-pre.3` — shell Snake, Canvas, input et assets
@@ -162,7 +162,7 @@ emulator -list-avds
L'absence d'un outil ne doit pas être masquée. Elle devient un prérequis concret de `0-pre.2` ou motive un fix borné de `0-pre.1`.
La gate `0-pre.1` validée le 2026-09-20 a confirmé Rust `1.94.1`, Tauri CLI `2.11.3`, Node `24.21.0`, npm `11.19.0`, wasm-bindgen `0.2.128`, OpenJDK `25.0.4.1`, `ANDROID_HOME=/home/sinus/DEV/AndroidSDK`, un appareil ARM64 réel et trois AVD disponibles. `JAVA_HOME` est vide mais Java est résolu depuis le `PATH`. `NDK_HOME` n'est pas un prérequis à inventorier : Tauri a démontré pendant le smoke `0-pre.2` qu'il sélectionne le NDK side-by-side sous `$ANDROID_HOME/ndk/<version>` et injecte lui-même cette variable dans ses sous-processus. Le défaut observé concernait Java 25 face au wrapper Gradle 8.14.3, pas la résolution du NDK.
La gate `0-pre.1` validée le 2026-09-20 a confirmé Rust `1.94.1`, Tauri CLI `2.11.3`, Node `24.21.0`, npm `11.19.0`, wasm-bindgen `0.2.128`, OpenJDK `25.0.4.1`, `ANDROID_HOME=/home/sinus/DEV/AndroidSDK`, un appareil ARM64 réel et trois AVD disponibles. `JAVA_HOME` était vide et Java 25 était résolu depuis le `PATH`. Le smoke `0-pre.2` a confirmé que Tauri sélectionne lui-même le NDK side-by-side sous `$ANDROID_HOME/ndk/<version>` et injecte `NDK_HOME` dans ses sous-processus ; le défaut observé concernait Java 25 face au wrapper Gradle 8.14.3. Pour les commandes Android Tauri 2.11.x, `JAVA_HOME` doit donc pointer vers un JDK compatible, de préférence le JBR fourni par Android Studio.
## Critères de fermeture de 0.3.1

View File

@@ -1,74 +0,0 @@
<!-- file: history/0.3.1/0-pre.2.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.2
## Statut
Gate utilisateur exécutée le 2026-09-20 : validation partielle, `0.3.1-0-pre.2.fix.1` requis avant toute progression vers `0-pre.3`.
## Résultats propres
Les contrôles suivants passent sur `0-pre.2` :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean
Distribution layout audit: clean
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: terminé avec un warning build.rs
cargo test -p game-snake-poc-wasm --all-targets --all-features: 5 passed
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown: clean
wasm-bindgen: clean
Web/game-snake-poc npm install + npm run build: clean
crates/apps/game-snake-poc-tauri npm install: clean
cargo tauri android init: succès
```
Le build Web direct reste donc non régressé et l'initialisation Android Tauri produit correctement le projet Android local.
## Défaut Rust
`cargo clippy --workspace --all-targets --all-features -- -D warnings` échoue sur :
```text
crates/apps/game-snake-poc-tauri/build.rs
missing documentation for the crate
```
Pendant la compilation Android, le macro `tauri::mobile_entry_point` révèle également un `unused_must_use` parce que le point d'entrée annoté retourne `Result<(), String>`. Le fix sépare le wrapper mobile `()` de l'exécution interne retournant `Result` afin de conserver la propagation d'erreur sur Desktop sans laisser le point d'entrée Android ignorer implicitement le résultat.
## Défaut Gradle / Java
La CLI Tauri `2.11.3` sélectionne correctement le NDK side-by-side installé :
```text
Using installed NDK: .../AndroidSDK/ndk/30.0.14904198
```
Elle injecte ensuite elle-même `NDK_HOME` dans le sous-processus Android. L'absence de `NDK_HOME` dans l'environnement utilisateur n'est donc pas la cause du défaut.
`cargo tauri android dev` échoue plus tard lorsque le wrapper généré télécharge Gradle `8.14.3` puis l'exécute avec OpenJDK `25.0.4.1` :
```text
Unsupported class file major version 69
```
Le Gradle global présent dans le `PATH` n'est pas utilisé, puisque Tauri lance `gen/android/gradlew`.
## Direction du fix
Le correctif suit la migration Android déjà retenue upstream par Tauri :
```text
Gradle 9.6.1
Android Gradle Plugin 9.3.1
Kotlin Gradle Plugin 2.2.10
```
Le scaffold `gen/android` reste généré et ignoré. Un hook Node idempotent adapte le projet généré avant les hooks WASM/Vite, y compris les usages Gradle 9 supprimés dans `buildSrc`. Le build script Rust adapte après `tauri_build::build()` le bridge Gradle généré par `tauri-build 2.6.3`.
Aucun gameplay, Canvas, input, asset ou lifecycle de `0-pre.3` n'est ouvert par ce fix.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_distribution_layout.py
# version: 9
# version: 10
"""Audit the static distribution layout expected by supported POCs."""
@@ -34,7 +34,6 @@ REQUIRED_PATHS = (
"crates/apps/game-snake-poc-tauri/README.md",
"crates/apps/game-snake-poc-tauri/USAGE.md",
"crates/apps/game-snake-poc-tauri/package.json",
"crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs",
"crates/apps/game-snake-poc-tauri/tsconfig.json",
"crates/apps/game-snake-poc-tauri/vite.config.ts",
"crates/apps/game-snake-poc-tauri/tauri.conf.json",
@@ -157,11 +156,10 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
tauri_path = root / "crates/apps/game-snake-poc-tauri/tauri.conf.json"
tauri_rust_path = root / "crates/apps/game-snake-poc-tauri/src/tauri.rs"
build_script_path = root / "crates/apps/game-snake-poc-tauri/build.rs"
gradle_prepare_path = root / "crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs"
frontend_path = root / "crates/apps/game-snake-poc-tauri/frontend/ts/main.ts"
logging_path = root / "crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts"
wasm_runtime_path = root / "crates/apps/game-snake-poc-wasm/src/runtime.rs"
paths = (cargo_path, package_path, tauri_path, tauri_rust_path, build_script_path, gradle_prepare_path, frontend_path, logging_path, wasm_runtime_path)
paths = (cargo_path, package_path, tauri_path, tauri_rust_path, build_script_path, frontend_path, logging_path, wasm_runtime_path)
if not all(path.exists() for path in paths):
return violations
@@ -170,7 +168,6 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
tauri = tauri_path.read_text(encoding="utf-8")
tauri_rust = tauri_rust_path.read_text(encoding="utf-8")
build_script = build_script_path.read_text(encoding="utf-8")
gradle_prepare = gradle_prepare_path.read_text(encoding="utf-8")
frontend = frontend_path.read_text(encoding="utf-8")
logging = logging_path.read_text(encoding="utf-8")
wasm_runtime = wasm_runtime_path.read_text(encoding="utf-8")
@@ -179,14 +176,9 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
(cargo, 'tauri-plugin-tracing.workspace = true', "DIST-LAYOUT-022: Snake Tauri host must enable the tracing plugin"),
(package, '"wasm:dev": "cargo build -p game-snake-poc-wasm', "DIST-LAYOUT-023: Snake Tauri dev hook must build the shared Snake WASM adapter"),
(package, 'wasm-bindgen ../../../../builds/sasedev-games/', "DIST-LAYOUT-024: Snake Tauri bindings must be generated outside the repository"),
(package, '"android:prepare": "node scripts/prepare-android-gradle.mjs"', "DIST-LAYOUT-036: Snake Tauri host must prepare the generated Android Gradle project through its native Node hook"),
(package, '"dev": "npm run android:prepare && npm run wasm:dev && vite"', "DIST-LAYOUT-037: Snake Tauri dev hook must prepare Gradle before WASM/Vite"),
(package, '"build": "npm run android:prepare && npm run wasm:build && tsc && vite build"', "DIST-LAYOUT-038: Snake Tauri build hook must prepare Gradle before WASM/Vite"),
(gradle_prepare, 'const GRADLE_VERSION = "9.6.1";', "DIST-LAYOUT-039: Snake Tauri Android preparation must pin the reviewed Gradle 9 version"),
(gradle_prepare, 'const AGP_VERSION = "9.3.1";', "DIST-LAYOUT-040: Snake Tauri Android preparation must pin the reviewed AGP version"),
(gradle_prepare, 'const KOTLIN_VERSION = "2.2.10";', "DIST-LAYOUT-041: Snake Tauri Android preparation must pin the reviewed Kotlin plugin version"),
(gradle_prepare, 'org.gradle.process.ExecOperations', "DIST-LAYOUT-042: Snake Tauri Gradle 9 preparation must migrate removed Project.exec usage"),
(build_script, 'val implementation = configurations.getByName', "DIST-LAYOUT-043: Snake Tauri build script must repair the tauri-build Gradle bridge for Gradle 9"),
(package, '"dev": "npm run wasm:dev && vite"', "DIST-LAYOUT-036: Snake Tauri dev hook must leave Android generation to Tauri"),
(package, '"build": "npm run wasm:build && tsc && vite build"', "DIST-LAYOUT-037: Snake Tauri build hook must leave Android generation to Tauri"),
(build_script, "tauri_build::build();", "DIST-LAYOUT-038: Snake Tauri build script must delegate Android integration to tauri-build"),
(tauri, '"script": "npm run dev"', "DIST-LAYOUT-025: Tauri must own the frontend development hook"),
(tauri, '"script": "npm run build"', "DIST-LAYOUT-026: Tauri must own the frontend production hook"),
(tauri_rust, '#[tauri::mobile_entry_point]', "DIST-LAYOUT-027: Snake Tauri run entry point must support Tauri mobile"),
@@ -201,6 +193,8 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
violations.append(message)
forbidden_fragments = (
(package, "python", "DIST-LAYOUT-033: Snake Tauri build hooks must not use Python"),
(package, "android:prepare", "DIST-LAYOUT-039: Snake Tauri build hooks must not patch generated Android Gradle files"),
(build_script, "gen/android", "DIST-LAYOUT-040: Snake Tauri build script must not mutate generated Android Gradle files"),
(tauri, "python", "DIST-LAYOUT-034: Snake Tauri configuration must not use Python build orchestration"),
(frontend, "console.", "DIST-LAYOUT-035: Snake Tauri application frontend must not log directly through console.*"),
)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_project_workspace_rules.py
# version: 6
# version: 7
"""Audit mechanically verifiable games.sasedev workspace boundaries."""
@@ -15,6 +15,13 @@ import tomllib
SEMVER = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0-pre|1-alpha|2-beta|3-rc)\.[1-9][0-9]*(?:\.fix\.[1-9][0-9]*)?)?$", re.ASCII)
def is_generated_path(path: pathlib.Path, root: pathlib.Path) -> bool:
"""Return whether a path belongs to an ignored generated tree."""
relative = path.relative_to(root)
return "gen" in relative.parts
def main() -> int:
"""Run project-specific workspace audits."""
@@ -86,10 +93,14 @@ def main() -> int:
errors.append(
f"DOC-011: documentary directory entry point must use 000-README.md: {readme_path.relative_to(root).as_posix()}"
)
forbidden_assets = [path for path in (root / "crates").rglob("assets") if path.is_dir()]
forbidden_assets = [
path for path in (root / "crates").rglob("assets") if path.is_dir() and not is_generated_path(path, root)
]
for path in forbidden_assets:
errors.append(f"GAME-ASSET-001: assets directory forbidden inside crates: {path.relative_to(root).as_posix()}")
for java_path in sorted(root.rglob("*.java")):
if is_generated_path(java_path, root):
continue
if not java_path.is_relative_to(root / "Android"):
errors.append(f"GAME-ANDROID-001: Java source outside Android/: {java_path.relative_to(root).as_posix()}")