CMake构建工具
前置内容:[GCC|GDB]编译调试套件
前言
CMake 是一个跨平台的开源构建工具,用于管理和构建 C++ 项目。它允许开发人员使用简单的配置文件来描述项目的构建过程,然后根据这些配置文件生成特定于平台的构建系统(如 Makefile、Visual Studio 项目等),从而实现跨平台的项目构建。
CMake 的主要特点和用途包括:
- 跨平台性:CMake 可以生成适用于各种操作系统和构建工具的构建文件,包括 Linux、Windows、macOS 等,以及各种编译器和 IDE。
- 简单的配置文件:CMake 使用 CMakeLists.txt 文件来描述项目的构建过程,这些配置文件语法简洁清晰,易于理解和维护。
- 模块化设计:CMake 支持模块化的项目结构,允许将项目划分为多个子目录,并在各个子目录中管理不同的组件和库。
- 多配置构建:CMake 支持多配置构建,可以同时生成 Debug 和 Release 版本的构建文件,并且可以轻松切换配置。
- 集成测试:CMake 提供了内置的测试支持,可以轻松地添加单元测试和集成测试,并与各种测试框架集成。
- 外部依赖管理:CMake 支持管理项目的外部依赖,可以自动下载和构建第三方库。
- 自定义构建规则:CMake 允许开发者定义自定义的构建规则和命令,以满足特定项目的需求。
一. 语法特性
-
基本语法格式
指令(参数 1 参数 2...) #例如 set([变量名] [赋给变量的值])
- 参数使用括弧括起
- 参数之间使用空格或分号分开
-
指令是大小写无关的,参数和变量是大小写相关的
-
变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
set(HELLO hello.cpp) # 定义一个 CMake 变量 HELLO,其值为 hello.cpp。 # 创建了一个名为 hello 的可执行文件,它由 main.cpp 和 hello.cpp 两个源文件组成。 # 这意味着编译器会编译这两个源文件,并将它们链接成一个名为 hello 的可执行文件。 # 变量 ${HELLO} 的值是 hello.cpp 并且 指令大小写不敏感 故以下两条指令效果相同 add_executable(hello main.cpp hello.cpp) ADD_EXECUTABLE(hello main.cpp ${HELLO})
二. 重要指令和CMake常用变量
2.1 重要指令
-
cmake_minimum_required 指定CMake的最小版本要求
- 语法: cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])
# CMake最小版本要求为2.8.3 cmake_minimum_required(VERSION 2.8.3)
-
project 定义工程名称,并可指定工程支持的语言
- 语法: project(projectname [CXX] [C] [Java])
# 指定工程名为HELLOWORLD project(HELLOWORLD)
-
set 显式的定义变量
- 语法:set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
# 定义SRC变量,其值为sayhello.cpp hello.cpp set(SRC sayhello.cpp hello.cpp)
-
include_directories 向工程添加多个特定的头文件搜索路径 —>相当于指定g++编译器的-I参数
- 语法: include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
# 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径 include_directories(/usr/include/myincludefolder ./include)
-
link_directories 向工程添加多个特定的库文件搜索路径 —>相当于指定g++编译器的-L参数
- 语法: link_directories(dir1 dir2 …)
# 将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径 link_directories(/usr/lib/mylibfolder ./lib)
-
add_library 生成库文件
- 语法: add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 … sourceN)
# 通过变量 SRC 生成 libhello.so 共享库 add_library(hello SHARED ${SRC})
-
add_compile_options 添加编译参数
- 语法:add_compile_options( …)
# 添加编译参数 -Wall -std=c++11 -O2 add_compile_options(-Wall -std=c++11 -O2)
-
add_executable 生成可执行文件
- 语法:add_executable(exename source1 source2 … sourceN)
# 编译main.cpp生成可执行文件main add_executable(main main.cpp)
-
target_link_libraries 为 target 添加需要链接的共享库 -->相同于指定g++编译器-l参数
- 语法: target_link_libraries(target library1<debug | optimized> library2…)
# 将hello动态库文件链接到可执行文件main target_link_libraries(main hello)
-
add_subdirectory 向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
- 语法: add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- add_subdirectory(<子目录名> [二进制输出目录])
# 添加src子目录,src中需有一个CMakeLists.txt add_subdirectory(src)
-
aux_source_directory 发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
- 语法: aux_source_directory(dir VARIABLE)
# 定义SRC变量,其值为当前目录下所有的源代码文件 aux_source_directory(. SRC) # 编译SRC变量所代表的源代码文件,生成main可执行文件 add_executable(main ${SRC})
-
target_include_directories 用于为指定的目标(通常是库或可执行文件)设置头文件搜索路径。
-
target_include_directories(target_name [SYSTEM] [BEFORE] [INTERFACE|PUBLIC|PRIVATE] [items1...] [INTERFACE|PUBLIC|PRIVATE] [items2...] ...)
-
target_name
:要设置头文件搜索路径的目标的名称。 -
SYSTEM
:可选参数,用于指定路径是系统目录,可以用于区分编译器警告等。 -
BEFORE
:可选参数,用于指定添加的路径是否应该添加到之前已经存在的路径之前。 -
INTERFACE|PUBLIC|PRIVATE
:可选参数,用于指定添加的路径是作为 INTERFACE、PUBLIC 还是 PRIVATE。具体含义如下:
INTERFACE
:指定添加的路径是接口路径,会被传递给目标的依赖项。PUBLIC
:指定添加的路径不仅会对目标自身生效,还会被传递给目标的依赖项。PRIVATE
:指定添加的路径仅对目标自身生效,不会被传递给目标的依赖项。
-
items
:要添加的路径,可以是目录名或变量名。
-
-
target_include_directories(my_target PUBLIC ${PROJECT_SOURCE_DIR}/include) #把 ${PROJECT_SOURCE_DIR}/include 目录添加到 my_target 目标的头文件搜索路径中,并且这个路径会被传递给 my_target 的依赖项。
-
2.2 常用变量
-
CMAKE_C_FLAGS gcc编译选项
-
CMAKE_CXX_FLAGS g++编译选项
# 在CMAKE_CXX_FLAGS编译选项后追加-std=c++11 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # 该命令将 -std=c++11 添加到 CMAKE_CXX_FLAGS 变量的末尾,这个变量包含了C++编译器的所有编译选项。 #这样做的效果是,当 CMake 生成 Makefile 或者其他构建系统的配置文件时,会自动将 -std=c++11 作为编译选项传递给编译器,以指定使用 C++11 标准进行编译。
-
CMAKE_BUILD_TYPE 编译类型(Debug, Release)
# 设定编译类型为debug,调试时需要选择debug set(CMAKE_BUILD_TYPE Debug) # 设定编译类型为release,发布时需要选择release set(CMAKE_BUILD_TYPE Release)
-
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
_BINARY_DIR
这三个变量指代的内容是一致的。如果是 in source build,指的就是工程顶层目录。如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,不过现在,你可以理解为他们是一致的。
-
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR_SOURCE_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在 in source build时,他跟 CMAKE_BINARY_DIR 等变量一致。PROJECT_SOURCE_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
- CMAKE_C_COMPILER:指定C编译器
- CMAKE_CXX_COMPILER:指定C++编译器
- EXECUTABLE_OUTPUT_PATH:可执行文件输出的存放路径
- LIBRARY_OUTPUT_PATH:库文件输出的存放路径
三. CMake编译工程
CMake目录结构:项目主目录存在一个CMakeLists.txt文件
两种方式设置编译规则:
- 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加子目录即可;
- 包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中;
3.1 编译流程
在 linux 平台下使用 CMake 构建C/C++工程的流程如下:
-
手动编写 CMakeLists.txt。
-
执行命令
cmake PATH
生成 Makefile ( PATH 是顶层CMakeLists.txt 所在的目录 )。-
# important tips . # 表示当前目录 ./ # 表示当前目录 .. # 表示上级目录 ../ # 表示上级目录
-
-
执行命令
make
进行编译。
3.2 两中构建方式
-
内部构建(in-source build):不推荐使用
-
内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起会显得杂乱无章。
## 内部构建 # 在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件 cmake . # 执行make命令,生成target make
-
-
外部构建(out-of-source build):推荐使用
-
新建一个build文件.将编译输出文件与源文件放到不同目录中
## 外部构建 # 1. 在当前目录下,创建build文件夹 mkdir build # target_include_directories 是一个 CMake 命令,用于为指定的目标(通常是库或可执行文件)设置头文件搜索路径。 2. 进入到build文件夹 cd build # 3. 编译上级目录的CMakeLists.txt,生成Makefile和其他文件 cmake .. # 4. 执行make命令,生成target make
-
四. CMake实例
CMakeLists.txt编写
回顾一下编译的流程:
- 预处理(Preprocessing): 在这一步,编译器会处理源文件,并执行以’#'开头的预处理指令,例如#include和#define。预处理器会将这些指令替换为相应的内容,生成一个被处理过的源文件。
- 编译(Compilation): 经过预处理的源文件接下来会被编译成中间表示形式,通常是汇编语言或机器码的形式,这取决于编译器和编译选项。编译器将源代码转换为与特定目标架构相关的低级语言表示。
- 汇编(Assembly): 在这一阶段,编译器会将编译生成的中间表示形式转换为目标平台上的汇编代码,这些汇编代码是由特定于目标平台的指令组成的。每一条高级语言的语句通常会被转换为多条汇编指令。
- 链接(Linking): 最后,链接器将编译生成的目标文件以及任何所需的库文件合并在一起,生成可执行文件。这包括静态链接和动态链接两种方式:
- 静态链接(Static Linking): 静态链接器将目标文件中所需的所有库文件的代码和数据合并到最终的可执行文件中。这意味着可执行文件独立于外部的库文件。
- 动态链接(Dynamic Linking): 动态链接器在运行时将可执行文件与所需的库文件进行链接。这意味着可执行文件只包含对库的引用,而不是库的实际代码和数据。
4.1 最小CMake工程
# Set the minimum version of CMake that can be used
cmake_minimum_required(VERSION 3.0)
# Set the project name
project (HELLO)
# Add an executable
add_executable(hello_cmake main.cpp)
4.2 多目录 工程
文件结构:
#指定了 CMake 的最低版本为 3.0。这意味着如果使用的 CMake 版本低于 3.0,则会产生错误并终止构建过程。
cmake_minimum_required(VERSION 3.0)
#定义了项目的名称为 SWAP。
project(SWAP)
#将 include 目录添加到头文件搜索路径中,以便编译器能够找到项目中的头文件。
include_directories( include )
# 编译这两个源文件,并将它们链接成一个名为 main 的可执行文件。
add_executable(main main.cpp src/swap.cpp)
4.3 多目录 工程- 直接编译
(感觉差前后文, 没看明白, 跳过)
参考的 文档
#指定了 CMake 的最低版本为 3.0。这意味着如果使用的 CMake 版本低于 3.0,则会产生错误并终止构建过程。
cmake_minimum_required(VERSION 3.0)
#定义了项目的名称为 SWAP。
project(SWAP)
#将 include 目录添加到头文件搜索路径中,以便编译器能够找到项目中的头文件。
include_directories( include )
#指定了要构建的源文件目录为 src,并将构建后的结果存储在变量 DIR_SRCS 中。
# DIR_SRCS 是一个 CMake 变量,用于存储子目录中的源文件列表
# 向当前 CMakeLists.txt 文件添加一个子目录,并在该子目录中执行另一个 CMakeLists.txt 文件。
add_subdirectory( src DIR_SRCS )
#创建一个名为 swap_02 的可执行文件,该可执行文件由 ${TEST_MATH} 变量中列出的源文件构建而成。
add_executable(swap_02 ${TEST_MATH})
#链接目标可执行文件 ${FS_BUILD_BINARY_PREFIX}sqrt 与 ${LIBRARIES} 中列出的库文件。${FS_BUILD_BINARY_PREFIX} 变量可能是一些前缀,用于区分构建的可执行文件的名称。
target_link_libraries(${FS_BUILD_BINARY_PREFIX}sqrt ${LIBRARIES})
4.4 多目录工程-生成库编译CMake构建工具
# Set the minimum version of CMake that can be used
cmake_minimum_required(VERSION 3.0)
预处理(Preprocessing):
在这一步,编译器会处理源文件,并执行以'#'开头的预处理指令,例如#include和#define。预处理器会将这些指令替换为相应的内容,生成一个被处理过的源文件。
编译(Compilation):
经过预处理的源文件接下来会被编译成中间表示形式,通常是汇编语言或机器码的形式,这取决于编译器和编译选项。编译器将源代码转换为与特定目标架构相关的低级语言表示。
汇编(Assembly):
在这一阶段,编译器会将编译生成的中间表示形式转换为目标平台上的汇编代码,这些汇编代码是由特定于目标平台的指令组成的。每一条高级语言的语句通常会被转换为多条汇编指令。
链接(Linking):
最后,链接器将编译生成的目标文件以及任何所需的库文件合并在一起,生成可执行文件。这包括静态链接和动态链接两种方式:
静态链接(Static Linking): 静态链接器将目标文件中所需的所有库文件的代码和数据合并到最终的可执行文件中。这意味着可执行文件独立于外部的库文件。
动态链接(Dynamic Linking): 动态链接器在运行时将可执行文件与所需的库文件进行链接。这意味着可执行文件只包含对库的引用,而不是库的实际代码和数据。
#project name
project(helloworld)
#add compile options
add_compile_options("-Wall -std=c++11")
#set CMAKE_BUILD_TYPE
set( CMAKE_BUILD_TYPE Debug )
# set output binary path
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
############################################################
# Create a library
############################################################
#Generate the static library from the library sources,创建的库的名称为 sum_library,并且将 src/sum.cpp 文件编译成一个静r态库
add_library( sum_library STATIC src/sum.cpp )
#这个命令将 ${PROJECT_SOURCE_DIR}/include 目录添加到静态库 sum_library 的头文件搜索路径中。这意味着当其他项目使用 sum_library 时,它们可以通过 #include 指令引用 sum_library 中的头文件,而无需指定完整的路径。这样做可以提高代码的可读性和可移植性。
target_include_directories( sum_library PUBLIC ${PROJECT_SOURCE_DIR}/include )
############################################################
# Create an executable
############################################################
# Add an executable with the above sources
add_executable( sum_01 src/helloworld.cpp )
# link the new sum_01 target with the sum_library target
target_link_libraries( sum_01 sum_library )
编译前:
helloworld.cpp为主函数
── build
├── CMakeLists.txt
├── include
│ └── functions.h
└── src
├── helloworld.cpp
└── sum.cpp
3 directories, 4 files
编译中:
cd build
cmake ..
make
编译后:
执行:
cd bin
./sum_01
# 执行结果
hello world!
24