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
Эту структуру можно дополнять, например, так:
project/ `--dev/ |--debug/ | *.exe, *.dll |--release/ | *.exe, *.dll |--lib/ | |--debug/ | | *.lib | `--release/ | *.lib `--project/ |--module-1/ | |--h/ | `--o/ | |--debug/ | `--release/ |--module-2/ | |--h/ | `--o/ | |--debug/ | `--release/ | ... `--module-N/ |--h/ `--o/ |--debug/ `--release/
Преимуществами такой структуры каталогов автор считает то, что не происходит смешения различных типов файлов в одном каталоге. Библиотеки располагаются в своем каталоге, исполнительные модули в своем, заголовочные в своем.
Исходные тексты большинства сложных проектов можно разделить на исходные тексты самого проекта (т.е. тесксты, реализующие прикладную логику проекта) и исходные тексты использованных подпроектов. Например, проект библиотеки по работе с сокетами может использовать подпроект "умных указателей" 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). В результате появится архив, содержащий только то, что относится к самому проекту, но не к его подпроектам. Этот архив необходимо передать заинтересованым в нем разработчикам.
При получении архива проекта необходимо:
$ mkdir some_prj
$ cd some_prj $ rar x some_prj default.4xx
$ mxxc -d UNPACK
$ mxxc -sf prj_man/install.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> |
Одним из достоинств включения подпроектов в объемлющий проект является то, что можно перекомпилировать все исходные тексты в одном режиме (debug или release) с одинаковыми настройками компилятора. Но может оказаться, что некоторые подпроекты требуют специальных настроек компилятора. Например, явного включения режима RTTI и разрешения исключений (обычные настройки для Visual C++ 6.0). Для того, чтобы объемлющий проект мог учесть все требования своих подпроектов, а так подпроектов подпроектов (и т.д.) применяется следующая схема:
#include "mbapi_srv_2/globals.4xx" cpp_add_include_path( stdIncludePath += "." ); //#define USE_MEMCHECK_2 #if defined( USE_MEMCHECK_2 ) #include "memcheck_2/globals.4xx" #if !defined( _MEMCHECK_2__INSIDE_ ) defines += "USE_MEMCHECK_2"; #include "memcheck_2/version.4xx" libs += "lib/memcheck.cln" + memcheck_version; libs += "lib/memcheck.srv" + memcheck_version; dependOnPrjs += "memcheck_2/prj.4xx"; #endif #endif #if defined( GNU ) #if !defined( POSIX ) #define POSIX #endif #if defined( POSIX ) #if defined( FREEBSD ) cppOptions += "-pthread"; linkOptions += "-pthread"; defines += "_THREAD_SAFE"; #else sysLibs += "pthread"; #endif #endif linkOptions += "-L/usr/lib"; #if defined( SPARC ) linkOptions += "-lsocket"; #endif cppOptions += "-fPIC"; #endif |
#if !defined( _MBAPI_SRV_2__GLOBALS_4XX_ ) #define _MBAPI_SRV_2__GLOBALS_4XX_ #include "so_4/globals.4xx" #include "so_agent_lib_1/globals.4xx" #include "so_sysconf_2/globals.4xx" #include "mbapi_global_2/globals.4xx" #endif |
#if !defined( _SO_4__GLOBALS_4XX_ ) #define _SO_4__GLOBALS_4XX_ #if !defined( THREADS_1_DLL ) #define THREADS_1_DLL #endif #include "threads_1/globals.4xx" #include "oess_1/globals.4xx" #include "smart_ref_3/globals.4xx" #define SO_4_DLL #if !defined( SHARED_RTL ) #define SHARED_RTL #endif #if defined( MSC ) cppOptions += "/GR"; #endif #endif |
#include "mbapi_srv_2/version.4xx" |
#include "mbapi_srv_2/version.4xx" defines += "VERBOSE_OUTPUT"; defines += "ENABLE_INTERNAL_TRACING"; |
Данный файл locals.4xx остался от времен, когда не было файла globals.4xx. Тогда второй locals.4xx содержал специфические настройки объемлющего проекта. Но были сложности с учетом специфических настроек подпроектов. В результате появился файл globals.4xx, а второй файл locals.4xx остался, хотя теперь он играет не столь важную роль. |
Файл 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. Если же пространства имен не используются, то необходимо соответствующим образом именовать все открытые идентфикаторы (имена классов, функций, констант, элементов перечисления и т.д.). |
Включение номера версии в само имя проекта решает проблему полной несовместимости версий одного проекта. Но существуют так же проблема, когда для перехода на новую версию объемлющий проект должен быть перекомпилирован, после чего он окажется неработоспособен со скомпилированными предыдущими версиями подпроектов. Например, пусть в проекте 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.
На самом деле описанный способ организации проектов допускает использование в качестве подпроектов отдельных частей какого-либо проекта.
На практике возникают две ситуации, в которых какой-либо проект может использоваться в других проектах по частям:
Для использования в объемлюшем проекте отдельных компонентов какого-либо подпроекта необходимо:
#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> |
/* Файл 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 компонента 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> |
/* Файл 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> |
В некоторых 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 будет указано
|