Размышления о скриптах конфигурации и векторах возможностей

Давно хотел поразглагольствовать о таких вещах, как скрипты configure. Они косвенно вносят большой вклад в мою головную боль в рабочее время в эти дни. Учитывая, что я возобновляю ведение личного блога здесь, давайте посмотрим, смогу ли я превратить эту тираду в пост.

Что такое configure

Предположим, вам нужно скомпилировать некоторое программное обеспечение из исходного tarball. Старая unix-традиция выглядит так: ./configure && make && make install. В части make нет ничего причудливого, но зачем вообще нужна configure?

Ну, configure — это просто сценарий оболочки, который проверяет среду сборки и генерирует некоторый заголовочный файл C для включения в исходный код. Каждый проект имеет свой уникальный сценарий configure, который проверяет различные вещи, заголовки, функции, любую функцию, которая нужна исходному коду, чтобы проверить ее наличие во время сборки и обеспечить путь отступления кода, если она отсутствует.

Скажем, исходному коду нужно вызвать функцию foo, которая существует только на некоторых поддерживаемых платформах проекта. Если configure обнаружит foo, он запишет строку #define HAVE_FOO 1 в сгенерированный заголовок. Затем исходный код может включать автоматически сгенерированный заголовок функции, использовать объявления CPP, такие как #if defined(HAVE_FOO), чтобы решить, существует ли функция foo в среде сборки.

configure обычно автоматически генерируется из шаблона с помощью autoconf. В некоторых проектах это может быть написанный вручную скрипт на python. Существуют также системы сборки, такие как cmake, которые полностью берут на себя роль configure, самостоятельно исследуют среду сборки и генерируют заголовок функции.

В любом случае, мои разглагольствования здесь относятся только к идее обнаружения особенностей во время сборки, и не имеют отношения к тому, как configure на самом деле реализована (хотя это тоже достаточно раздражает для отдельного поста в блоге).

Что такое вектор признаков

Сколько макросов HAVE_ у вас в проекте?

~/ubuntu/ghc$ grep -rIF HAVE_ | wc -l
842
Вход в полноэкранный режим Выйти из полноэкранного режима

Подождите секунду. Большинство из них должны быть простыми дубликатами, например, HAVE_FOO с большой вероятностью встречается в нескольких местах исходного текста. Следует действительно проверить, сколько возможностей (заголовков, функций и т.д.) проверяется командой configure.

~/ubuntu/ghc$ grep -rIF AC_CHECK_ | wc -l
171
Вход в полноэкранный режим Выход из полноэкранного режима

Приведенное выше число является заниженной оценкой, поскольку в autoconf одна строка AC_CHECK_HEADERS или AC_CHECK_FUNCS может проверять несколько функций.

Теперь мы можем ввести понятие «вектор признаков»: N-мерный булев вектор, где N соответствует количеству вещей, которые вы проверяете во время сборки. Каждое значение вектора признаков — это точка в пространстве признаков, определяющая конфигурацию времени сборки.

Насколько велико пространство признаков?

  • Определенно не так велико, как 2^N. Большинство измерений не ортогональны, можно представить скопления вещей, которые либо существуют как единое целое, либо не существуют вообще.
  • Тем не менее, это намного больше, чем пространство, в котором люди действительно тестируют CI, чтобы избежать гниения битов.

Моя тирада — это второй пункт выше.

К чему эта тирада

  • В GHC можно передавать различные аргументы configure для включения/выключения таких функций, как unreg codegen, большое адресное пространство, родной менеджер ввода-вывода и т.д. Конфигурация по умолчанию проходит набор тестов, но как только вы начнете изменять конфигурацию configure, ожидайте неудачных тестовых случаев. По крайней мере, эти случаи должны быть явно помечены как хрупкие/сломанные в этих конфигурациях!
  • В GHC, unix, возможно, в других местах, которые я взломал и забыл: API развивается, но люди забывают обновить код в #else — охраняемых частях, потому что он не тестируется на CI, возможно, эта конкретная CPP-проверяемая вещь, как считается, существует на всех платформах. Ну, WASI — это довольно ограниченная платформа, так что все эти гнилые части возвращаются, чтобы укусить вас, когда я нацеливаюсь на WASI.

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

Кроме того, это не просто вопрос «покрытия кода». Вполне возможно достичь высокого уровня покрытия, тестируя всего несколько векторов возможностей, оставляя при этом потенциально сломанные конфигурации времени сборки в тени.

Как решить эту проблему

Большинство программ, написанных с помощью тонны #ifdef, не обладают мышлением вектора возможностей, и у них нет логики тестирования для этого:

  • Выполните случайное тестирование в стиле QuickCheck в пространстве признаков. Генерируйте вектор признаков, запускайте тесты по нему, а «уменьшение» — это просто перемещение точки ближе к известной базовой точке, конфигурации по умолчанию, которую вы получаете при настройке на типичной платформе без каких-либо пользовательских аргументов. Это позволяет обнаружить сбои, возникающие из-за сложных & непреднамеренных взаимодействий между различными измерениями в векторе признаков.
  • Скрыть некоторые существующие автоопределяемые признаки. Это позволяет проводить тестирование на ограниченных/экзотических платформах, но при этом выполнять тесты на обычной платформе. Реализовать это нетривиально, особенно для вещей в стандартных библиотеках, но это должно быть возможно с помощью прагмы poison, создания оберток cc или даже изолированных системных корней.

Оцените статью
Procodings.ru
Добавить комментарий