© 2005, Иван Равин
При работе с большими проектами появляется потребность в их разбиении на более мелкие специализированные модули. Например, нередко "релиз" программы включает в себя исполняемый файл, а также набор библиотек. Это могут быть динамически загружаемые библиотеки (dll), ActiveX библиотеки, пакеты (bpl).
Delphi предоставляет механизм группировки проектов, собирая их в Project Group – файлы (.bpg и .bdsgroup). Открыв такой файл, можно собрать большой проект, выполнив команду Project -> Build All Projects. При должной настройке каталогов в проектах (см. например статью на DelphiPlus.org) мы в результате сборки получаем готовый релиз, из которого дальше можно готовить дистрибутив.
Казалось бы, что еще надо? А надо исключить "ручной" этап подготовки дистрибутива. Сборка файлов, генерация баз могут быть выполнены с помощью bat – файлов и скриптов. Средства дополнительной обработки модулей (например, установка защиты, архивирование, сборка инсталлятора) также могут работать из командной строки. А вот выполнение команды Build All Projects требует открытия Delphi, да еще, чтобы у текущего пользователя были установлены все необходимые компоненты (даже если они есть на компьютере, это еще не значит, что они есть на палитре у конкретного пользователя). Плюс, возможно, необходима ручная установка флагов компиляции для того или иного вида дистрибутива. Все это можно автоматизировать, написав пакетный файл для компиляции нашей группы проектов с помощью компилятора командной строки dcc32.exe.
Но и тут возникают проблемы: надо будет отслеживать соответствие между нашей группой проектов и этим bat-файлом. Лучше всего исключить эту зависимость, написав универсальный bat-файл, который будет использовать информацию из файла группы проектов. Взглянем на такой файл (дальше речь пойдет об использовании групп проектов до Delphi7 включительно. Начиная с Delphi8 формат файлов изменился, но несложно написать программу для преобразования xml в bpg файл. Возможно, стоит попробовать применить результаты данной статьи с использованием XML средств сборки, например, Ant.
Итак, откроем в текстовом редакторе простейший файл bpg:
#------------------------------------------------------------------------------
VERSION = BWS.01
#------------------------------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#------------------------------------------------------------------------------
PROJECTS = Project1.exe
#------------------------------------------------------------------------------
default: $(PROJECTS)
#------------------------------------------------------------------------------
Project1.exe: Project1.dpr
$(DCC)
Кто хоть раз работал с утилитой MAKE, сразу скажет, что это make-скрипт. Что мы тут видим:
Вот и решение: нужно выполнить команду "make -f ProjectGroup1.bpg" и группа скомпилируется.
Но не тут-то было. На первом же проекте вы получите ошибку, что компилятор не может найти тот или иной файл. Скорее всего, это VCLx0 или файлы от компонент, которые применяются в вашем проекте. Почему же все компилируется в Delphi? А потому, что Delphi знает, где искать нужные файлы: в путях из переменной среды Library Path, значение которой записано в реестре.
Средства командной строки не позволяют читать реестр Windows, поэтому создадим make-файл Compsearch.mki, куда скопируем значение ключа "HKCU\Software\Borland\Delphi\x.0\Library\Search Path".
Можно этот файл структурировать, например так:
< Compsearch.mki >
BROOT = c:\borland\delphi5
BORLAND = $(BROOT)\Objrepos;$(BROOT)\Projects\Bpl
ICS = C:\VCL\ics\DELPHI\D5
MIDWARE = C:\VCL\midware\DELPHI\D5
JVCL = C:\VCL\jvcl\lib\D5;C:\VCL\jcl\lib\D5;C:\VCL\jvcl\common
LIBPATH = $(JVCL);$(ICS);$(MIDWARE);$(BORLAND)
RESPATH = C:\VCL\JVCL\Resources
Вернемся к нашей группе проектов. Добавим несколько строк. Вот кусочек текста:
...
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
# файл с путями к библиотекам
!include CompSearch.mki
# Корень проекта
ROOTDIR = c:\work
# каталог с этим файлом и файлом compile.bat
SRCROOTDIR = $(ROOTDIR)\source
# Опции компилятора
COMPILER = 5
DCP_DIR = $(ROOTDIR)\DCU
DCU_DIR = $(ROOTDIR)\DCU\$(COMPILER)
DCCOPT = -LN$(DCP_DIR) -i$(LIBPATH) -n$(DCU_DIR) -u$(DCU_DIR);$(LIBPATH) \
-r$(RESPATH) \
-w- -h- -b -q
# Переопределение стандартного макроса DCC
DCC = $(DCC) $(DCCOPT)
BRCC = $(ROOT)\bin\brcc32.exe $**
#------------------------------------------------------------------------------
...
Если вы создали заранее все каталоги (DCP_DIR, DCU_DIR, а также те, что указаны в Output Directory проектов), то, возможно, этого будет достаточно для компиляции вашей группы проектов. При этом Delphi по-прежнему сможет работать с вашим bpg-файлом, не изменяя введенной вами информации. Но, если проекты лежат в отдельных каталогах, компиляция не будет выполнена.
Посмотрим на правило из более сложного файла bpg:
Doc5.bpl: ..\Common\Document\Doc5.dpkПосмотрим также на сам файл проекта Doc5.dpk. Оказывается, что файлы записаны в проекте с относительными путями, поэтому, чтобы сработал макрос DCC, необходимо перейти в каталог проекта, выполнить компиляцию и вернуться обратно, например, так:
$(DCC)
Doc5.bpl: ..\Common\Document\Doc5.dpk
CD Common\Document
$(DCC)
CD ..\..\
Можно ли переопределить макрос DCC так, чтобы не писать эти строки? Все макросы представляют собой "строчные" команды. В то же время выполнение 3 DOS-команд в одной строке невозможно. Поэтому мы не сможем нужным образом переопределить макроподстановку DCC, и нам придется добавлять строчки с командой CD в каждое make-правило. При большом количестве проектов это неудобно, да и неизвестно, сможет ли Delphi работать с таким файлом.
Есть ли выход?
В Project Jedi VCL была создана специальная утилита конвертации Bpg2make, но, как оказывается, можно обойтись и без нее, более того, bpg файл можно использовать и для пост-обработки проектов, например, установки защиты на скомпилированные модули. Как окажется далее, достаточно будет добавить только 1 строчку в bpg-файл.
Идея заключается в подмене макроса DCC таким образом, чтобы в нем вызывался bat-файл, который:
А в самом bpg-файле должна быть сделана такая подстановка (о назначении 2 параметра будет сказано позже):
DCC = compile.bat $** $(SRCROOTDIR)В результате, в файл compile.bat с помощью параметра №1 будет передан базовый файл правила. Например, в приведенном выше примере при сборке Doc5.bpl make "развернет" этот вызов в следующий:
compile.bat ..\Common\Document\Doc5.dpk c:\work\SourceОстается в этом bat-файле вычислить каталог, перейти в него и выполнить компиляцию проекта. Обратный переход был бы нужен, если бы вызов выглядел так:
DCC = call compile.bat ..\Common\Document\Doc5.dpkВ данном же случае командный интерпретатор избавляет нас от необходимости обратного перехода. Однако средствами командной строки сложно из строки выделить путь. Оказывается, это может сделать утилита Make с помощью модификаторов макроса $**:
Как ими воспользоваться? Предлагается из пакетного файла Compile.bat снова вызвать make.exe, передав через параметр имя универсального make-файла, умеющего собирать любой проект. Итак, bat-файл оказался несложным (тут нам понадобится параметр №2 – каталог с файлом compile.mak):
<compile.bat>(файл содержит средства прерывания при ошибках – exit 1; расширение базового файла дописывается символами "dummy").
make.exe -f %2\compile.mak %1dummy
@if not errorlevel 1 goto exit
exit 1
:exit
Осталось подготовить универсальный скрипт compile.mak, который содержит правила сборки проектов, заканчивающихся расширением dprdummy и dpkdummy:
<compile.mak>
# неявное правило для всех файлов с расширением dprdummy
.dpr.dprdummy:
cd $(**D)
$(ROOT)\bin\dcc32.exe $(**F) $(DCCOPT)
# неявное правило для всех файлов с расширением dpkdummy
.dpk.dpkdummy:
cd $(**D)
$(ROOT)\bin\dcc32.exe $(**F) $(DCCOPT)
Итак, что же мы получим при сборке нашего примера?
Compile.bat ..\Common\Document\Doc5.dpk c:\work\source
make.exe -f c:\work\source\compile.mak ..\Common\Document\Doc5.dpkdummy
cd ..\Common\Document
$(ROOT)\bin\dcc32.exe Doc5.dpk $(DCCOPT)
Согласно неявному правилу .dpk.dpkdummy, для целевых файлов с расширением dpkdummy ищутся базовые файлы с тем же именем, но расширением dpk, поэтому макроопределение $(**F) развернулось в нужную нам строку Doc5.dpk. Задача выполнена.
В этих же неявных правилах можно описать обработку флагов компиляции и пост-обработку откомпилированных файлов.
Остается одна хитрость. В начале данной статьи было показано, как в bpg файл добавить нужные каталоги. Пока этот файл один, проблем нет. Когда их много, почему бы не сделать все это один раз в каком-нибудь include-файле? Например, в уже известном нам compile.mak. Заодно можно в нем же переопределить макрос DCC. Вот что получается:
<compile.mak>
#------------------------------------------------------------------------------
# Вставить в .bpg после объявления DCC, и до описания правил:
# !include c:\work\source\compile.mak
#------------------------------------------------------------------------------
ROOTDIR = c:\work
# каталог с этим файлом и файлом compile.bat
SRCROOTDIR = $(ROOTDIR)\source
# файл с путями к библиотекам
!include $(SRCROOTDIR)\CompSearch.mki
# Переопределение стандартного макроса DCC
DCC = $(SRCROOTDIR)\compile.bat $** $(SRCROOTDIR)
#------------------------------------------------------------------------------
# Опции компилятора
COMPILER = 5
DCP_DIR = $(ROOTDIR)\DCU
DCU_DIR = $(ROOTDIR)\DCU\$(COMPILER)
DCCOPT = -LN$(DCP_DIR) -i$(LIBPATH) -n$(DCU_DIR) -u$(DCP_DIR);$(LIBPATH) \
-r$(RESPATH) \
-w- -h- -b -q
#------------------------------------------------------------------------------
# неявное правило для всех файлов с расширением dprdummy
.dpr.dprdummy:
cd $(**D)
$(ROOT)\bin\dcc32.exe $(**F) $(DCCOPT)
# неявное правило для всех файлов с расширением dpkdummy
.dpk.dpkdummy:
cd $(**D)
$(ROOT)\bin\dcc32.exe $(**F) $(DCCOPT)
Сюда же можно включить содержимое файла Compsearch.mki, но я предпочитаю держать его отдельно, поскольку пути установки библиотек на разных компьютерах могут различаться. Структура же рабочих каталогов при использовании репозитория будет на всех компьютерах одинаковой (ROOTDIR, SRCROOTDIR и т.д).
Итак, один раз готовим файлы Compsearch.mki, Compile.bat и Compile.mak (все в каталоге ROOTDIR) – и проблема автоматической компиляции решена. Остается добавить в ваш bpg-файл единственную строчку (где-то после определения DCC):
#------------------------------------------------------------------------------
VERSION = BWS.01
#------------------------------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
!include c:\work\source\compile.mak
#------------------------------------------------------------------------------
PROJECTS = Project1.exe
#------------------------------------------------------------------------------
default: $(PROJECTS)
#------------------------------------------------------------------------------
Project1.exe: Project1.dpr
$(DCC)
Теперь перейдите в каталог с bpg-файлом, выполните команду "make -f ProjectGroup1.bpg" и все ваши проекты будут собраны. Для сборки конкретного проекта добавьте его имя: make.exe -f ProjectGroup1.bpg Project1.exe. Возможно, этот способ не заработает, если где-то в путях есть пробелы. Я стараюсь избегать пробелов в рабочих каталогах, и таких тестов не проводил. Но уверен, что расстановка кавычек (") в нужных местах решит проблемы.
Возможно, вам захочется провести дополнительную обработку откомпилированных файлов (strip relocation, шифрование, сжатие). Просто дополните неявные правила .dprdummy и .dpkdummy нужными командами. Для их вызова очень помогут макросы:
Смотрите также описание MAKE. Следует помнить, что Make в Borland Delphi несколько отличается и обладает меньшими возможностями.
В упоминавшемся ранее ресурсе достаточно подробно расписана структура каталогов проекта. Я бы добавил к приведенной структуре каталоги DCU, и, если необходимо, OBJ. Кроме того, если вы используете несколько компиляторов, то каталог DCU следует разделить на подкаталоги: DCU\5, DCU\7, DCU\9. Все откомпилированные модули должны попадать в эти каталоги (настройка Unit Output Directory или –n$(DCU_DIR) для dcc32). Это позволяет упростить настройку путей поиска в ваших проектах – в Search Path достаточно добавить директорию DCU\x вместо перечисления путей к исходникам проектов, от которых зависит текущий проект. Отказ от следования этому правилу приводит к тому, что проект становится "непереносимым" - он может быть собран только на компьютере разработчика, поскольку сильно зависит от настроек среды. Попробуйте, например, откомпилировать исходники проекта Гедемин.
Если ваш проект содержит пакеты, то стоит выделить отдельный каталог для DCP-файлов – настройте DCP output directory всех проектов на него (-LN$(DCP_DIR)). Как правило, версия Delphi должна входить в название пакета (например, VCL50), поэтому нет необходимости разделять каталоги DCP файлов по версиям компилятора, как это сделано для файлов DCU. DCP - файл содержит заголовки входящих в него модулей, а также конкатенацию входящих в пакет модулей (DCU). Поэтому, если ваш проект является пакетом или использует пакеты, то нет необходимости при компиляции искать DCU-файлы. Наоборот, Search Path таких проектов должен указывать на каталог с DCP – файлами.
Вот возможная структура каталогов:
|--$(ROOT_DIR)Именно она использована в универсальном файле compile.mak. Я использую пакеты, поэтому в опциях компилятора указал путь к DCP – файлам:
|--DCU – тут находятся файлы DCP (DCP_DIR)
|--5
|--7 - тут DCU файлы (DCU_DIR)
|--9
|--Source – тут compile.mak, compile.bat и compsearch.mki. (SRCROOTDIR)
|--Projects
|-- Project1
...
-u$(DCP_DIR);$(LIBPATH)Без пакетов надо было написать:
-u$(DCU_DIR);$(LIBPATH)
Итак, вот они правила:
Правильная настройка DCU и DCP каталогов не только поможет в управлении кодом, но и облегчит отладку сложных проектов, поскольку в сборку будут попадать именно те файлы, которые ожидаются, а не "завалявшиеся" в случайных местах и уже неактуальные dcu и dcp.
Copyright© 2005, Иван Равин Специально для Delphi Plus
Пожалуйста, оцените статью