Génération de fichiers AAB Android pour GCompris

Posté par  (site web personnel) . Édité par Lawless, BAud, palm123, Benoît Sibaud et Ysabeau 🧶. Modéré par NeoX. Licence CC By‑SA.
Étiquettes :
29
21
fév.
2022
Éducation

GCompris est une application éducative pour les enfants de 2 à 10 ans. À chaque sortie, une dépêche sur LinuxFr.org décrit les changements.
Pour une fois, cette dépêche ne concerne pas une sortie mais est un peu plus technique et raconte mes péripéties pour générer un paquet dans le nouveau format Android AAB (Android App Bundle) sorti en mai 2018 (d’après https://android-developers.googleblog.com/2021/06/the-future-of-android-app-bundles-is.html).

Sommaire

L’historique

Pour l’histoire, GCompris a été ré-écrit en C++/QML/JavaScript en 2014 et est disponible sur Android depuis 2015. Il est aussi disponible sur Windows, Linux, macOS, Ubuntu Touch et la compilation gérait à un moment Sailfish OS (mais bon, ce n’est probablement plus fonctionnel depuis que nous sommes passés à Qt 5.9 minimum).

Il y a deux dépendances externes que nous compilons via un ExternalModule : qml-box2d (qui est un wrapper de Box2D en QML) et OpenSSL (compilé pour Android seulement).

J’avais mis en place la compilation avec CMake depuis quasiment le début en 2014.
J’étais jeune à l’époque et moins expérimenté, donc il y a eu pas mal d’essais, de bidouilles et autres incantations pour que ça marche ©.

GCompris essaie de gérer les 2-3 précédentes versions LTS de Qt. La version actuelle est compilable à partir de la version Qt 5.9.

Pour Android, nous essayons de gérer à la fois Qt 5.12 et Qt 5.15 pour la prochaine version (la 5.12 nous permet de déployer sur les Android 4.1, la 5.15 à partir d'Android 5.0), sachant qu’il y aura une coupure nette avec Qt 6, car il n’y a pas la compatibilité QML entre les versions 5 et 6 (qui elle cible à partir d'Android 6.0).

Qt 5.14 et Android

Pour en revenir au sujet principal, Qt, depuis sa version 5.14 permet de générer à la fois des APK mais aussi des AAB pour Android.
La génération d’un binaire (APK ou AAB) passe par l’outil androiddeployqt qui prend en entrée un fichier JSON définissant les propriétés pour créer le binaire et divers autres paramètres pour créer en mode debug, release, signé…
La version fournie depuis Qt 5.14 possède entre autres une option pour générer un AAB.
Malheureusement, suite aux besoins qui ont évolué, le fichier JSON a aussi été modifié et n’est plus compatible avec l’ancien.
Pareil, pour le fichier de « AndroidManifest.xml » qui a évolué de façon non compatible.

Par défaut CMake permet de ne générer que pour une seule architecture. La solution proposée par Qt est de définir les architectures que l’on veut déployer dans la configuration CMake : ANDROID_BUILD_ABI_abi, avec abi étant dans armeabi-v7a, arm64-v8a, x86 et x86_64. Ensuite il s’occupera tout seul de compiler n fois le logiciel en créant des ExternalProject pour chaque architecture sauf pour l’architecture définie par ANDROID_ABI (qui, elle, sera faite dans le dossier de build par défaut).

Un autre billet de blog, écrit par un autre membre la communauté KDE, permet de voir les changements.

Les embrouilles

Comme indiqué précédemment, GCompris a fait sa propre tambouille pour faire fonctionner les générations. Par exemple, nous disposons de nos propres targets dans CMake (apk_debug, apk_release) alors que Qt fournit par défaut une target APK.
Nous surchargeons aussi le fichier JSON et AndroidManifest.xml pour les remplir à notre façon.
OpenSSL était généré via un script qui ne fonctionnait que pour deux targets (avec un bon if dans le CMakeLists.txt pour choisir).
Les bibliothèques compilées (Box2D, GCompris et OpenSSL) n’étaient pas générées au bon endroit par défaut et ça rendait la création des AAB compliquée.
La ligne pour lancer CMake était… lourde et compliquée :

   cmake -DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
    -DCMAKE_BUILD_TYPE=release \
    -DANDROID_ARCHITECTURE=arm \
     DQt5_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5 \
    -DQt5Qml_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Qml \
    -DQt5Network_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Network \
    -DQt5Core_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Core \
    -DQt5Quick_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Quick \
    -DQt5Gui_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Gui \
    -DQt5Multimedia_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Multimedia \
    -DQt5Svg_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Svg \
    -DQt5Widgets_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Widgets \
    -DQt5LinguistTools_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5LinguistTools \
    -DQt5Sensors_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5Sensors \
    -DQt5AndroidExtras_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5AndroidExtras \
    ..

Un peu de nettoyage

Au lieu de foncer tête la première dans le tas, il a d’abord fallu rendre le code existant un peu plus propre et se remettre en phase le plus possible avec la façon que Qt utilise pour générer les APK.

Comme tout bon développeur, j’ai donc regardé ce qui était fait ailleurs et surtout la doc de Qt.
Dans ce fichier, on voit comment Qt gère les architectures, les arguments qu’il fournit à ses projets pour les autres architectures, les cibles… Bref, le Graal de la documentation !
La variable un peu « magique » pour générer les AAB à garder en mémoire est CMAKE_LIBRARY_OUTPUT_DIRECTORY qui aura dans les sous-projets la valeur du dossier de sortie du projet principal.
La première étape a donc été de nettoyer notre propre code pour pouvoir compiler avec seulement les mêmes options qui sont passées l’ExternalProject (car sinon, nous n’arriverions pas à compiler pour les autres architectures).

cmake -DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
  -DCMAKE_ANDROID_API=16 \
  -DCMAKE_BUILD_TYPE=Release \
  -DANDROID_ABI=$1 \
  -DCMAKE_FIND_ROOT_PATH=${Qt5_BaseDIR}/${QtTarget}/lib/ \
  -DQt5_DIR=${Qt5_BaseDIR}/${QtTarget}/lib/cmake/Qt5

Au lieu de mettre directement CMAKE_PREFIX_PATH, on a gardé Qt5_DIR (et dans le CMakeLists.txt, on met CMAKE_PREFIX_PATH = Qt5_DIR), pour ne pas  impacter la compilation pour les autres les systèmes d’exploitation.

Pour OpenSSL, KDAB fournit des scripts pour compiler sur toutes les plateformes. En s’inspirant de ce qui était fait, on a réussi à tout gérer dans l’ExternalProject, en gérant proprement l’architecture, sans avoir recours à des scripts externes et gérer l’installation dans le bon dossier (le CMAKE_LIBRARY_OUTPUT_DIRECTORY du dessus !).

Jusqu’ici, rien qui ne permet de mieux compiler en Qt 5.15 (que ce soit des APK ou des AAB) mais du code bien plus propre et plus maintenable qui fonctionne toujours sur Qt 5.12.

Mise à jour du code pour gérer Qt 5.14

Le plugin qml-box2d est une dépendance externe qui se base sur qmake pour compiler.
qmake, dans les projets depuis 5.14, génère maintenant cinq fichiers Makefile : un par architecture (Makefile.Armeabi-v7a, Makefile.Arm64-v8a, Makefile.X86 et Makefile.X86_64) et un principal qui va appeler les quatre autres. Évidemment, ce n’est pas ce que l’on souhaite, on veut juste compiler pour notre architecture (et chaque sous-projet compilera pour la sienne). Ce n’est pas grave, nous avons notre petite variable ANDROID_ABI qui contient : armeabi-v7a, arm64-v8a, x86 et x86_64 donc il suffit de le concaténer via CMake au moment d’appeler le build de Box2D et de faire un make -f Makefile.${ANDROID_ABI}.
Le problème, si vous n’avez pas remarqué, est que les Makefile ont leur première lettre de l’architecture en majuscule. Il n’y a pas de solution « facile » pour convertir la première lettre seulement, donc on a dû utiliser une solution pas très belle mais qui marche.

Trop propre ?

Évidemment, après avoir testé, cela fonctionnait presque bien. Il manquait la bibliothèque Box2D dans les paquets… Après avoir réfléchi à comment faire sans toucher aux autres plateformes, une solution extrêmement crade (mais fonctionnelle…) était de rajouter une copie du fichier dans le « bon » dossier.

Autre souci, les APK générés faisaient 10 Mio de plus que la version compilée avec Qt 5.12. Suite au passage à Qt 5.14, il y a eu des changements sur la façon de gérer les « assets » (modules QML…) de Qt. Avant, les fichiers étaient copiés dans le dossier assets directement, mais depuis 5.14, ils mettent tous les fichiers dans un fichier de ressources rcc.
Pour je ne sais quelle raison non expliquée dans la documentation, dans le système de build, ils ont décidé de ne plus compresser les fichiers rcc qui sont dans les assets.

Côté GCompris, on génère des rcc par activité qui contiennent le code QML, JavaScript et les images associées. Il y en a pour 80 Mio actuellement et évidemment, avec la non compression de ces fichiers, on perdait 10 Mio de compression.
La solution « temporaire » a été de dupliquer le fichier build.gradle qui gère la compilation Android pour commenter cette non-compression.

À un certain point, il faudra voir pour migrer les rcc des activités dans des vrais plugins QML. L’avantage premier sera la compilation de ce code (donc plus rapide d’après les posts de blog : https://www.qt.io/blog/the-new-qtquick-compiler-technology et https://www.qt.io/blog/the-numbers-performance-benefits-of-the-new-qt-quick-compiler).
Mais dans ce cas-là, cela voudra dire qu’on compile pour chaque architecture les activités (sachant que les images, qui prennent le plus de place, ne seront pas plus compressées) et donc cela risque d’exploser la taille du fichier AAB (voir dessous pour plus de détails sur son contenu) et ne sera plus portable entre différentes plateformes (on aurait pu imaginer à un certain point créer une activité sous Linux, la copier sur un serveur et pouvoir l’installer puis la charger dynamiquement sur n’importe quel système d’exploitation).

Autres subtilités

Il y a eu d’autres subtilités sur lesquelles je ne vais pas trop m’étaler, par exemple, le dossier dans lequel on génère les assets doit s’appeler android-build (ce qui évite les copies).
On a dû garder nos propres fichiers JSON plutôt que d’utiliser celui fourni par Qt, car, de ce que j’ai compris de la doc, les variables sont des propriétés attachées à la cible de la bibliothèque et je n’ai pas eu le temps de me pencher plus dessus.

Alors, ça marche ?

Et bien oui ! La génération des APK séparément fonctionne en passant les bons paramètres.
Pour les AAB, qui dit « nouveau format » dit « mais comment ça marche et j’en fais quoi de ça moi ».

Il existe un utilitaire bundletool qui permet de générer un fichier APKS à partir d’un fichier AAB. Un APKS est une archive contenant plusieurs APK.
Dans notre cas, il y a trois types d’APK dans l’archive :

  • base-${ANDROID_ABI} (arm64_v8a, armeabi_v7a, x86 et x86_64)
  • base-hdpi, base-ldpi… (un par résolution)
  • base-master.apk (le fichier contenant les assets, commun à toutes les architectures)

Lorsqu’un périphérique demandera à Google de télécharger le jeu, il va se baser sur l’architecture et la résolution pour ne fournir que les trois APK nécessaires et installer ce qu’il faut.

Une documentation basique a été écrite dans le wiki de GCompris pour savoir comment le tester et déployer sur son périphérique Android avec l’outil bundletool.

Sachant que Google a rendu obligatoire depuis août 2021 la création des nouveaux projets sur le Google Play Store à passer obligatoirement par des fichiers AAB, il ne saurait tarder que même les projets qui étaient créés avant doivent aussi y passer bientôt.

Un autre avantage est qu’en utilisant un AAB, la limite de l’APK téléchargé est maintenant de 150 Mio à la place de 100 Mio pour un APK unique (GCompris est très proche de cette limite).

  • # Les APK toujours disponibles sur F-Droid ?

    Posté par  . Évalué à 9.

    Merci pour ce retour, c'est quand même vachement intéressant.

    Par contre, le gros défaut des AAB, c'est que c'est forcément lié à Google, il me semble. Merci de me contredire, ça me fera plaisir.

    Et les magasins alternatifs peuvent-ils gérer les AAB ? Sinon, est-ce que des versions mise-à-jour seront disponibles sur F-Droid malgré tout ?

    Les APK, c'est bien. J'ai une tablette à la maison qui est verrouillée : elle n'a pas Internet. Je télécharge l'APK à la main, transfert via Bluetooth, et pouf, GCompris est disponible.

    • [^] # Re: Les APK toujours disponibles sur F-Droid ?

      Posté par  (site web personnel) . Évalué à 9.

      Le format AAB est bien exclusif au Google Play Store pour le moment.

      Oui, j'ai laissé la possibilité de faire des APK et les mises à jour seront mises sur F-Droid (ou même sur le site de GCompris directement). Nous planifions une 2.2 dans la semaine et le code est déjà fait pour publier cette version sur F-Droid, j'attends juste le tag sur le code.

  • # Merci pour ce post!

    Posté par  (site web personnel) . Évalué à 6.

    Merci pour les détails concernant l’empaquetage, et de mon point de vue, pour les détails et liens concernant la gestion des dépendances externes. Je me pose beaucoup de questions similaires et je soufre beaucoup avec CMake (comme je l'ai lu quelque part, CMake est assez horrible, on perd beaucoup de temps avant de parvenir à faire ce que l'on veux, mais les autres systèmes sont pires…). De mon côté, la difficulté réside dans le support aussi bien de paquets pré-compilés que la compilation aisée par l'utilisateur final (et parvenir à gérer toutes les options dans les deux cas, que se soit avec des dépendances externes ou bien que d'autres applications puissent trouver nos binaires en multi plateforme et sans nécessairement que l'utilisateur n'ai les droits admin). Je vais sans doute beaucoup revenir vers ce post et les liens qui y figurent!

    • [^] # Re: Merci pour ce post!

      Posté par  (site web personnel) . Évalué à 5.

      Effectivement, les paquets ne sont pas toujours facile à faire.

      La grosse partie de notre code pour gérer les paquets est dans https://invent.kde.org/education/gcompris/-/blob/master/src/core/CMakeLists.txt#L316-388

      Il y a quelques valeurs codées en dur (les versions des dépendances comme libssl par exemple) dont j'aimerais bien me débarrasser mais je n'ai pas pris le temps de regarder. Nous ne sommes que 2 à faire des paquets, donc j'adapte mes chemins en local pour que cela marche le peu de fois où je dois en faire.

      Pour Linux, nous fournissons des zip autoextractibles via le générateur STGZ donc pas besoin d'être admin pour pouvoir le lancer.

      Pour Windows, à la fois des exécutables NSIS où il faut bien faire attention au nombre de slash et backslash mais aussi depuis peu des .msi. Je pense qu'il faut être admin sur les 2 par contre, mais je n'ai pas vérifié.

      Pour MacOS, c'est un DragNDrop mais n'ayant pas de mac, je n'ai pas plus de détails.

      La gestion des dépendances de Qt est fait via fixup_bundle et nécessite pour compiler avec MinGW sous Windows la version 3.22 de cmake minimum.

      Pour Android, tout est centralisé dans le fichier du dossier correspondant.

      Tous les paquets sont disponibles sur https://download.kde.org/stable/gcompris/qt/.

  • # Qbs n'est pas morts!

    Posté par  . Évalué à 3.

    Pour info et sans vouloir lancer un débat sans fin sur CMake versus…
    Qbs [https://doc.qt.io/qbs/qml-qbsmodules-android-sdk.html#details] permet la génération de fichiers apk/aab multi-architecture en une seule passe.

    • [^] # Re: Qbs n'est pas morts!

      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 23 février 2022 à 22:51.

      Je ne connais pas assez qbs pour me prononcer, ses capacités en plus ou moins par rapport à CMake ni ses différences pour mettre en place la compilation multiplateformes, gérer des sous projets avec des systèmes de compilation différents (ici, qml-box2d compile avec qmake, openssl avec un configure/make), avoir des targets spécifiques (on se sert aussi de CMake pour récupérer les fichiers de traduction, les compiler, créer des fichiers rcc pour les activités)…

      Ici les problèmes étaient pas mal de "vieux" code et le fait d'avoir des sous-modules à compiler et installer au bon endroit. Je ne sais pas si avoir un autre système de build aurait changé grand chose à ces soucis.

      • [^] # Re: Qbs n'est pas morts!

        Posté par  . Évalué à 4.

        Mon poste était surtout pour mentionner qu'il n'y pas que CMake pour compiler nos projets multi-platformes. Et qu'utiliser Qbs, à mon avis, facilite beaucoup la vie.

        Participant à son développement, je suis, bien sûre complètement impartial…

        A noter que Qbs n'utilise pas gradle mais génère lui même toute la chaîne de compilation Android et que le multi-arch est natif.

        Par contre, si vous voulez passer à Qt6, la compilation multi-arch devient plus difficile car les architectures sont séparées. Je ne pense pas que CMake puisse à ce jour en générer des paquets (apk, aab).

        Autre info sur l'optimisation de la taille: Depuis Qt 6.3, l'analyseur de dépendances qml (qmpimportscanner) appelé par androiddeployqt est beaucoup plus efficace. Avant il "aspirait" indistinctement la totalité des sous répertoires d'une ressource qml.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.