======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 .