Comprobaciones de Dexpreopt y <uses-library>

Android 12 tiene cambios en el sistema de compilación para 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 de 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 bibliotecas compartidas que utiliza un módulo Java (una biblioteca o una aplicación) se conoce como su contexto de cargador de clases (CLC). Para garantizar la exactitud de dexpreopt, los CLC de tiempo de compilación y tiempo de ejecución deben coincidir. El CLC en tiempo de compilación es lo que usa el compilador dex2oat en el momento de dexpreopt (está registrado en los archivos ODEX), y el 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 compilación y tiempo de ejecución deben coincidir por motivos tanto de corrección como de rendimiento. Para la corrección, es necesario manejar clases duplicadas. Si las dependencias de la biblioteca compartida en el tiempo de ejecución son diferentes a las que se usan para la compilación, algunas de las clases pueden resolverse de manera diferente, lo que provoca errores sutiles en el 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 que se ve 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 pueden dexoptarse en segundo plano y almacenarse en el disco.

Áreas afectadas de Android

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

Rompiendo cambios

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> en él, porque el sistema de compilación no puede leer archivos arbitrarios cuando genera reglas de compilación (por motivos de rendimiento). Además, el manifiesto puede estar empaquetado dentro de un APK o precompilado. Por lo tanto, la información <uses-library> debe estar presente en los archivos de compilación ( Android.bp o Android.mk ).

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

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

Ruta de migración

Siga estos pasos para arreglar una compilación rota:

  1. Deshabilite globalmente la verificación de 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 causar una falta de coincidencia de CLC en el momento del arranque seguida de dexopt.

  2. Solucione los módulos que fallaron antes de que deshabilitara globalmente la verificación de tiempo de compilación agregando la información necesaria <uses-library> a sus archivos de compilación (consulte Corrección de roturas para obtener más información). 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. Deshabilite dexpreopt para no desperdiciar tiempo de compilación y almacenamiento en artefactos que se rechazan en el arranque.

  4. Vuelva a habilitar globalmente la comprobación de tiempo de compilación deshabilitando 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> . Archivo de errores si es necesario.

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

Reparación de roturas

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

Error de compilación: discrepancia de CLC

El sistema de compilación realiza una verificación de coherencia en el tiempo de compilación entre la información en 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 etiquetas <uses-library> en el manifiesto con la información de <uses-library> en los archivos de compilación. Si la verificación falla, el error se ve 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, dependiendo de 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 el 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 de verificación hace que el sistema de compilación degrade el filtro del compilador dex2oat para verify en dexpreopt, lo que deshabilita la compilación AOT por completo para este módulo.
  • Para una solución de línea de comandos rápida y global , use 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 a 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 de 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á allí, 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) de su nombre de módulo (en el sistema de compilación).

    Para corregir esto temporalmente, agregue provide_uses_lib provides_uses_lib: "<library-name>" en la definición de la biblioteca de 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 brindó una solución, agregue uses_libs: ["<library-module-name>"] para las bibliotecas requeridas, o Optional_uses_libs: ["<library-module-name>"] para las bibliotecas optional_uses_libs: ["<library-module-name>"] de Android.bp definición 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) de su nombre de módulo (en el sistema de compilación). Si es así, arregle esto temporalmente agregando LOCAL_PROVIDES_USES_LIBRARY := <library-name> en el archivo Android.mk de la biblioteca, o agregue provide_uses_lib provides_uses_lib: "<library-name>" en el archivo Android.bp de la biblioteca (ambos casos son posibles ya que un módulo Android.mk podría depender de una biblioteca Android.bp ). Para una solución a largo plazo, solucione el problema subyacente: cambie el nombre del módulo de la 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 jar 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. Arregle temporalmente la compilación al deshabilitar dexpreopt para el módulo problemático.

Android.bp (propiedades del módulo):

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

Android.mk (variables de módulo):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Registre un error para investigar cualquier escenario no compatible.

Error de compilación: falta la dependencia de la biblioteca

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

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

"Y" depends on undefined module "X"

Este es un mensaje de error de muestra para los módulos de 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 tales 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 <uses-library> del manifiesto es com.android.X , pero el nombre del módulo de la biblioteca es solo X , se produce un error. Para resolver este caso, dígale 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 de Android.bp (propiedad del módulo):

provides_uses_lib: “com.android.X”,

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

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Discrepancia de CLC en el momento del arranque

En el primer arranque, busque en Logcat los mensajes relacionados con la falta de coincidencia 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 de la forma 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 comprobación de 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 similar a un á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 personalizadas): 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 directas <uses-library> especificadas en el manifiesto (el classpath). Cada nodo de un árbol CLC es un nodo <uses-library> que puede tener sus propios <uses-library> .

Debido a que las dependencias <uses-library> son un gráfico acíclico dirigido y no necesariamente un árbol, CLC puede contener varios subárboles para la misma biblioteca. En otras palabras, CLC es el gráfico de dependencia "desplegado" en un árbol. La duplicación es solo en un 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 de Java utilizadas por la biblioteca o la 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.

En el dispositivo (tiempo de ejecución) CLC

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 recursivamente 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 frente a estático). En Android, las bibliotecas compartidas de Java son las que se enumeran 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 esta 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 que PackageManager construya CLC en tiempo de ejecución: etiquetas <uses-library> en el manifiesto y dependencias de bibliotecas compartidas en configuraciones XML.

CLC en el host (tiempo de compilación)

CLC no solo se necesita al cargar una biblioteca o una aplicación, también se necesita al compilar una. La compilación puede ocurrir 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 bibliotecas compartidas). Sin embargo, Dexpreopt 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 de tiempo de compilación que usa dexpreopt y el CLC de tiempo de ejecución que usa 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, se rechazará el código compilado por AOT creado por dexpreopt. 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 = '

La discrepancia de CLC en tiempo de compilación y tiempo de ejecución se informa en logcat durante el arranque. 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 la aplicación se dexopten o se ejecuten sin optimizaciones (por ejemplo, es posible que el código de la aplicación deba extraerse en la memoria del APK, una operación muy costosa).

Una biblioteca compartida puede ser opcional u 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 no hay una biblioteca opcional, se omite y no se agrega al CLC. Si hay una discrepancia entre el estado de tiempo de compilación y tiempo de ejecución (la biblioteca opcional está presente en un caso, pero no en el otro), entonces los CLC de tiempo de compilación y tiempo de ejecución no coinciden y el código compilado se rechaza.

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

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

Soong puede calcular automáticamente algunas de las etiquetas <uses-library> faltan para una biblioteca o aplicación determinada, como las bibliotecas SDK en el cierre de dependencia transitivo 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.