17% скидка на размещение рекламы на площадках devby — до 20 ноября. Клац!
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) этот шаблон может показаться слишком большим. Но даже в случае одного кроссплатформенного модуля с тестами использование шаблона станет оправданным.

 

Читайте также
Python установил сразу два рекорда на рынке языков программирования
Python установил сразу два рекорда на рынке языков программирования
Python установил сразу два рекорда на рынке языков программирования
2 комментария
«Спасите C++»: отец языка программирования просит помощи у сообщества
«Спасите C++»: отец языка программирования просит помощи у сообщества
«Спасите C++»: отец языка программирования просит помощи у сообщества
9 комментариев
TIOBE: «быстрые» языки программирования растут лучше всех
TIOBE: «быстрые» языки программирования растут лучше всех
TIOBE: «быстрые» языки программирования растут лучше всех
2 комментария
TIOBE назвал «язык года»-2024
TIOBE назвал «язык года»-2024
TIOBE назвал «язык года»-2024
1 комментарий

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

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

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

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

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