Инструменты пользователя

Инструменты сайта


cmake:notes

CMAKE Notes

Очень хорошие видео-уроки от cppProsto. Ссылка на 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:

  1. создать поддиректорию "example_1/build"
  2. открыть эту директорию в командной строке: "cd c:/путь…/example_1/build"
  3. сгенерировать все необходимое для сборки: "cmake -G "MinGW Makefiles" .."
  4. собрать 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

MSYS2

cmake -G "Ninja" \
      -DCMAKE_MAKE_PROGRAM=path_to/ninja \
      --toolchain=path_to/toolchain-nto-x86.cmake ..
cmake --build .
cmake/notes.txt · Последнее изменение: 2024/01/17 12:54 — vasco