======CMAKE Notes====== Очень хорошие видео-уроки от [[https://www.youtube.com/watch?v=4-viiFusYso&t=871s?\|cppProsto]]. Ссылка на [[https://github.com/cCppProsto/cmake|GitHub]]. Это лучшее, что мне попалось при первой встрече с CMake. Краткий конспект для себя чтобы быстро вспомнить: =====Lesson1, простейшая сборка EXE файла===== Структура файлов: * example_1 * main.cpp * test.c * CMakeLists.txt - скрипт для сборки проекта для 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: - создать поддиректорию //"example_1/build"// - открыть эту директорию в командной строке: //"cd c:/путь.../example_1/build"// - сгенерировать все необходимое для сборки: //"cmake -G "MinGW Makefiles" .."// - собрать EXE файл: //"cmake --build ."// Последним параметром с 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 лежит: * C:\Code\protobuf * bin - здесь лежит компилятор protoc.exe * include - *.cpp, *.h и прочее от google необходимое для сборки в проекте использующем protobuf * lib - скомпилированные при установке protobuf библиотеки "*.a". =====Lesson2, пример генерации *.h файла с версией сборки===== Версия ПО задается в сборочном файле 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}) * //configure_file// указывают сконвертировать ver.h.in в version.h, причем version.h создается в директории сборки. * //include_directories// подключают пути, в которых лежат файлы необходимые для сборки. Структура директорий: * Example_2 * src * main.cpp * stest.cpp * include * path.h.in - файл для конвертации в path.h * ver.h.in - файл для конвертации в version.h * stest.hpp * CMakeLists.txt Для сборки снова создать поддиректорию exmple_2/build, и из нее запускать сборку проекта аналогичным образом. Эта директория и будет являться PROJECT_BINARY_DIR. =====Lesson3, сборка библиотеки и EXE===== #-------------------- 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_3 * include * lic_scr * sLibrary.cpp * sLibrary.h * src * CMakeLists.txt Здесь указаны только отличия, остальное осталось неизменно с Example_2. =====Lesson4, сборка с внешней библиотекой===== Если библиотека есть в уже собранном виде (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 #) * /libs/shared - путь к директории с динамическими библиотеками * /libs/static - путь к директории со статическими библиотеками Если не прописывать link_directories, тогда пришлось бы прописывать полный путь к библиотеке, что неудобно если их несколько: target_link_libraries( ${PROJECT_NAME} /libs/shared/cmake_example_4_shared_lib =====Lesson5, модульная сборка проектов===== Хорошо когда проекты/библиотеки можно собирать по отдельности, а не так, что одним файлом CMakeLists.txt собираются все зависимости необходимые для текущего проекта. Удобно когда каждую библиотеку можно собрать по отдельности своим CMakeLists.txt файлом. Отладить, проверить работоспособность, а потом включить эту сборку в проект выше по иерархии. Например есть три проекта: * main * sub_project1 * sub_project2 * CMakeLists.txt - собирает sub_project1, sub_project2, main Вот его код: 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)//, собирающимися из под-проектов: ===add_subdirectory(sub_project1)=== 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 ) ===add_subdirectory(sub_project2)=== 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, то отлаживаться было бы значительно сложнее. =====Lesson6, find_package===== Список модулей, которые 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//, поэтому * find_package(_OpenSSL) - будет искать //Find_OpenSSL.cmake// в установочных путях cmake * и в пользовательских путях, добавленных через CMAKE_MODULE_PATH * если //Find_OpenSSL.cmake// не будет найден, то cmake выдаст ошибку: 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**)// генерация прерывается в случае неудачи. =====Сборка с разными Toolchain===== [[https://wiki.sensi.org/dokuwiki/doku.php?id=msys2_mingw|MSYS2]] cmake -G "Ninja" \ -DCMAKE_MAKE_PROGRAM=path_to/ninja \ --toolchain=path_to/toolchain-nto-x86.cmake .. cmake --build .