Dexpreopt e <uses-library> Verificações

O Android 12 tem alterações no sistema de compilação para 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 desta 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 biblioteca compartilhada usada por um módulo Java (uma biblioteca ou um aplicativo) é conhecida como contexto do carregador de classe (CLC). Para garantir a exatidão do dexpreopt, os CLCs de tempo de construção e tempo de execução devem coincidir. CLC em tempo de construção é o que o compilador dex2oat usa no momento do dexpreopt (é registrado nos arquivos ODEX) e 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 de tempo de execução devem coincidir por motivos de correção e desempenho. Para estar correto, é 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 poderão ser resolvidas de maneira diferente, causando erros sutis em 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 tempo de execução, ele rejeitará os artefatos dexpreopt e executará o dexopt. Para inicializações subsequentes, isso é bom porque os aplicativos podem ser dexotados em segundo plano e armazenados no disco.

Áreas afetadas do Android

Isso afeta todos os aplicativos e bibliotecas Java que possuem 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 possuem suas próprias bibliotecas e aplicativos.

Quebrar alterações

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> contidas nele, porque o sistema de compilação não tem permissão para ler arquivos arbitrários ao gerar regras de compilação (por motivos de desempenho). Além disso, o manifesto pode ser empacotado dentro de um APK ou pré-construído. Portanto, as informações <uses-library> devem estar presentes nos arquivos de build ( Android.bp ou Android.mk ).

Anteriormente, o ART usava uma solução alternativa que ignorava as dependências da 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 construção podem causar quebras de construção (causadas por uma incompatibilidade de CLC no tempo de construção) ou regressões no tempo de primeira inicialização (causadas por uma falha no CLC no tempo de inicialização). incompatibilidade seguida por dexopt).

Caminho de migração

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

  1. Desative globalmente a verificação em tempo de construçã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 momento da 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 em tempo de construção e o dexpreopt para os casos problemáticos, por módulo. Desative o dexpreopt para não desperdiçar tempo de construção e armazenamento em artefatos que são rejeitados na inicialização.

  4. Reative globalmente a verificação em tempo de construção desmarcando PRODUCT_BROKEN_VERIFY_USES_LIBRARIES que foi definido na Etapa 1; a compilação não deve falhar após essa alteração (devido às etapas 2 e 3).

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

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

Consertar quebras

As seções a seguir explicam como consertar tipos específicos de quebra.

Erro de compilação: incompatibilidade de CLC

O sistema de compilação faz uma verificação de coerência no 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 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á semelhante a este:

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

  • Para uma correção temporária para todo o produto , defina PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true no makefile do produto. A verificação de coerência em tempo de construção ainda é executada, mas uma falha na verificação não significa uma falha na construção. Em vez disso, uma falha na verificação faz com que o sistema de compilação faça downgrade do filtro do compilador dex2oat para verify no dexpreopt, o 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 . Tem o mesmo efeito que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , mas destina-se ao uso na linha de comando. A variável de ambiente substitui a variável de produto.
  • Para obter 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 a inspeção AndroidManifest.xml ou do 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) do nome do módulo (no sistema de compilação).

    Para corrigir isso temporariamente, adicione 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 ao 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 possui 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 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 da biblioteca desconhecido

Se o sistema de compilação não conseguir encontrar um caminho para um jar DEX <uses-library> (seja 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 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 não suportados.

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 construção para Y pode resultar em um erro de construção devido à dependência ausente, X.

Este é um exemplo de mensagem de erro para módulos Android.bp:

"Y" depends on undefined module "X"

Este é um exemplo de mensagem de erro 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 tem um nome diferente do nome de seu módulo correspondente 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 momento da inicialização

Na primeira inicialização, pesquise 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 no formato mostrado 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 com defeito. Para corrigir isso, certifique-se de que a verificação em tempo de construção do módulo seja aprovada. Se isso não funcionar, então o seu caso pode ser especial e 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 no momento da 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 sentido estrito (abrange apenas bibliotecas, não APKs ou carregadores de classes personalizadas): é 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 grafo de dependência “desdobrado” em uma árvore. A duplicação ocorre apenas no 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).

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, PackageManager obtém todas as suas 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 só conhece bibliotecas compartilhadas. A definição de compartilhado neste uso difere de seu significado usual (como em compartilhado versus 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ções que permitem PackageManager construir 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, mas também ao compilar um. A compilação pode acontecer no dispositivo (dexopt) ou durante a compilação (dexpreopt). Como o dexopt ocorre no dispositivo, ele possui as mesmas informações que PackageManager (manifestos e dependências de biblioteca compartilhada). O Dexpreopt, entretanto, ocorre no host e em um ambiente totalmente diferente, e precisa obter as mesmas informações do sistema de compilação.

Assim, o CLC em tempo de construção usado pelo dexpreopt e o CLC em tempo de execução usado pelo PackageManager são a mesma coisa, mas calculados 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 AOT criado por dexpreopt será rejeitado. Para verificar a igualdade dos 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 em tempo de construção e tempo de execução é relatada no logcat durante a inicialização. Procure-o com este comando:

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

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

Uma biblioteca compartilhada pode ser opcional ou obrigatória. Do ponto de vista do dexpreopt, uma biblioteca necessária deve estar presente no momento da construção (sua ausência é um erro de construção). Uma biblioteca opcional pode estar presente ou ausente no momento da construçã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 do tempo de construção e do tempo de execução (a biblioteca opcional está presente em um caso, mas não no outro), então os CLCs de tempo de construção e de tempo de execução não correspondem e o código compilado será rejeitado.

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

Às vezes, as tags <uses-library> estão faltando 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 transitivamente por meio de outra biblioteca.

Nem todas as tags <uses-library> podem ser calculadas dessa forma, mas quando possível, é preferível deixar o Soong adicionar entradas de manifesto automaticamente; é menos sujeito 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.