четверг, 27 октября 2011 г.

Что-то мне в последнее время не нравится make...

Что-то мне начал надоедать make. А так хочется быть гибким, хочется применять всякие новостные методики, но make мешается.

В принципе конечно все проблемы разрешаются, но что-то мне не хочется их решать. Да пусть этих Makefile вообще не будет. В каждом каталоге лежат, чтоб им пусто было. Лучшая система сборки та, которой нету.

Да, я, как всегда, занимаюсь велосипедостроением, но давайте обо всем по порядку.

Очень хочется применять TDD. Так, чтобы про настоящему, с максимальным покрытием кода. Состояния полного просветления я еще не достиг, и иногда начинаю просто писать код. Но вскоре понимаю, что действую неправильно. Если, ради того, чтобы сошелся тест надо написать еще один классик, то для нового класса тоже нужны тесты. Причем до того, как я начну его реализовывать. Хобби проект очень хорошее место для того, чтобы отточить навыки в TDD и освоить на практике новый стандарт c++.

Ради тестирования структура проекта выстраивается так, чтобы функция main была бы отдельно и не мешалась, а остальной код можно было бы использовать как с упомянутой выше main, так и в рамках тестового модуля. Классики TDD об этом почему-то не пишут. Может быть потому, что они все работают на java. Но в c ++ иначе никак не получится.

Итак, весь рабочий код, без main, удобно держать в отдельной папке. При компиляции эта папка сворачивается в один объектный модуль. Здесь я конечно не говорю про большие проекты из десятков библиотек, там иерархия сборки немного более сложная. Мы будем говорить о проектах, где некоторый набор исходных текстов собирается в один или несколько исполняемых модулей. Исходные тексты могут быть разложены по подкаталогам, это по моему правильно, и не меняет сильно дела. И в итоге получается, что логика сборки весьма проста и прямолинейна.

Кроме того, это помогает ускорить процесс сборки. Собирать цель из сотен объектных модулей по моему долго. Гораздо оптимальнее поделить модули на группы, каждую группу сворачивать в бандл, а потом просто слинковать несколько получившихся бандлов. Такая система используется, например, в ядре linux. А для TDD есть прямой резон в том, чтобы при сборке минимизировать количество компилируемых/линкуемых файлов. Поскольку в TDD изменения сильно локализованы и незначительны по размеру, а сборки — достаточно часты.

Короче, нужна максимально быстрая сборка. И make с этим более-менее справляется, хотя к его скорости и есть серьёзные претензии у сообщества.

Кроме того нужна минимальная пересборка. И с этим make справляется, если прикрутить к нему файлы с зависимостями.

Но если, в полном соответствии с лучшими практиками, как то рефакторинг, мы начнем переименовывать include файлы, то у нас съезжают зависимости, и мы получаем ошибку сборки. И это происходит не смотря на то, что другие файлы в списках зависимостей претерпели изменения и на самом деле сборка цела. Чтобы преодолеть это, надо обновлять файл зависимости. Честно говоря, не могу придумать, как сделать это выборочно, а полная очистка вызывает полную пересборку, чего мы хотели бы избежать.

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

И тогда я написал скрипт, который умеет превращать каталог в один объектный модуль. Конечно, с учетом всех модификаций и изменений. Этот, очень простой скрипт на питоне можно найти здесь.

Его использование дает несколько больших плюсов:
  • Мне больше не нужны Makefile в подкаталогах. От корневого Makefile отказываться, конечно, не стоит. Но на его плечи ложиться сравнительно небольшая нагрузка, как то свести все воедино, и подчистить за собой.
  • Зависимости я определяю на лету, думаю что препроцессор отрабатывает сравнительно быстро, и мне нет смысла с этим заморачиваться пока у меня проект не перерос какие-то разумные размеры.
  • Мне больше нет необходимости наблюдать вывод сборки. Я могу сделать дружественные сообщения, вплоть до прогресса. Это можно было сделать и в Makefile, но там это не так гибко.

Сам про себе скрипт, конечно не универсален, и имеет минусы:
  • Все исходные тексты, для полноты картины даже main.cpp, необходимо раскладывать по отдельным папочкам. Ведь не для того все это затевалось, чтобы потом некоторые файлы собирать чем-то еще?
  • Пока он никак не многопоточен. Это большой минус и его можно решить по разному. Можно, например, запускать несколько каталогов паралельно - самое простое. Или можно встроить в скрипт пул компиляторов, но тогда это сильно утяжелит его, думаю в этом нет особой необходимости.

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