# cmake 01-basic

# 什么是 cmake

image-20230320003127778

之前在 linux 虚拟机上安装各种工具或者编译文件时候常用到 cmake, 那么什么是 cmake 呢?

CMake 是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装 (编译过程)。他能够输出各种各样的 makefile 或者 project 文件,CMake 的组态档取名为 CMakeLists.txt。也就是在 CMakeLists.txt 这个文件中写 cmake 代码。接下来就系统的学习一下 cmake 的语法

# (一) hello-cmake

# main.cpp

#include <iostream>
int main(int argc, char *argv[])
{
   std::cout << "Hello CMake!" << std::endl;
   return 0;
}

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5) #设置 CMake 最小版本
project (hello_cmake) #设置工程名
add_executable(hello_cmake main.cpp) #生成可执行文件

生成与工程同名的二进制文件的办法

cmake_minimum_required(VERSION 2.6)
project (hello_cmake)
add_executable(${PROJECT_NAME} main.cpp)

project (hello_cmake) 函数执行时会生成一个变量,是 PROJECT_NAME,PROJECTNAME表示PROJECTNAME变量的值为hellocmake,所以把{PROJECT_NAME}表示PROJECT_NAME变量的值为hello_cmake,所以把 {PROJECT_NAME} 用在 add_executable () 里可以生成可执行文件名字叫 hello_cmake

# 构建、编译和运行

构建有外部构建和内部构建两种,推荐使用外部构建,我们可以创建一个可以位于文件系统上任何位置的构建文件夹。 所有临时构建和目标文件都位于此目录中,以保持源代码树的整洁。

拿本例子来说:

运行下述代码,新建 build 构建文件夹,并运行 cmake 命令

mkdir build  新建build构建文件夹
cd build/
cmake ..
这时其实可以直接make

构建系统是需要指定 CMakeLists.txt 所在路径,此时在 build 目录下,所以用 .. 表示 CMakeLists.txt 在上一级目录。

此时在 build 目录下会生成 Makefile 文件,然后调用编译器来实际编译和链接项目:

cmake --build .

--build 指定编译生成的文件存放目录,其中就包括可执行文件, . 表示存放到当前目录

image-20230320210319645

同时如果点开 build 文件夹就可以看到,build 文件夹下生成了许多二进制文件,如果要从头开始重新创建 cmake 环境,只需删除构建目录 build,然后重新运行 cmake。 所以外部构建非常方便

# (二) hello-headers

# 文件树

├── CMakeLists.txt
├── include
│   └── Hello.h
└── src
    ├── Hello.cpp
    └── main.cpp

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5)#最低 CMake 版本
project (hello_headers)# 工程名
set(SOURCES
    src/Hello.cpp
    src/main.cpp
)#创建一个变量,名字叫 SOURCE。它包含了所有的 cpp 文件。
add_executable(hello_headers ${SOURCES})#用所有的源文件生成一个可执行文件,因为这里定义了 SOURCE 变量,所以就不需要罗列 cpp 文件了
#等价于命令:     add_executable (hello_headers src/Hello.cpp src/main.cpp)
target_include_directories(hello_headers
    PRIVATE 
        ${PROJECT_SOURCE_DIR}/include
)#设置这个可执行文件 hello_headers 需要包含的库的路径
#PROJECT_SOURCE_DIR 指工程顶层目录
#PROJECT_Binary_DIR 指编译目录
#PRIVATE 指定了库的范围
  • PRIVATE - 目录被添加到目标(库)的包含路径中。
  • INTERFACE - 目录没有被添加到目标(库)的包含路径中,而是链接了这个库的其他目标(库或者可执行程序)包含路径中
  • PUBLIC - 目录既被添加到目标(库)的包含路径中,同时添加到了链接了这个库的其他目标(库或者可执行程序)的包含路径中

也就是说,根据库是否包含这个路径,以及调用了这个库的其他目标是否包含这个路径,可以分为三种 scope。

# CMake 中的常用的变量 (一些目录)

CMake 语法指定了许多变量,可用于在项目或源代码树中找到有用的目录。 其中一些包括:

Variable Info
CMAKE_SOURCE_DIR 根源代码目录,工程顶层目录。暂认为就是 PROJECT_SOURCE_DIR
CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在的路径
PROJECT_SOURCE_DIR 工程顶层目录
CMAKE_BINARY_DIR 运行 cmake 的目录。外部构建时就是 build 目录
CMAKE_CURRENT_BINARY_DIR The build directory you are currently in. 当前所在 build 目录
PROJECT_BINARY_DIR 暂认为就是 CMAKE_BINARY_DIR

想体会一下,可以在 CMakeLists 中,利用 message()命令输出一下这些变量。

另外,这些变量不仅可以在 CMakeLists 中使用,同样可以在源代码.cpp 中使用。

# 源文件变量 (不建议这样做)

回到代码的部分,这里创建了一个包含多个源文件的变量 SOURCES (不建议这样做,可能会导致 glob 命令在添加新的源文件后不会显示正确的结果)

set(SOURCES
    src/Hello.cpp
    src/main.cpp
)
add_executable(${PROJECT_NAME} ${SOURCES})

# 包含头文件

当您有其他需要包含的文件夹(文件夹里有头文件)时,可以使用以下命令使编译器知道它们: target_include_directories()。 编译此目标时,这将使用 - I 标志将这些目录添加到编译器中,例如 -I / 目录 / 路径

target_include_directories(target
    PRIVATE
        ${PROJECT_SOURCE_DIR}/include
)

PRIVATE 标识符指定包含的范围。 这对库很重要。

对于公共的头文件,最好在 include 文件夹下建立子目录。

传递给函数 target_include_directories () 的目录,应该是所有包含目录的根目录,然后在这个根目录下建立不同的文件夹,分别写头文件。

这样使用的时候,不需要写 ${PROJECT_SOURCE_DIR}/include,而是直接选择对应的文件夹里对应头文件。下面是例子: #include "static/Hello.h" 而不是 #include "Hello.h" 使用此方法意味着在项目中使用多个库时,头文件名冲突的可能性较小。

# (三)Static Library

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(hello_library)
############################################################
# Create a library
############################################################
#库的源文件 Hello.cpp 生成静态库 hello_library
add_library(hello_library STATIC 
    src/Hello.cpp
)
target_include_directories(hello_library
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
)
# target_include_directories 为一个目标(可能是一个库 library 也可能是可执行文件)添加头文件路径。
############################################################
# Create an executable
############################################################
# Add an executable with the above sources
#指定用哪个源文件生成可执行文件
add_executable(hello_binary 
    src/main.cpp
)
#链接可执行文件和静态库
target_link_libraries( hello_binary
    PRIVATE 
        hello_library
)
#链接库和包含头文件都有关于 scope 这三个关键字的用法。

# 创建静态库

add_library()函数用于从某些源文件创建一个库,默认生成在构建文件夹。 写法如下:

add_library(hello_library STATIC
    src/Hello.cpp
)

在 add_library 调用中包含了源文件,用于创建名称为 libhello_library.a 的静态库。

# 链接库

创建将使用这个库的可执行文件时,必须告知编译器需要用到这个库。 可以使用 target_link_library()函数完成此操作。add_executable () 连接源文件,target_link_libraries () 连接库文件。

add_executable(hello_binary
    src/main.cpp
)
target_link_libraries( hello_binary
    PRIVATE
        hello_library
)

这告诉 CMake 在链接期间将 hello_library 链接到 hello_binary 可执行文件。 同时,这个被链接的库如果有 INTERFACE 或者 PUBLIC 属性的包含目录,那么,这个包含目录也会被传递( propagate )给这个可执行文件。

# (四)Shared Library

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(hello_library)
############################################################
# Create a library
############################################################
#根据 Hello.cpp 生成动态库
add_library(hello_library SHARED 
    src/Hello.cpp
)
#给动态库 hello_library 起一个别的名字 hello::library
add_library(hello::library ALIAS hello_library)
#为这个库目标,添加头文件路径,PUBLIC 表示包含了这个库的目标也会包含这个路径
target_include_directories(hello_library
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
)
############################################################
# Create an executable
############################################################
#根据 main.cpp 生成可执行文件
add_executable(hello_binary
    src/main.cpp
)
#链接库和可执行文件,使用的是这个库的别名。PRIVATE 表示
target_link_libraries( hello_binary
    PRIVATE 
        hello::library
)

# 创建动态库

add_library()函数用于从某些源文件创建一个动态库,默认生成在构建文件夹。 写法如下:

add_library(hello_library SHARED
    src/Hello.cpp
)

在 add_library 调用中包含了源文件,用于创建名称为 libhello_library.so 的动态库。

# (五)build-type

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
#如果没有指定则设置默认编译方式
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  #在命令行中输出 message 里的信息
  message("Setting build type to 'RelWithDebInfo' as none was specified.")
  #不管 CACHE 里有没有设置过 CMAKE_BUILD_TYPE 这个变量,都强制赋值这个值为 RelWithDebInfo
  set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
  # 当使用 cmake-gui 的时候,设置构建级别的四个可选项
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
    "MinSizeRel" "RelWithDebInfo")
endif()
project (build_type)
add_executable(cmake_examples_build_type main.cpp)
#这里的注释只说明注释后每一句的作用

# 构建级别

CMake 具有许多内置的构建配置,可用于编译工程。 这些配置指定了代码优化的级别,以及调试信息是否包含在二进制文件中。

这些优化级别,主要有:

  • Release —— 不可以打断点调试,程序开发完成后发行使用的版本,占的体积小。 它对代码做了优化,因此速度会非常快,

    在编译器中使用命令: -O3 -DNDEBUG 可选择此版本。

  • Debug —— 调试的版本,体积大。

    在编译器中使用命令: -g 可选择此版本。

  • MinSizeRel—— 最小体积版本

    在编译器中使用命令: -Os -DNDEBUG 可选择此版本。

  • RelWithDebInfo—— 既优化又能调试。

    在编译器中使用命令: -O2 -g -DNDEBUG 可选择此版本。

# 设置级别的方式

# CMake 图形界面

cmake-gui

# CMake 命令行中

在命令行运行 CMake 的时候, 使用 cmake 命令行的 - D 选项配置编译类型

cmake .. -DCMAKE_BUILD_TYPE=Release

# (六) Compile Flags

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
#强制设置默认 C++ 编译标志变量为缓存变量,如 CMake(五) build type 所说,该缓存变量被定义在文件中,相当于全局变量,源文件中也可以使用这个变量
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)
project (compile_flags)
add_executable(cmake_examples_compile_flags main.cpp)
#为可执行文件添加私有编译定义
target_compile_definitions(cmake_examples_compile_flags 
    PRIVATE EX3
)

# 编译标志

编译标志是编译器在编译过程中使用的一些参数,用于指定编译器的行为。不同的编译标志会影响编译器的行为,从而影响生成的可执行文件的行为。例如,使用不同的编译标志可以控制编译器的优化级别、警告级别、调试信息等。1

在 CMake 中,可以使用以下方法设置编译标志:

  • 通过 target_compile_definitions () 函数设置某个目标的编译标志,这是现代 CMake 中设置 C++ 标志的推荐方法。
  • 设置默认编译标志,可以使用以下方法:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "set c++ compiler flags" FORCE)
  • 设置全局 C 编译器标志,可以使用以下方法:参考
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEX2" CACHE STRING "set c compiler flags" FORCE)

不同的编译标志会影响编译器的行为,从而影响生成的可执行文件的行为。例如,使用不同的编译标志可以控制编译器的优化级别、警告级别、调试信息等。

# (七) Including Third Party

# CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
# Set the project name
project (third_party_include)
# find a boost install with the libraries filesystem and system
#使用库文件系统和系统查找 boost install
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
#这是第三方库,而不是自己生成的静态动态库
# check if boost was found
if(Boost_FOUND)
    message ("boost found")
else()
    message (FATAL_ERROR "Cannot find Boost")
endif()
# Add an executable
add_executable(third_party_include main.cpp)
# link against the boost libraries
target_link_libraries( third_party_include
    PRIVATE
        Boost::filesystem
)

# CMake 解析

几乎所有不平凡的项目都将要求包含第三方库,头文件或程序。 CMake 支持使用 find_package()函数查找这些工具的路径。 这将从 CMAKE_MODULE_PATH 中的文件夹列表中搜索格式为 “FindXXX.cmake” 的 CMake 模块。 在 linux 上,默认搜索路径将是 /usr/share /cmake/ Modules。 在我的系统上,这包括对大约 142 个通用第三方库的支持。

此示例要求将 Boost 库安装在默认系统位置。

# Finding a Package

如上所述,find_package()函数将从 CMAKE_MODULE_PATH 中的文件夹列表中搜索 “FindXXX.cmake” 中的 CMake 模块。 find_package 参数的确切格式取决于要查找的模块。 这通常记录在 FindXXX.cmake 文件的顶部。

find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)

参数:

Boost - 库名称。 这是用于查找模块文件 FindBoost.cmake 的一部分

1.46.1 - 需要的 boost 库最低版本

REQUIRED - 告诉模块这是必需的,如果找不到会报错

COMPONENTS - 要查找的库列表。从后面的参数代表的库里找 boost

可以使用更多参数,也可以使用其他变量。 在后面的示例中提供了更复杂的设置。

# 1.8 用 clang 编译工程 (我略了)

使用 CMake 进行构建时,可以设置 C 和 C ++ 编译器。 此示例与 hello-cmake 示例相同,不同之处在于它显示了将编译器从默认的 gcc 更改为 clang 的最基本方法。

# 1.9 用 ninja 构建工程 (略啦)

CMake 是一个元构建系统,可用于为许多其他构建工具创建构建文件。 这个例子展示了如何让 CMake 使用 ninja 构建工具。

# 1.10 导入第三方库到目标

# 导入目标

Imported targets 是 FindXXX 模块导出的只读目标。

在 CMake 命令中包含 boost 这个库:

target_link_libraries( imported_targets
      PRIVATE
          Boost::filesystem
  )
  • 作用是自动链接 Boost

    filesystem 和 Boost :: system 库,同时还包括 Boost 头文件目录。

# 1.11 设置 C++ 标准

CMake 支持传递一个变量给函数 CMAKE_CXX_COMPILER_FLAG 来编译程序。 然后将结果存储在您传递的变量中。

For example:

include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)

# 2.1 子项目

本示例说明如何包含子项目。 顶级 CMakeLists.txt 调用子目录中的 CMakeLists.txt 来创建以下内容:

  • sublibrary1 - 一个静态库
  • sublibrary2 - 只有头文件的库
  • subbinary - 一个可执行文件

文件树如下:

$ tree
.
├── CMakeLists.txt
├── subbinary
│   ├── CMakeLists.txt
│   └── main.cpp
├── sublibrary1
│   ├── CMakeLists.txt
│   ├── include
│   │   └── sublib1
│   │       └── sublib1.h
│   └── src
│       └── sublib1.cpp
└── sublibrary2
    ├── CMakeLists.txt
    └── include
        └── sublib2
            └── sublib2.h
更新于 阅读次数