Воспроизводимые сборки

Введение

F-Droid работает над тем чтобы распространить воспроизводимые сборки в экосистеме свободного программного обеспечения для Android. Цель состоит в том, чтобы сделать процессы сборки программного обеспечения повторяемыми, чтобы любой мог запустить их неоднократно и получить абсолютно такой же APK, как в оригинальном релизе. Наша работа сосредоточена на трех основных направлениях:

  • Наше окружение сборки разработано так, чтобы упростить воспроизведение сборок, при этом и само оно является воспроизводимым и проверяемым.
  • Мы отслеживаем проблемы в самих инструментах сборки, которые мешают воспроизведению сборок, помогаем поддерживающим эти инструменты их исправлять и публикуем обходные решения для разработчиков приложений на этой веб-странице.
  • Мы помогаем сторонним разработчикам приложений, поставляемых через f-droid.org, исправлять проблемы с воспроизведением сборок, предоставляя им поддержку, отправляя сообщения о проблемах и предлагая изменения в исходном коде.

F-Droid проверяет воспроизводимость сборок с помощью копирования подписи APK и сравнения оригинальной сборки с нашими пересобранными версиями. Чтобы узнать, можно ли воспроизводимо пересобрать конкретное приложение на нашем сервере сборки, проверьте «Статус воспроизводимости» на странице данного приложения на этом сайте. Это помогает нам выявлять изменения факторов со временем.

Золотым стандартом в воспроизводимых сборках для противодействия проблемам Доверчивого доверия является метод Двойной разнородной компиляции. Его основная идея — использовать два полностью разных набора инструментов сборки, чтобы получить идентичные бинарные файлы. Достичь такого стандарта сложно, но это очень ценно. Некоторые шаги для его достижения можно делать постепенно. Приближаясь к этой цели, F-Droid способен воспроизводить APK, собранные разработчиком на его собственной системе. Часто такие сборки выполняются с разными инструментами или на других ОС. Чтобы увидеть, какие приложения поддерживают такой подход, проверьте их метаданные сборки на наличие полей Binaries: или binary:.

Воспроизводимые сигнатуры

F-Droid проверяет воспроизводимость сборок с помощью подписи APK (разновидность встроенной подписи), которая требует копирования подписи из подписанного APK в неподписанный, а затем проверяет, верифицирован ли последний. Старые подписи v1 (JAR) охватывают только содержимое APK (например, метаданные в ZIP и порядок не имеют значения), а подписи v2/v3 охватывают все остальные байты в APK. Таким образом, для корректной проверки APK должны быть полностью идентичны и до, и после подписывания (за исключением собственно подписи).

Копирование подписи использует тот же алгоритм, который apksigner использует при подписывании APK. Поэтому важно, чтобы разработчики (upstream) делали то же самое при подписании APK, в идеале, используя apksigner для создания подписей. Сам apksigner также воспроизводимо собирается в Debian.

Проверочные сборки

Многие люди или организации будут заинтересованы в воспроизведении сборок, чтобы убедиться, что сборки f-droid.org соответствуют оригинальному источнику и ничего не было изменено. В этом случае полученные APK не публикуются для установки. Сервер Verification Server автоматизирует этот процесс.

Контекст

Довольно много сборок уже проверяются без дополнительных усилий, поскольку Java-код часто компилируется в один и тот же байткод широким диапазоном версий Java. Инструментарий build-tools в Android SDK будет создавать различия в результирующих XML, PNG и т. д. файлах, но это обычно не является проблемой, поскольку в build.gradle включена точная версия build-tools для использования.

Все, что собрано с использованием NDK, будет гораздо более чувствительным. Например, даже при сборке с использованием одной и той же версии NDK (например, r13b), но на разных платформах (например, macOS против Ubuntu), полученные двоичные файлы будут иметь различия.

Кроме того, нам придется следить за тем, что содержит временные метки или пути сборки, чувствительно к порядку сортировки и т. д.

Google также работает над воспроизводимостью сборок приложений для Android, поэтому использование последних версий Android SDK помогает. Один из конкретных случаев - начиная с Gradle Android Plugin v2.2.2, временные метки в метаданных ZIP-файла APK автоматически обнуляются.

Публикация пакетов APK с подписью вышестоящего разработчика

Приложение может быть настроено так, чтобы публиковать подписанные бинарные файлы от вышестоящего разработчика после проверки их соответствия файлам, собранным по рецепту сборки из fdroiddata. Публикация происходит только при полном совпадении. Это означает, что F-Droid может подтвердить, что приложение является свободным программным обеспечением, при этом используя оригинальные подписи APK от разработчика. Эта процедура реализована как часть fdroid publish. Проверка воспроизводимости на этапе публикации следует этой логике:

Блок-схема для проверки воспроизводимости

Публикация исключительно APK с подписью (вышестоящего) разработчика

При таком подходе все метаданные должны быть такими же, как обычно, с добавлением директив Binaries или Builds.binary, указывающих, откуда брать двоичные файлы (APK), и директивы AllowedAPKSigningKeys, гарантирующей использование ожидаемого ключа подписи. В этом случае F-Droid никогда не будет пытаться публиковать APK, подписанные F-Droid. Если fdroid publish сможет убедиться, что загруженный APK совпадает с APK, собранным по рецепту fdroiddata, то загруженный APK будет опубликован. В противном случае F-Droid пропустит публикацию этой версии приложения.

Публикация и APK, подписанный (вышестоящим) разработчиком, и подписанный F-Droid

Такой подход позволяет публиковать как APK, подписанные разработчиком (upstream), так и APK, подписанные F-Droid. Это позволяет нам отправлять обновления для пользователей, которые установили приложения не из F-Droid (например, Play Store), а из других источников, и в то же время отправлять обновления для приложений, которые были созданы и подписаны F-Droid.

Для этого необходимо извлечь и добавить подписи разработчиков (upstream) в fdroiddata. Эти подписи затем копируются в неподписанный APK, созданный по рецепту из fdroiddata. Мы предоставляем команду для легкого извлечения подписей из APK:

$ cd /path/to/fdroiddata
$ fdroid signatures F-Droid.apk

Вместо локальных файлов в fdroid signatures можно передавать HTTPS URL.

Файлы подписи извлекаются в каталог метаданных приложения, готовые к использованию с помощью fdroid publish. Подпись состоит из 2-6 файлов: подпись v1 (манифест, файл подписи и файл блока подписи) и/или подпись v2/v3 (блок подписи APK и смещение); если APK был подписан v1, например, с помощью signflinger, а не apksigner, то также будет присутствовать файл differences.json`. Результат извлечения одного из них будет похож на эти списки файлов:

$ ls metadata/org.fdroid.fdroid/signatures/1000012/  # подпись только v1
CIARANG.RSA  CIARANG.SF  MANIFEST.MF
$ ls metadata/your.app/signatures/42/                # подпись v1 + v2/v3
APKSigningBlock  APKSigningBlockOffset  MANIFEST.MF  YOURKEY.RSA  YOURKEY.SF

Инструменты

Получения списка различий APK

Мы рекомендуем использовать diffoscope, чтобы легко найти разницу между эталонным APK, предоставленным разработчиком приложения, и APK, который произвел fdroidserver.

APK, созданный fdroidserver, можно найти либо в каталоге fdroiddata/build/com.example.app/app/build/outputs/apk/prod/release/example-1.0.0-prod-release-unsigned.apk (при локальном запуске), либо в артефактах конвейера (при использовании GitLab CI). Настройте путь соответствующим образом (например, для версий, отличных от prod).

Расстановка приоритетов и устранение различий

HOWTO: diff & fix APKs for Reproducible Builds на вики F-Droid содержит подробную информацию о различных видах различий, которые часто встречаются, какие различия обычно должны быть приоритетными при отладке, и как исправить распространенные проблемы.

В нем также показано, как использовать различные специализированные инструменты, которые могут дать лучшие результаты, когда диффоскопа недостаточно.

Воспроизводимые инструменты APK

Скрипты из reproducible-apk-tools (доступны в fdroiddata как srclib) могут помочь сделать сборки воспроизводимыми, например, исправляя символы перевода строки (CRLF против LF) или делая порядок в ZIP детерминированным, если устранение причины различий не является реалистичным вариантом. В зависимости от специфики, эти скрипты должны использоваться вышестоящими разработчиками перед тем как подписывать своих APK, или рецептом fdroiddata, или обоими.

Изначально созданный для внесения недетерминизма в процессы сборки, disorderfs может делать и обратное: делать чтение из файловой системы детерминированным. В некоторых случаях это может сделать, например, resources.arsc воспроизводимым. Вот пример из существующего рецепта:

$ mv my.app my.app_underlying
$ disorderfs --sort-dirents=yes --reverse-dirents=no my.app_underlying my.app

Потенциальные источники невоспроизводимых сборок

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

См. также этот выпуск GitLab.

Ошибка: сборки Android Studio имеют недетерминированный порядок ZIP

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

NB: это должно быть исправлено в плагине Android Gradle (com.android.tools.build:gradle / com.android.application) 7.1.X и более поздних версий.

При сборке APK с помощью Android Studio порядок ZIP-записей в APK может отличаться от порядка в APK, собранных с помощью прямого вызова gradle, что влияет на воспроизводимость; порядок может быть совершенно недетерминированным и даже отличаться между разными сборками одного и того же исходного кода.

Обходной путь для старых версий - вызывать gradle напрямую (как при сборке F-Droid или CI), минуя Android Studio:

$ ./gradlew assembleRelease

NB: в зависимости от конфигурации подписи, это может потребовать подписать APK с помощью apksigner, так как в этом случае подпись не выполняется Android Studio.

apksigner из build-tools >= 35.0.0-rc1 выводит непроверяемые APK

Использование apksigner из build-tools версии 34 позволяет получить APK, проверяемые apksigcopier, но более новые версии не работают. Мы отслеживаем эту проблему в #3299 и есть дополнительная информация в apksigcopier issues 105. Образы Github Actions CI Ubuntu, начиная с июля 2024 года, содержат 35-ю версию, поэтому необходимо вручную выбрать версию apksigner из 34, вместо стандартного шаблона “последняя версия”.

Ошибка: baseline.prof не является детерминированным

Иногда файл baseline.prof не является воспроизводимым при повторных сборках. Есть несколько возможных обходных путей:

  1. Перезапускать сборку, пока файл не совпадет.
  2. Использовать тот же номер ядра CPU, что и у вышестоящего разработчика. 1.Отключить базовый профиль. Добавьте следующий код в build.gradle:
  tasks.whenTaskAdded { task ->
      if (task.name.contains("ArtProfile")) {
          task.enabled = false
      }
  }

или это в build.gradle.kts:

  tasks.whenTaskAdded {
    if (name.contains("ArtProfile")) {
        enabled = false
    }
  }

Ошибка: baseline.profm не является детерминированным

Нестабильный assets/dexopt/baseline.profm (для просмотра может потребоваться учетная запись Google).

См. также этот обзор обходных путей.

Ошибка: coreLibraryDesugaring не является детерминированным

NB: это должно быть исправлено в R8 (com.android.tools:r8) 3.0.69 и более поздних версиях.

В некоторых случаях сборки не воспроизводятся из-за ошибки в coreLibraryDesugaring (для просмотра может потребоваться учетная запись Google); это повлияло на NewPipe.

Ошибка: различия в окончании строк в сборках для Windows и Linux

Различия между сборками под Windows и Linux делают сборки невоспроизводимыми (для просмотра может потребоваться учетная запись Google).

Обходной путь - запустить fix-newlines.py на неподписанном APK с “неправильными” окончаниями строк, чтобы изменить их с LF на CRLF (или наоборот с помощью --from-crlf) и zipalign его снова после этого.

Конкуренция: воспроизводимость может зависеть от количества процессоров/ядер

Это может повлиять на файлы .dex (хотя это, кажется, редкость) или нативный код (например, Rust).

В качестве обходного пути можно использовать только 1 процессор/ядро:

export CPUS_MAX=1
export CPUS=$(getconf _NPROCESSORS_ONLN)
for (( c=$CPUS_MAX; c<$CPUS; c++ )) ; do echo 0 > /sys/devices/system/cpu/cpu$c/online; done

NB: это решение влияет на всю машину, поэтому рекомендуется использовать его в непостоянной виртуальной машине или контейнере.

Для кода Rust можно установить codegen-units = 1.

См. также этот выпуск GitLab.

Пути сборки встроенных устройств

Встроенные пути сборки являются источником проблем с воспроизводимостью, влияющих на приложения, созданные, например, с помощью Flutter, python-for-android или использующие нативный код (например, Rust, C/C++, любой вид libfoo.so). Приложения, полностью написанные на Java и/или Kotlin, как правило, не страдают.

Часто самое простое решение - всегда использовать один и тот же рабочий каталог при сборке; например, /builds/fdroid/fdroiddata/build/your.app.id (F-Droid CI), /home/vagrant/build/your.app.id (F-Droid build server), /tmp/build или создать зеркальное отражение используемых папок, например, для macOS /Users/runner.

NB: использование подкаталога в доступном для записи во всем мире /tmp может иметь последствия для безопасности (в многопользовательских системах).

Если путь к SDK оказывается встроенным в Flutter, можно переместить SDK на указанный путь в рецепте и настроить его с помощью: flutter config --android-sdk <путь>, т.к. установки ANDROID_SDK_ROOT может быть недостаточно.

Если для библиотеки strip не был выполнен корректно, то в ней может сохраняться отладочная информация, которая обычно содержит множество путей. Включение «очистки» (strip) позволяет удалить их. Это можно сделать, правильно указав версию NDK или передав параметр -s линкеру. Также это можно выполнить вручную, например, с помощью llvm-strip.

Встроенные временные метки

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

Зачистка родной библиотеки

Похоже, что удаление родных библиотек, например, libfoo.so, может вызвать периодические проблемы с воспроизводимостью. Важно использовать точную версию NDK при пересборке, например, r21e. Иногда может помочь отключение зачистки. Gradle, похоже, по умолчанию удаляет общие библиотеки, даже если приложение получает общие библиотеки через библиотеку AAR. Вот как отключить эту функцию в Gradle:

android {
    packagingOptions {
        doNotStrip '**/*.so'
    }
}

NDK build-id

На разных машинах сборки используются разные пути к NDK и разные пути к проекту (и, соответственно, к его директории jni). Это приводит к разным путям к исходным файлам в отладочных символах, что заставляет компоновщик генерировать разные build-id, которые сохраняются после зачистки.

Одним из возможных решений является передача --build-id=none компоновщику, что полностью отключит генерацию build-id.

Стиль хэша NDK

LLVM передает разные значения по умолчанию компоновщикам на разных платформах. После того, как этот коммит был слит в NDK, --hash-style=gnu будет использоваться в Debian по умолчанию. Чтобы изменить стиль хэша, --hash-style=gnu можно передать компоновщику.

Строка версии clang из NDK в разделе .comment

Начиная с NDK r26, строка с версией Clang в разделе комментариев отличается при сборке на MacOS и Linux. Она выглядит примерно следующим образом

Android (12027248, +pgo, -bolt, +lto, +mlgo, based on r522817) clang version 18.0.1 (https://android.googlesource.com/toolchain/llvm-project d8003a456d14a3deb8054cdaa529ffbf02d9b262)

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

objcopy --remove-section .comment <файл>

platform ревизии

В 2014 году инструменты Android SDK были изменены на размещение двух элементов данных в AndroidManifest.xml как часть процесса сборки: platformBuildVersionName и platformBuildVersionCode. platformBuildVersionName включает “ревизию” пакета платформы, на основе которого производится сборка (например, android-23), однако разные “ревизии” одного и того же пакета платформы не могут быть установлены параллельно. Кроме того, инструменты SDK не поддерживают указание требуемой ревизии в процессе сборки. Это часто приводит к тому, что при воспроизводимой сборке единственным отличием является атрибут platformBuildVersionName.

Платформа platform - это часть Android SDK, которая представляет собой стандартную библиотеку, установленную на телефоне. Их версия состоит из двух частей: “код версии”, который представляет собой целое число, обозначающее релиз SDK, и “ревизия”, которая представляет собой версии исправлений для каждой платформы. Эти версии можно увидеть в прилагаемом файле build.prop. Каждая ревизия имеет свой номер в ro.build.version.incremental. Gradle не имеет возможности указать ревизию в compileSdkVersion или targetSdkVersion. Одновременно может быть установлена только одна platform-23, в отличие от build-tools, где каждый релиз может быть установлен параллельно.

Вот два примера, в которых все различия предположительно связаны с разными версиями платформы:

PNG Крушение/Дробление

Стандартной частью процесса сборки Android является запуск какого-либо инструмента для оптимизации PNG, например aapt singleCrunch, pngcrush, zopflipng или optipng. Они не дают детерминированного результата, и вопрос о причинах этого остается открытым. Поскольку PNG обычно фиксируются в исходном репозитории, обходным решением этой проблемы является запуск выбранного вами инструмента для PNG-файлов, а затем фиксация этих изменений в исходном репозитории (например, git). Затем отключите процесс оптимизации PNG по умолчанию, добавив это в build.gradle:

android {
    aaptOptions {
        cruncherEnabled = false
    }
}

Обратите внимание, что такие инструменты, как svgo, могут выполнять аналогичную оптимизацию SVG-файлов.

PNG, созданные на основе векторных рисунков

Android Gradle plugin generates PNG resources from vector drawables for old Android versions. К сожалению, сгенерированные PNG-файлы не воспроизводятся.

Вы можете отключить генерацию PNG, добавив это в build.gradle:

android {
    defaultConfig {
        vectorDrawables.generatedDensities = []
    }
}

Оптимизатор R8

Оказывается, что некоторые оптимизации R8 не являются детерминированными и выдают разный байткод при разных запусках сборки.

Например, R8 пытается оптимизировать использование ServiceLoader, составляя статический список всех сервисов в коде. Порядок этого списка может быть разным (или даже неполным) при каждом запуске сборки. Единственный способ избежать такого поведения - отключить подобную оптимизацию, объявив оптимизированные классы в proguard-rules.pro:

-keep class kotlinx.coroutines.CoroutineExceptionHandler
-keep class kotlinx.coroutines.internal.MainDispatcherFactory

Будьте осторожны с R8. Всегда многократно тестируйте свои сборки и отключайте оптимизации, которые дают недетерминированный результат.

Если байткод DEX отличается и зависит от количества ядер CPU, попробуйте обновить R8 до версии 8.6.33, 8.7.20, 8.8 или более новой, так как в них были исправлены несколько связанных с этим проблем.

Классы DEX идут в неправильном порядке

Даже если содержимое будет одинаковым, если порядок имен файлов классов изменен, воспроизводимость будет нарушена. Эта проблема была исправлена для связок (bundles) в AGP 8.8, но мы также наблюдали эту проблему и для APK. Сначала попробуйте использовать более новую версию AGP.

Сократитель (сжиматель) ресурсов

Можно уменьшить размер APK-файла, удалив из пакета неиспользуемые ресурсы. Это полезно, когда проект зависит от некоторых раздутых библиотек, таких как AppCompat, особенно при использовании R8/ProGuard code shrinking.

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

Информация о VCS

Начиная с Android Gradle Plugin 8.3, информация о VCS генерируется по умолчанию и включается в apk в META-INF/version-control-info.textproto, например.

repositories {
  system: GIT
  local_root_path: "$PROJECT_DIR"
  revision: "3a443877cd53e37d85cbc52adc8cfd558919d373"
}

Хотя мы понимаем, что разработчики производят и сборку, и тестирование, как часть своего рабочего процесса, пожалуйста, загружайте релизные APK, собранные только после того, как добавите метку в репозиторий, и с чистым деревом, и на основе именно того коммита, которому соответствует метка (т.е. без локальных изменений или оставшихся артефактов от предыдущих сборок). Только в исключительных случаях, когда вы не можете этого сделать, следует отключать vcsInfo (так как в противном случае это могло бы вызвать проблемы), что можно сделать следующим образом:

    buildTypes {
        release {
           vcsInfo.include false
        }
    }

Метаданные ZIP

APK используют формат файлов ZIP, который изначально был разработан для файловой системы MSDOS FAT. Разрешения файлов UNIX были добавлены в качестве расширения. Для APK нужен только самый базовый формат ZIP, без расширений. Эти расширения часто удаляются в процессе подписания финального релиза. Но в процессе сборки APK их можно добавить. Например:

--- a2dp.Vol_137.apk
+++ sigcp_a2dp.Vol_137.apk
@@ -1,50 +1,50 @@
--rw----     2.0 fat     8976 bX defN 79-Nov-30 00:00 AndroidManifest.xml
--rw----     2.0 fat  1958312 bX defN 79-Nov-30 00:00 classes.dex
--rw----     1.0 fat    78984 bx stor 79-Nov-30 00:00 resources.arsc
+-rw-rw-rw-  2.3 unx     8976 b- defN 80-000-00 00:00 AndroidManifest.xml
+-rw----     2.4 fat  1958312 b- defN 80-000-00 00:00 classes.dex
+-rw-rw-rw-  2.3 unx    78984 b- stor 80-000-00 00:00 resources.arsc

Несоответствующие цепочки инструментов

Разные цепочки инструментов могут создавать разные двоичные файлы. Обычный случай - когда для сборки APK используется более одной версии/расширения JDK. Иногда даже Gradle может смешивать версии JDK для сборки APK. Чтобы избежать подобных проблем, неиспользуемые JDK следует удалить.

В APK diff в файлах classes.dex будут присутствовать такие записи, как, например, Java 17 против Java 11:

-    .annotation system Ldalvik/annotation/Signature;
-        value = {
-            "()V"
-        }
-    .end annotation

Или вот так, например, Java 17 против Java 21:

-    .annotation system Ldalvik/annotation/MethodParameters;
-        accessFlags = {
-            0x8010
-        }
-        names = {
-            null
-        }
-    .end annotation

Разные версии NDK также создают разные двоичные файлы. Обычно это можно определить по метаданным, например, версии LLD, в нативных библиотеках. Однако, начиная с NDK r26d, наблюдается странное поведение: иногда при установке NDK изменяются только секции .shstrtab в ELF нативных библиотек. Нативные библиотеки могут быть собраны вместе с приложением или взяты из maven repo. Если AGP обнаружит, что NDK установлен, он будет использовать NDK для удаления нативных lib, но на самом деле он только испортит секцию .shstrtab нативной lib. Необходимо тщательно проверить установку NDK, чтобы убедиться, что она совпадает с установкой upstream, включая версию NDK и то, используется ли она AGP.

Поддержка страниц размером 16 КБ

Начиная с Android 15, Android поддерживает устройства, настроенные на использование страниц размером 16 КБ (устройства с 16 КБ). Если ваше приложение использует какие-либо библиотеки NDK, прямо или косвенно через SDK, то вам придется переделать свое приложение, чтобы оно работало на устройствах с 16 КБ. Дополнительная информация здесь

Инструкции для конкретного языка

Нативные библиотеки могут быть собраны с помощью различных инструментов и языков. Хотя они страдают от схожих воспроизводимых проблем при сборке, методы их устранения отличаются. Ниже приведены некоторые известные решения:

ndk-build

LOCAL_CFLAGS += <параметры компилятора> и LOCAL_LDFLAGS += -Wl,<параметры линковки> можно добавить в файлы Android.mk или в build.gradle/build.gradle.kts:

android {
    defaultConfig {
        externalNativeBuild {
            ndkBuild {
                arguments "LOCAL_CFLAGS+=<параметры компилятора> LOCAL_LDFLAGS+=-Wl,<параметры линковки>"
            }
        }
    }
}
CMake

Для версий CMake начиная с 3.13, add_compile_options("<параметры компилятора>") и add_link_options(LINKER:<параметры линковки>) могут быть добавлены глобально в CMakeLists.txt, например, чтобы удалить build-id:

add_link_options("LINKER:--build-id=none")

Эта команда оказывает влияние только на библиотеки, добавленных после вызова этой команды, поэтому она должна быть в начале файла CMake. Для версий CMake до 3.13, вместо этого можно использовать target_compile_options(<цель> PRIVATE <параметры компилятора>) и target_link_libraries(<цель> LINKER:<параметры линковки>), указав их для каждой отдельной цели. Параметры линковки также можно установить в файлах Gradle:

android {
    defaultConfig {
        externalNativeBuild.cmake {
          cFlags "-Wl,--build-id" // or
          arguments "-DCMAKE_SHARED_LINKER_FLAGS=-Wl,<параметры линковки>"
        }
    }
}

Флаг -ffile-prefix-map можно использовать для удаления встроенного пути сборки. Его можно добавить напрямую в CMakeLists.txt:

add_compile_options("-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.")

или в build.gradle:

externalNativeBuild {
    cmake {
        cFlags "-ffile-prefix-map=${rootDir}=."
        cppFlags "-ffile-prefix-map=${rootDir}=."
    }
}
Golang

Аргументы компоновщика могут быть добавлены в CGO_LDFLAGS. Некоторые другие полезные аргументы, которые можно передать go build, это -ldflags="-buildid=", -trimpath (чтобы избежать встроенных путей сборки) и -buildvcs=false.

Rust

Аргументы компилятора и компоновщика можно добавить в Cargo build.rustflags и rustc Codegen Options. Аргументы компоновщика могут быть добавлены с помощью -C link-args=-Wl,<параметры линковки>, а также, чтобы стереть пути сборки, можно добавить --remap-path-prefix=<старый>=<новый>.

Инструментарий Rust должен быть привязан к той же версии, что и в upstream. Это можно сделать при установке rustup с помощью команды rustup-init.sh -y --default-toolchain <version>.

Когда крейт openssl использует вендовую сборку OpenSSL, либу OpenSSL нужно настроить особым образом, чтобы она была воспроизводимой. SOURCE_DATE_EPOCH может быть установлен для удаления встроенных временных меток, а CARGO_TARGET_DIR может быть установлен в абсолютный путь, например /tmp/build, чтобы сделать путь к сборке воспроизводимым на разных машинах. NDK также должен находиться в том же пути, что можно решить, связав его с тем же путем.

Путь CARGO_HOME также играет важную роль и в итоге оказывается в собранных библиотеках, рекомендуется не менять его между сборками, например, экспортировать его перед запуском rustup, или любыми другими командами сборки, и не забывайте подхватывать переменные среды с помощью source из файла $CARGO_HOME/env.

Инструкции для конкретной библиотеки

Некоторые библиотеки генерируют недетерминированный код из-за временных меток, несортированных итераций и т.д. Некоторые известные исправления задокументированы ниже:

AboutLibraries Плагин Gradle

Вы можете запретить этому плагину (com.mikepenz.aboutlibraries.plugin) добавлять временную метку в создаваемый им JSON-файл, добавив это в build.gradle:

aboutLibraries {
    // Удалять файл отметки времени "generated", чтобы обеспечить воспроизводимость сборок
    excludeFields = ["generated"]
}

Для build.gradle.kts вместо этого добавьте следующее:

aboutLibraries {
    // Удалять файл отметки времени "generated", чтобы обеспечить воспроизводимость сборок
    excludeFields = arrayOf("generated")
}
EventBus

Он генерирует недетерминированный код, который можно отсортировать после создания классов. Подробные инструкции можно найти в исходном коде Eternity.

переход на воспроизводимые сборки

СДЕЛАТЬ

  • порядок сортировки jar для APK
  • Версии aapt дают разные результаты (имена подпапок XML и res/)

Источники