Проверка Dexpreopt и <uses-library>

В Android 12 внесены системные изменения сборки для AOT-компиляции файлов DEX (dexpreopt) для модулей Java, которые имеют зависимости <uses-library> . В некоторых случаях эти изменения системы сборки могут нарушить сборку. Используйте эту страницу, чтобы подготовиться к поломкам, и следуйте рецептам на этой странице, чтобы исправить и смягчить их.

Dexpreopt — это процесс предварительной компиляции библиотек и приложений Java. Dexpreopt происходит на хосте во время сборки (в отличие от dexopt , который происходит на устройстве). Структура зависимостей общей библиотеки, используемая модулем Java (библиотекой или приложением), называется контекстом загрузчика классов (CLC). Чтобы гарантировать правильность dexpreopt, CLC времени сборки и времени выполнения должны совпадать. CLC времени сборки — это то, что компилятор dex2oat использует во время dexpreopt (оно записано в файлах ODEX), а CLC времени выполнения — это контекст, в котором предварительно скомпилированный код загружается на устройство.

Эти CLC времени сборки и времени выполнения должны совпадать по причинам как корректности, так и производительности. Для корректности необходимо обрабатывать повторяющиеся классы. Если зависимости разделяемой библиотеки во время выполнения отличаются от тех, которые используются для компиляции, некоторые классы могут разрешаться по-другому, вызывая тонкие ошибки во время выполнения. На производительность также влияет проверка дублирующихся классов во время выполнения.

Затронутые варианты использования

Первая загрузка является основным вариантом использования, на который влияют эти изменения: если ART обнаруживает несоответствие между CLC времени сборки и времени выполнения, он отклоняет артефакты dexpreopt и вместо этого запускает dexopt. Для последующих загрузок это нормально, потому что приложения могут быть загружены в фоновом режиме и сохранены на диске.

Затронутые области Android

Это влияет на все приложения и библиотеки Java, которые во время выполнения зависят от других библиотек Java. В Android есть тысячи приложений, и сотни из них используют общие библиотеки. Пострадают и партнеры, поскольку у них есть собственные библиотеки и приложения.

Критические изменения

Система сборки должна знать зависимости <uses-library> , прежде чем она сгенерирует правила сборки dexpreopt. Однако он не может получить прямой доступ к манифесту и прочитать в нем теги <uses-library> , поскольку системе сборки не разрешено читать произвольные файлы при создании правил сборки (по соображениям производительности). Более того, манифест может быть упакован внутри APK или предварительно собранного файла. Поэтому информация <uses-library> должна присутствовать в файлах сборки ( Android.bp или Android.mk ).

Ранее ART использовал обходной путь, который игнорировал зависимости общих библиотек (известные как &-classpath ). Это было небезопасно и вызывало небольшие ошибки, поэтому обходной путь был удален в Android 12.

В результате модули Java, которые не предоставляют правильную информацию <uses-library> в своих файлах сборки, могут вызвать сбои сборки (вызванные несоответствием CLC во время сборки) или регрессии времени первой загрузки (вызванные CLC во время загрузки). несоответствие с последующим dexopt).

Путь миграции

Выполните следующие действия, чтобы исправить сломанную сборку:

  1. Глобально отключите проверку времени сборки для определенного продукта, установив

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    в make-файле продукта. Это исправляет ошибки сборки (кроме особых случаев, перечисленных в разделе Исправление поломок ). Однако это временный обходной путь, который может привести к несоответствию CLC во время загрузки, за которым следует dexopt.

  2. Исправьте модули, которые вышли из строя до того, как вы глобально отключили проверку во время сборки, добавив необходимую информацию <uses-library> в их файлы сборки (подробности см. в разделе Исправление сбоев ). Для большинства модулей это требует добавления нескольких строк в Android.bp или Android.mk .

  3. Отключите проверку во время сборки и dexpreopt для проблемных случаев для каждого модуля. Отключите dexpreopt, чтобы не тратить время сборки и хранилище на артефакты, которые отклоняются при загрузке.

  4. Глобально повторно включите проверку во время сборки, сняв PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , установленный на шаге 1; сборка не должна завершаться ошибкой после этого изменения (из-за шагов 2 и 3).

  5. Исправьте модули, которые вы отключили на шаге 3, по одному, затем снова включите dexpreopt и проверку <uses-library> . Файл ошибки, если это необходимо.

Проверки <uses-library> во время сборки применяются в Android 12.

Устранение поломок

В следующих разделах рассказывается, как исправить определенные типы поломок.

Ошибка сборки: несоответствие CLC

Система сборки во время сборки выполняет проверку согласованности между информацией в файлах Android.bp или Android.mk и манифестом. Система сборки не может прочитать манифест, но может генерировать правила сборки для чтения манифеста (при необходимости извлекая его из APK) и сравнивать теги <uses-library> в манифесте с информацией <uses-library> в файлы сборки. Если проверка не проходит, ошибка выглядит так:

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

Как следует из сообщения об ошибке, существует несколько решений, в зависимости от срочности:

  • Для временного исправления для всего продукта установите PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true в make-файле продукта. Проверка согласованности во время сборки по-прежнему выполняется, но сбой проверки не означает сбой сборки. Вместо этого сбой проверки заставляет систему сборки понизить фильтр компилятора dex2oat для verify в dexpreopt, что полностью отключает компиляцию AOT для этого модуля.
  • Для быстрого глобального исправления с помощью командной строки используйте переменную среды RELAX_USES_LIBRARY_CHECK=true . Он имеет тот же эффект, что и PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , но предназначен для использования в командной строке. Переменная среды переопределяет переменную продукта.
  • Для устранения основной причины ошибки сообщите системе сборки о тегах <uses-library> в манифесте. Проверка сообщения об ошибке показывает, какие библиотеки вызывают проблему (как и проверка AndroidManifest.xml или манифеста внутри APK, который можно проверить с помощью ` aapt dump badging $APK | grep uses-library ).

Для модулей Android.bp :

  1. Найдите отсутствующую библиотеку в свойстве libs модуля. Если они есть, Soong обычно добавляет такие библиотеки автоматически, за исключением следующих особых случаев:

    • Библиотека не является библиотекой SDK (она определена как java_library , а не как java_sdk_library ).
    • Имя библиотеки (в манифесте) отличается от имени модуля (в системе сборки).

    Чтобы временно исправить это, добавьте в определение библиотеки Android.bp provides_uses_lib: "<library-name>" . В качестве долгосрочного решения устраните основную проблему: преобразуйте библиотеку в библиотеку SDK или переименуйте ее модуль.

  2. Если на предыдущем шаге решение не было найдено, добавьте uses_libs: ["<library-module-name>"] для необходимых библиотек или optional_uses_libs: ["<library-module-name>"] для дополнительных библиотек Android.bp определение модуля. Эти свойства принимают список имен модулей. Относительный порядок библиотек в списке должен совпадать с порядком в манифесте.

Для модулей Android.mk :

  1. Убедитесь, что имя библиотеки (в манифесте) отличается от имени модуля (в системе сборки). Если это так, временно исправьте это, добавив LOCAL_PROVIDES_USES_LIBRARY := <library-name> в файл Android.mk библиотеки или добавив Provides_uses_lib: provides_uses_lib: "<library-name>" в файл Android.bp библиотеки (оба случая возможны, так как модуль Android.mk может зависеть от библиотеки Android.bp ). Для долгосрочного решения исправьте основную проблему: переименуйте библиотечный модуль.

  2. Добавьте LOCAL_USES_LIBRARIES := <library-module-name> для необходимых библиотек; добавьте LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> для дополнительных библиотек в определение модуля Android.mk . Эти свойства принимают список имен модулей. Относительный порядок библиотек в списке должен быть таким же, как в манифесте.

Ошибка сборки: неизвестный путь к библиотеке

Если система сборки не может найти путь к JAR-файлу <uses-library> DEX (либо путь сборки на хосте, либо путь установки на устройстве), сборка обычно завершается ошибкой. Невозможность найти путь может указывать на то, что библиотека настроена каким-то неожиданным образом. Временно исправьте сборку, отключив dexpreopt для проблемного модуля.

Android.bp (свойства модуля):

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

Android.mk (переменные модуля):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Сообщите об ошибке, чтобы исследовать любые неподдерживаемые сценарии.

Ошибка сборки: отсутствует зависимость от библиотеки

Попытка добавить <uses-library> X из манифеста модуля Y в файл сборки для Y может привести к ошибке сборки из-за отсутствующей зависимости X.

Это пример сообщения об ошибке для модулей Android.bp:

"Y" depends on undefined module "X"

Это пример сообщения об ошибке для модулей 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

Распространенным источником таких ошибок является то, что имя библиотеки отличается от имени соответствующего модуля в системе сборки. Например, если запись манифеста <uses-library>com.android.X , а имя модуля библиотеки — просто X , это вызывает ошибку. Чтобы решить эту проблему, сообщите системе сборки, что модуль с именем X предоставляет <uses-library> именем com.android.X .

Это пример для библиотек Android.bp (свойство модуля):

provides_uses_lib: “com.android.X”,

Это пример для библиотек Android.mk (переменная модуля):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Несоответствие CLC во время загрузки

При первой загрузке найдите в logcat сообщения, связанные с несоответствием CLC, как показано ниже:

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

На выходе могут быть сообщения в форме, показанной здесь:

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

Если вы получили предупреждение о несоответствии CLC, найдите команду dexopt для неисправного модуля. Чтобы исправить это, убедитесь, что проверка модуля во время сборки прошла успешно. Если это не сработает, то у вас может быть особый случай, который не поддерживается системой сборки (например, приложение, которое загружает другой APK, а не библиотеку). Система сборки не обрабатывает все случаи, потому что во время сборки невозможно точно знать, что приложение загружает во время выполнения.

Контекст загрузчика классов

CLC представляет собой древовидную структуру, описывающую иерархию загрузчика классов. Система сборки использует CLC в узком смысле (она охватывает только библиотеки, а не APK-файлы или загрузчики пользовательских классов): это дерево библиотек, представляющее транзитивное закрытие всех зависимостей <uses-library> библиотеки или приложения. Элементы верхнего уровня CLC — это прямые зависимости <uses-library> указанные в манифесте (путь к классам). Каждый узел дерева CLC является узлом <uses-library> , который может иметь свои собственные <uses-library> .

Поскольку зависимости <uses-library> представляют собой направленный ациклический граф, а не обязательно дерево, CLC может содержать несколько поддеревьев для одной и той же библиотеки. Другими словами, CLC — это граф зависимостей, «развернутый» в дерево. Дублирование только на логическом уровне; фактические базовые загрузчики классов не дублируются (во время выполнения для каждой библиотеки существует один экземпляр загрузчика классов).

CLC определяет порядок поиска библиотек при разрешении классов Java, используемых библиотекой или приложением. Порядок поиска важен, поскольку библиотеки могут содержать повторяющиеся классы, и класс разрешается до первого совпадения.

На устройстве (во время выполнения) CLC

PackageManagerframeworks/base ) создает CLC для загрузки модуля Java на устройство. Он добавляет библиотеки, перечисленные в тегах <uses-library> в манифесте модуля, как элементы CLC верхнего уровня.

Для каждой используемой библиотеки PackageManager получает все ее зависимости <uses-library> (указаны как теги в манифесте этой библиотеки) и добавляет вложенный CLC для каждой зависимости. Этот процесс рекурсивно продолжается до тех пор, пока все конечные узлы построенного CLC-дерева не станут библиотеками без зависимостей <uses-library> .

PackageManager знает только об общих библиотеках. Определение слова «общий» в этом случае отличается от его обычного значения (например, «общий» и «статический»). В Android общие библиотеки Java перечислены в конфигурациях XML, установленных на устройстве ( /system/etc/permissions/platform.xml ). Каждая запись содержит имя общей библиотеки, путь к ее JAR-файлу DEX и список зависимостей (другие общие библиотеки, которые эта использует во время выполнения и указывает в тегах <uses-library> в своем манифесте).

Другими словами, есть два источника информации, которые позволяют PackageManager создавать CLC во время выполнения: теги <uses-library> в манифесте и зависимости от общей библиотеки в конфигурациях XML.

CLC на хосте (во время сборки)

CLC требуется не только при загрузке библиотеки или приложения, но и при их компиляции. Компиляция может происходить либо на устройстве (dexopt), либо во время сборки (dexpreopt). Поскольку dexopt выполняется на устройстве, он содержит ту же информацию, что и PackageManager (зависимости манифестов и общих библиотек). Однако Dexpreopt работает на хосте в совершенно другой среде и должен получать ту же информацию от системы сборки.

Таким образом, CLC времени сборки, используемый dexpreopt, и CLC времени выполнения, используемый PackageManager , являются одним и тем же, но вычисляются двумя разными способами.

CLC времени сборки и времени выполнения должны совпадать, иначе код, скомпилированный AOT, созданный dexpreopt, будет отклонен. Чтобы проверить равенство CLC времени сборки и времени выполнения, компилятор dex2oat записывает CLC времени сборки в файлы *.odex (в поле classpath заголовка файла OAT). Чтобы найти сохраненный CLC, используйте эту команду:

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

Несоответствие CLC времени сборки и времени выполнения сообщается в logcat во время загрузки. Найдите его с помощью этой команды:

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

Несоответствие отрицательно сказывается на производительности, так как заставляет библиотеку или приложение либо быть извлечено, либо работать без оптимизации (например, код приложения может потребоваться извлечь в память из APK, что очень затратно).

Общая библиотека может быть как необязательной, так и обязательной. С точки зрения dexpreopt, необходимая библиотека должна присутствовать во время сборки (ее отсутствие является ошибкой сборки). Необязательная библиотека может как присутствовать, так и отсутствовать во время сборки: если она присутствует, она добавляется в CLC, передается в dex2oat и записывается в файл *.odex . Если дополнительная библиотека отсутствует, она пропускается и не добавляется в CLC. Если есть несоответствие между состоянием времени сборки и времени выполнения (необязательная библиотека присутствует в одном случае, но не в другом), то CLC времени сборки и времени выполнения не совпадают, и скомпилированный код отклоняется.

Расширенные сведения о системе сборки (фиксатор манифеста)

Иногда теги <uses-library> отсутствуют в исходном манифесте библиотеки или приложения. Это может произойти, например, если одна из транзитивных зависимостей библиотеки или приложения начинает использовать другой <uses-library> , а манифест библиотеки или приложения не обновляется для его включения.

Сун может автоматически вычислять некоторые из отсутствующих тегов <uses-library> для данной библиотеки или приложения, как библиотеки SDK в замыкании транзитивной зависимости библиотеки или приложения. Закрытие необходимо, поскольку библиотека (или приложение) может зависеть от статической библиотеки, которая зависит от библиотеки SDK, и, возможно, может снова зависеть транзитивно через другую библиотеку.

Не все теги <uses-library> могут быть вычислены таким образом, но, когда это возможно, предпочтительнее позволить Soong автоматически добавлять записи манифеста; он менее подвержен ошибкам и упрощает обслуживание. Например, когда многие приложения используют статическую библиотеку, добавляющую новую зависимость <uses-library> , все приложения необходимо обновлять, что сложно поддерживать.