Dexpreopt et <uses-library> chèques

Android 12 a apporté des modifications au système de compilation AOT de fichiers DEX (dexpreopt) pour les modules Java qui ont des dépendances <uses-library> . Dans certains cas, ces modifications du système de build peuvent interrompre les builds. Utilisez cette page pour vous préparer aux casses et suivez les recettes sur cette page pour les réparer et les atténuer.

Dexpreopt est le processus de compilation anticipée de bibliothèques et d'applications Java. Dexpreopt se produit sur l'hôte au moment de la construction (contrairement à dexopt , qui se produit sur l'appareil). La structure des dépendances de bibliothèque partagée utilisée par un module Java (une bibliothèque ou une application) est connue sous le nom de contexte de chargeur de classe (CLC). Pour garantir l'exactitude de dxpreopt, les CLC au moment de la construction et de l'exécution doivent coïncider. Le CLC au moment de la construction est ce que le compilateur dex2oat utilise au moment dexpreopt (il est enregistré dans les fichiers ODEX), et le CLC au moment de l'exécution est le contexte dans lequel le code précompilé est chargé sur l'appareil.

Ces CLC au moment de la construction et au moment de l'exécution doivent coïncider pour des raisons à la fois d'exactitude et de performances. Pour plus d'exactitude, il est nécessaire de gérer les classes en double. Si les dépendances de la bibliothèque partagée au moment de l'exécution sont différentes de celles utilisées pour la compilation, certaines classes peuvent être résolues différemment, provoquant de subtils bogues d'exécution. Les performances sont également affectées par les vérifications d'exécution pour les classes en double.

Cas d'utilisation concernés

Le premier démarrage est le principal cas d'utilisation affecté par ces modifications : si ART détecte une inadéquation entre les CLC au moment de la construction et au moment de l'exécution, il rejette les artefacts dexpreopt et exécute dexopt à la place. Pour les démarrages ultérieurs, cela convient car les applications peuvent être désoptées en arrière-plan et stockées sur le disque.

Zones concernées d'Android

Cela affecte toutes les applications et bibliothèques Java qui ont des dépendances d'exécution sur d'autres bibliothèques Java. Android propose des milliers d'applications, et des centaines d'entre elles utilisent des bibliothèques partagées. Les partenaires sont également concernés, car ils disposent de leurs propres bibliothèques et applications.

Annuler les modifications

Le système de build doit connaître les dépendances <uses-library> avant de générer des règles de build dexpreopt. Cependant, il ne peut pas accéder directement au manifeste et lire les balises <uses-library> qu'il contient, car le système de build n'est pas autorisé à lire des fichiers arbitraires lorsqu'il génère des règles de build (pour des raisons de performances). De plus, le manifeste peut être intégré à un APK ou à un fichier prédéfini. Par conséquent, les informations <uses-library> doivent être présentes dans les fichiers de build ( Android.bp ou Android.mk ).

Auparavant, ART utilisait une solution de contournement qui ignorait les dépendances de bibliothèque partagée (connue sous le nom de &-classpath ). Ceci n’était pas sûr et provoquait des bugs subtils, la solution de contournement a donc été supprimée dans Android 12.

Par conséquent, les modules Java qui ne fournissent pas d'informations <uses-library> correctes dans leurs fichiers de build peuvent provoquer des interruptions de build (causées par une incompatibilité CLC au moment de la construction) ou des régressions au moment du premier démarrage (causées par un CLC au moment du démarrage). inadéquation suivie de dexopt).

Chemin de migration

Suivez ces étapes pour réparer une version cassée :

  1. Désactivez globalement la vérification au moment de la construction pour un produit particulier en définissant

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    dans le makefile du produit. Cela corrige les erreurs de build (sauf cas particuliers, répertoriés dans la section Correction des casses ). Cependant, il s'agit d'une solution de contournement temporaire qui peut entraîner une incompatibilité CLC au démarrage, suivie d'un dexopt.

  2. Corrigez les modules qui ont échoué avant de désactiver globalement la vérification au moment de la construction en ajoutant les informations <uses-library> nécessaires à leurs fichiers de construction (voir Correction des pannes pour plus de détails). Pour la plupart des modules, cela nécessite l'ajout de quelques lignes dans Android.bp ou dans Android.mk .

  3. Désactivez la vérification au moment de la construction et l'option d'expreopt pour les cas problématiques, module par module. Désactivez dexpreopt afin de ne pas perdre de temps de construction et de stockage sur des artefacts rejetés au démarrage.

  4. Réactivez globalement la vérification au moment de la construction en désactivant PRODUCT_BROKEN_VERIFY_USES_LIBRARIES qui a été défini à l'étape 1 ; la construction ne devrait pas échouer après ce changement (en raison des étapes 2 et 3).

  5. Corrigez les modules que vous avez désactivés à l'étape 3, un par un, puis réactivez dexpreopt et la vérification <uses-library> . Enregistrez les bugs si nécessaire.

Les vérifications <uses-library> au moment de la construction sont appliquées dans Android 12.

Réparer les casses

Les sections suivantes vous expliquent comment réparer des types spécifiques de casse.

Erreur de construction : incompatibilité CLC

Le système de build effectue une vérification de cohérence au moment de la construction entre les informations contenues dans les fichiers Android.bp ou Android.mk et le manifeste. Le système de build ne peut pas lire le manifeste, mais il peut générer des règles de build pour lire le manifeste (en l'extrayant d'un APK si nécessaire) et comparer les balises <uses-library> du manifeste avec les informations <uses-library> dans les fichiers de construction. Si la vérification échoue, l'erreur ressemble à ceci :

error: mismatch in the <uses-library> tags between the build system and the manifest:
    - required libraries in build system: []
                     vs. in the manifest: [org.apache.http.legacy]
    - optional libraries in build system: []
                     vs. in the manifest: [com.x.y.z]
    - tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
        <uses-library android:name="com.x.y.z"/>
        <uses-library android:name="org.apache.http.legacy"/>

note: the following options are available:
    - to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
    - to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
    - to fix the check, make build system properties coherent with the manifest
    - see build/make/Changes.md for details

Comme le suggère le message d'erreur, il existe plusieurs solutions, selon l'urgence :

  • Pour un correctif temporaire à l'échelle du produit , définissez PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true dans le makefile du produit. La vérification de cohérence au moment de la construction est toujours effectuée, mais un échec de vérification ne signifie pas un échec de construction. Au lieu de cela, un échec de vérification oblige le système de construction à rétrograder le filtre du compilateur dex2oat pour verify dans dexpreopt, ce qui désactive entièrement la compilation AOT pour ce module.
  • Pour un correctif rapide et global en ligne de commande , utilisez la variable d'environnement RELAX_USES_LIBRARY_CHECK=true . Il a le même effet que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , mais destiné à être utilisé sur la ligne de commande. La variable d'environnement remplace la variable de produit.
  • Pour trouver une solution à la cause première de l’erreur, informez le système de build des balises <uses-library> dans le manifeste. Une inspection du message d'erreur montre quelles bibliothèques sont à l'origine du problème (tout comme l'inspection de AndroidManifest.xml ou du manifeste à l'intérieur d'un APK qui peut être vérifié avec ` aapt dump badging $APK | grep uses-library `).

Pour les modules Android.bp :

  1. Recherchez la bibliothèque manquante dans la propriété libs du module. Si c'est le cas, Soong ajoute normalement ces bibliothèques automatiquement, sauf dans ces cas particuliers :

    • La bibliothèque n'est pas une bibliothèque SDK (elle est définie comme java_library plutôt que java_sdk_library ).
    • La bibliothèque a un nom de bibliothèque différent (dans le manifeste) de son nom de module (dans le système de build).

    Pour résoudre ce problème temporairement, ajoutez provides_uses_lib: "<library-name>" dans la définition de la bibliothèque Android.bp . Pour une solution à long terme, corrigez le problème sous-jacent : convertissez la bibliothèque en bibliothèque SDK ou renommez son module.

  2. Si l'étape précédente n'a pas fourni de résolution, ajoutez uses_libs: ["<library-module-name>"] pour les bibliothèques requises, ou optional_uses_libs: ["<library-module-name>"] pour les bibliothèques facultatives sur Android.bp définition du module. Ces propriétés acceptent une liste de noms de modules. L'ordre relatif des bibliothèques sur la liste doit être le même que l'ordre dans le manifeste.

Pour les modules Android.mk :

  1. Vérifiez si la bibliothèque a un nom de bibliothèque différent (dans le manifeste) de son nom de module (dans le système de build). Si c'est le cas, corrigez ce problème temporairement en ajoutant LOCAL_PROVIDES_USES_LIBRARY := <library-name> dans le fichier Android.mk de la bibliothèque, ou ajoutez provides_uses_lib: "<library-name>" dans le fichier Android.bp de la bibliothèque (les deux cas sont possibles puisqu'un module Android.mk peut dépendre d'une bibliothèque Android.bp ). Pour une solution à long terme, corrigez le problème sous-jacent : renommez le module de bibliothèque.

  2. Ajoutez LOCAL_USES_LIBRARIES := <library-module-name> pour les bibliothèques requises ; ajoutez LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> pour les bibliothèques facultatives à la définition Android.mk du module. Ces propriétés acceptent une liste de noms de modules. L'ordre relatif des bibliothèques sur la liste doit être le même que dans le manifeste.

Erreur de build : chemin de bibliothèque inconnu

Si le système de build ne trouve pas de chemin vers un fichier jar DEX <uses-library> (soit un chemin de construction sur l'hôte, soit un chemin d'installation sur l'appareil), la construction échoue généralement. L'échec de la recherche d'un chemin peut indiquer que la bibliothèque est configurée d'une manière inattendue. Corrigez temporairement la version en désactivant dexpreopt pour le module problématique.

Android.bp (propriétés du module) :

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (variables du module) :

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Déposez un bug pour enquêter sur tout scénario non pris en charge.

Erreur de build : dépendance de bibliothèque manquante

Une tentative d'ajout <uses-library> X du manifeste du module Y au fichier de build pour Y peut entraîner une erreur de build en raison de la dépendance manquante, X.

Voici un exemple de message d'erreur pour les modules Android.bp :

"Y" depends on undefined module "X"

Voici un exemple de message d'erreur pour les modules Android.mk :

'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it

Une source courante de telles erreurs est lorsqu'une bibliothèque est nommée différemment du nom de son module correspondant dans le système de construction. Par exemple, si l'entrée du manifeste <uses-library> est com.android.X , mais que le nom du module de bibliothèque est simplement X , cela provoque une erreur. Pour résoudre ce cas, indiquez au système de build que le module nommé X fournit une <uses-library> nommée com.android.X .

Ceci est un exemple pour les bibliothèques Android.bp (propriété du module) :

provides_uses_lib: “com.android.X”,

Ceci est un exemple pour les bibliothèques Android.mk (variable de module) :

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Incompatibilité CLC au démarrage

Au premier démarrage, recherchez dans logcat les messages liés à l'incompatibilité CLC, comme indiqué ci-dessous :

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

La sortie peut contenir des messages du type affiché ici :

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

Si vous recevez un avertissement de non-concordance CLC, recherchez une commande dexopt pour le module défectueux. Pour résoudre ce problème, assurez-vous que la vérification au moment de la construction du module réussit. Si cela ne fonctionne pas, il s'agit peut-être d'un cas particulier qui n'est pas pris en charge par le système de build (comme une application qui charge un autre APK, pas une bibliothèque). Le système de build ne gère pas tous les cas, car au moment de la construction, il est impossible de savoir avec certitude ce que l'application charge au moment de l'exécution.

Contexte du chargeur de classe

Le CLC est une structure arborescente qui décrit la hiérarchie des chargeurs de classes. Le système de build utilise CLC au sens étroit (il couvre uniquement les bibliothèques, pas les APK ou les chargeurs de classes personnalisées) : c'est une arborescence de bibliothèques qui représente la fermeture transitive de toutes les dépendances <uses-library> d'une bibliothèque ou d'une application. Les éléments de niveau supérieur d'un CLC sont les dépendances directes <uses-library> spécifiées dans le manifeste (le chemin de classe). Chaque nœud d'une arborescence CLC est un nœud <uses-library> qui peut avoir ses propres sous-nœuds <uses-library> .

Étant donné que les dépendances <uses-library> sont un graphe acyclique orienté et pas nécessairement un arbre, CLC peut contenir plusieurs sous-arbres pour la même bibliothèque. En d’autres termes, CLC est le graphe de dépendances « déplié » en arbre. La duplication n'est qu'à un niveau logique ; les chargeurs de classe sous-jacents réels ne sont pas dupliqués (au moment de l'exécution, il existe une seule instance de chargeur de classe pour chaque bibliothèque).

CLC définit l'ordre de recherche des bibliothèques lors de la résolution des classes Java utilisées par la bibliothèque ou l'application. L'ordre de recherche est important car les bibliothèques peuvent contenir des classes en double et la classe est résolue à la première correspondance.

Sur l'appareil (exécution) CLC

PackageManager (dans frameworks/base ) crée un CLC pour charger un module Java sur l'appareil. Il ajoute les bibliothèques répertoriées dans les balises <uses-library> dans le manifeste du module en tant qu'éléments CLC de niveau supérieur.

Pour chaque bibliothèque utilisée, PackageManager récupère toutes ses dépendances <uses-library> (spécifiées sous forme de balises dans le manifeste de cette bibliothèque) et ajoute un CLC imbriqué pour chaque dépendance. Ce processus se poursuit de manière récursive jusqu'à ce que tous les nœuds feuilles de l'arborescence CLC construite soient des bibliothèques sans dépendances <uses-library> .

PackageManager ne connaît que les bibliothèques partagées. La définition de partagé dans cet usage diffère de sa signification habituelle (comme dans partagé ou statique). Sous Android, les bibliothèques partagées Java sont celles répertoriées dans les configurations XML installées sur l'appareil ( /system/etc/permissions/platform.xml ). Chaque entrée contient le nom d'une bibliothèque partagée, un chemin d'accès à son fichier jar DEX et une liste de dépendances (autres bibliothèques partagées que celle-ci utilise au moment de l'exécution et spécifiées dans les balises <uses-library> de son manifeste).

En d'autres termes, il existe deux sources d'informations qui permettent à PackageManager de construire CLC au moment de l'exécution : les balises <uses-library> dans le manifeste et les dépendances de bibliothèque partagée dans les configurations XML.

CLC sur l'hôte (au moment de la construction)

CLC n'est pas seulement nécessaire lors du chargement d'une bibliothèque ou d'une application, il l'est également lors de sa compilation. La compilation peut avoir lieu soit sur l'appareil (dexopt), soit pendant la construction (dexpreopt). Étant donné que dexopt s'effectue sur l'appareil, il contient les mêmes informations que PackageManager (manifestes et dépendances de bibliothèque partagée). Dexpreopt, cependant, s'effectue sur l'hôte et dans un environnement totalement différent, et doit obtenir les mêmes informations du système de construction.

Ainsi, le CLC au moment de la construction utilisé par dexpreopt et le CLC au moment de l'exécution utilisé par PackageManager sont la même chose, mais calculés de deux manières différentes.

Les CLC au moment de la construction et de l'exécution doivent coïncider, sinon le code compilé par AOT créé par dexpreopt est rejeté. Pour vérifier l'égalité des CLC au moment de la construction et de l'exécution, le compilateur dex2oat enregistre le CLC au moment de la construction dans les fichiers *.odex (dans le champ classpath de l'en-tête du fichier OAT). Pour trouver le CLC stocké, utilisez cette commande :

oatdump --oat-file=<FILE> | grep '^classpath = '

Une incompatibilité CLC au moment de la construction et à l'exécution est signalée dans logcat lors du démarrage. Recherchez-le avec cette commande :

logcat | grep -E 'ClassLoaderContext [az ]+ mismatch'

L'inadéquation est mauvaise pour les performances, car elle oblige la bibliothèque ou l'application soit à être désoptée, soit à s'exécuter sans optimisations (par exemple, le code de l'application peut devoir être extrait en mémoire de l'APK, une opération très coûteuse).

Une bibliothèque partagée peut être facultative ou obligatoire. Du point de vue dexpreopt, une bibliothèque requise doit être présente au moment de la construction (son absence est une erreur de construction). Une bibliothèque facultative peut être présente ou absente au moment de la construction : si elle est présente, elle est ajoutée au CLC, transmise à dex2oat et enregistrée dans le fichier *.odex . Si une bibliothèque facultative est absente, elle est ignorée et n'est pas ajoutée au CLC. S'il y a une incompatibilité entre l'état au moment de la construction et celui de l'exécution (la bibliothèque facultative est présente dans un cas, mais pas dans l'autre), alors les CLC au moment de la construction et à l'exécution ne correspondent pas et le code compilé est rejeté.

Détails du système de construction avancé (fixateur de manifeste)

Parfois, les balises <uses-library> sont absentes du manifeste source d’une bibliothèque ou d’une application. Cela peut se produire, par exemple, si l'une des dépendances transitives de la bibliothèque ou de l'application commence à utiliser une autre balise <uses-library> et que le manifeste de la bibliothèque ou de l'application n'est pas mis à jour pour l'inclure.

Soong peut calculer automatiquement certaines des balises <uses-library> manquantes pour une bibliothèque ou une application donnée, comme les bibliothèques SDK dans la fermeture de dépendance transitive de la bibliothèque ou de l'application. La fermeture est nécessaire car la bibliothèque (ou l'application) peut dépendre d'une bibliothèque statique qui dépend d'une bibliothèque SDK, et peut éventuellement dépendre à nouveau de manière transitive via une autre bibliothèque.

Toutes les balises <uses-library> ne peuvent pas être calculées de cette façon, mais lorsque cela est possible, il est préférable de laisser Soong ajouter automatiquement des entrées de manifeste ; c'est moins sujet aux erreurs et simplifie la maintenance. Par exemple, lorsque de nombreuses applications utilisent une bibliothèque statique qui ajoute une nouvelle dépendance <uses-library> , toutes les applications doivent être mises à jour, ce qui est difficile à maintenir.