Как запустить C препроцессор в Boost.Build V2

Библиотеки Boost не использует make файлы или какую-либо другую более менее стандартную систему компиляции. Вместо этого Boost использует собственную систему сборки - Boost.Build V2. Система построена вокруг Boost.Jam, который, в свою очередь, является наследником Perforce Jam, - интерпретатора скриптов Jam. Вокруг возможностей Boost.Jam, который, между прочем, состоит из единственного исполняемого файла, навёрнута система скриптов изолирующая разработчика от особенностей конкретного компилятора и платформы.

Прелесть Boost.Build заключается в том, что с его помощью можно легко и свободно конструировать проекты, состоящие из множества библиотек, исполняемых модулей и кучи других файлов. Проблема, однако, заключается в том, что вся эта прелесть спрятана за довольно необычным и от того не слишком понятным скриптовым языком. Недостаток документации (хотя разработчики вкладывают немало сил в неё) и совершенно непонятные сообщения об ошибках не добавляют системе очков.

Отдельного рассказа стоит процесс разработки расширений для Boost.Build. Щедрое рассыпание отладочной печати по базовым скриптам системы, камлание бубном и гадание на кофейной гуще – зачастую просто необходимы в этом нелегком деле. Не смотря на всё это, Boost.Build V2 – моя любимая система сборки проектов. Заложенные в ней идеи процентов на 80-90% соответствуют идеалу. Реализация правда несколько хромает.

В качестве примера чего стоит написание расширения для Boost.Build расскажу как я писал обертку для запуска препроцессора Visual C++. Для начала небольшой экскурс в архитектуру. Boost.Build использует так называемый генераторы для выполнения любых преобразований над файлами. Например чтобы скомпилировать исходник на C в объектный файл, используется генератор, преобразующий C файлы в OBJ, с помощью выбранного компилятора. В моём случае я хотел их исходника на С или C++ получить I файл, содержащий вывод препроцессора.

Итак начинаем со стандартного шаблона генератора (который нужно сохранить как pp.jam - это важно):

# Импортируем нужные модули
import generators ;
import toolset ;
import type ;

# Регистрируем новый тип файла. "I" - имя типа, "i" - расширение файла
type.register I : i ;

# Регистрируем генератор для преобразования C в I. "pp.generate.c" - 
# полное имя правила, которое быдет вызвано для выполнения преобразования.
generators.register-standard pp.generate.c : C : I ;

# Правило, которое будет вызвано во время генерации
rule generate.c ( targets + : sources * : properties * )
{
}

# Команды, непосредственно работающие с файлами
actions generate.c
{
}

Пока что ничего не понятно, главное что это стандартный шаблон, который работает. :-) Следует упомянуть о разнице между правилами (rule foobar) и действиями (actions foobar). Интерпретатор Boost.Jam делает свою работу в два этапа: сначала он выполняет все правила, а затем все выбранные действия. Соответствие между правилами и действиями устанавливается очень просто – по имени. Если было вызвано правило foobar, то выполнятся и действия foobar, но потом. Если для правила или действий нет пары, то подразумевается что они есть, но пустые. В нашем случае есть правило generate.c и действия generate.c.

Далее добавляем команды для вызова препроцессора:

# Команды, непосредственно работающие с файлами
actions generate.c
{
    cl.exe /EP $(>) > $(<)
}

$(<) и $(>) соответствуют первому и второму параметру правила, т.е. это targers и sources соотвественно. Иными словами $(>) заменяется списком имен C файлов, а $(<) - списком имен I файлов. Все заботы о том, как преобразование C -> I, заданное генератором, доходит до вызова действий берет на себя система классов Boost.Build.

Чтобы задать какие именно файлы следует обработать нам понадобиться добавить вот такой код в Jamfile.v2:

# Импортируем наш модуль
import pp ;

# Собираем файл типа "i" с именем win32api из исходного файла win32api.c
i win32api : win32api.c ;

Теперь если посмотреть на то, какие команды генерирует этот код (bjam -ocommands.cmd) мы увидим следующее:

mkdir "bin"
mkdir "bin\\msvc-8.0"
mkdir "bin\\msvc-8.0\\debug"
mkdir "bin\\msvc-8.0\\debug\\threading-multi"
cl.exe /EP win32api.c > bin\\msvc-8.0\\debug\\threading-multi\\win32api.i

Уже хорошо, но в таком виде этот код совсем не годиться для использования. Во-первых, не указан путь к cl.exe. Во-вторых, нужен способ задать параметры компиляции – списки директорий для поиска включаемых файлов, директивы препроцессора, ключи компилятора.

А о том как это сделать читайте в следующей части. :-)

comments powered by Disqus