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

    в makefile продукта. Это исправляет ошибки сборки (за исключением особых случаев, перечисленных в разделе Исправление поломок ). Однако это временное решение, и оно может привести к несоответствию 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> . При необходимости отправьте сообщения об ошибках.

В Android 12 проверки <uses-library> во время сборки стали обязательными.

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

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

Ошибка сборки: несоответствие 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 в makefile продукта. Проверка согласованности во время сборки все еще выполняется, но сбой проверки не означает сбой сборки. Вместо этого сбой проверки заставляет систему сборки понизить фильтр компилятора 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 ).
    • Имя библиотеки (в манифесте) отличается от имени ее модуля (в системе сборки).

    Чтобы временно исправить это, добавьте provides_uses_lib: "<library-name>" в определение библиотеки Android.bp . Для долгосрочного решения исправьте основную проблему: преобразуйте библиотеку в библиотеку 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: "<library-name>" в файл Android.bp библиотеки (оба случая возможны, поскольку модуль Android.mk может зависеть от библиотеки Android.bp ). Для долгосрочного решения исправьте основную проблему: переименуйте модуль библиотеки.

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

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

Если система сборки не может найти путь к <uses-library> DEX jar (путь времени сборки на хосте или путь установки на устройстве), она обычно не может выполнить сборку. Невозможность найти путь может указывать на то, что библиотека настроена неожиданным образом. Временно исправьте сборку, отключив 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> , указанные в манифесте (classpath). Каждый узел дерева 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 ). Каждая запись содержит имя общей библиотеки, путь к ее файлу DEX jar и список зависимостей (другие общие библиотеки, которые эта библиотека использует во время выполнения и указывает в тегах <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 времени сборки и времени выполнения не совпадают, и скомпилированный код отклоняется.

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

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

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

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