Для С++ разработчика CMake может стать хорошим инструментом или головной болью. В зависимости от того, есть ли у него время и желание на изучение CMake. Трогать CPack, CTest в этой статье не стану.
Какие плюсы точно можно получить от CMake (если уметь гнуть его в свой велосипед):
-
кроссплатформенная компиляция от одних правил сборки для множества компиляторов (поддерживается около 50); возможность генерации проектных файлов для совсем различных IDE;
-
кроссплатформенная линковка с большим количеством библиотек (порядка 130 включённых в стандартный пакет скриптов поиска);
-
наличие документации (и даже учебника);
-
множество примеров использования, включая огромные проекты (KDE, Qt);
-
простой для понимания новичками язык программирования;
И какие минусы скорее всего придётся встретить:
-
невнятный язык для разработки;
-
поиск документации очень непрост и часто становится совсем неудобен;
-
скорее всего без специалиста, который уже пожевал кактусов с CMake будет очень мучительно искать как исправить определённые грабли принятые по умолчанию.
В этой статье я разберу созданные мной скрипты, которые по моему мнению упрощают использование CMake – хотя конечно и накладывают некоторые ограничения (которые легко убрать тем, кто умеет читать документацию по CMake (о ужас, на английском языке)).
Примеры CMake и некоторые ответы на вопросы всегда можно получить на сайте CMake. Приведу только выборку ссылок, для молодых и ещё спортивных:
-
http://www.cmake.org/cmake/help/documentation.html есть информация по установке, запуску и синтаксису CMake;
-
http://www.cmake.org/cmake/help/cmake_tutorial.html CMake Tutorial – первое, что я бы прочитал для быстрого старта и для эксперимента;
-
http://www.cmake.org/cmake/help/v2.8.10/cmake.html тяжёлая ссылка, для упоротых CMake наркоманов, которым надо изогнуть свой проект "как-то вот так";
-
http://www.cmake.org/Wiki/CMake много ссылок, на различные примеры и How-To.
Начальную структуру файлов каталогов можно увидеть здесь.
(Файл запуска, настроен на MSVC 2005 Win64 Debug. Но можете смело заводить под Linux/MacOS – просто надо выбрать свой генератор (cmake -G "Unix Makefiles" ., cmake -G "Xcode" . соответственно).
Подробнее по файлам:
-
CMakeLists.txt – файл входа для CMake;
-
_msvc_gen_2005_64.bat – файл, который запустит CMake и сгенерирует проект (для пользователей Windows, которые не освоили командную строку, почему то проще использовать bat файл);
-
environment.cmake (включён в CMakeLists.txt) – устанавливает настройки по умолчанию, добавляет каталог _cmake_scripts в качестве дополнительного каталога для поиска cmake скриптов, проверяет, установлены ли необходимые переменные, устанавливает переменные среды в зависимости от окружения, создаёт пре-настройки для поиска библиотеки;
-
каталог source – каталог, где предполагается размещать исходные коды проекта;
-
каталог tests – каталог, где предполагается размещать тесты для проекта;
-
каталог _cmake_stripts – каталог, где располагаются дополнительные cmake скрипты;
-
.gitignore, history, README.md – просто файлы с дополнительной информацией о версии, описании, и дополнительных настройках git.
Разберём CMakeLists.txt:
cmake_minimum_required( VERSION 2.8 ) # минимальная версия, на которой я проверял работу созданных макросов set_property( GLOBAL PROPERTY USE_FOLDERS ON ) # включить возможность создавать дополнительные каталоги в проектах IDE *1 set( CMAKE_CONFIGURATION_TYPES ${CMAKE_BUILD_TYPE} CACHE STRING "Configurations" FORCE ) # отключить конфигурации кроме установленной *2 project( ${SOLUTION_NAME} ) # имя проекта (должно передаваться параметром при запуске CMake) include( environment.cmake required ) # включить environment.cmake modules() # здесь устанавливает перечисление модулей (библиотек lib/dll, o/so) binaries() # здесь устанавливается перечисление исполнимых файлов (exe) compile() # эта строка запускает реальную генерацию всех под-проектов
*1 – по умолчанию CMake раскладывает исходные коды следующим образом: "Headers Files" – для заголовочных файлов, "Source Files" – каталог для исходного кода проекта. Данная настройка включает возможность создания своих каталогов для той иерархии, которая удобна команде разработчиков.
*2 – отключение конфигураций, кроме заданной, не является необходимой настройкой (и более того, иногда является вредной и некорректной настройкой). Однако, такой подход позволяет объеденить подходы для компиляции проектов в Windows/Linux.
Файл CMakeLists.txt подключает environment.cmake. Этот файл достаточно большой (аж 147 строк), поэтому в этой статье я что-то рассмотрю с кодом, что-то опишу словами.
Установка опций генерации проекта по умолчанию:
option( VERBOSE "should we say as much messages as possible" ON ) # опция ( $ cmake -DVERBOSE=OFF ) устанавливает по умолчанию выдачу информации отладки # во время генерации проектов option( BUILD_TESTS "Should we build tests for modules" ON ) # опция устанавливающая флаг необходимости сборки тестов для проекта # можно использовать в макросе 'compile' (см. ниже) при необходимости
Подключение макросов _cmake_scripts:
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/_cmake_scripts") include( utils )
Проверка наличия и корректного заполнения переменных компиляции:
test_variable_on_existance( SOLUTION_NAME ) test_variable_on_existance( CMAKE_BUILD_TYPE ) test_variable_on_equal_to_one_of_the_list( CMAKE_BUILD_TYPE Release Debug ReleaseWithDebugInfo )
Установка в переменную CMAKE_ADDRESS_MODEL особенности архитектуры, под которую происходит генерация проекта (32, 64 бита).
Установка опций компиляции для linux в зависимости от Debug/Release окружения:
if (Debug) add_definitions( " -O0 -g -Wall" ) else() add_definitions( " -O3 -Wall -Werror" ) endif()
Установка опций компиляции для MSVC:
add_definitions( -D_CRT_SECURE_NO_WARNINGS ) add_definitions( -D_WIN32_WINNT=0x0501 ) SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHa")
Установка переменных для результатирующих файлов:
set( SOLUTION_BINARY_DIR ${PROJECT_BINARY_DIR} ) set( EXECUTABLE_OUTPUT_PATH ${output_path} ) set( LIBRARY_OUTPUT_PATH ${output_path} ) set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${output_path} ) set( BINARIES_DIRECTORY ${PROJECT_BINARY_DIR}/bin_${CMAKE_ADDRESS_MODEL}/${CMAKE_BUILD_TYPE} )
Установка разделённого каталога (для различных архитектур) поиска Boost:
set( Boost_USE_STATIC_LIBS ON ) set( Boost_USE_MULTITHREADED ON ) set( BOOST_LIBRARYDIR "$ENV{BOOST_ROOT}/stage_${CMAKE_ADDRESS_MODEL}/lib" ) # установка каталога поска библиотек буста в зависимости от архитектуры
Установка правил поиска дополнительных библиотек:
set( SEARCH_PARAMETERS REQUIRED QUIET )
Включённый в environment.cmake файл _cmake_scripts/utils.cmake – это набор функций и скриптов, которые могут помочь при написании механизма генерации или необходимы для механизма генерации проектных файлов.
Макрос list_contains проверяет наличие значения в списке:
# macro( list_contains var value) # ... # пример использования set( LIST a;b;c;d;e ) list_contains( does_contain e ${LIST} ) if ( ${does_contain} ) MESSAGE( STATUS CONTAIN ) endif()
Функция test_variable_on_existance, которая проверяет существование переменной:
function(test_variable_on_existance variable) # пример использования был в файле environment.cmake
Функция test_variable_on_equal_to_one_of_the_list, которая проверяет, что переменная принадлежит списку:
function(test_variable_on_equal_to_one_of_the_list variable) # пример использования был в файле environment.cmake
Макрос create_string_from_list, который переводит список (набор) элеметнов в строку, используя пробел в качестве разделителя:
macro(create_string_from_list str) # пример использования ниже в макросе compile_project
Макрос binaries регистрирует в системе конфигурации проекта список исполняемых файлов для компиляции:
macro( binaries ) set( ${SOLUTION_NAME}_binaries ${ARGN} ) endmacro( binaries ) # пример использования в файле: CMakeLists.txt
Макрос compile_binaries запускает автоматическую компиляцию всех исполнимых файлов в каталоге:
macro( compile_binaries ) foreach (binary ${${SOLUTION_NAME}_binaries}) set( module_name ${binary} ) # устанавливает в переменную ${module_name} имя исполнимого файла add_subdirectory( ${binary} ) endforeach( binary ) endmacro( compile_binaries ) # пример использования в файле: sources/CMakeLists.txt
Макрос modules регистрирует в системе конфигурации проекта список модулей (библиотек) для компиляции. Также регистрирует переменные поиска модулей для их поиска скриптом compile_project:
macro( modules ) set( ${SOLUTION_NAME}_modules ${ARGN} ) foreach (module ${${SOLUTION_NAME}_modules}) set( ${module}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/sources/${module} ) # устанавливает переменную поиска файлов #include модуля set( ${module}_LIBRARIES ${module} ) # устанавливает переменную поиска объектного файла модуля *3 endforeach( module ) endmacro( modules ) # пример использования в файле CMakeLists.txt
3* – данные переменные впоследствии используются в скрипте compile_project для поиска других под-модулей solution.
Макрос compile вспомогательный, автоматически добавляет каталоги sources, tests для поиска.
Макрос compile_modules запускает автоматическую компиляцию всех модулей в каталоге:
macro( compile_modules ) foreach (module ${${SOLUTION_NAME}_modules}) set( module_name ${module} ) add_subdirectory( ${module} ) endforeach( module ) endmacro( compile_modules ) # пример использования в файле: sources/CMakeLists.txt
Макрос compile_tests запускает компиляцию тестов для всех модулей проекта (они должны располагаться в каталоге tests/<имя_модуля>_tests).
macro( compile_tests ) if (${RUN_PERFORMANCE_TESTS}) add_definitions( -DRUN_PERFORMANCE_TESTS ) # добавляет переменную окружения RUN_PERFORMANCE_TESTS # её можно использовать в #ifndef RUN_PERFORMANCE_TESTS / #endif endif(${RUN_PERFORMANCE_TESTS}) foreach (module ${${SOLUTION_NAME}_modules} ) if ( ${VERBOSE} ) message(STATUS "Compiling tests for ${module} module.") endif( ${VERBOSE} ) set( module_name ${module} ) # устанавливает имя модуля для которого созданы тесты set( tests_name ${module}_tests ) # устанавливает имя для исполняемого файла тестов модуля add_subdirectory( ${tests_name} ) endforeach( module ) endmacro( compile_tests ) # пример использования в файле: tests/CMakeLists.txt
Макрос add_source_list позволяет добавлять подкаталоги файловой системы в качестве подкаталогов проекта (например, для разделения исходного кода проекта так, как вам удобно.
macro( add_source_list project_name source_folder source_dir ) file(GLOB ${project_name}_${source_folder}_SOURCE_LIST ${source_dir} ) # поиск файлов заданных по шаблону set( ${project_name}_SOURCE_LIST ${${project_name}_SOURCE_LIST} ${${project_name}_${source_folder}_SOURCE_LIST} ) # создание группы файлов в проектной иерархии файлов source_group( ${source_folder} FILES ${${project_name}_${source_folder}_SOURCE_LIST} ) endmacro( add_source_list ) # пример использования: # add_source_list( ${module_name} algorithms "algorithms/*.*" ) # добавит в проект ${module_name} подкаталог algorithms - где будут храниться все файлы, которые # доступны по маске *.* в каталоге algorithms файловой системы
Самый большой макрос – compile_project (непотребные 42 строки). Этот макрос отвечает за генерацию непосредственно файлов проектов (и модулей, и бинарных файлов и тестов) с возможностью поиска всех зависимостей.
macro( compile_project project_name source_pattern header_pattern build_type solution_folder ) project( ${project_name} ) if (${VERBOSE} ) message(STATUS "* Creating project: ${project_name}(${build_type}) with '${solution_folder}' (${PROJECT_SOURCE_DIR}) from: ${source_pattern}, ${header_pattern}.") endif(${VERBOSE}) add_definitions( -D_SCL_SECURE_NO_WARNINGS ) add_definitions( -DSOURCE_DIR="${CMAKE_SOURCE_DIR}" ) # устанавливает переменную с исходным кодом add_definitions( -DBINARY_DIR="${BINARIES_DIRECTORY}" ) # устанавливает переменную с бинарными файлами file(GLOB ${project_name}_SOURCES ${source_pattern}) file(GLOB ${project_name}_HEADERS ${header_pattern}) file(GLOB ${project_name}_SOURCE_LIST ${${project_name}_SOURCE_LIST} ${${project_name}_SOURCES} ${${project_name}_HEADERS}) set( ${project_name}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR} ) # поиск исходного кода проекта в зависимости от шаблона foreach( dependencie ${ARGN} ) create_string_from_list( str ${${dependencie}_INCLUDE_DIRS} ) print_compile_status(" - Adding header dependencie: '${str}'") include_directories( ${${dependencie}_INCLUDE_DIRS} ) # подключение исходного кода зависимостей подпроекта endforeach( dependencie ) if ("${build_type}" STREQUAL "STATIC") add_library(${project_name} STATIC ${${project_name}_SOURCE_LIST} ) print_compile_status(" * Creating static library: ${project_name}") elseif( "${build_type}" STREQUAL "SHARED" ) add_library(${project_name} SHARED ${${project_name}_SOURCE_LIST} ) print_compile_status(" * Creating shared library: ${project_name}") elseif( "${build_type}" STREQUAL "BINARY" ) add_executable( ${PROJECT_NAME} ${${PROJECT_NAME}_SOURCE_LIST}) print_compile_status(" * Creating binary file: ${project_name}") endif() # установка цели компиляции для проекта if (NOT "${build_type}" STREQUAL "STATIC") foreach( dependencie ${ARGN} ) target_link_libraries( ${project_name} ${${dependencie}_LIBRARIES} ) create_string_from_list( str ${${dependencie}_LIBRARIES} ) print_compile_status(" - Adding library dependencie: ${str}") endforeach( dependencie ) endif() # подключение библиотек-зависимостей подпроекта set_property(TARGET ${project_name} PROPERTY FOLDER ${solution_folder}) endmacro( compile_project ) # пример использования: # compile_project( ${tests_name} "*.cpp" "*.h" BINARY tests ${tests_name} ${module_name} system_utilities Boost ) # ${tests_name} содержит название проекта (в данном случае теста) # "*.cpp" - поиск исходных кодов проекта # "*.h" - поиск заголовончх файлов проекта # BINARY - обозначает, что мы делаем исполняемый файл (также может быть STATIC, SHARED) # tests - имя каталога в IDE куда будет помещён проект # остальные параметры - это поиск исходных кодов и библиотек для линковки # мы будем искать исходные коды в каталоге проекта, в каталоге проекта который тестируется, # а также в каталогах определённых ${system_utilities_INCLUDE_DIRS}, ${Boost_INCLUDE_DIRS}
Макрос print_compile_status выводит строковое представление передаваемого элемента в случае VERBOSE=ON
macro( print_compile_status string ) # print_compile_status(" - Adding library dependencie: ${str}")
Макрос register_test регистрирует тест в системе CTest для возможности запуска c помощью make tests или ctest.
macro( register_test project_name tests_time_out tests_with_performance_time_out ) add_test( ${project_name} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${project_name} ) if (${RUN_PERFORMANCE_TESTS}) set_tests_properties ( ${PROJECT_NAME} PROPERTIES TIMEOUT ${tests_with_performance_time_out} ) else() set_tests_properties ( ${PROJECT_NAME} PROPERTIES TIMEOUT ${tests_time_out} ) endif(${RUN_PERFORMANCE_TESTS}) endmacro( register_test ) # пример использования: # register_test( ${tests_name} 1.0 2.0 ) # параметры: имя исполняемого файла, время ограничивающее тест для тестирования # 1.0 - когда не включены PERFORMANCE_TESTS (debug) # 2.0 - когда включены PERFORMANCE_TESTS (release)
В целом шаблон удобней использовать для решений, которые состоят из нескольких модулей или исполнимых файлов.
Для однопроектных решений (например, hello_world) этот шаблон может показаться слишком большим. Но даже в случае одного кроссплатформенного модуля с тестами использование шаблона станет оправданным.
Релоцировались? Теперь вы можете комментировать без верификации аккаунта.