December 27, 2022

Android и дистилляция

Чем дольше мы работаем c Android, тем больше становится размер его репозиториев. Если версия 4 была еще туда-сюда, и занимала всего каких-то 6.4 Gb что уже казалось ужасом (репозиторий такого размера SVN не переварит никогда), то дальше становилось только хуже. Собственно, это была одна из причин, по которой мы перешли с SVN на чистый Perforce. Аппетиты Google меж тем продолжали расти. У них же тоже Perforce, а он всё стерпит. Версия 6.0 уже занимала 36 гигабайт (36, Карл). Причем код Framework вырос незначительо (с 846 до 1400 Мб.). Основной объем занимает каталог Prebuilt. Он разросся с двух гигабайт до 7! Также разрослись внешние библиотеки.

Что лежит в prebuilt? В основном какие-то левые бинарники, которыми Android и собирается. Изначально у них были довольно простые требования. Типа, иметь gcc такой-то версии, bash такой-то, git и еще кое-что. Но чем дальше, тем больше Google понимал, что не смогут сделать нормальную среду сборки независимой от окружения. Оно и понятно. Если посмотреть внутрь, то там ТАКОЕ! Сыграл свою роль и уход от православного Make на самописные многостадийные системы сборки. В итоге было принято "стратегическое" решение не обращать внимание на то, что там установлено у юзера, а тупо затащить в репозиторий все бинарники, которые могут понадобиться при сборке. Начиная с JDK и кончая Clang. Так и было сделано. Ну а что, диски же дешевеют?

Как результат репозиторий Android 11 занимает уже не 36 гигабайт, а 120 гигабайт. И львиную долю (55 GB) всего этого беспредела по-прежнему занимает prebuilts. Как вы понимаете, разработчику (то есть мне) давно надоела такая ситуация. Во-первых, нужно много мета на SSD, во-вторых, нужно хранить все в VCS. Что не может радовать. Поэтому я задумался над решением, которое позволяло бы выделить из всего этого скопища файлов что-то действительно необходимое для сборки. Идея была проста - запустим сборку, посмотрим какие файлы будут прочитаны из репозитория во время процесса компиляции и вуаля!

Легко сказать, но трудно сделать! Процесс сборки с нуля занимает 8 часов. А сам репозиторий содержит более миллиона файлов. Потенциально мне мог бы помочь Inotify, но он не работает рекурсивно. А надеяться, что можно будет добавить все десятки тысяч директориев без просадки производительности не было и мысли. Тогда я задумался о FUSE. На просторах Github я нашел подходящий проект Rémi Flament - loggedfs. Он был создан для других целей и с другим дизайном, но подходил мне как основа для переделки. Так был рожден проект distillerFS - https://github.com/a-jelly/distillerFS

Я избавился от зависимостей pcre и libxm2, а также переписал код с С++ на чистый С (прости меня, Rémi!). Также я добавил конфигурацию на TOML, который более человеко-читаем чем XML, и логику сохранения журнала всех действий в памяти. Это позволило практически не снижая производительности и используя не запредельно много памяти запустить сборку в режиме логирования. Спустя восемь часов я имел полный список файлов, которые были РЕАЛЬНО необходимы. Конечно, каждая конфигурация сборки (скажем, если мы сменим hardware platform) может затронуть несколько другие файлы. Но ничто не мешает запустить все необходимые конфигурации, собрать по ним статистику и затем сделать join.

Были проблемы и с семантикой. Так, система сборки Android часто проверяет наличие файла, чтобы потом никак его не использовать. В этом случае файл можно не копировать, а просто сделать touch на это имя. В общем, через неделю экспериментов у меня получился скрипт, который создает из лога сборки новый репозиторий, но уже гораздо более компактный. После "дистилляции" обьем файлов для Android 11 уменьшился с 120 до 6 гигабайт!
Мне кажется - неплохая прибавка к пенсии. Понятно, что distillerFS можно использовать и для других целей, так что если вдруг у вас возникла необходимость в быстром логировании доступа к диску - можете пользоваться. Лицензия либеральная - Apache. Так было изначально, и я не стал ничего менять.

Сборка тривиальная:

sudo apt-get install libfuse-dev git clone https://github.com/a-jelly/distillerFS cd distillerFS make make install

Дистилляция тоже не сложная

start_distfs.sh cd OriginalRepo make stop_distfs.sh process_log.lua access.log /home/user/OriginalRepo /home/user/DistilledRepo > copy_distist.sh sh copy_dist.sh

Lua-скрипт прилагается. Понятно, что он заточен под мои цели, и вы можете доработать его рашпилем, если необходимо.
Такие дела.