Vérifications Dexpreopt et <uses-library>

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Android 12 a apporté des modifications au système de construction pour la compilation AOT des 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 bris et suivez les recettes de cette page pour les réparer et les atténuer.

Dexpreopt est le processus de compilation anticipée des bibliothèques et des applications Java. Dexpreopt se produit sur l'hôte au moment de la construction (par opposition à dexopt , qui se produit sur l'appareil). La structure des dépendances de bibliothèques partagées utilisées 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 dexpreopt, les CLC de construction et d'exécution doivent coïncider. Le CLC au moment de la construction est ce que le compilateur dex2oat utilise au moment de 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 de construction et d'exécution doivent coïncider pour des raisons à la fois d'exactitude et de performances. Pour être correct, 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 des classes peuvent être résolues différemment, provoquant des bogues d'exécution subtils. Les performances sont également affectées par les vérifications d'exécution des 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 incompatibilité entre les CLC de construction et d'exécution, il rejette les artefacts dexpreopt et exécute dexopt à la place. Pour les démarrages suivants, cela convient car les applications peuvent être supprimées en arrière-plan et stockées sur le disque.

Zones affecté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 a des milliers d'applications, et des centaines d'entre elles utilisent des bibliothèques partagées. Les partenaires sont également concernés, car ils ont leurs propres bibliothèques et applications.

Changements avec rupture

Le système de construction doit connaître les dépendances <uses-library> avant de générer des règles de construction 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 construction n'est pas autorisé à lire des fichiers arbitraires lorsqu'il génère des règles de construction (pour des raisons de performances). De plus, le manifeste peut être intégré à un APK ou à un pré-construit. 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 des bibliothèques partagées (appelée &-classpath ). Cela n'était pas sûr et provoquait des bogues subtils, de sorte que la solution de contournement a é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 construction peuvent provoquer des ruptures de construction (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 démarrage décalage suivi de dexopt).

Chemin de migration

Suivez ces étapes pour réparer une version endommagé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 construction (sauf cas particuliers, répertoriés dans la section Corriger les pannes ). Cependant, il s'agit d'une solution de contournement temporaire qui peut entraîner une incompatibilité CLC au démarrage suivie de 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 Corriger les ruptures pour plus de détails). Pour la plupart des modules, cela nécessite d'ajouter quelques lignes dans Android.bp , ou dans Android.mk .

  3. Désactivez la vérification au moment de la construction et dexpreopt 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 qui sont 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 (à cause 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> . Fichier bugs si nécessaire.

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

Réparer les bris

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

Erreur de génération : non-concordance CLC

Le système de génération effectue une vérification de cohérence au moment de la génération entre les informations des fichiers Android.bp ou Android.mk et le manifeste. Le système de construction ne peut pas lire le manifeste, mais il peut générer des règles de construction pour lire le manifeste (en l'extrayant d'un APK si nécessaire) et comparer les balises <uses-library> dans le 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 la cohérence au moment de la construction est toujours effectuée, mais un échec de la vérification ne signifie pas un échec de la construction. Au lieu de cela, un échec de vérification oblige le système de construction à rétrograder le filtre du compilateur dex2oat pour le 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 une solution permettant de corriger l'erreur à l'origine , informez le système de génération 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 d' 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 construction).

    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 solution, ajoutez uses_libs: ["<library-module-name>"] pour les bibliothèques requises, ou optional_uses_libs: ["<library-module-name>"] pour les bibliothèques facultatives à Android.bp définition du module. Ces propriétés acceptent une liste de noms de module. L'ordre relatif des bibliothèques dans 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 construction). Si c'est le cas, corrigez cela 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 car 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 module. L'ordre relatif des bibliothèques dans la liste doit être le même que dans le manifeste.

Erreur de compilation : chemin de bibliothèque inconnu

Si le système de construction ne trouve pas de chemin vers un jar <uses-library> DEX (soit un chemin de construction sur l'hôte, soit un chemin d'installation sur l'appareil), il échoue généralement la construction. L'échec de la recherche d'un chemin peut indiquer que la bibliothèque est configurée de manière inattendue. Corrigez temporairement la construction 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 de module) :

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Signalez un bogue pour enquêter sur les scénarios non pris en charge.

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

Une tentative d'ajout de <uses-library> X du manifeste du module Y au fichier de construction pour Y peut entraîner une erreur de construction 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 de son module correspondant dans le système de construction. Par exemple, si l'entrée manifeste <uses-library> est com.android.X , mais que le nom du module de bibliothèque est juste X , cela provoque une erreur. Pour résoudre ce cas, indiquez au système de construction que le module nommé X fournit un <uses-library> nommé com.android.X .

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

provides_uses_lib: “com.android.X”,

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

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Non-concordance 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 avoir des messages de la forme montrée ici :

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

Si vous obtenez 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 se peut que le vôtre soit un cas particulier qui n'est pas pris en charge par le système de construction (comme une application qui charge un autre APK, pas une bibliothèque). Le système de construction 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 classe. Le système de construction utilise CLC dans un sens étroit (il ne couvre que les bibliothèques, pas les APK ou les chargeurs de classes personnalisées) : c'est un arbre 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'un arbre 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 dirigé, 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épendance "déplié" à un 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 (d'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> du manifeste du module en tant qu'éléments CLC de niveau supérieur.

Pour chaque bibliothèque utilisée, PackageManager obtient 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é vs statique). Dans 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 lors 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 génération)

CLC n'est pas seulement nécessaire lors du chargement d'une bibliothèque ou d'une application, il est également nécessaire lors de la compilation d'une. La compilation peut avoir lieu soit sur l'appareil (dexopt), soit pendant la construction (dexpreopt). Étant donné que dexopt a lieu sur l'appareil, il contient les mêmes informations que PackageManager (manifestes et dépendances de bibliothèque partagée). Dexpreopt, cependant, se déroule sur l'hôte et dans un environnement totalement différent, et il 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 de construction et d'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 = '

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

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

L'incompatibilité est mauvaise pour les performances, car elle oblige la bibliothèque ou l'application à être dexoptée ou à 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 de construction et d'exécution (la bibliothèque facultative est présente dans un cas, mais pas dans l'autre), les CLC de construction et d'exécution ne correspondent pas et le code compilé est rejeté.

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

Parfois, les balises <uses-library> manquent dans le 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 <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 les entrées du manifeste ; il 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.