[О блоге] [наверх] [пред] [2020-11-14 16:17:10+03:00] [401c0f635a1cdfb01068a48a4cdf40791d3db458]
Темы: [redo]

Замена autoutils redo системой

https://redo.readthedocs.io/en/latest/cookbook/redoconf-simple/
В моей C программе всё усложняется конфигурация сборки: есть
опциональные библиотеки, есть выбор методов/библиотек, появилась
зависимость между некоторыми переменными конфигурации. Если выбран метод
использования криптографии из библиотеки YYY, а не XXX, то всякие
XXX_CFLAGS=${XXX_CFLAGS:-`$PKG_CONFIG --cflags xxx`} исполняться не
должны. Их можно просто закомментировать, но это ручной труд. Добавлять
массу if-ов в сам конфиг (который обычный shell файл) -- тоже плохо,
хотя в нём уже есть hardcoded вещи типа добавления -pthread если
включено использование pthread mutex-ов. Плюс пользователю нужно явно
задавать pkgconf/pkg-config команду например, хотя она может быть
определена сама. Некоторые команды (plantuml, dmalloc) опциональны. В
общем или существенно усложнять простой config, который выглядит просто
как какой-то .ini файл, либо... что-то придумывать.

Определение команд, определение наличия библиотек (у меня опциональные
libtai, dmalloc, libtap например) -- всё это отдельные изолированные
друг от друга задачи. Между ними могут быть зависимости, как например
$CRYPTO_CFLAGS зависит от флагов конкретно выбранной криптобиблиотеки.
Раньше config содержал вереницу переменных внутри которых были вызовы
pkgconf, которые запускались все последовательно. Всё это (отдельные
задачи, зависимости между ними) явно задача для системы сборки!

Я помнил что в apenwarr/redo реализации есть redoconf -- штука которая
как-раз для замены ./configure ада, в которой можно набросать отдельными
файлами всякие детекторы. Я по сути взял из неё идеи, но саму
использовать не стал. Во-первых, там намёки на GNU Make. Во-вторых, она
банально просто где-то падала и не работала у меня -- что-то не так в
shell-скриптах. В-третьих, это полноценный такой framework, с кучей
библиотечных shell функций и обёрток -- требующий и изучения и уже не
такой и прекрасный как redo, не привязанный к языкам или чему-то
подобному. В-четвёртых, там очень много делается через создание shell
файлов с соответствующими костылями для всякого escape-а данных.
В-пятых, там подход заточен чтобы делать один большой и жирный compile
скрипт, содержащий все CFLAGS/LDFLAGS/LDLIBS для сборки целей. Я же у
себя для каждой конкретной .o-цели явно задаю и управляю какие конкретно
опции и флаги передаются -- чтобы лучше понимать что от чего зависит.

Но основную идею я заимствовал. Если программа использует $CC, то можно
сделать вот такой детектор, который учитывает и переменные окружения:

    $ cat conf/cmd/cc.do
    echo "${CC:-`command -v cc`}"

можно добавить зависимость от цели учитывающей изменение переменных
окружения (0676a1348e8ffe14a7840c8699d3afdc6857e24c) и учитывать не
поменялось ли значение самой цели:

    $ cat conf/cmd/cc.do
    redo-ifchange ../vars
    . ../vars
    echo "${CC:-`command -v cc`}" > $3
    command -v redo-stamp > /dev/null || exit 0
    redo-stamp < $3

проверка наличия redo-stamp чтобы работало под реализациями без этой
команды -- всё будет работать, просто изменение переменных окружения не
заставит пересобираться (если системы не основаны на хэшах, конечно же).
Программа которой нужна cc команда:

    redo-ifchange ../conf/cmd/cc
    read CC < ../conf/cmd/cc
    $CC ...

Сделать более сложные цели для определения команд, возможно с выводом в
stderr о том что команда не найдена и будет использоваться пустышка --
тривиально. Раньше у меня всё глобально зависело в целом от конфига и
переменных окружения (его касающиеся). Теперь команды сборки
документации зависят только от cmd/makeinfo, cmd/plantuml (который
подставит пустышку, с предупреждением, если настоящий не найден) и
изменение cmd/cc не повлияет на пересборку документации. Плюс для сборки
некоторые .o файлов нужны знания о PCSC библиотеке, добываемые через
pkgconf. Для других .o файлов нужен LibreSSL, тоже через pkgconf. Обе .o
зависят от разных conf/flags/{crypto,pcsc} флагов и поэтому вызовы
pkgconf-а могут быть распараллелены. Определение наличия и
работоспособности libtai библиотеки делается через сборку (прямо внутри
conf/flags/tai) временного файла с C кодом, но знание о $TAI_*FLAGS
нужно только при сборке log.o файла -- пока он дожидается сборки и
проверки наличия libtai, множество других целей уже успеет собраться,
так как они не зависят от conf/flags/tai.

Многие .o зависят от криптографических библиотек. Но им без разницы
какая именно библиотека будет использована. Поэтому я использую общие
$CRYPTO_*FLAGS, значение которых зависит от выбранного метода
криптографии:

    $ cat conf/flags/crypto
    redo-ifchange ../methods
    . ../methods
    src=crypto.$CRYPTO_METHOD
    redo-ifchange $src
    cat $src

а сама значение флагов для CRYPTO_METHOD=SSL (для примера):

    $ cat conf/flags/crypto.ssl
    redo-ifchange ../../config ../cmd/pkg-config
    . ../../config
    read PKG_CONFIG < ../cmd/pkg-config
    cat > $3 <<EOF
    ${SSL_CFLAGS:-`$PKG_CONFIG --cflags libcrypto`}
    ${SSL_LDFLAGS:-`$PKG_CONFIG --libs-only-L libcrypto`}
    ${SSL_LDLIBS:-`$PKG_CONFIG --libs-only-l libcrypto`}
    EOF
    command -v redo-stamp > /dev/null || exit 0
    redo-stamp < $3

тут и зависимость от конкретной pkg-config реализации и возможность
переопределения SSL_* и выхлоп просто в виде трёх строчек в файл.
Библиотеке которой надо использовать CRYPTO_*:

    redo-ifchange ../conf/flags/crypto
    read CRYPTO_CFLAGS < ../conf/flags/crypto
    # или
    { read X ; read X ; read CRYPTO_LDLIBS ; } < ../conf/flags/crypto

Подход с read-ом мне люб тем, что не надо париться с экранированием и
генерированием валидного shell-кода. Изначально у меня был lib.rc с
библиотечными shell функциями, но под конец полностью исчез.

В итоге, что я получил?

* возможность лёгкого добавления детекторов как и в autoconf. Сейчас у
  меня в проекте 14 conf/cmd и 19 conf/flags (всякие флаги сборки кода).
  Плюс с зависимостями между ними
* полное распараллеливание работы этих детекторов
* зависимость конкретных целей сборок совершенно разнородных вещей
  (документация, .o файлы, исполняемые, .a) от только нужным им командам
  и флагам/настройкам
* при изменении переменных окружения (хочу быстро всё пересобрать с
  CC=clang-NEW redo clean install) будут пересобраны только зависящие от
  CC цели (например сгенерированный asn1Parser-ом файл .c из .asn1
  спецификации не будет затронут). Зависимость только от конкретики

Это всё просто офигенно! Есть небольшой недостаток моего подхода: вместо
"redo-ifchange ../vars ; . ../vars" у меня большее количество
зависимостей и из-за read-а каждое чтение каждой команды/флага занимает
строчку. Но зато чистейший pure POSIX shell, никаких shell фунок
самопальных, отсутствие хаков и возни с экранированием строк, которая
присутствовала раньше (а у apenwarr/redoconf так вообще там целые экраны
кода для этого).

Для того чтобы узнать какие вообще опции можно изменить/настроить в
apenwarr/redoconf -- вызывают отдельные функи "регистрирующие" что у нас
есть на руках. У меня в каждом "детекторе" команд/флагов/настроек есть
возможность влияния на него через переменные окружения или тем что
прописано в config-файле. Де-факто это всегда запись/использование
переменных в виде ${WHATEVER}. Что мешает мне просто из всех .do
навыдёргивать эти переменные?

    $ cat conf/vars.list.do
    perl -ne 'print "$1\n" if /\$\{(\w+):?.*\}/' *.do cmd/*.do flags/*.do |
        sort | uniq | fmt > $3
    command -v redo-stamp > /dev/null || exit 0
    redo-stamp < $3

Почему в одном месте perl, а в другом sed? Так исторически сложилось,
лень переделывать :-). После выполнения, я получу красивый полный
список:

    AR ARM_CFLAGS ARM_LDFLAGS ARM_LDLIBS ASN1PARSER CC CFLAGS CLANG_FORMAT
    [...]
    TAI_LDFLAGS TAI_LDLIBS TAP_CFLAGS TAP_LDFLAGS TAP_LDLIBS TASN1_CFLAGS
    TASN1_LDFLAGS TASN1_LDLIBS VERSION

Без описания переменных правда. Но пока все они говорят сами за себя и
должны быть понятны. Для их описания уже понадобится конечно куда-то
зашивать эту метаинформацию, например просто в виде комментария рядом с
переменной оставлять и чуть-чуть усложнённым perl скриптом выдёргивать.

Когда я начал всю эту переделку, то совершенно полностью не был уверен в
результате и вообще будет ли оно того стоить. Превзошло все ожидания!

Единственное чего у меня совершенно нет, в отличии от apenwarr/redoconf
-- возможности работы в отдельной изолированной build директории, что
вообще конечно хорошим тоном является. Пока даже не думал как это сделать.
Можно просто наворотить себе git worktree в отдельных директориях, как хак.

    [оставить комментарий]