Support us

Шаблон для проектов на С++ с помощью Boost, CMake. Подробнее о приготовленном CMake

Оставить комментарий
Шаблон для проектов на С++ с помощью Boost, CMake. Подробнее о приготовленном CMake

Для С++ разработчика CMake может стать хорошим инструментом или головной болью. В зависимости от того, есть ли у него время и желание на изучение CMake. Трогать CPack, CTest в этой статье не стану.

читать дальше

Какие плюсы точно можно получить от CMake (если уметь гнуть его в свой велосипед):

  1. кроссплатформенная компиляция от одних правил сборки для множества компиляторов (поддерживается около 50); возможность генерации проектных файлов для совсем различных IDE;

  2. кроссплатформенная линковка с большим количеством библиотек (порядка 130 включённых в стандартный пакет скриптов поиска);

  3. наличие документации (и даже учебника);

  4. множество примеров использования, включая огромные проекты (KDE, Qt);

  5. простой для понимания новичками язык программирования;

И какие минусы скорее всего придётся встретить:

  1. невнятный язык для разработки;

  2. поиск документации очень непрост и часто становится совсем неудобен;

  3. скорее всего без специалиста, который уже пожевал кактусов с CMake будет очень мучительно искать как исправить определённые грабли принятые по умолчанию.

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

Примеры CMake и некоторые ответы на вопросы всегда можно получить на сайте CMake. Приведу только выборку ссылок, для молодых и ещё спортивных:

Начальную структуру файлов каталогов можно увидеть здесь.

(Файл запуска, настроен на MSVC 2005 Win64 Debug. Но можете смело заводить под Linux/MacOS – просто надо выбрать свой генератор (cmake -G "Unix Makefiles" ., cmake -G "Xcode" . соответственно).

Подробнее по файлам:

  1. CMakeLists.txt – файл входа для CMake;

  2. _msvc_gen_2005_64.bat – файл, который запустит CMake и сгенерирует проект (для пользователей Windows, которые не освоили командную строку, почему то проще использовать bat файл);

  3. environment.cmake (включён в CMakeLists.txt) – устанавливает настройки по умолчанию, добавляет каталог _cmake_scripts в качестве дополнительного каталога для поиска cmake скриптов, проверяет, установлены ли необходимые переменные, устанавливает переменные среды в зависимости от окружения, создаёт пре-настройки для поиска библиотеки;

  4. каталог source – каталог, где предполагается размещать исходные коды проекта;

  5. каталог tests – каталог, где предполагается размещать тесты для проекта;

  6. каталог _cmake_stripts – каталог, где располагаются дополнительные cmake скрипты;

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

 

Место солидарности беларусского ИТ-комьюнити

Далучайся!

Читайте также
10 курсов по C++ (июнь 2023)
10 курсов по C++ (июнь 2023)
10 курсов по C++ (июнь 2023)
С++, несмотря на свой солидный возраст, остается одним из основных языков программирования, который применется очень широко: от разработки ПО до создания игр. В сети много ресурсов, которые помогут освоить этот язык. Советуем обратить внимаение на подборку команды Digitaldefynd, котрую мы дополнили. В ней как платные, так и бесплатные ресурсы для людей с разным уровнем подготовки и знаний С++.
1 комментарий
Как оплачиваются самые популярные языки GitHub и какой прогноз
Как оплачиваются самые популярные языки GitHub и какой прогноз
Как оплачиваются самые популярные языки GitHub и какой прогноз
Google создала «убийцу» С++
Google создала «убийцу» С++
Google создала «убийцу» С++
4 комментария
С++ по последним рейтингам растет больше всех языков программирования. Кажется, время пройти курсы
С++ по последним рейтингам растет больше всех языков программирования. Кажется, время пройти курсы
С++ по последним рейтингам растет больше всех языков программирования. Кажется, время пройти курсы

Хотите сообщить важную новость? Пишите в Telegram-бот

Главные события и полезные ссылки в нашем Telegram-канале

Обсуждение
Комментируйте без ограничений

Релоцировались? Теперь вы можете комментировать без верификации аккаунта.

Комментариев пока нет.