# cmake 01-basic
# 什么是 cmake
之前在 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, {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
指定编译生成的文件存放目录,其中就包括可执行文件, .
表示存放到当前目录
同时如果点开 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