Очень хорошие видео-уроки от cppProsto. Ссылка на GitHub. Это лучшее, что мне попалось при первой встрече с CMake.
Краткий конспект для себя чтобы быстро вспомнить:
Структура файлов:
cmake_minimum_required(VERSION 3.5) # "example_1" - имя итогового exe файла, далее это имя доступно в переменной ${PROJECT_NAME} project (example_1) #set(CMAKE_BUILD_TYPE Debug) #set(CMAKE_BUILD_TYPE Release) - активно по умолчанию # Флаги для компилятора С++ # Два варианта выбора стандарта языка, назначение CMAKE_CXX_STANDARD # set(CMAKE_CXX_STANDARD 11) # enable C++11 standard # set(CMAKE_CXX_STANDARD 14) # enable C++14 standard # set(CMAKE_CXX_STANDARD 17) # enable C++17 standard # или как передача флагов компилятору # означает CMAKE_CXX_FLAGS = "${CMAKE_CXX_FLAGS} || -std=c++98" # где ${CMAKE_CXX_FLAGS} - обращение к текущему значению переменной # set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98") # set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++03") # set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") # set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y") # использовать самый новый поддерживаемый стандарт # Флаги для компилятора Си # set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c89") # set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c90") # set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") # Wall - Enable Warnings All #add_definitions(-Wall -O2) # or set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") # 1 - Собрать EXE файл из конкретных файлов, необходимо дописывать каждый новый файл вручную # add_executable(${PROJECT_NAME} main.cpp test.c) # 2 - Собрать EXE файл из файлов собранных в переменную (список) SOURCES. #set(SOURCES main.cpp test.c) #add_executable(${PROJECT_NAME} ${SOURCES}) # 3 - Собрать EXE файл из всех *.cpp (C++) и *.c (Си) файлов в директории src file(GLOB CPPS "src/*.cpp" C "src/*.c") add_executable(${PROJECT_NAME} ${CPPS} ${C})
Для сборки с компилятором MinGW64 в Windows:
Последним параметром с cmake передается путь. При вызове генерации путь "..", говорит что CMakeLists.txt лежит сразу при выходе из директории build. А при сборке ".", говорит что собирать надо все в текущей директории.
При необходимости указать путь к внешней библиотеке используется CMAKE_PREFIX_PATH.
> cmake.exe -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH=C:\Code\protobuf ..\example > cmake --build .
..\example - это путь к CMakeLists.txt
Например, для компиляции *.proto файла в файлы *.cpp и *.h необходимо указать путь, где лежит библиотека protobuf от Google. В моем случае protobuf лежит:
Версия ПО задается в сборочном файле CMakeLists.txt, в котором генерируются *.h файлы, которые подключаются к проекту и передают версию ПО в глобальной переменной.
cmake_minimum_required(VERSION 3.5) project (cmake_example_2) # The version number. set (cmake_example_2_VERSION_MAJOR 1) set (cmake_example_2_VERSION_MINOR 2) set (cmake_example_2_VERSION_PATCH 3) set (cmake_example_2_VERSION_TWEAK 4) set (cmake_example_2_VERSION "${cmake_example_2_VERSION_MAJOR}.${cmake_example_2_VERSION_MINOR}.${cmake_example_2_VERSION_PATCH}.${cmake_example_2_VERSION_TWEAK}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -Wall") file(GLOB CPP_SOURCES "src/*.cpp") # Call configure files on ver.h.in to set the version. configure_file( "${PROJECT_SOURCE_DIR}/include/ver.h.in" "${PROJECT_BINARY_DIR}/version.h" ) configure_file( "${PROJECT_SOURCE_DIR}/include/path.h.in" "${PROJECT_BINARY_DIR}/path.h" ) include_directories("${PROJECT_BINARY_DIR}") include_directories("${PROJECT_SOURCE_DIR}/include") add_executable(${PROJECT_NAME} ${CPP_SOURCES})
Структура директорий:
Для сборки снова создать поддиректорию exmple_2/build, и из нее запускать сборку проекта аналогичным образом. Эта директория и будет являться PROJECT_BINARY_DIR.
#-------------------- Build a SHARED library from sources (DLL) ----------------- add_library(${PROJECT_NAME}_lib SHARED lib_src/sLibrary.cpp ) add_library(${PROJECT_NAME}_lib::lib_1 ALIAS ${PROJECT_NAME}_lib) #Задание псевдонима target_include_directories(${PROJECT_NAME}_lib PUBLIC ${PROJECT_SOURCE_DIR}/lib_src )
#-------------------- Build a STATIClibrary from sources (LIB) ----------------- add_library(${PROJECT_NAME}_lib STATIC lib_src/sLibrary.cpp ) target_include_directories(${PROJECT_NAME}_lib PUBLIC ${PROJECT_SOURCE_DIR}/lib_src )
#------------------- Create an executable -------------------------------------- file(GLOB CPP_SOURCES "src/*.cpp") add_executable(${PROJECT_NAME} ${CPP_SOURCES}) #------------------- Link with SHAREDLibrary -------------------------------------- # link the new library target with the binary target target_link_libraries( ${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_lib::lib_1 ) #------------------- OR Link with STATIC Library -------------------------------------- # link the new library target with the binary target target_link_libraries( ${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_lib )
Структура директорий:
Здесь указаны только отличия, остальное осталось неизменно с Example_2.
Если библиотека есть в уже собранном виде (dll / lib), то подключить ее к проекту можно через link_directories. Т.е. подключается директория где лежат уже собранные библиотеки. При сборке EXE линковка произойдет с выбранной библиотекой cmake_example_4_shared_lib / cmake_example_4_static_lib.
#------------------- Create an executable -------------------------------------- file(GLOB CPP_SOURCES "src/*.cpp") link_directories(${CMAKE_SOURCE_DIR}/libs/shared) #link_directories(${CMAKE_SOURCE_DIR}/libs/static) add_executable(${PROJECT_NAME} ${CPP_SOURCES}) target_link_libraries( ${PROJECT_NAME} cmake_example_4_shared_lib ) #target_link_libraries( ${PROJECT_NAME} # cmake_example_4_static_lib #)
Если не прописывать link_directories, тогда пришлось бы прописывать полный путь к библиотеке, что неудобно если их несколько:
target_link_libraries( ${PROJECT_NAME} /libs/shared/cmake_example_4_shared_lib
Хорошо когда проекты/библиотеки можно собирать по отдельности, а не так, что одним файлом CMakeLists.txt собираются все зависимости необходимые для текущего проекта. Удобно когда каждую библиотеку можно собрать по отдельности своим CMakeLists.txt файлом. Отладить, проверить работоспособность, а потом включить эту сборку в проект выше по иерархии.
Например есть три проекта:
Вот его код:
cmake_minimum_required (VERSION 3.5) project(main) # Add sub directories add_subdirectory(sub_project1) add_subdirectory(sub_project2) add_subdirectory(main)
Проект main зависит от двух под-проектов, поэтому он собирается последним. Вот его CMakeLists.txt, который выполнится при add_subdirectory(main):
project(sub_main) # Create the executable add_executable(${PROJECT_NAME} main.cpp) # This will cause the include directories for that target to be added to this project target_link_libraries(${PROJECT_NAME} sub::lib_c sub::lib_cpp )
Здесь происходит линковка с библиотеками (алиасы sub::lib_c и sub::lib_cpp), собирающимися из под-проектов:
cmake_minimum_required(VERSION 3.5) # Set the project name project (sublibrary_c) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") # Add a library with the above sources add_library(${PROJECT_NAME} src/sub_c.c) add_library(sub::lib_c ALIAS ${PROJECT_NAME}) target_include_directories( ${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
cmake_minimum_required(VERSION 3.5) # Set the project name project (sublibrary_cpp) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y") # Add a library with the above sources add_library(${PROJECT_NAME} src/sub_cpp.cpp) add_library(sub::lib_cpp ALIAS ${PROJECT_NAME}) target_include_directories( ${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
Плюсом такого подхода является простая отладка. Если общая сборка сломалась, то есть возможность отключить сборку каких-то под-проектов и методом исключения найти, который проект перестал собираться. Достаточно закомментировать лишь некоторые строчки add_subdirectory(). Если бы сборка подпроектов была настроена в одном CMakeLists.txt, то отлаживаться было бы значительно сложнее.
Список модулей, которые cmake умеет искать представлено тут - https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html
cmake_minimum_required (VERSION 3.5) project(package_find) # пути по которым будут искаться пакеты при find_package set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) set(all_found_result "ok") # вставка внешнего /cmake/modules/test.cmake с прочими find_package или чем-то еще, для примера include(test) # будет искаться файл Find_OpenSSL.cmake, там должны быть скрипты проверки наличия модуля find_package(_OpenSSL) # Add an executable add_executable(package_find main.cpp) if(all_found_result STREQUAL "ok") message("all libraries was found") else() message( FATAL_ERROR "Not all libraries was found!" ) #FATAL_ERROR - прервать генерацию endif() # Можно выводить сообщения # https://cmake.org/cmake/help/v3.0/command/message.html message( STATUS " status " ) message( WARNING " warning " ) message( AUTHOR_WARNING " author warning " ) message( SEND_ERROR " send error " ) message( DEPRECATION " deprecation " ) message( FATAL_ERROR " fatal error " )
В примере изменен поиск стандартного OpenSSL модуля на свой собственный _OpenSSL, поэтому
Could not find a package configuration file provided by "_OpenSSL" with any of the following names: _OpenSSLConfig.cmake _openssl-config.cmake Add the installatin prefix of "_OpenSSL" to CMAKE_PREFIX_PATH or set "_OpenSSL_DIR" to a directory containing one of the above files. If "_OpenSSL" provides a separate development package or SDK, be sure it has been installed.
В примере модуль _OpenSSL используется чтобы показать, как cmake ищет модули. В файле /cmake/modules/Find_OpenSSL.cmake вызывается стандартный скрипт поиска OpenSSL:
find_package(OpenSSL) # проверяем, что OpenSSL пакет установлен if(NOT OPENSSL_FOUND) set(all_found_result "not found") message("OpenSSL not found!") else() message("system OpenSSL library found. Version - ${OPENSSL_VERSION}") endif() # проверяем, что найдены исходники - директория include if(OPENSSL_INCLUDE_DIR) message("OpenSSL include directory found.") else() set(all_found_result "not found") message("OpenSSL include directory not found!") endif() # проверяем, что найдены библиотеки if(OPENSSL_LIBRARIES) message("The libraries needed to use OpenSSL found.") else() set(all_found_result "not found") message("The libraries needed to use OpenSSL not found!") endif()
При вызове find_package(_OpenSSL, REQUIRED) генерация прерывается в случае неудачи.
cmake -G "Ninja" \ -DCMAKE_MAKE_PROGRAM=path_to/ninja \ --toolchain=path_to/toolchain-nto-x86.cmake .. cmake --build .