Deexpreopt e <usa-libreria> controlli

Android 12 presenta modifiche al sistema di build per la compilazione AOT di file DEX (dexpreopt) per i moduli Java che hanno dipendenze <uses-library> . In alcuni casi queste modifiche al sistema di build possono interrompere le build. Utilizza questa pagina per prepararti alle rotture e segui le ricette in questa pagina per risolverle e mitigarle.

Dexpreopt è il processo di compilazione anticipata di librerie e app Java. Dexopt avviene sull'host in fase di compilazione (al contrario di dexopt , che avviene sul dispositivo). La struttura delle dipendenze della libreria condivisa utilizzata da un modulo Java (una libreria o un'app) è nota come contesto del caricatore di classi (CLC). Per garantire la correttezza di dexpreopt, i CLC build-time e run-time devono coincidere. CLC in fase di compilazione è ciò che il compilatore dex2oat utilizza al momento di dexpreopt (è registrato nei file ODEX) e CLC in fase di esecuzione è il contesto in cui il codice precompilato viene caricato sul dispositivo.

Questi CLC in fase di compilazione e in fase di esecuzione devono coincidere per motivi sia di correttezza che di prestazioni. Per correttezza, è necessario gestire le classi duplicate. Se le dipendenze della libreria condivisa in fase di esecuzione sono diverse da quelle utilizzate per la compilazione, alcune classi potrebbero essere risolte in modo diverso, causando sottili bug di runtime. Le prestazioni sono influenzate anche dai controlli di runtime per le classi duplicate.

Casi d'uso interessati

Il primo avvio è il caso d'uso principale interessato da queste modifiche: se ART rileva una mancata corrispondenza tra CLC in fase di compilazione e in fase di esecuzione, rifiuta gli artefatti dexpreopt ed esegue invece dexopt. Per gli avvii successivi questo va bene perché le app possono essere dexoptate in background e archiviate su disco.

Aree interessate di Android

Ciò influisce su tutte le app e le librerie Java che hanno dipendenze di runtime da altre librerie Java. Android ha migliaia di app e centinaia di queste utilizzano librerie condivise. Anche i partner sono interessati, poiché hanno le proprie librerie e app.

Interrompere i cambiamenti

Il sistema di compilazione deve conoscere le dipendenze <uses-library> prima di generare regole di compilazione dexpreopt. Tuttavia, non può accedere direttamente al manifest e leggere i tag <uses-library> in esso contenuti, poiché al sistema di compilazione non è consentito leggere file arbitrari quando genera regole di compilazione (per motivi di prestazioni). Inoltre, il manifest potrebbe essere inserito all'interno di un APK o di un file precompilato. Pertanto, le informazioni <uses-library> devono essere presenti nei file di build ( Android.bp o Android.mk ).

In precedenza ART utilizzava una soluzione alternativa che ignorava le dipendenze della libreria condivisa (nota come &-classpath ). Questo non era sicuro e causava bug sottili, quindi la soluzione alternativa è stata rimossa in Android 12.

Di conseguenza, i moduli Java che non forniscono informazioni <uses-library> corrette nei file di build possono causare interruzioni della build (causate da una mancata corrispondenza CLC in fase di compilazione) o regressioni al momento del primo avvio (causate da una CLC in fase di avvio mancata corrispondenza seguita da dexopt).

Percorso migratorio

Segui questi passaggi per correggere una build danneggiata:

  1. Disabilita globalmente il controllo in fase di compilazione per un particolare prodotto impostando

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    nel makefile del prodotto. Questo corregge gli errori di creazione (ad eccezione di casi speciali, elencati nella sezione Correzione delle rotture ). Tuttavia, si tratta di una soluzione temporanea e può causare una mancata corrispondenza del CLC all'avvio seguito da dexopt.

  2. Correggi i moduli che non funzionavano prima di disabilitare globalmente il controllo in fase di compilazione aggiungendo le informazioni <uses-library> necessarie ai relativi file di build (vedi Correzione delle interruzioni per i dettagli). Per la maggior parte dei moduli ciò richiede l'aggiunta di alcune righe in Android.bp o in Android.mk .

  3. Disabilita il controllo in fase di compilazione e dexpreopt per i casi problematici, in base al modulo. Disabilita dexpreopt in modo da non sprecare tempo di creazione e spazio di archiviazione su artefatti che vengono rifiutati all'avvio.

  4. Riabilita globalmente il controllo in fase di compilazione annullando l'impostazione PRODUCT_BROKEN_VERIFY_USES_LIBRARIES impostata nel passaggio 1; la compilazione non dovrebbe fallire dopo questa modifica (a causa dei passaggi 2 e 3).

  5. Correggi i moduli che hai disabilitato nel passaggio 3, uno alla volta, quindi riabilita dexpreopt e il controllo <uses-library> . Segnala i bug se necessario.

I controlli <uses-library> in fase di compilazione vengono applicati in Android 12.

Correggere le rotture

Le sezioni seguenti spiegano come riparare specifici tipi di rotture.

Errore di creazione: mancata corrispondenza CLC

Il sistema di compilazione esegue un controllo di coerenza in fase di compilazione tra le informazioni nei file Android.bp o Android.mk e il manifest. Il sistema di compilazione non può leggere il manifest, ma può generare regole di compilazione per leggere il manifest (estraendolo da un APK se necessario) e confrontare i tag <uses-library> nel manifest con le informazioni <uses-library> in i file di compilazione. Se il controllo fallisce, l'errore sarà simile al seguente:

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

Come suggerisce il messaggio di errore, esistono più soluzioni, a seconda dell'urgenza:

  • Per una correzione temporanea a livello di prodotto , impostare PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true nel makefile del prodotto. Il controllo della coerenza in fase di compilazione viene comunque eseguito, ma un errore del controllo non significa un errore di compilazione. Invece, un errore di controllo fa sì che il sistema di compilazione effettui il downgrade del filtro del compilatore dex2oat per verify in dexpreopt, che disabilita completamente la compilazione AOT per questo modulo.
  • Per una soluzione rapida e globale da riga di comando , utilizzare la variabile di ambiente RELAX_USES_LIBRARY_CHECK=true . Ha lo stesso effetto di PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , ma è destinato all'uso sulla riga di comando. La variabile di ambiente sovrascrive la variabile di prodotto.
  • Per una soluzione che risolva la causa principale dell'errore, rendere il sistema di compilazione consapevole dei tag <uses-library> nel manifest. Un'ispezione del messaggio di errore mostra quali librerie causano il problema (così come l'ispezione di AndroidManifest.xml o del manifest all'interno di un APK che può essere controllato con ` aapt dump badging $APK | grep uses-library `).

Per i moduli Android.bp :

  1. Cerca la libreria mancante nella proprietà libs del modulo. Se è lì, Soong normalmente aggiunge automaticamente tali librerie, tranne in questi casi speciali:

    • La libreria non è una libreria SDK (è definita come java_library anziché java_sdk_library ).
    • La libreria ha un nome di libreria diverso (nel manifest) dal nome del modulo (nel sistema di compilazione).

    Per risolvere questo problema temporaneamente, aggiungi provides_uses_lib: "<library-name>" nella definizione della libreria Android.bp . Per una soluzione a lungo termine, risolvi il problema di fondo: converti la libreria in una libreria SDK o rinomina il suo modulo.

  2. Se il passaggio precedente non ha fornito una soluzione, aggiungi uses_libs: ["<library-module-name>"] per le librerie richieste o optional_uses_libs: ["<library-module-name>"] per le librerie facoltative su Android.bp definizione del modulo. Queste proprietà accettano un elenco di nomi di moduli. L'ordine relativo delle librerie nell'elenco deve essere lo stesso dell'ordine nel manifest.

Per i moduli Android.mk :

  1. Controlla se la libreria ha un nome di libreria diverso (nel manifest) dal nome del modulo (nel sistema di compilazione). In tal caso, correggilo temporaneamente aggiungendo LOCAL_PROVIDES_USES_LIBRARY := <library-name> nel file Android.mk della libreria o aggiungi provides_uses_lib: "<library-name>" nel file Android.bp della libreria (in entrambi i casi sono possibili poiché un modulo Android.mk potrebbe dipendere da una libreria Android.bp ). Per una soluzione a lungo termine, risolvi il problema di fondo: rinomina il modulo della libreria.

  2. Aggiungi LOCAL_USES_LIBRARIES := <library-module-name> per le librerie richieste; aggiungi LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> per le librerie opzionali alla definizione Android.mk del modulo. Queste proprietà accettano un elenco di nomi di moduli. L'ordine relativo delle librerie nell'elenco deve essere lo stesso del manifest.

Errore di creazione: percorso della libreria sconosciuto

Se il sistema di compilazione non riesce a trovare un percorso per un jar DEX <uses-library> (un percorso in fase di compilazione sull'host o un percorso di installazione sul dispositivo), in genere la compilazione non riesce. L'incapacità di trovare un percorso può indicare che la libreria è configurata in un modo imprevisto. Correggi temporaneamente la build disabilitando dexpreopt per il modulo problematico.

Android.bp (proprietà del modulo):

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

Android.mk (variabili del modulo):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Segnala un bug per indagare su eventuali scenari non supportati.

Errore di creazione: dipendenza della libreria mancante

Un tentativo di aggiungere <uses-library> X dal manifest del modulo Y al file di build per Y potrebbe provocare un errore di build a causa della dipendenza mancante, X.

Questo è un messaggio di errore di esempio per i moduli Android.bp:

"Y" depends on undefined module "X"

Questo è un messaggio di errore di esempio per i moduli 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 fonte comune di tali errori è quando una libreria ha un nome diverso da quello del modulo corrispondente nel sistema di compilazione. Ad esempio, se la voce <uses-library> del manifest è com.android.X , ma il nome del modulo della libreria è solo X , si verifica un errore. Per risolvere questo caso, comunica al sistema di compilazione che il modulo denominato X fornisce una <uses-library> denominata com.android.X .

Questo è un esempio per le librerie Android.bp (proprietà del modulo):

provides_uses_lib: “com.android.X”,

Questo è un esempio per le librerie Android.mk (variabile del modulo):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Mancata corrispondenza del CLC all'avvio

Al primo avvio, cerca nel logcat i messaggi relativi alla mancata corrispondenza CLC, come mostrato di seguito:

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

L'output può contenere messaggi nel formato mostrato qui:

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

Se ricevi un avviso di mancata corrispondenza CLC, cerca un comando dexopt per il modulo difettoso. Per risolverlo, assicurati che il controllo in fase di compilazione del modulo venga superato. Se non funziona, il tuo potrebbe essere un caso speciale non supportato dal sistema di compilazione (come un'app che carica un altro APK, non una libreria). Il sistema di compilazione non gestisce tutti i casi, perché in fase di compilazione è impossibile sapere con certezza cosa carica l'app in fase di esecuzione.

Contesto del caricatore di classi

Il CLC è una struttura ad albero che descrive la gerarchia del caricatore di classi. Il sistema di compilazione utilizza CLC in senso stretto (copre solo le librerie, non gli APK o i caricatori di classi personalizzate): è un albero di librerie che rappresenta la chiusura transitiva di tutte le dipendenze <uses-library> di una libreria o app. Gli elementi di primo livello di un CLC sono le dipendenze dirette <uses-library> specificate nel manifest (il classpath). Ogni nodo di un albero CLC è un nodo <uses-library> che potrebbe avere i propri sottonodi <uses-library> .

Poiché le dipendenze <uses-library> sono un grafico aciclico diretto e non necessariamente un albero, CLC può contenere più sottoalberi per la stessa libreria. In altre parole, CLC è il grafico delle dipendenze "spiegato" in un albero. La duplicazione è solo a livello logico; gli effettivi caricatori di classi sottostanti non sono duplicati (in fase di esecuzione c'è una singola istanza del caricatore di classi per ogni libreria).

CLC definisce l'ordine di ricerca delle librerie durante la risoluzione delle classi Java utilizzate dalla libreria o dall'app. L'ordine di ricerca è importante perché le librerie possono contenere classi duplicate e la classe viene risolta nella prima corrispondenza.

CLC sul dispositivo (in fase di esecuzione).

PackageManager (in frameworks/base ) crea un CLC per caricare un modulo Java sul dispositivo. Aggiunge le librerie elencate nei tag <uses-library> nel manifest del modulo come elementi CLC di livello superiore.

Per ogni libreria utilizzata, PackageManager ottiene tutte le sue dipendenze <uses-library> (specificate come tag nel manifest di quella libreria) e aggiunge un CLC nidificato per ogni dipendenza. Questo processo continua in modo ricorsivo finché tutti i nodi foglia dell'albero CLC costruito sono librerie senza dipendenze <uses-library> .

PackageManager è a conoscenza solo delle librerie condivise. La definizione di condiviso in questo utilizzo differisce dal suo significato abituale (come condiviso vs. statico). In Android, le librerie condivise Java sono quelle elencate nelle configurazioni XML installate sul dispositivo ( /system/etc/permissions/platform.xml ). Ogni voce contiene il nome di una libreria condivisa, un percorso al suo file jar DEX e un elenco di dipendenze (altre librerie condivise che questa utilizza in fase di runtime e specifica nei tag <uses-library> nel suo manifest).

In altre parole, ci sono due fonti di informazioni che consentono a PackageManager di costruire CLC in fase di esecuzione: tag <uses-library> nel manifest e dipendenze della libreria condivisa nelle configurazioni XML.

CLC sull'host (in fase di compilazione).

CLC non è necessario solo quando si carica una libreria o un'app, ma è necessario anche durante la compilazione. La compilazione può avvenire sul dispositivo (dexopt) o durante la compilazione (dexpreopt). Poiché dexopt avviene sul dispositivo, ha le stesse informazioni di PackageManager (manifest e dipendenze della libreria condivisa). Dexpreopt, tuttavia, avviene sull'host e in un ambiente completamente diverso e deve ottenere le stesse informazioni dal sistema di compilazione.

Pertanto, il CLC in fase di compilazione utilizzato da dexpreopt e il CLC in fase di esecuzione utilizzato da PackageManager sono la stessa cosa, ma calcolati in due modi diversi.

I CLC in fase di compilazione e in fase di esecuzione devono coincidere, altrimenti il ​​codice compilato AOT creato da dexpreopt viene rifiutato. Per verificare l'uguaglianza dei CLC in fase di compilazione e di runtime, il compilatore dex2oat registra il CLC in fase di compilazione nei file *.odex (nel campo classpath dell'intestazione del file OAT). Per trovare il CLC memorizzato, utilizzare questo comando:

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

La mancata corrispondenza tra CLC in fase di compilazione e in fase di esecuzione viene segnalata nel logcat durante l'avvio. Cercalo con questo comando:

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

La mancata corrispondenza è dannosa per le prestazioni, poiché costringe la libreria o l'app a essere dexoptata o a funzionare senza ottimizzazioni (ad esempio, potrebbe essere necessario estrarre il codice dell'app in memoria dall'APK, un'operazione molto costosa).

Una libreria condivisa può essere facoltativa o obbligatoria. Dal punto di vista dexpreopt, una libreria richiesta deve essere presente al momento della compilazione (la sua assenza è un errore di compilazione). Una libreria opzionale può essere presente o assente al momento della compilazione: se presente, viene aggiunta al CLC, passata a dex2oat e registrata nel file *.odex . Se una libreria opzionale è assente, viene ignorata e non viene aggiunta al CLC. Se c'è una mancata corrispondenza tra lo stato in fase di compilazione e quello in fase di esecuzione (la libreria facoltativa è presente in un caso, ma non nell'altro), i CLC in fase di compilazione e in fase di esecuzione non corrispondono e il codice compilato viene rifiutato.

Dettagli avanzati del sistema di build (riparatore manifest)

A volte i tag <uses-library> mancano nel manifest di origine di una libreria o di un'app. Ciò può verificarsi, ad esempio, se una delle dipendenze transitive della libreria o dell'app inizia a utilizzare un altro tag <uses-library> e il manifest della libreria o dell'app non viene aggiornato per includerlo.

Soong può calcolare automaticamente alcuni dei tag <uses-library> mancanti per una determinata libreria o app, come le librerie SDK nella chiusura delle dipendenze transitive della libreria o dell'app. La chiusura è necessaria perché la libreria (o l'app) potrebbe dipendere da una libreria statica che a sua volta dipende da una libreria SDK ed eventualmente potrebbe dipendere nuovamente in modo transitivo tramite un'altra libreria.

Non tutti i tag <uses-library> possono essere calcolati in questo modo, ma quando possibile è preferibile lasciare che Soong aggiunga automaticamente le voci manifest; è meno soggetto a errori e semplifica la manutenzione. Ad esempio, quando molte app utilizzano una libreria statica che aggiunge una nuova dipendenza <uses-library> , tutte le app devono essere aggiornate, il che è difficile da mantenere.