Для С++ разработчика 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) этот шаблон может показаться слишком большим. Но даже в случае одного кроссплатформенного модуля с тестами использование шаблона станет оправданным.
Релоцировались? Теперь вы можете комментировать без верификации аккаунта.