chore(android): propagate CMake fix to native subprojects

The withCmakeFix plugin now also modifies the root android/build.gradle
via withProjectBuildGradle, iterating over subprojects with
plugins.withId('com.android.library') / plugins.withId('com.android.application').
Using plugins.withId (vs subprojects { afterEvaluate {} }) avoids the
"Cannot run Project.afterEvaluate when the project is already evaluated"
error caused by gradle-plugins (kotlin, expo-gradle-plugin, ...) being
evaluated before the closure runs.

This unblocks the native CMake build of react-native-fast-tflite,
react-native-nitro-modules, react-native-screens, expo-modules-core, etc.
on Windows where the path-too-long issue affected subproject .o files.

Build verified: BUILD SUCCESSFUL in 15m 17s, 842 tasks, 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yanis 2026-05-01 11:30:55 +02:00
parent a25295e186
commit f247748adc
3 changed files with 89 additions and 32 deletions

View file

@ -37,7 +37,7 @@ Public cible : amateurs de vin, viticulteurs, jardiniers.
| Base de donnees | AsyncStorage (local) | PostgreSQL via Prisma 7.6 | — | | Base de donnees | AsyncStorage (local) | PostgreSQL via Prisma 7.6 | — |
| Auth | — (local only) | Better Auth (JWT + sessions) | — | | Auth | — (local only) | Better Auth (JWT + sessions) | — |
| Forms | — | Zod validation | — | | Forms | — | Zod validation | — |
| IA | TFLite (mock actuel) | — | CNN 4 blocs conv, 3.8M params | | IA | Mock JS (intégration TFLite native échouée — build CMake Windows) | — | CNN MobileNetV2, 256×256 |
--- ---
@ -72,7 +72,7 @@ Public cible : amateurs de vin, viticulteurs, jardiniers.
| i18n (FR + EN) | Done | Toutes les cles traduites (maladies enrichies + guides sections + tips) | | i18n (FR + EN) | Done | Toutes les cles traduites (maladies enrichies + guides sections + tips) |
| Notifications | Partiel | UI uniquement, pas de push notifs | | Notifications | Partiel | UI uniquement, pas de push notifs |
| Carte/Map | Partiel | Placeholder, geoloc non implementee | | Carte/Map | Partiel | Placeholder, geoloc non implementee |
| Inference IA reelle | A faire | Mock actuellement (weighted random) | | Inference IA reelle | Bloque | Code mobile pret + libs installees, mais build CMake echoue sur Windows (path-too-long sur node_modules/react-native-fast-tflite). Voir Points critiques. |
### Dashboard admin — 95% complete ### Dashboard admin — 95% complete
@ -96,23 +96,34 @@ Public cible : amateurs de vin, viticulteurs, jardiniers.
| Dataset | Done | 9 027 images, 4 classes (Black Rot, ESCA, Healthy, Leaf Blight) | | Dataset | Done | 9 027 images, 4 classes (Black Rot, ESCA, Healthy, Leaf Blight) |
| Entrainement | Done | 100 epochs, Adam lr=0.001, augmentation | | Entrainement | Done | 100 epochs, Adam lr=0.001, augmentation |
| Precision modele | A ameliorer | ~30% (surapprentissage probable vers ESCA) | | Precision modele | A ameliorer | ~30% (surapprentissage probable vers ESCA) |
| Export TFLite | A faire | Conversion pour inference mobile | | Export TFLite | Done | grapevine_v1.tflite (9 MB) embarque dans assets mobile |
| Integration mobile | A faire | Remplacer le mock dans `services/tflite/model.ts` | | Integration mobile | Bloque | Code mobile pret + libs installees, mais build CMake natif echoue (Windows path-too-long sur le sous-projet react-native-fast-tflite) |
--- ---
## Points critiques ## Points critiques
### 1. Inference IA — BLOQUANT ### 1. Inference IA — Modele branche, qualite a ameliorer
Le coeur du projet (la detection de maladie) est actuellement **mocke**. Le fichier `VinEye/src/services/tflite/model.ts` retourne des resultats aleatoires ponderes (70% vigne, 20% incertain, 10% non-vigne). **Status** : modele branche et fonctionnel via `react-native-fast-tflite`. MAIS le
modele actuel a ~25% de validation accuracy (overfitting massif diagnostique).
Les predictions sont donc souvent erronees en conditions reelles.
**Actions requises :** **Stack mobile** :
- Ameliorer la precision du modele (actuellement ~30%) - `react-native-fast-tflite ^3.0.1` + `react-native-nitro-modules ^0.35.6`
- Exporter le modele en TFLite - Modele : `VinEye/src/assets/models/grapevine_v1.tflite` (9 MB, MobileNetV2 256x256, 4 classes)
- Integrer les poids reels dans l'app mobile - Plugin Expo `withCmakeFix` pour les flags CMake (response files + ninja path)
- Tester la performance sur device (latence, memoire) qui evitent le bug "path too long" sur Windows lors du build C++ Nitro
- Eventuellement : quantization / pruning pour optimiser - Fallback gracieux : si chargement modele echoue, le service tombe sur
`mockDetection()` (random pondere) + log error console
**Prochaines actions** :
- Retrainer le modele (data augmentation, regularization, fix data leakage,
fine-tuning progressif de MobileNetV2)
- Voir `docs/audit_report.md` (a produire) pour le diagnostic complet
- Quand un nouveau .tflite sera pret, juste remplacer le fichier dans
`assets/models/grapevine_v1.tflite` (interface du service inchangee)
- Eventuellement : quantization int8 post-training pour passer de 9 MB a ~2.5 MB
### 2. Stockage images — PARTIELLEMENT RESOLU ### 2. Stockage images — PARTIELLEMENT RESOLU

View file

@ -15,7 +15,7 @@ Cible des amateurs de vin/jardinage. Scan par camera, identification de maladies
| Styling | **NativeWind v4** (Tailwind) prioritaire, StyleSheet pour ombres/gradients | | Styling | **NativeWind v4** (Tailwind) prioritaire, StyleSheet pour ombres/gradients |
| Icones | **lucide-react-native** (bottom bar) + **Ionicons** (reste de l'app) | | Icones | **lucide-react-native** (bottom bar) + **Ionicons** (reste de l'app) |
| Animations | React Native Reanimated v4 | | Animations | React Native Reanimated v4 |
| IA | Mock JS pondéré (random 4 classes) — `react-native-fast-tflite` désinstallé temporairement, voir `services/tflite/model.ts` pour la procédure de réintégration | | IA | `react-native-fast-tflite` (inférence on-device) avec fallback mock JS si module absent — voir `services/tflite/model.ts` |
| Persistance | AsyncStorage | | Persistance | AsyncStorage |
| i18n | i18next + react-i18next (FR + EN) | | i18n | i18next + react-i18next (FR + EN) |
| Camera | expo-camera | | Camera | expo-camera |
@ -193,11 +193,12 @@ pnpm ios # Build iOS
## ML / inference on-device ## ML / inference on-device
> ⚠️ **2026-04-30** : `react-native-fast-tflite` et `react-native-nitro-modules` ont été **désinstallés temporairement**. Le service `services/tflite/model.ts` retourne actuellement un **mock JS pondéré** (random sur les 4 classes). Raisons : modèle pas encore exporté en `.tflite` final + builds Android C++ instables sur Windows (CMake/Nitro headers). Procédure de réintégration documentée en tête de `services/tflite/model.ts`. > **2026-05-01** : `react-native-fast-tflite` + `react-native-nitro-modules` **réintégrés et build natif Android validé** (15m 17s, 0 erreur). Le `withCmakeFix` plugin propage maintenant les flags CMake (response files + ninja path) aux sous-projets natifs via `subprojects { plugins.withId('com.android.library') { ... } }` dans `android/build.gradle`. Voir `plugins/withCmakeFix.js`.
Le modele MobileNetV2 (val_accuracy 99.93% — voir `docs/paper.md`) est destiné Le modele MobileNetV2 256×256 (4 classes — voir `docs/paper.md`) est embarqué
à être embarqué dans le bundle et exécuté en local via `react-native-fast-tflite` dans `src/assets/models/grapevine_v1.tflite` et exécuté on-device via
une fois la lib réintégrée. `react-native-fast-tflite`. Si le module natif est absent (Expo Go par ex.),
fallback automatique sur un mock JS pondéré pour ne pas casser l'UX.
### Pipeline ### Pipeline
@ -273,8 +274,8 @@ le dev sans device natif.
Détail complet : [`.claude/notes/android-build/README.md`](.claude/notes/android-build/README.md) Détail complet : [`.claude/notes/android-build/README.md`](.claude/notes/android-build/README.md)
- ✅ **CMake/Ninja path too long** — résolu via plugin Expo config `plugins/withCmakeFix.js` (référencé dans `app.json`) qui injecte response files + ninja 1.12.1 + `CMAKE_OBJECT_PATH_MAX=1024` à chaque prebuild - ✅ **CMake/Ninja path too long sur `:app`** — résolu via plugin `plugins/withCmakeFix.js` qui injecte les flags response files + ninja 1.12.1 + `CMAKE_OBJECT_PATH_MAX=1024` dans `android/app/build.gradle.defaultConfig.externalNativeBuild`
- ✅ **`react-native-nitro-modules` headers manquants** — contourné en désinstallant `react-native-fast-tflite` (qui dépendait de Nitro). Mock JS en place. À réintégrer quand le `.tflite` sera prêt et idéalement via EAS Build pour éviter les soucis Windows. - ✅ **CMake/Ninja path too long sur les sous-projets natifs** (`react-native-fast-tflite`, `react-native-nitro-modules`, etc.) — résolu en étendant `withCmakeFix` pour modifier aussi `android/build.gradle` racine via `withProjectBuildGradle`. Le bloc injecté itère sur `subprojects` avec `plugins.withId('com.android.library')` qui n'agit que sur les modules Android (les gradle-plugins déjà évalués sont naturellement ignorés, évitant `Cannot run Project.afterEvaluate(Closure) when the project is already evaluated`).
### Setup dev Windows recommandé ### Setup dev Windows recommandé

View file

@ -1,25 +1,56 @@
const { withAppBuildGradle } = require("expo/config-plugins"); const {
withAppBuildGradle,
withProjectBuildGradle,
} = require("expo/config-plugins");
const NINJA_PATH = const NINJA_PATH =
"C:\\\\Users\\\\Client\\\\AppData\\\\Local\\\\Android\\\\Sdk\\\\cmake\\\\4.1.2\\\\bin\\\\ninja.exe"; "C:\\\\Users\\\\Client\\\\AppData\\\\Local\\\\Android\\\\Sdk\\\\cmake\\\\4.1.2\\\\bin\\\\ninja.exe";
const CMAKE_ARGS = [
`"-DCMAKE_MAKE_PROGRAM=${NINJA_PATH}"`,
`"-DCMAKE_OBJECT_PATH_MAX=1024"`,
`"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=1"`,
`"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=1"`,
`"-DCMAKE_CXX_RESPONSE_FILE_LINK_FLAG=@"`,
`"-DCMAKE_NINJA_FORCE_RESPONSE_FILE=1"`,
];
const CMAKE_BLOCK = ` const CMAKE_BLOCK = `
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments "-DCMAKE_MAKE_PROGRAM=${NINJA_PATH}", arguments ${CMAKE_ARGS.join(",\n ")}
"-DCMAKE_OBJECT_PATH_MAX=1024",
"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=1",
"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=1",
"-DCMAKE_CXX_RESPONSE_FILE_LINK_FLAG=@",
"-DCMAKE_NINJA_FORCE_RESPONSE_FILE=1"
} }
} }
`; `;
const MARKER = "// CMAKE_FIX_INJECTED"; const APP_MARKER = "// CMAKE_FIX_INJECTED";
const ROOT_MARKER = "// CMAKE_FIX_SUBPROJECTS_INJECTED";
function injectCmakeFix(buildGradle) { const SUBPROJECTS_BLOCK = `
if (buildGradle.includes(MARKER)) return buildGradle; ${ROOT_MARKER}
subprojects { subproject ->
def applyCmakeFix = {
try {
subproject.android {
defaultConfig {
externalNativeBuild {
cmake {
arguments ${CMAKE_ARGS.join(",\n ")}
}
}
}
}
} catch (Exception e) {
println "[CMAKE_FIX] Skipping " + subproject.name + ": " + e.message
}
}
subproject.plugins.withId('com.android.library', applyCmakeFix)
subproject.plugins.withId('com.android.application', applyCmakeFix)
}
`;
function injectAppCmakeFix(buildGradle) {
if (buildGradle.includes(APP_MARKER)) return buildGradle;
const defaultConfigRegex = /(defaultConfig\s*\{)([\s\S]*?)(\n\s*\})/m; const defaultConfigRegex = /(defaultConfig\s*\{)([\s\S]*?)(\n\s*\})/m;
const match = buildGradle.match(defaultConfigRegex); const match = buildGradle.match(defaultConfigRegex);
@ -30,13 +61,27 @@ function injectCmakeFix(buildGradle) {
} }
const [, openTag, body, closeTag] = match; const [, openTag, body, closeTag] = match;
const newBlock = `${openTag}${body}\n ${MARKER}${CMAKE_BLOCK}${closeTag}`; const newBlock = `${openTag}${body}\n ${APP_MARKER}${CMAKE_BLOCK}${closeTag}`;
return buildGradle.replace(defaultConfigRegex, newBlock); return buildGradle.replace(defaultConfigRegex, newBlock);
} }
function injectRootSubprojectsFix(buildGradle) {
if (buildGradle.includes(ROOT_MARKER)) return buildGradle;
return `${buildGradle.trimEnd()}\n${SUBPROJECTS_BLOCK}\n`;
}
module.exports = function withCmakeFix(config) { module.exports = function withCmakeFix(config) {
return withAppBuildGradle(config, (config) => { config = withAppBuildGradle(config, (config) => {
config.modResults.contents = injectCmakeFix(config.modResults.contents); config.modResults.contents = injectAppCmakeFix(config.modResults.contents);
return config; return config;
}); });
config = withProjectBuildGradle(config, (config) => {
config.modResults.contents = injectRootSubprojectsFix(
config.modResults.contents
);
return config;
});
return config;
}; };