Использование Cog в make-процессе

Автор: Александр Бельченко
Дата: 20 июня 2005

Данная статья рассказывает о некоторых методиках использования Cog в сборке проекта при помощи make.

Что такое Cog

Cog -- это простой, но в тоже время мощный инструмент для генерации текстов [программ] при помощи средств языка Python. Cog -- это программа, которая пишет программы.

Cog позволяет вставлять в исходные тексты программ участки, содержание которых зависит от различных внешних данных. Тем самым достаточно легко делать специализированные генераторы для участков исходного кода; использование таких генераторов очень сильно облегчает жизнь, когда вам нужно многократно (хотя можно и однократно) корректировать текст программы в соответствии с четким алгоритмом. А использование языка питон, а не какого-нибудь специального искусственного языка, очень сильно облегчает жизнь, поскольку в нашем распоряжении оказывается вся мощь питона и все стандартные (и нестандартные) библиотеки питона. Учитывая, что питон имеет очень мощные способности по работе с текстом, а также с разными форматами (в том числе и XML), то могу смело заявить, что в своей области Cog -- это просто супер-инструмент, которому нет равных.

Конкретнее о возможностях Cog можно ознакомиться из его описания на сайте автора Неда Бэтчелдера. Несколько примеров использования Cog см. здесь.

Cog и ваш проект

Одиночный выстрел и стрельба очередями

Условно можно разделить Cog-генераторы по частоте использования на однократные и многократные. Это не самые лучшие термины, какие я мог придумать, к тому же подобное деление весьма условно.

Однократные генераторы используются преимущественно в момент написания конкретного участка кода. Модификация такого кода происходит обычно очень редко. Причиной использования генератора для таких участков может быть тот факт, что требуется написать много однотипного кода, который легко поддается автоматической генерации. Либо необходимо сформировать инициализированный массив, значения элементов которого рассчитываются по сложной математической формуле, и такой массив, как правило, редко меняется.

Многократные генераторы пишутся в расчете на частое использование в течении жизни проекта. Они могут производить автоматическую генерацию кода на основе каких-то внешних данных. Например, в одном из моих последних проектов я использовал автоматическую генерацию Си-кода для работы с данными, которые хранились во внешней flash-памяти, и имели четкую заранее определенную структуру. Структура и набор данных определялась в виде описания на языке Python. Из этого описания затем формировалась Си-структура данных и специальный массив с описанием этой структуры для обеспечения доступа к данным с верхнего уровня (по аналогии с системой Пирамида).

Запуск Cog при сборке проекта

Запуск вручную

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

cog:
        cog.py -r foo.c
        cog.py -r bar.c

Тогда набрав просто

make cog

вы запустите cog-обработку для файлов foo.c и bar.c. Если главная цель носит имя all, то командой

make cog all

вы запустите обработку и сборку главной цели.

Подобный метод хорош для однократных генераторов, а также для ситуаций, когда год генератора содержится в самом исходном файле.

Автоматический запуск. Построение зависимостей

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

Для того, чтобы cog-обработка запускалась автоматически каждый раз, когда она нужна, необходимо иметь способ построить зависимости, которыми будет руководиться make при сборке проекта. Т.е. в результате cog-обработки должен получаться некий файл, который будет использоваться в качестве make-цели.

Если обработке подвергается, например, файл foo.c, то использовать его в качестве цели будет плохой идеей. Отчасти потому, что содержимое этого файла может изменяться как в результате cog-обработки, так и при ручной правке.

Можно использовать некоторый вспомогательный (dummy) файл, содержимое которого не имеет значения (для make важна лишь дата модификации файла). Такой вспомогательный файл можно было бы сформировать следующим образом:

foo.cog: foo.c
        cog.py -r foo.c
        echo done > foo.cog

Если при генерации используются данные из внешних файлов, например, bar.ext, то правило сборки можно было бы переписать следующим образом:

foo.cog: foo.c bar.ext
        cog.py -r foo.c
        echo done > foo.cog

Описанный метод имеет один огромный недостаток: он не будет работать. Видно, что cog-обработка будет запускаться каждый раз при изменении даты файла foo.c (или bar.ext). А поскольку дата foo.c будет меняться в результате cog-обработки, то make просто зациклится.

Что же делать?

Я решил для себя эту проблему простым способом: выделил тело cog-генератора в отдельный питоновый файл (например, foo.py для рассматриваемого примера). А в теле foo.c генератор вырождается в импорт этого файла (и, возможно, вызов функции из этого файла):

[[[cog import foo ]]]

Побочным эффектом от импорта является создание файла содержащего скомпилированный байт-код для файла foo.py -- он будет носить название foo.pyc. Этот файл можно использовать как имя цели, вместо foo.cog.

В таком случае правило cog-обработки можно записать так:

foo.pyc: foo.py bar.ext
        cog.py -r foo.c
        echo done > foo.pyc

Список имен cog-целей удобно присвоить некоторой make-переменной:

cog_targets := foo.pyc zzz.pyc

И затем использовать в списке зависимостей для построения главной цели проекта. Например:

program.exe: $(cog_targets) $(obj_files)
        $(LINK) $(obj_files)

Указывая список cog-целей перед списком объектных файлов, мы тем самым заставляем make сначала провести всю необходимую cog-обработку, а затем уже компилировать исходные файлы.

Если вы работаете с IDE

Хотя целью данной статьи является показать способы использования Cog при сборке проекта, используя make, однако стоит сказать пару слов и про IDE.

В некоторых средах разработки (например, Softune) настройка опций компиляции-сборки проекта допускает указание так называемых пред- и пост- обработчиков (PreAction, PostAction). Как следует из названий, можно задать команду, которая будет выполняться перед началом компиляции файла или после успешного завершения компиляции.

Добавление в PreAction команды запуска Cog позволит перед каждой компиляцией конкретных файлов запускать для них Cog-обработку.

Примеры Cog-генераторов

Несколько примеров приведены на следующей странице.