Cтруктура и организация C++ проектов с использованием mxx

Е.А.Охотников
2003.06.22

Предисловие

Автор занимается проблемами организации рациональной работы с проектами на C++ начиная с 1995. С этого времи используемая автором структра проектов постоянно трансформировалась и, хочется надеятся, совершенствовалась. Ниже описывается структура, которая была выработана к началу 2003 года.

Структрура каталогов проекта и управление компиляцией проекта тесно связаны друг с другом. В качестве удобной и переносимой системы компиляции автор использует собственную разработку -- утилиту mxxc и набор шаблонов mxx-4-add-on. Развитие mxx шло параллельно работе по определению рациональной структуры каталогов. При этом возможности mxx во многом определяли, как будет выглядеть файловая структура проектов. А изменяющаяся структура проектов накладывала новые требования на mxx.

В результате описываемая ниже структура проектов завязана на возможности и средства, предоставляемые mxx. А текущая версия mxx и, особенно, шаблонов mxx-4-add-on ориентированны на данную структуру проектов.

Автор выражает глубокую признательность всем, кто вольно или невольно был причастен к выработке данной структуры проектов. А так же тем, кто был вынужден использовать в своей работе mxx и mxx-4-add-on.

Структура одного проекта

Одним из важнейших факторов удобства работы с каким-либо проектом является организация файловой структуры для исходных файлов проекта. Хотя понятие удобства является субъективным - как именование классов и переменных в программе, так и именование и расположение исходных файлов определяется личными пристрастиями каждого разработчика. Исходя из личных предпочтений автора стандартный шаблон C++ для компиляции C++ проектов предъявляет некоторые требования к расположению объектных файлов. Причину возникновения этих требований необходимо объяснить на примерах.

Довольно распространненой практикой сейчас является помещение одного проекта (модуля) в один каталог с небольшим количеством подкаталогов. Например, при использовании среды разработки Visual C++ разработчику предлагается приблизительно следующая структура каталогов:

module/
|  *.hpp, *.cpp, *.rc, проектные файлы и т.д.
|--Release/
|  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
|     в режиме оптимизации
|--Debug/
|  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
|     в отладочном режиме
`--Res/
   *.rc1, *.bmp, *.ico и другие ресурсные файлы

Если разрабатывается проект, в котором содержится несколько модулей, то дерево каталогов может выглядеть приблизительно так:

project/
|--module-1/
|  |  *.hpp, *.cpp, *.rc, проектные файлы и т.д.
|  |--Release/
|  |  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
|  |     в режиме оптимизации
|  |--Debug/
|  |  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
|  |     в отладочном режиме
|  `--Res/
|     *.rc1, *.bmp, *.ico и другие ресурсные файлы
|--module-2/
|  |  *.hpp, *.cpp, *.rc, проектные файлы и т.д.
|  |--Release/
|  |  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
|  |     в режиме оптимизации
|  |--Debug/
|  |  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
|  |     в отладочном режиме
|  `--Res/
|     *.rc1, *.bmp, *.ico и другие ресурсные файлы
|
|  . . .
|
`--module-N/
   |  *.hpp, *.cpp, *.rc, проектные файлы и т.д.
   |--Release/
   |  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
   |     в режиме оптимизации
   |--Debug/
   |  *.obj, *.res, *.dll, *.exe и другие результаты компиляции
   |     в отладочном режиме
   `--Res/
      *.rc1, *.bmp, *.ico и другие ресурсные файлы

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

Если же проект состоит из нескольких модулей, для каждого из которых создается подобная структура, то возникают трудности в использовании файлов одного подпроекта в другом подпроекте. Это относится как к заголовочным, так и к .lib-, .dll- и .exe-файлам. И если для .hpp- и .lib-файлов можно определять пути поиска у компилятора и линкера, то для .dll- и .exe-файлов приходится что-то придумывать. Например, как быть, если module-2, module-3 - это DLL-библиотеки, которые используются EXE-файлами модулей module-5, module-7, которые используются EXE-файлом модуля module-N?

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

project/
|  *.exe, *.dll, *.mak
`--src/
   |--module-1/
   |     *.hpp, *.cpp, *.obj, *.lib, *.rc, *.res, *.mak
   |--module-2/
   |     *.hpp, *.cpp, *.obj, *.lib, *.rc, *.res, *.mak
   |  ...
   `--module-N/
         *.hpp, *.cpp, *.obj, *.lib, *.rc, *.res, *.mak

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

В свое время автор опробовал различные способы организации файловой структуры для проектов. В результате наиболее удобным оказался следующий способ организации файлов проекта:

project/
|  архивные файлы
`--dev/
   |  *.exe, *.dll, *.4xx (для компиляции и настройки всего проекта)
   |--lib/
   |     *.lib
   |--project/
   |  |--module-1/
   |  |  |  *.cpp, *.rc, *.4xx
   |  |  |--h/
   |  |  |     *.hpp, *.h
   |  |  `--o/
   |  |        *.obj, *.res
   |  |--module-2/
   |  |  |  *.cpp, *.rc, *.4xx
   |  |  |--h/
   |  |  |     *.hpp, *.h
   |  |  `--o/
   |  |        *.obj, *.res
   |  |  ...
   |  `--module-N/
   |     |  *.cpp, *.rc, *.4xx
   |     |--h/
   |     |     *.hpp, *.h
   |     `--o/
   |           *.obj, *.res
   |--used_project_1/
   |  |--module-1-1/
   |  |  |  *.cpp, *.rc, *.4xx
   |  |  |--h/
   |  |  |     *.hpp, *.h
   |  |  `--o/
   |  |  ...
   |  `--module-1-N/
   |     |  *.cpp, *.rc, *.4xx
   |     |--h/
   |     |     *.hpp, *.h
   |     `--o/
   |           *.obj, *.res
   |  ...
   `--used_project_N/
      |--module-1/
      |  |  *.cpp, *.rc, *.4xx
      |  |--h/
      |  |     *.hpp, *.h
      |  `--o/
      |        *.obj, *.res
      |  ...
      `--module-N/
         |  *.cpp, *.rc, *.4xx
         |--h/
         |     *.hpp, *.h
         `--o/
               *.obj, *.res

Эту структуру можно дополнять, например, так:

Преимуществами такой структуры каталогов автор считает то, что не происходит смешения различных типов файлов в одном каталоге. Библиотеки располагаются в своем каталоге, исполнительные модули в своем, заголовочные в своем.

Использование подпроектов

Исходные тексты большинства сложных проектов можно разделить на исходные тексты самого проекта (т.е. тесксты, реализующие прикладную логику проекта) и исходные тексты использованных подпроектов. Например, проект библиотеки по работе с сокетами может использовать подпроект "умных указателей" auto_ptr:

socklib/
`--dev/
   |--socklib/
   `--auto_ptr/

В тоже время проект socklib может входить как подпроект в проект по реализации SSL/TLS протоколов:

tls_lib/
`--dev/
   |--tls_lib/
   |--socklib/
   `--auto_ptr/

А проект tls_lib может входить как подпроект в проект по реализации HTTPS-клиентских соединений:

https_cln/
`--dev/
   |--https_cln/
   |--tls_lib/
   |--socklib/
   `--auto_ptr/

И т.д. Подобная структура проекта позволяет легко перенести исходные тексты любого проекта в более высокоуровневый и сложный проект. При этом высокоуровневый проект так же может использовать часть подпроектов напрямую. Например, auto_ptr.

Наличие исходных текстов подпроектов в самом проекте имеет следующие преимущества:

Обратной стороной наличия исходных текстов проекта является распухание архива с исходными текстами. Так для сложных проектов размер архива со всеми исходными текстами может превышать 500Kb. И пересылка таких архивов по e-mail может стать неприятным занятием. Особенно, когда используется модемное dial-up соединение. И когда текст самого объемлющего проекта составляет всего 5-10% от общего объема исходных текстов.

Эта проблема становится особенно актуальной для коллектива территориально удаленных разработчиков, обменивающихся результатами свой работы через e-mail. В таких случаях оказывается, что у каждого из них уже есть исходные тексты большинства подпроектов. Например, auto_ptr, socklib, tls_lib. Эти проекты уже стабилизировались, изменения в них вносятся достаточно редко. Основное внимание уделяется разработке https_cln и проектов, которые используют https_cln. И распространять новую версию https_cln хотелось бы без всех подпроектов. В этом случае возникает необходимость в описываемой ниже организации всех проектов в виде репозитория.

Репозиторий

У каждого разработчика есть каталог, в котором располагаются все самостоятельные проекты. Этот каталог играет роль репозитория проектов. Все проекты в репозитории имеют одинаковую структуру. Например:

/home/eao/prj
          |--auto_ptr_3/
          |  `--dev/
          |     `--auto_ptr_3/
          |--cpp_util_2/
          |  `--dev/
          |     `--cpp_util_2/
          |...
          `--smart_ref_3/
             `--dev/
                `--smart_ref_3/

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

Для автоматического копирования исходных текстов подпроектов в объемлющий проект предназначен mxx-скрипт prj_man/install.4xx, входящий в состав mxx-4-add-on. Для успешной работы prj_man/install.4xx в каждом проекте необходимо иметь два специальных файла: required.4xx и make_src_distr.4xx. Располагаются они в самом верхнем каталоге с исходными текстами проекта (т.е. в каталоге some_prj/dev/some_prj). В файле required.4xx указываются все проекты, необходимые данному объемлющему проекту. Например:

#include <prj_man/required/start.4xx>

required_prjs += "cpp_util_2";
required_prjs += "auto_ptr_3";
required_prjs += "memcheck_2";
required_prjs += "smart_ref_3";
required_prjs += "cls_2";
required_prjs += "so_4";
required_prjs += "so_agent_lib_1";
required_prjs += "so_sysconf_2";
required_prjs += "aggregate_protocol_1";	
required_prjs += "mbapi_global_2.current";
required_prjs += "mbapi_global_2.current/sms";
required_prjs += "mbapi_global_2.current/mbank_generic";

#include <prj_man/required/finish.4xx>

В файле make_src_distr.4xx указываются правила отбора из всех находящихся в каталоге проекта файлов тех файлов, которые являются исходными текстами и которые необходимы для компиляции проекта как подпроекта в другом объемлющем проекте. Обычно файл make_src_distr.4xx содержит шаблонное значение:

#include <prj_man/distr/start.4xx>

#include <prj_man/distr/finish.4xx>

которое предписывает взять только исходные тексты из каталога самого проекта (т.е. some_prj/dev/some_prj). Но могут быть и более сложные случаи:

#include <prj_man/distr/start.4xx>

include_path += "dev/log";

include_path += "dev/etc/bee_line";
include_path += "dev/etc/megafon";
include_path += "dev/etc/mts";
include_path += "dev/etc/db_scripts/bee_line";
include_path += "dev/etc/db_scripts/megafon";
include_path += "dev/etc/db_scripts/mts";

include_text_file += "dev/etc/db_scripts/a_deliver_response.ddl";

#include <prj_man/distr/finish.4xx>

который предписывает взять так же исходные тексты из указынных каталогов и еще добавить файл dev/etc/db_scripts/a_deliver_response.ddl. А так же:

#include <prj_man/distr/start.4xx>

exclude_path += "dev/mbapi_cln_2/mbank";
exclude_path += "dev/mbapi_cln_2/sms";

#include <prj_man/distr/finish.4xx>

который предписывает исключить из копирования все исходные тексты, находящиеся в указанных подкаталогах.

В самом верхнем каталоге проекта (т.е. <repository>/some_prj) должен находится файл default.4xx, который определяет, как должна осуществляться архивация и разархивация проектов. Например:

#include <prj_man/arc/start.4xx>

#undef NOCNVRT

text_file_masks += [ "*.pem" ];

include_path += [
	"dev/etc",
	"dev/lib",
	"dev/log",
	"dev/test",
	"dev/sample",
	"doxygen" ];

include_text_file += [ "doxygen/Doxyfile" ];

#include <prj_man/arc/finish.4xx>

Благодоря тому, что файл управления архивацией/разархивацией проектов назвывается default.4xx (имя, которое ищет mxxc в текущем каталоге, если mxxc явно не задан файл для обработки), то для архивации и разархивации проекта достаточно выйти в самый верхний каталог проекта и запустить mxxc:

# Архивация проекта.
$ mxxc -d PACK

# Разархивация проекта.
$ mxxc -d UNPACK

В результате, для того, чтобы отправить проект другому разработчику необходимо выйти в самый верхний каталог проекта и запустить архивацию (для архивации применяется архиватор rar). В результате появится архив, содержащий только то, что относится к самому проекту, но не к его подпроектам. Этот архив необходимо передать заинтересованым в нем разработчикам.

При получении архива проекта необходимо:

  1. Создать в репозитории подкаталог с именем проекта:
    $ mkdir some_prj
  2. Поместить полученный архив в этот каталог.
  3. Извлечь из архива файл default.4xx:
  4. $ cd some_prj
    $ rar x some_prj default.4xx
  5. Запустить разархивацию проекта:
  6. $ mxxc -d UNPACK
  7. Запустить инсталляцию используемых подпроектов:
  8. $ mxxc -sf prj_man/install.4xx

Файлы prj.4xx и lprj.4xx

Поскольку подпроекты входят в проект в виде исходных текстов необходимо обеспечить компиляцию всех подпроектов до компиляции объемлющего проекта. Для этого в каждом проекте создается два файла-скрипта, управляющих компиляцией. Первый из них, lprj.4xx занимается компиляцией только самого проекта, без продпроектов. Второй, prj.4xx запускает компиляцию всех подпроектов и, только затем, самого проекта. Поэтому для prj.4xx, как правило, используется стандартный mxx-шаблон composite:

#include <composite/start.4xx>

prjs += "cls_2/prj.4xx";
prjs += "threads_1/prj.4xx";
prjs += "so_4/prj.4xx";
prjs += "so_agent_lib_1/prj.4xx";
prjs += "oopssl_1/prj.4xx";
prjs += "so_openssl_1/prj.4xx";
prjs += "mbapi_global_2/prj.4xx";

prjs += "mbapi_cln_2/lprj.4xx";

#include <composite/finish.4xx>

Практика показывает, что в файле prj.4xx перечисляются те же самые проекты, что и в файле required.4xx. Что иногда приводит к тому, что при добавлении еще одного подпроекта его имя включается только в один из файлов -- либо в prj.4xx, либо в required.4xx.

Компиляция проекта запускается из каталога dev/. Для того, чтобы это было делать удобно, в каталог dev/ помещается файл default.4xx, содержащий, обычно, указание компиляции всего одного проекта:

#include <composite/start.4xx>

prjs += "aggregate_protocol_1/prj.4xx";

#include <composite/finish.4xx>

Файлы globals.4xx, locals.4xx и locals.4xx

Одним из достоинств включения подпроектов в объемлющий проект является то, что можно перекомпилировать все исходные тексты в одном режиме (debug или release) с одинаковыми настройками компилятора. Но может оказаться, что некоторые подпроекты требуют специальных настроек компилятора. Например, явного включения режима RTTI и разрешения исключений (обычные настройки для Visual C++ 6.0). Для того, чтобы объемлющий проект мог учесть все требования своих подпроектов, а так подпроектов подпроектов (и т.д.) применяется следующая схема:

Файл globals.4xx используется в "глобальном" locals.4xx. "Глобальный" и "локальный" locals.4xx используются в файле lprj.4xx, управляющим компиляцией одного проекта:

#include <start.4xx>

#include "locals.4xx"
#include "aggregate_protocol_1/locals.4xx"

#include "oess_1/version.4xx"

appMode = "dll";
screenMode = "console";

#if !defined( UNIX )
	target = "aggregate_protocol" + aggregate_protocol_version;
	impLib = "lib/aggregate_protocol" +
		aggregate_protocol_version + ".lib";
#else
	target = "lib/aggregate_protocol" + aggregate_protocol_version;
#endif

defines += "AGGREGATE_PROTOCOL_1_PRJ";

cppSource += "aggregate_protocol_1/impl/tlvs.cpp";
cppSource += "aggregate_protocol_1/impl/real_parser.cpp";

cppSource += "aggregate_protocol_1/msg_base.cpp";
cppSource += "aggregate_protocol_1/messages.cpp";
cppSource += "aggregate_protocol_1/incoming_parser.cpp";

libs += "lib/oess_defs" + oess_version;
libs += "lib/oess_io" + oess_version;
libs += "lib/oess_tlv" + oess_version;

#include <finish.4xx>

Именно при помощи двух директив #include, загружащих файлы locals.4xx происходит настройка компилятора для компиляции конкретного проекта.

Версии проектов

Сложным вопросом при разработке программ является изменение версий используемых продпроектов. Можно выделить три основных типа изменений:

Наибольщее количество проблем доставляет случай, когда новая версия проекта оказывается несовместимой со старыми версиями. Особенно, когда этот подпроект используется несколькими подпроектами одновременно. Например, подпроект threads v.1.0 использует auto_ptr v.1.0. Проект oess v.1.0 использует как threads v.1.0, так и auto_ptr v.1.0. Появляется auto_ptr v.2.0, не совместимая с предыдущей версией. К этой версии адаптируется подпроект threads v.1.1. Но версия threads v.1.1 не изменяет своего интерфеса, поэтому oess v.1.0 должен легко перейти на threads v.1.1. Но проблема в том, что исходные тексты auto_ptr v.2.0 (которые необходимы подпроекту threads) и auto_ptr v.1.0 (которые необходимы самому oess) должны разместиться в одном каталоге -- oess/dev/auto_ptr. Возникает конфликт, который можно разрешить только переводом oess на новую версию auto_ptr.

Для преодоления подобных проблем предлагается включить в имя проекта наиболее значимый номер версии. Например, auto_ptr_1, threads_1, oess_1, auto_ptr_2. Под такими именами проекты присутствуют в репозитории. Под такими именами создаются каталоги с исходными текстами внутри каталога dev/. В этом случае файловая структура для проекта oess_1 при появлении auto_ptr v.2.0 и threads v.1.1 приняла бы вид:

oess_1/
`--dev/
   |--auto_ptr_1/
   |--auto_ptr_2/
   |--oess_1/
   `--threads_1/

Что позволит поместить в один объемлющий проект две несовместимые версии одного подпроекта.

Кроме добавления наиболее значимого номера версии в имя проекта таким же образом необходимо проименовать открытые интерфейсы проекта (пространства имен, макросы, символы препроцессора). Если используются пространства имен, то эта задача упрощается -- достаточно включить номер версии в имя самого верхнего пространства имен. Например, auto_ptr_2, oess_1. Если же пространства имен не используются, то необходимо соответствующим образом именовать все открытые идентфикаторы (имена классов, функций, констант, элементов перечисления и т.д.).

Файл version.4xx

Включение номера версии в само имя проекта решает проблему полной несовместимости версий одного проекта. Но существуют так же проблема, когда для перехода на новую версию объемлющий проект должен быть перекомпилирован, после чего он окажется неработоспособен со скомпилированными предыдущими версиями подпроектов. Например, пусть в проекте threads_1 v.1.0 метод ожидания на семафоне не имел параметра timeout. В версии threads_1 v.1.1 в этот метод параметр timeout был добавлен как необязательный. Для проекта oess_1, который использует threads_1 это означает, что нужно всего лишь перекомпилировать oess_1. Но oess_1 окажется прилинкован к threads_1.dll версии 1.1. И не сможет работать с threads_1.dll версии 1.0.

Эта проблема широко известна под Windows, хотя в Unix-е она имеет очень простое и красивое решение -- включать в имя DLL максимально полный номер версии. Т.е. для проекта threads_1 создавать не безликую threads_1.dll, а threads_1.0.dll и threads_1.1.dll.

Для того, чтобы в объемлющем проекте было легко контролировать номера версий подпроектов в каждом проекте должен существовать файл version.4xx. Этот файл размещается в самом верхнем каталоге с исходными текстами проекта (т.е. в some_prj/dev/some_prj, там же, где globals.4xx, required.4xx, ...). Обычно version.4xx имеет вид:

string	aggregate_protocol_version;
aggregate_protocol_version = ".1.0.3";

Т.е. объявляется переменная вида <prj>.version. Эта переменная затем в файлах lprj.4xx используется как для формирования имени результирующего модуля (exe, dll или lib), так и для формирования имени библиотеки, к которой необходимо линковаться:

#include <start.4xx>

#include "locals.4xx"
/* Так как mbapi_cln_2/locals.4xx загружает
   mbapi_cln_2/version.4xx, то переменная mbapi_cln_version
   становиться видимой. */
#include "mbapi_cln_2/locals.4xx"

/* Загружаются номера версий всех используемых подпроектов. */
#include "cls_2/version.4xx"
#include "threads_1/version.4xx"
#include "oess_1/version.4xx"
#include "so_4/version.4xx"
#include "so_agent_lib_1/version.4xx"
#include "oopssl_1/version.4xx"
#include "so_openssl_1/version.4xx"
#include "mbapi_global_2/version.4xx"

appMode = "dll";
screenMode = "console";

/* Номер версии используется для формирования имени
   результирующего модуля. А для Windows еще и библиотеки
   импорта. */
#if !defined( UNIX )
	target = "mbapi_cln" + mbapi_cln_version;
	impLib = "lib/mbapi_cln" + mbapi_cln_version + ".lib";
#else
	target = "lib/mbapi_cln" + mbapi_cln_version;
#endif

defines += "MBAPI_CLN_2_PRJ";

cppSource += "mbapi_cln_2/cfg/sops_channel.cpp";
cppSource += "mbapi_cln_2/cfg/mbapi.cpp";
cppSource += "mbapi_cln_2/cfg/all.cpp";

cppSource += "mbapi_cln_2/mbapi/ssl_controller.cpp";
cppSource += "mbapi_cln_2/mbapi/evt_queue.cpp";
cppSource += "mbapi_cln_2/mbapi/std_msg_registrator.cpp";
cppSource += "mbapi_cln_2/mbapi/extern_so_rt_adapter.cpp";
cppSource += "mbapi_cln_2/mbapi/a_mbapi_cln.cpp";
cppSource += "mbapi_cln_2/mbapi/sobj_thread.cpp";

cppSource += "mbapi_cln_2/extern_so_rt.cpp";
cppSource += "mbapi_cln_2/ret_code.cpp";
cppSource += "mbapi_cln_2/mbapi.cpp";

/* Номера версий используется для формирования имен
   библиотек, к которым необходимо линковаться. */
libs += "lib/cls" + cls_version;
libs += "lib/threads" + threads_version;
libs += "lib/so_4" + so_version;
libs += "lib/so_agent_lib" + so_agent_lib_version;
libs += "lib/oopssl" + oopssl_version;
libs += "lib/so_openssl" + so_openssl_version;

libs += "lib/mbapi_global" +
	mbapi_global_version;

#if !defined( UNIX )
	sysLibs += "libeay32.lib";
	sysLibs += "ssleay32.lib";
#else
	sysLibs += "ssl";
#endif

#include <finish.4xx>

Использование большого проекта по частям

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

На самом деле описанный способ организации проектов допускает использование в качестве подпроектов отдельных частей какого-либо проекта.

На практике возникают две ситуации, в которых какой-либо проект может использоваться в других проектах по частям:

Для использования в объемлюшем проекте отдельных компонентов какого-либо подпроекта необходимо:

  1. Чтобы все компоненты, которые могут использоваться самостоятельно, реализовывались как подкаталоги в своем проекте. Т.е. если есть компонент some_comp проекта some_prj, то он должен быть реализован в каталоге some_prj/dev/some_prj/some_comp. Например: stdsn из oess_1 реализуется в oess_1/dev/oess_1/stdsn. Коммуникация через разделяемую память для safe_msg_1 реализуется в safe_msg_1/dev/safe_msg_1/comm/smem.
  2. Для использования компонента подпроекта в объемлющем проекте в файле required.4xx объемлющего проекта нужно указать имя подкаталога с компонентом:
    #include <prj_man/required/start.4xx>
    
    required_prjs += "cpp_util_2";
    required_prjs += "auto_ptr_3";
    required_prjs += "cls_2";
    required_prjs += "smart_ref_3";
    required_prjs += "memcheck_2";
    required_prjs += "threads_1";
    /* Использование всего двух компонентов из всего
       проекта oess_1 */
    required_prjs += "oess_1/stdsn";
    required_prjs += "oess_1/util_cpp_serializer";
    
    #include <prj_man/required/finish.4xx>
    или
    #include <prj_man/required/start.4xx>
    
    required_prjs += "cpp_util_2";
    required_prjs += "auto_ptr_3";
    required_prjs += "memcheck_2";
    required_prjs += "smart_ref_3";
    required_prjs += "cls_2";
    required_prjs += "so_4";
    required_prjs += "so_agent_lib_1";
    required_prjs += "so_sysconf_2";
    required_prjs += "aggregate_protocol_1";
    /* Использования ядра проекта mbapi_global_2,
       без специфической функциональности. */
    required_prjs += "mbapi_global_2";
    /* Использование специфических компонентов
       проекта mbapi_global_2. */
    required_prjs += "mbapi_global_2/sms";
    required_prjs += "mbapi_global_2/mbank_generic";
    
    #include <prj_man/required/finish.4xx>
  3. В каждом из самостоятельных компонентов нужно создавать свои файлы required.4xx
    /* Файл required.4xx компонента oess_1/stdsn. */
    #include <prj_man/required/start.4xx>
    
    required_prjs += "cpp_util_2";
    required_prjs += "memcheck_2";
    
    required_prjs += "threads_1";
    
    required_prjs += "oess_1/defs";
    required_prjs += "oess_1/io";
    
    #include <prj_man/required/finish.4xx>
    и make_src_distr.4xx
    /* Файл make_src_distr.4xx компонента oess_1/stdsn. */
    #include <prj_man/distr/start.4xx>
    
    include_path = [ "dev/oess_1/stdsn" ];
    
    include_text_file += [
    	"dev/oess_1/start.4xx",
    	"dev/oess_1/finish.4xx",
    	"dev/oess_1/stdsn_start.4xx",
    	"dev/oess_1/stdsn_finish.4xx" ];
    
    #include <prj_man/distr/finish.4xx>
  4. Если проект изначально предполагает существование самостоятельных компонентов, то файл make_src_distr.4xx должен учитывать это:
    /* Файл make_src_distr.4xx проекта mbapi_global_2. */
    #include <prj_man/distr/start.4xx>
    
    exclude_path += "dev/mbapi_global_2/mbank_generic";
    exclude_path += "dev/mbapi_global_2/sms";
    
    #include <prj_man/distr/finish.4xx>
    Такой файл make_src_distr.4xx показывает, что проект mbapi_global_2 сам по себя является ядром, которое может быть расширено специфической функциональностью. И для работы с этой функциональностью в объемлющем проекте нужно явно подключать компоненты mbapi_global_2/mbank_generic, mbapi_global_2/sms.

Стабильная и текущая ветви развития проектов

В некоторых Unix-проектах, в частности FreeBSD, принято здравое разделение развития проекта на стабильную и нестабильную (текущую) ветви. Выпустив стабильную (release) версию продукта, весь код этой версии переходит в стабильную ветвь, например, 4.5-stable. Создание новой версии происходит в рамках текущей ветви, например, 4.6-current. Соответственно развитие этих ветвей идет параллельно. В стабильную версию не вносится новой функциональности, а только исправления. Новая же функциональность добавляется в текущей ветви. Как только текущая ветвь достигнет достаточной функциональной полноты и стабильности она преобразуется в release-версию. Т.е. появляется ветвь 4.6-stable и новая ветвь 4.7-current.

Шаблон prj_man из mxx-4-add-on поддерживает возможность подобного разделения проектов на ветви. Здесь важно, что стабильная и текущие ветви проектов располагаются на одном уровне в репозитории. При этом обе ветви используют одинаковое имя проекта. Например, ветви oess 1.0 и oess 1.1 используют имя oess_1. Но в каталоге репозитория нельзя создать два каталога с одним именем! Поэтому одно расшрирение имени каталога проекта в репозитории может использоваться для обозначания ветви проекта. Например, oess_1 означает стабильную версию 1.0. А oess_1.current -- текущую версию 1.1.

Для самого проекта наличия расширения в имени каталога в репозитории не оказывает никакого влияния. Расширение обрабатывается в файле required.4xx объемлющего проекта. Если, например, в required.4xx указать:

required_prjs += "oess_1.current/defs";
required_prjs += "oess_1.current/io";

то при запуске prj_man/install.4xx исходные тексты компонета defs будут браться из oess_1.current/dev/oess_1/defs, а компонента io, соответственно, из oess_1.current/dev/oess_1/io.

1. Поддержка нескольких ветвей проекта в репозитории находится в стадии обсуждения. Здесь описана самая первая реализация это поддержки. Возможно, со временем, подход к хранению ветвей проекта в репозитории измениться.
2. Если в имени проекта есть несколько расширений, то в качестве имени ветви проекта используется последнее из них. Т.е., если в required.4xx будет указано
required_prjs += "oess_1.0.7.current/io";
То исходные тексты компонента io будут искаться в oess_1.0.7.current/dev/oess_1.0.7/io. Возможно, подход к включению и обработке имени ветви в имя каталога проекта в репозитории измениться в будущем.


Hosted by uCoz