Dexpreopt y <uses-library> cheques

Android 12 tiene cambios en el sistema de compilación en la compilación AOT de archivos DEX (dexpreopt) para módulos Java que tienen dependencias <uses-library> . En algunos casos, estos cambios en el sistema de compilación pueden interrumpir las compilaciones. Utilice esta página para prepararse para las roturas y siga las recetas de esta página para solucionarlas y mitigarlas.

Dexpreopt es el proceso de compilación anticipada de bibliotecas y aplicaciones Java. Dexpreopt ocurre en el host en el momento de la compilación (a diferencia de dexopt , que ocurre en el dispositivo). La estructura de dependencias de biblioteca compartida utilizada por un módulo Java (una biblioteca o una aplicación) se conoce como contexto de cargador de clases (CLC). Para garantizar la exactitud de dexpreopt, los CLC de tiempo de compilación y de ejecución deben coincidir. CLC en tiempo de compilación es lo que usa el compilador dex2oat en el momento dexpreopt (se registra en los archivos ODEX), y CLC en tiempo de ejecución es el contexto en el que se carga el código precompilado en el dispositivo.

Estos CLC de tiempo de construcción y tiempo de ejecución deben coincidir por razones tanto de corrección como de rendimiento. Para que sea correcto, es necesario manejar clases duplicadas. Si las dependencias de la biblioteca compartida en tiempo de ejecución son diferentes a las utilizadas para la compilación, algunas de las clases podrían resolverse de manera diferente, provocando errores sutiles en tiempo de ejecución. El rendimiento también se ve afectado por las comprobaciones en tiempo de ejecución de clases duplicadas.

Casos de uso afectados

El primer arranque es el caso de uso principal afectado por estos cambios: si ART detecta una discrepancia entre los CLC en tiempo de compilación y en tiempo de ejecución, rechaza los artefactos dexpreopt y ejecuta dexopt en su lugar. Para arranques posteriores, esto está bien porque las aplicaciones se pueden dexoptar en segundo plano y almacenar en el disco.

Áreas afectadas de Android

Esto afecta a todas las aplicaciones y bibliotecas de Java que dependen del tiempo de ejecución de otras bibliotecas de Java. Android tiene miles de aplicaciones y cientos de ellas utilizan bibliotecas compartidas. Los socios también se ven afectados, ya que tienen sus propias bibliotecas y aplicaciones.

cambios de pausa

El sistema de compilación necesita conocer las dependencias <uses-library> antes de generar reglas de compilación dexpreopt. Sin embargo, no puede acceder al manifiesto directamente y leer las etiquetas <uses-library> que contiene, porque el sistema de compilación no puede leer archivos arbitrarios cuando genera reglas de compilación (por razones de rendimiento). Además, el manifiesto puede estar empaquetado dentro de un APK o prediseñado. Por lo tanto, la información <uses-library> debe estar presente en los archivos de compilación ( Android.bp o Android.mk ).

Anteriormente, ART utilizaba una solución alternativa que ignoraba las dependencias de bibliotecas compartidas (conocida como &-classpath ). Esto no era seguro y causaba errores sutiles, por lo que la solución alternativa se eliminó en Android 12.

Como resultado, los módulos Java que no proporcionan información <uses-library> correcta en sus archivos de compilación pueden causar fallas en la compilación (causadas por una discrepancia en el CLC en el momento de la compilación) o regresiones en el tiempo del primer arranque (causadas por un CLC en el tiempo de arranque). desajuste seguido de dexopt).

Ruta de migración

Siga estos pasos para arreglar una compilación rota:

  1. Deshabilite globalmente la verificación del tiempo de compilación para un producto en particular configurando

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    en el archivo MAKE del producto. Esto corrige errores de compilación (excepto en casos especiales, enumerados en la sección Reparación de roturas ). Sin embargo, esta es una solución temporal y puede provocar que el CLC en el momento del arranque no coincida seguido de dexopt.

  2. Repare los módulos que fallaron antes de deshabilitar globalmente la verificación del tiempo de compilación agregando la información <uses-library> necesaria a sus archivos de compilación (consulte Reparación de roturas para obtener más detalles). Para la mayoría de los módulos, esto requiere agregar algunas líneas en Android.bp o en Android.mk .

  3. Deshabilite la verificación en tiempo de compilación y dexpreopt para los casos problemáticos, por módulo. Desactive dexpreopt para no perder tiempo de compilación y almacenamiento en artefactos que se rechazan en el arranque.

  4. Vuelva a habilitar globalmente la verificación del tiempo de compilación desarmantando PRODUCT_BROKEN_VERIFY_USES_LIBRARIES que se configuró en el Paso 1; la compilación no debería fallar después de este cambio (debido a los pasos 2 y 3).

  5. Repare los módulos que deshabilitó en el Paso 3, uno a la vez, luego vuelva a habilitar dexpreopt y la verificación <uses-library> . Archivar errores si es necesario.

Las comprobaciones <uses-library> en tiempo de compilación se aplican en Android 12.

Reparar roturas

Las siguientes secciones le indican cómo reparar tipos específicos de roturas.

Error de compilación: CLC no coincide

El sistema de compilación realiza una verificación de coherencia en tiempo de compilación entre la información de los archivos Android.bp o Android.mk y el manifiesto. El sistema de compilación no puede leer el manifiesto, pero puede generar reglas de compilación para leer el manifiesto (extrayéndolo de un APK si es necesario) y comparar las etiquetas <uses-library> en el manifiesto con la información <uses-library> en los archivos de compilación. Si la verificación falla, el error se verá así:

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

Como sugiere el mensaje de error, existen múltiples soluciones, según la urgencia:

  • Para una solución temporal para todo el producto , establezca PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true en el archivo MAKE del producto. La verificación de coherencia en tiempo de compilación aún se realiza, pero una falla en la verificación no significa una falla en la compilación. En cambio, una falla en la verificación hace que el sistema de compilación degrade el filtro del compilador dex2oat para verify en dexpreopt, lo que deshabilita completamente la compilación AOT para este módulo.
  • Para una solución rápida y global desde la línea de comandos , utilice la variable de entorno RELAX_USES_LIBRARY_CHECK=true . Tiene el mismo efecto que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , pero está diseñado para usarse en la línea de comandos. La variable de entorno anula la variable de producto.
  • Para encontrar una solución para corregir la causa raíz del error, informe al sistema de compilación de las etiquetas <uses-library> en el manifiesto. Una inspección del mensaje de error muestra qué bibliotecas causan el problema (al igual que la inspección AndroidManifest.xml o el manifiesto dentro de un APK que se puede verificar con ` aapt dump badging $APK | grep uses-library `).

Para módulos Android.bp :

  1. Busque la biblioteca que falta en la propiedad libs del módulo. Si está ahí, Soong normalmente agrega dichas bibliotecas automáticamente, excepto en estos casos especiales:

    • La biblioteca no es una biblioteca SDK (se define como java_library en lugar de java_sdk_library ).
    • La biblioteca tiene un nombre de biblioteca diferente (en el manifiesto) del nombre de su módulo (en el sistema de compilación).

    Para solucionar este problema temporalmente, agregue provides_uses_lib: "<library-name>" en la definición de la biblioteca Android.bp . Para una solución a largo plazo, solucione el problema subyacente: convierta la biblioteca en una biblioteca SDK o cambie el nombre de su módulo.

  2. Si el paso anterior no proporcionó una solución, agregue uses_libs: ["<library-module-name>"] para las bibliotecas requeridas, u optional_uses_libs: ["<library-module-name>"] para las bibliotecas opcionales en Android.bp definición Android.bp del módulo. Estas propiedades aceptan una lista de nombres de módulos. El orden relativo de las bibliotecas en la lista debe ser el mismo que el orden en el manifiesto.

Para módulos Android.mk :

  1. Compruebe si la biblioteca tiene un nombre de biblioteca diferente (en el manifiesto) del nombre de su módulo (en el sistema de compilación). Si es así, solucione este problema temporalmente agregando LOCAL_PROVIDES_USES_LIBRARY := <library-name> en el archivo Android.mk de la biblioteca, o agregue provides_uses_lib: "<library-name>" en el archivo Android.bp de la biblioteca (ambos casos son posibles ya que un módulo Android.mk puede depender de una biblioteca Android.bp ). Para una solución a largo plazo, solucione el problema subyacente: cambie el nombre del módulo de biblioteca.

  2. Agregue LOCAL_USES_LIBRARIES := <library-module-name> para las bibliotecas requeridas; agregue LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> para bibliotecas opcionales a la definición de Android.mk del módulo. Estas propiedades aceptan una lista de nombres de módulos. El orden relativo de las bibliotecas en la lista debe ser el mismo que en el manifiesto.

Error de compilación: ruta de biblioteca desconocida

Si el sistema de compilación no puede encontrar una ruta a un archivo DEX <uses-library> (ya sea una ruta de tiempo de compilación en el host o una ruta de instalación en el dispositivo), generalmente falla la compilación. Si no se encuentra una ruta, puede indicar que la biblioteca está configurada de alguna manera inesperada. Corrija temporalmente la compilación deshabilitando dexpreopt para el módulo problemático.

Android.bp (propiedades del módulo):

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

Android.mk (variables del módulo):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Presente un error para investigar cualquier escenario no compatible.

Error de compilación: falta dependencia de biblioteca

Un intento de agregar <uses-library> X desde el manifiesto del módulo Y al archivo de compilación para Y podría resultar en un error de compilación debido a la dependencia faltante, X.

Este es un mensaje de error de muestra para los módulos Android.bp:

"Y" depends on undefined module "X"

Este es un mensaje de error de muestra para los módulos 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

Una fuente común de este tipo de errores es cuando una biblioteca tiene un nombre diferente al de su módulo correspondiente en el sistema de compilación. Por ejemplo, si la entrada del manifiesto <uses-library> es com.android.X , pero el nombre del módulo de la biblioteca es solo X , se produce un error. Para resolver este caso, informe al sistema de compilación que el módulo denominado X proporciona una <uses-library> denominada com.android.X .

Este es un ejemplo para las bibliotecas Android.bp (propiedad del módulo):

provides_uses_lib: “com.android.X”,

Este es un ejemplo de bibliotecas Android.mk (variable de módulo):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

No coincide el CLC en el momento del arranque

En el primer arranque, busque en logcat mensajes relacionados con la discrepancia de CLC, como se muestra a continuación:

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

La salida puede tener mensajes del formato que se muestra aquí:

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

Si recibe una advertencia de discrepancia de CLC, busque un comando dexopt para el módulo defectuoso. Para solucionarlo, asegúrese de que pase la verificación del tiempo de compilación del módulo. Si eso no funciona, entonces el suyo podría ser un caso especial que no es compatible con el sistema de compilación (como una aplicación que carga otro APK, no una biblioteca). El sistema de compilación no maneja todos los casos, porque en el momento de la compilación es imposible saber con certeza qué carga la aplicación en tiempo de ejecución.

Contexto del cargador de clases

El CLC es una estructura en forma de árbol que describe la jerarquía del cargador de clases. El sistema de compilación usa CLC en un sentido estricto (cubre solo bibliotecas, no APK ni cargadores de clases personalizados): es un árbol de bibliotecas que representa el cierre transitivo de todas las dependencias <uses-library> de una biblioteca o aplicación. Los elementos de nivel superior de un CLC son las dependencias <uses-library> directas especificadas en el manifiesto (el classpath). Cada nodo de un árbol CLC es un nodo <uses-library> que puede tener sus propios subnodos <uses-library> .

Debido a que las dependencias <uses-library> son un gráfico acíclico dirigido y no necesariamente un árbol, CLC puede contener múltiples subárboles para la misma biblioteca. En otras palabras, CLC es el gráfico de dependencia "desplegado" en un árbol. La duplicación es sólo a nivel lógico; los cargadores de clases subyacentes reales no están duplicados (en tiempo de ejecución hay una única instancia de cargador de clases para cada biblioteca).

CLC define el orden de búsqueda de las bibliotecas al resolver las clases Java utilizadas por la biblioteca o aplicación. El orden de búsqueda es importante porque las bibliotecas pueden contener clases duplicadas y la clase se resuelve en la primera coincidencia.

CLC en el dispositivo (tiempo de ejecución)

PackageManager (en frameworks/base ) crea un CLC para cargar un módulo Java en el dispositivo. Agrega las bibliotecas enumeradas en las etiquetas <uses-library> en el manifiesto del módulo como elementos CLC de nivel superior.

Para cada biblioteca utilizada, PackageManager obtiene todas sus dependencias <uses-library> (especificadas como etiquetas en el manifiesto de esa biblioteca) y agrega un CLC anidado para cada dependencia. Este proceso continúa de forma recursiva hasta que todos los nodos hoja del árbol CLC construido sean bibliotecas sin dependencias <uses-library> .

PackageManager solo reconoce las bibliotecas compartidas. La definición de compartido en este uso difiere de su significado habitual (como en compartido versus estático). En Android, las bibliotecas compartidas de Java son aquellas que figuran en las configuraciones XML que están instaladas en el dispositivo ( /system/etc/permissions/platform.xml ). Cada entrada contiene el nombre de una biblioteca compartida, una ruta a su archivo jar DEX y una lista de dependencias (otras bibliotecas compartidas que ésta usa en tiempo de ejecución y especifica en las etiquetas <uses-library> en su manifiesto).

En otras palabras, hay dos fuentes de información que permiten PackageManager construir CLC en tiempo de ejecución: etiquetas <uses-library> en el manifiesto y dependencias de biblioteca compartida en configuraciones XML.

CLC en el host (en el momento de la compilación)

CLC no solo es necesario al cargar una biblioteca o una aplicación, también es necesario al compilar una. La compilación puede realizarse en el dispositivo (dexopt) o durante la compilación (dexpreopt). Dado que dexopt se lleva a cabo en el dispositivo, tiene la misma información que PackageManager (manifiestos y dependencias de biblioteca compartida). Dexpreopt, sin embargo, se lleva a cabo en el host y en un entorno totalmente diferente, y tiene que obtener la misma información del sistema de compilación.

Por lo tanto, el CLC en tiempo de compilación usado por dexpreopt y el CLC en tiempo de ejecución usado por PackageManager son lo mismo, pero se calculan de dos maneras diferentes.

Los CLC en tiempo de compilación y en tiempo de ejecución deben coincidir; de lo contrario, el código compilado con AOT creado por dexpreopt será rechazado. Para verificar la igualdad de los CLC en tiempo de compilación y en tiempo de ejecución, el compilador dex2oat registra el CLC en tiempo de compilación en los archivos *.odex (en el campo classpath del encabezado del archivo OAT). Para encontrar el CLC almacenado, use este comando:

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

Durante el arranque, se informa en logcat una discrepancia entre el CLC en tiempo de compilación y en tiempo de ejecución. Búscalo con este comando:

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

La falta de coincidencia es mala para el rendimiento, ya que obliga a que la biblioteca o aplicación se dexopte o se ejecute sin optimizaciones (por ejemplo, es posible que sea necesario extraer el código de la aplicación en la memoria del APK, una operación muy costosa).

Una biblioteca compartida puede ser opcional o obligatoria. Desde el punto de vista de dexpreopt, una biblioteca requerida debe estar presente en el momento de la compilación (su ausencia es un error de compilación). Una biblioteca opcional puede estar presente o ausente en el momento de la compilación: si está presente, se agrega al CLC, se pasa a dex2oat y se registra en el archivo *.odex . Si falta una biblioteca opcional, se omite y no se agrega al CLC. Si hay una discrepancia entre el estado del tiempo de compilación y del tiempo de ejecución (la biblioteca opcional está presente en un caso, pero no en el otro), entonces los CLC del tiempo de compilación y del tiempo de ejecución no coinciden y el código compilado se rechaza.

Detalles avanzados del sistema de compilación (reparador de manifiestos)

A veces faltan etiquetas <uses-library> en el manifiesto fuente de una biblioteca o aplicación. Esto puede suceder, por ejemplo, si una de las dependencias transitivas de la biblioteca o aplicación comienza a usar otra etiqueta <uses-library> y el manifiesto de la biblioteca o aplicación no se actualiza para incluirla.

Soong puede calcular automáticamente algunas de las etiquetas <uses-library> que faltan para una biblioteca o aplicación determinada, como las bibliotecas del SDK en el cierre de dependencia transitiva de la biblioteca o aplicación. El cierre es necesario porque la biblioteca (o aplicación) podría depender de una biblioteca estática que depende de una biblioteca SDK y posiblemente podría volver a depender transitivamente a través de otra biblioteca.

No todas las etiquetas <uses-library> se pueden calcular de esta manera, pero cuando sea posible, es preferible dejar que Soong agregue entradas de manifiesto automáticamente; es menos propenso a errores y simplifica el mantenimiento. Por ejemplo, cuando muchas aplicaciones usan una biblioteca estática que agrega una nueva dependencia <uses-library> , todas las aplicaciones deben actualizarse, lo cual es difícil de mantener.