Verificações Dexpreopt e <uses-library>

O Android 12 tem alterações no sistema de compilação para a compilação AOT de arquivos DEX (dexpreopt) para módulos Java que possuem dependências <uses-library> . Em alguns casos, essas alterações no sistema de compilação podem interromper as compilações. Use esta página para se preparar para quebras e siga as receitas nesta página para corrigi-las e mitigá-las.

Dexpreopt é o processo de compilação antecipada de bibliotecas e aplicativos Java. Dexpreopt acontece no host no momento da compilação (ao contrário de dexopt , que acontece no dispositivo). A estrutura de dependências de bibliotecas compartilhadas usadas por um módulo Java (uma biblioteca ou um aplicativo) é conhecida como seu contexto de carregador de classes (CLC). Para garantir a exatidão do dexpreopt, os CLCs de tempo de construção e tempo de execução devem coincidir. O CLC em tempo de construção é o que o compilador dex2oat usa no tempo dexpreopt (está registrado nos arquivos ODEX), e o CLC em tempo de execução é o contexto no qual o código pré-compilado é carregado no dispositivo.

Esses CLCs de tempo de construção e tempo de execução devem coincidir por motivos de correção e desempenho. Para correção, é necessário lidar com classes duplicadas. Se as dependências da biblioteca compartilhada em tempo de execução forem diferentes daquelas usadas para compilação, algumas das classes podem ser resolvidas de forma diferente, causando bugs sutis de tempo de execução. O desempenho também é afetado pelas verificações de tempo de execução para classes duplicadas.

Casos de uso afetados

A primeira inicialização é o principal caso de uso afetado por essas alterações: se o ART detectar uma incompatibilidade entre os CLCs de tempo de construção e de execução, ele rejeitará os artefatos dexpreopt e executará o dexopt. Para inicializações subsequentes, isso é bom porque os aplicativos podem ser removidos em segundo plano e armazenados em disco.

Áreas afetadas do Android

Isso afeta todos os aplicativos e bibliotecas Java que têm dependências de tempo de execução de outras bibliotecas Java. O Android tem milhares de aplicativos e centenas deles usam bibliotecas compartilhadas. Os parceiros também são afetados, pois têm suas próprias bibliotecas e aplicativos.

Mudanças de última hora

O sistema de compilação precisa conhecer as dependências <uses-library> antes de gerar regras de compilação dexpreopt. No entanto, ele não pode acessar o manifesto diretamente e ler as tags <uses-library> nele, porque o sistema de compilação não tem permissão para ler arquivos arbitrários quando gera regras de compilação (por motivos de desempenho). Além disso, o manifesto pode ser empacotado dentro de um APK ou pré-compilado. Portanto, as informações <uses-library> devem estar presentes nos arquivos de compilação ( Android.bp ou Android.mk ).

Anteriormente, o ART usava uma solução alternativa que ignorava as dependências de biblioteca compartilhada (conhecida como &-classpath ). Isso não era seguro e causava bugs sutis, então a solução alternativa foi removida no Android 12.

Como resultado, os módulos Java que não fornecem informações corretas <uses-library> em seus arquivos de compilação podem causar quebras de compilação (causadas por uma incompatibilidade de CLC de tempo de compilação) ou regressões de tempo de primeira inicialização (causadas por um CLC de inicialização). incompatibilidade seguida por dexopt).

Caminho de migração

Siga estas etapas para corrigir uma compilação quebrada:

  1. Desabilite globalmente a verificação de tempo de compilação para um produto específico definindo

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    no makefile do produto. Isso corrige erros de compilação (exceto em casos especiais, listados na seção Corrigindo quebras ). No entanto, esta é uma solução temporária e pode causar incompatibilidade de CLC no tempo de inicialização seguida de dexopt.

  2. Corrija os módulos que falharam antes de você desabilitar globalmente a verificação de tempo de compilação adicionando as informações <uses-library> necessárias aos seus arquivos de compilação (consulte Corrigindo quebras para obter detalhes). Para a maioria dos módulos, isso requer a adição de algumas linhas em Android.bp ou em Android.mk .

  3. Desative a verificação de tempo de construção e dexpreopt para os casos problemáticos, por módulo. Desative o dexpreopt para não perder tempo de compilação e armazenamento em artefatos que são rejeitados na inicialização.

  4. Reative globalmente a verificação de tempo de compilação desativando PRODUCT_BROKEN_VERIFY_USES_LIBRARIES que foi definido na Etapa 1; a compilação não deve falhar após essa alteração (por causa das etapas 2 e 3).

  5. Corrija os módulos que você desativou na Etapa 3, um de cada vez, depois reative o dexpreopt e a verificação <uses-library> . Arquivos de bugs se necessário.

As verificações <uses-library> em tempo de compilação são aplicadas no Android 12.

Consertando quebras

As seções a seguir informam como corrigir tipos específicos de quebra.

Erro de compilação: incompatibilidade de CLC

O sistema de compilação faz uma verificação de coerência em tempo de compilação entre as informações nos arquivos Android.bp ou Android.mk e o manifesto. O sistema de compilação não pode ler o manifesto, mas pode gerar regras de compilação para ler o manifesto (extraí-lo de um APK, se necessário) e comparar as tags <uses-library> no manifesto com as informações <uses-library> em os arquivos de construção. Se a verificação falhar, o erro será assim:

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 a mensagem de erro sugere, existem várias soluções, dependendo da urgência:

  • Para uma correção temporária em todo o produto , defina PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true no makefile do produto. A verificação de coerência em tempo de compilação ainda é executada, mas uma falha de verificação não significa uma falha de compilação. Em vez disso, uma falha de verificação faz com que o sistema de compilação faça downgrade do filtro do compilador dex2oat para verify em dexpreopt, que desativa totalmente a compilação AOT para este módulo.
  • Para uma correção rápida e global da linha de comando , use a variável de ambiente RELAX_USES_LIBRARY_CHECK=true . Ele tem o mesmo efeito que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , mas destinado ao uso na linha de comando. A variável de ambiente substitui a variável do produto.
  • Para uma solução para corrigir a causa raiz do erro, informe o sistema de compilação das tags <uses-library> no manifesto. Uma inspeção da mensagem de erro mostra quais bibliotecas causam o problema (assim como inspecionar AndroidManifest.xml ou o manifesto dentro de um APK que pode ser verificado com ` aapt dump badging $APK | grep uses-library `).

Para módulos Android.bp :

  1. Procure a biblioteca ausente na propriedade libs do módulo. Se estiver lá, Soong normalmente adiciona essas bibliotecas automaticamente, exceto nestes casos especiais:

    • A biblioteca não é uma biblioteca SDK (é definida como java_library em vez de java_sdk_library ).
    • A biblioteca tem um nome de biblioteca diferente (no manifesto) de seu nome de módulo (no sistema de compilação).

    Para corrigir isso temporariamente, adicione Android.bp provides_uses_lib: "<library-name>" na definição da biblioteca Android.bp. Para uma solução de longo prazo, corrija o problema subjacente: converta a biblioteca em uma biblioteca SDK ou renomeie seu módulo.

  2. Se a etapa anterior não forneceu uma resolução, adicione uses_libs: ["<library-module-name>"] para bibliotecas necessárias ou optional_uses_libs: ["<library-module-name>"] para bibliotecas opcionais no Android.bp definição do módulo. Essas propriedades aceitam uma lista de nomes de módulos. A ordem relativa das bibliotecas na lista deve ser igual à ordem no manifesto.

Para módulos Android.mk :

  1. Verifique se a biblioteca tem um nome de biblioteca diferente (no manifesto) do nome do módulo (no sistema de compilação). Se isso acontecer, corrija isso temporariamente adicionando LOCAL_PROVIDES_USES_LIBRARY := <library-name> no arquivo Android.mk da biblioteca ou adicione providers_uses_lib provides_uses_lib: "<library-name>" no arquivo Android.bp da biblioteca (ambos os casos são possíveis, pois um módulo Android.mk pode depender de uma biblioteca Android.bp ). Para uma solução de longo prazo, corrija o problema subjacente: renomeie o módulo da biblioteca.

  2. Adicione LOCAL_USES_LIBRARIES := <library-module-name> para bibliotecas necessárias; adicione LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> para bibliotecas opcionais à definição Android.mk do módulo. Essas propriedades aceitam uma lista de nomes de módulos. A ordem relativa das bibliotecas na lista deve ser a mesma do manifesto.

Erro de compilação: caminho de biblioteca desconhecido

Se o sistema de compilação não puder encontrar um caminho para um jar DEX <uses-library> (um caminho de tempo de compilação no host ou um caminho de instalação no dispositivo), ele geralmente falha na compilação. Uma falha ao encontrar um caminho pode indicar que a biblioteca está configurada de alguma forma inesperada. Corrija temporariamente a compilação desativando o dexpreopt para o módulo problemático.

Android.bp (propriedades do módulo):

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

Android.mk (variáveis ​​do módulo):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Registre um bug para investigar quaisquer cenários sem suporte.

Erro de compilação: dependência de biblioteca ausente

Uma tentativa de adicionar <uses-library> X do manifesto do módulo Y ao arquivo de compilação para Y pode resultar em um erro de compilação devido à dependência ausente, X.

Esta é uma mensagem de erro de exemplo para módulos Android.bp:

"Y" depends on undefined module "X"

Esta é uma mensagem de erro de exemplo para 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

Uma fonte comum de tais erros é quando uma biblioteca é nomeada de forma diferente de seu módulo correspondente é nomeado no sistema de compilação. Por exemplo, se a entrada do manifesto <uses-library> for com.android.X , mas o nome do módulo da biblioteca for apenas X , isso causará um erro. Para resolver esse caso, informe ao sistema de compilação que o módulo chamado X fornece uma <uses-library> chamada com.android.X .

Este é um exemplo para bibliotecas Android.bp (propriedade do módulo):

provides_uses_lib: “com.android.X”,

Este é um exemplo para bibliotecas Android.mk (variável de módulo):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Incompatibilidade de CLC no tempo de inicialização

Na primeira inicialização, procure no logcat mensagens relacionadas à incompatibilidade de CLC, conforme mostrado abaixo:

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

A saída pode ter mensagens da forma mostrada aqui:

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

Se você receber um aviso de incompatibilidade de CLC, procure um comando dexopt para o módulo defeituoso. Para corrigi-lo, certifique-se de que a verificação de tempo de compilação do módulo seja aprovada. Se isso não funcionar, o seu pode ser um caso especial que não é compatível com o sistema de compilação (como um aplicativo que carrega outro APK, não uma biblioteca). O sistema de compilação não lida com todos os casos, porque em tempo de compilação é impossível saber com certeza o que o aplicativo carrega em tempo de execução.

Contexto do carregador de classes

O CLC é uma estrutura semelhante a uma árvore que descreve a hierarquia do carregador de classes. O sistema de compilação usa CLC em um sentido restrito (abrange apenas bibliotecas, não APKs ou carregadores de classe personalizados): é uma árvore de bibliotecas que representa o fechamento transitivo de todas as dependências <uses-library> de uma biblioteca ou aplicativo. Os elementos de nível superior de um CLC são as dependências diretas <uses-library> especificadas no manifesto (o caminho de classe). Cada nó de uma árvore CLC é um nó <uses-library> que pode ter seus próprios subnós <uses-library> .

Como as dependências <uses-library> são um gráfico acíclico direcionado e não necessariamente uma árvore, o CLC pode conter várias subárvores para a mesma biblioteca. Em outras palavras, CLC é o gráfico de dependência "desdobrado" para uma árvore. A duplicação é apenas em um nível lógico; os carregadores de classes subjacentes reais não são duplicados (em tempo de execução, há uma única instância do carregador de classes para cada biblioteca).

O CLC define a ordem de pesquisa das bibliotecas ao resolver classes Java usadas pela biblioteca ou aplicativo. A ordem de pesquisa é importante porque as bibliotecas podem conter classes duplicadas e a classe é resolvida para a primeira correspondência.

No dispositivo (tempo de execução) CLC

PackageManager (em frameworks/base ) cria um CLC para carregar um módulo Java no dispositivo. Ele adiciona as bibliotecas listadas nas tags <uses-library> no manifesto do módulo como elementos CLC de nível superior.

Para cada biblioteca usada, o PackageManager obtém todas as dependências <uses-library> (especificadas como tags no manifesto dessa biblioteca) e adiciona um CLC aninhado para cada dependência. Este processo continua recursivamente até que todos os nós folha da árvore CLC construída sejam bibliotecas sem dependências <uses-library> .

PackageManager está ciente apenas de bibliotecas compartilhadas. A definição de compartilhado neste uso difere de seu significado usual (como em compartilhado vs. estático). No Android, as bibliotecas compartilhadas Java são aquelas listadas nas configurações XML instaladas no dispositivo ( /system/etc/permissions/platform.xml ). Cada entrada contém o nome de uma biblioteca compartilhada, um caminho para seu arquivo jar DEX e uma lista de dependências (outras bibliotecas compartilhadas que esta usa em tempo de execução e especifica nas tags <uses-library> em seu manifesto).

Em outras palavras, existem duas fontes de informação que permitem que o PackageManager construa o CLC em tempo de execução: tags <uses-library> no manifesto e dependências de biblioteca compartilhada em configurações XML.

CLC no host (tempo de construção)

O CLC não é necessário apenas ao carregar uma biblioteca ou aplicativo, também é necessário ao compilar um. A compilação pode ocorrer no dispositivo (dexopt) ou durante a compilação (dexpreopt). Como o dexopt ocorre no dispositivo, ele possui as mesmas informações que o PackageManager (manifestos e dependências de bibliotecas compartilhadas). O Dexpreopt, no entanto, ocorre no host e em um ambiente totalmente diferente, e precisa obter as mesmas informações do sistema de compilação.

Assim, o CLC de tempo de construção usado pelo dexpreopt e o CLC de tempo de execução usado pelo PackageManager são a mesma coisa, mas computados de duas maneiras diferentes.

Os CLCs de tempo de construção e tempo de execução devem coincidir, caso contrário, o código compilado por AOT criado por dexpreopt será rejeitado. Para verificar a igualdade de CLCs de tempo de construção e de tempo de execução, o compilador dex2oat registra o CLC de tempo de construção nos arquivos *.odex (no campo classpath do cabeçalho do arquivo OAT). Para encontrar o CLC armazenado, use este comando:

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

A incompatibilidade de CLC de tempo de construção e tempo de execução é relatada no logcat durante a inicialização. Procure por ele com este comando:

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

A incompatibilidade é ruim para o desempenho, pois força a biblioteca ou o aplicativo a ser excluído ou executado sem otimizações (por exemplo, o código do aplicativo pode precisar ser extraído na memória do APK, uma operação muito cara).

Uma biblioteca compartilhada pode ser opcional ou obrigatória. Do ponto de vista dexpreopt, uma biblioteca necessária deve estar presente no momento da compilação (sua ausência é um erro de compilação). Uma biblioteca opcional pode estar presente ou ausente no momento da compilação: se presente, ela é adicionada ao CLC, passada para dex2oat e registrada no arquivo *.odex . Se uma biblioteca opcional estiver ausente, ela será ignorada e não será adicionada ao CLC. Se houver uma incompatibilidade entre o status de tempo de compilação e de tempo de execução (a biblioteca opcional está presente em um caso, mas não no outro), os CLCs de tempo de compilação e tempo de execução não correspondem e o código compilado é rejeitado.

Detalhes avançados do sistema de compilação (corretor de manifesto)

Às vezes, as tags <uses-library> estão ausentes no manifesto de origem de uma biblioteca ou aplicativo. Isso pode acontecer, por exemplo, se uma das dependências transitivas da biblioteca ou do aplicativo começar a usar outra tag <uses-library> e o manifesto da biblioteca ou do aplicativo não for atualizado para incluí-la.

Soong pode calcular algumas das tags <uses-library> ausentes para uma determinada biblioteca ou aplicativo automaticamente, como as bibliotecas SDK no fechamento de dependência transitiva da biblioteca ou aplicativo. O fechamento é necessário porque a biblioteca (ou aplicativo) pode depender de uma biblioteca estática que depende de uma biblioteca SDK e, possivelmente, pode depender novamente de forma transitiva por meio de outra biblioteca.

Nem todas as tags <uses-library> podem ser computadas desta forma, mas quando possível, é preferível deixar Soong adicionar entradas de manifesto automaticamente; é menos propenso a erros e simplifica a manutenção. Por exemplo, quando muitos aplicativos usam uma biblioteca estática que adiciona uma nova dependência <uses-library> , todos os aplicativos devem ser atualizados, o que é difícil de manter.