Иногда наступает момент, когда начинаешь задавать вопрос: А как лучше хранить исходный код, чтобы он в итоге не превращался в кучу файлов с циклическими зависимостями?
Всё началось несколько лет назад, когда я решил попробовать сделать свою библиотеку. Уже тогда я попробовал Boost и его The Unit Test Framework, и начал вливаться в CMake, как для кроссплатформенности, так и для простоты перехода между компиляторами. В общем к данному времени вопрос как хранить исходные коды, файлы сборки, вспомогательные и тестовые файлы получил несколько ответов и вариантов из которых по объективным, а иногда и по соображениям удобства, был сделан выбор.
Чтобы усложнить понимание я, конечно, дам несколько ссылок.
- Пример библиотеки с которой и пишется этот пост: https://github.com/sidorovis/system_utilities.
- Boost Unit Test Framework: http://www.boost.org/doc/libs/1_52_0/libs/test/doc/html/index.html.
- CMake: http://cmake.org/
Но в последствии буду пытаться объяснять и описывать так, как будто вы их не прочитали.
Проекту нужен Boost, версии 1_47 будет достаточно. Надеюсь более поздние версии тоже подойдут.
Начнём со структуры каталога проекта.
$ ls -A system_utilities/ 3rd_party CMakeLists.txt README _cmake_scripts _gcc_gen.sh _msvc_gen_2005_32.bat _msvc_gen_2005_64.bat _msvc_gen_2010_64_debug.bat _msvc_gen_2010_64_release.bat _start_msvc.bat _xcode_gen.sh environment.cmake sources tests
Намного удобней параллельно изучать исходный код используя Web интерфейс Github, но в любом случае отмечу, что все исходные коды языка C++ находятся в каталогах "sources" и "tests".
Каталог проекта содержит следующие каталоги/файлы. Это описание проекта с точки зрения организации исходного кода на жёстком диске:
- Каталог 3rd_party - содержит скрипты компиляции библиотеки Boost под Windows для 32-х и 64-х битных версий.
- Файл CMakeLists.txt - входной файл для CMake, первый файл, который просматривается для генерации файлов проектов/решений (projects/solutions) или Makefile'ов.
- Файл README - краткое описание библиотеки и модулей библиотеки.
- _cmake_scripts - каталог содержащий мои скрипты CMake, которые объединяют и упрощают описание зависимостей каждого модуля.
- _gcc_gen.sh, _msvc_gen_2010_64_debug.bat, ..., _xcode_gen.sh - скрипты запуска CMake. Они содержат путь к библиотеке Boost (при необходимости), а также хитрость, которая позволяет размещать все генерируемые в отдельном подкаталоге, на засоряя исходный код.
- environment.cmake - вспомогательный файл для CMake, его я не буду разбирать в этой статье.
- Каталог sources - исходные коды модулей библиотеки.
- Каталог tests - исходные коды тестов для модулей библиотеки.
Рассмотрим _msvc_gen_2005_64.bat - скрипт, который генерирует solution для MSVC 2005 64-х битной версии. Его можно запустить без параметров (он сгенерирует solution для Debug версии в каталог _build_Debug_64) и с параметром "Release", для Release версии. Для MSVC можно было бы генерировать один solution, но во-первых для Linux такая политика не является удачной, и во-вторых гораздо проще писать последующие проекты - если они не смогут найти каталог "_build_Release_64" - значит скомпилировать 64-х битную Release версию не удастся.
$ cat ./_msvc_gen_2005_64.bat set BOOST_ROOT=d:\usr\boost_1_47_0 set BUILD_TYPE=Debug if [%1]==[Release] ( set BUILD_TYPE=Release ) set BUILD_FOLDER=_build_%BUILD_TYPE%_64 if not exist %BUILD_FOLDER% ( mkdir %BUILD_FOLDER% ) cd %BUILD_FOLDER% cmake -DVERBOSE=OFF -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DSOLUTION_NAME=system_utilities -G "Visual Studio 8 2005 Win64" ../ cd ../ echo "%BUILD_FOLDER%/system_utilities.sln" > _start_msvc.bat
Главной задачей для этого файла является создание каталога _build_<BUILD_TYPE>_<ADDRESS_MODEL>, в котором будут сгенерированы все необходимые для компиляции файлы/каталоги. Скрипт также генерирует скрипт запуска IDE (_start_msvc.bat). Жирным шрифтом выделено то, что позволяет генерировать файлы проекта в отдельном каталоге и тем самым не засоряет каталог с исходным кодом.
Библиотека устроена таким образом, что ни один модуль не может быть создан без соответствующего каталога с тестирующим исполнительным файлом. Что само по себе заставляет разработчиков модулей, как минимум задуматься о необходимости создания пустых тестов.
Рассмотрим точку входа для генерации solution. Файл CMakeLists.txt в корне system_utilities.
cmake_minimum_required(VERSION 2.8) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set( CMAKE_CONFIGURATION_TYPES ${CMAKE_BUILD_TYPE} CACHE STRING "Configurations" FORCE ) project( ${SOLUTION_NAME} ) include( environment.cmake required ) if(WIN32) modules( time_tracker ts_queue property_reader task_processor logger ts_logger queue_logger file_logger limited_file_logger timer system_processor windows_service ) elseif(UNIX) modules( time_tracker timer logger ts_logger ts_queue property_reader task_processor queue_logger file_logger limited_file_logger system_processor ) endif(WIN32)
Этот файл загружает все написанные мной CMake функции и макросы, которые впоследствии упростят механизм генерации проектов с учётом зависимостей.
Макрос modules - устанавливает список модулей и последовательно запускает для генерации подкаталоги "sources", "tests".
Каталог "sources", содержит каталоги всех модулей библиотеки и файл CMakeLists.txt - на который опирается CMake для генерации файлов проектов модулей библиотеки, содержимое этого файла:
compile_modules()
Вроде пока не сложно и не так много кода. Как выглядят каталоги модулей рассмотрим на примере limited_file_logger. Этот модуль журналирует события в файл с ограничением файла по размеру.
Каталог модуля состоит из следующих файлов:
CMakeLists.txt limited_file_logger.cpp limited_file_logger.h
На самом деле файлов с исходным кодом (*.cpp, *.h) может быть сколько угодно - главное, чтобы они компилировались учитывая настройки и зависимости проекта, которые хранятся в файле CMakeLists.txt этого каталога.
find_package( Boost 1.41 ${SEARCH_PARAMETERS} COMPONENTS filesystem thread regex date_time system ) compile_project( ${module_name} "*.cpp" "*.h" STATIC libraries file_logger logger Boost )
В этом файле две команды:
- Найти необходимые для данного проекта библиотеки Boost.
-
Сгенерировать цель для компиляции (MSVC-проект), со следующими параметрами:
- Наименование цели - это название модуля ("limited_file_logger");
- Включить все файлы "*.cpp" в качестве файлов исходного кода;
- Включить все файлы "*.h: в качестве файлов заголовков;
- Модуль будет статически линковаться;
- Зависимостями, являются "file_logger", "logger", все найденные библиотеки "Boost".
Почему я не ищу все библиотеки, а тут не перечисляю только необходимые? Таким образом я никогда не забуду о том, какие именно библиотеки буста линкуются. Строка генерации цели выглядит лаконичной и простой. Поиск компонент - это стандарт языка CMake и поэтому он не вызывает сложности у людей ранее использовавших CMake.
Перейдём к каталогу "tests".
Он также содержит набор каталогов для тестирования каждого модуля (и система не соберёт solution, если таких каталогов будет не хватать) и файл CMakeLists.txt:
compile_tests()
Каждый каталог содержит все необходимые файлы для создания исполнимого файла - который тестирует каждый модуль.
Рассмотрим на примере теста для модуля limited_file_logger. Каталог tests/limited_file_logger содержит:
CMakeLists.txt limited_file_logger_tests.cpp test_registrator.cpp test_registrator.h
Файлы test_registrator.cpp и test_registrator.h - это файлы организующие порядок запуска тестов. Файл limited_file_logger_tests.cpp - содержит непосредственно код тестов модуля. И файл CMakeLists.txt содержит следующее:
find_package( Boost 1.41 ${SEARCH_PARAMETERS} COMPONENTS filesystem thread regex date_time system unit_test_framework ) compile_project( ${tests_name} "*.cpp" "*.h" BINARY tests ${module_name} file_logger logger queue_logger task_processor ts_queue time_tracker Boost ) register_test( ${tests_name} 1.0 1.5 )
В отличии от предыдущего проекты, мы генерируем исполнимый файл (BINARY). В зависимостях появляются модули queue_logger, task_processor ts_queue и time_tracker.
А также строка register_test - которая зарегистрирует тест в CTest (он будет запускаться при вызове "cmake test"). Цифры 1.0, 1.5 - это допустимое время работы теста соответственно для Debug (1 секунда) и Release (1.5 секунды) версий.
Какие плюсы я вижу при такой организации файловой системы.
- Все генерируемые файлы лежат себе отдельно и не смогут попасть в систему контроля версий "случайно". Их легко удалять при необходимости и заново перегенерировать всё с нуля. Поэтому даже если ваша IDE испортит файлы проектов - то их всегда можно заново сгенерировать.
- Написать модуль без создания заглушки для тестирования не представляется возможным.
- Код по организации файлов в цели (проекты) минималистичен и прост к изучению как молодыми и неопытными разработчиками, так и опытными но не имевшими дело с CMake разработчиками.
- Собрать такую библиотеку любому разработчику не составит трудностей (намного больше трудностей появится из-за необходимости скомпилировать Boost под Windows).
И конечно же недоработки и минусы:
- Отсутствие возможности скомпилировать одной кнопкой. К сожалению, я оттягиваю это на тот момент, когда под Windows появится менеджер пакетов - с помощью которого компиляция всех возможных (и необходимых) Boost станет простой. На данный момент используя стандартные настройки компилирования Boost, мы получим только 32-х или 64-х битную версию.
- Необходимость большого количества скриптов запуска CMake (фактически можно обойтись небольшим количеством если вынести все настройки в параметры скриптов).
Жду комментариев или сурового порицательного молчания.
Релоцировались? Теперь вы можете комментировать без верификации аккаунта.