一、CMake工具
CMake是一个跨平台的安装(编译)工具,它可以用简单的语句来描述所有平台的安装(编译)过程。以下是对CMake的详细解析:
1.1、CMake的基本概念
-
定义:CMake是一个跨平台的自动化建构系统,它使用一种名为CMakeLists.txt的配置文件来控制软件编译过程。
-
功能:CMake可以编译源代码、制作程序库、产生适配器(wrapper),还可以用任意的顺序建构执行档。它支持in-place建构(二进档和源代码在同一个目录树中)和out-of-place建构(二进档在别的目录里),因此可以很容易从同一个源代码目录树中建构出多个二进档。
-
特点:CMake能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性。它并不直接建构出最终的软件,而是产生标准的建构档(如Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件。
1.2、CMake的使用
-
安装:CMake可以在多种操作系统上安装,包括Linux、Windows和macOS等。在Linux上,通常可以使用包管理器(如apt-get)来安装CMake。在Windows上,可以从CMake的官方网站下载安装程序进行安装。
-
配置文件:CMake的核心是CMakeLists.txt文件,它告诉CMake如何构建项目。通过CMakeLists.txt,可以决定工程文件生成二进制文件、动态库、静态库文件,以及链接各种库文件。
-
构建过程:
-
cmake步骤:CMake首先扫描计算机以查看一些默认设置,这些设置包括库的位置以及在系统上安装软件的位置。然后,它使用CMakeLists.txt文件来配置项目。
-
make步骤:接下来,使用系统上的make命令(在Linux上是GUN Make,在其他系统上可能是不同的make工具)来编译程序。这个过程通常是将人类可读的源代码转换成机器语言。
-
make install步骤:最后,在make install一步中,那些编译过的文件将被拷贝到计算机上合适的位置。
-
1.3、CMake的进阶使用
-
变量:CMake支持多种类型的变量,包括预定义变量、环境变量、系统信息等。这些变量可以在CMakeLists.txt文件中使用,以控制构建过程。
-
条件控制和循环语句:CMake还提供了if条件控制和while、foreach等循环语句,以及macro宏定义和function函数等高级功能,使得CMakeLists.txt文件可以更加灵活和强大。
-
查找库和包含目录:在构建过程中,CMake可以查找指定的库文件和包含目录,并设置链接库搜索目录和target需要链接的库。这使得构建过程更加自动化和便捷。
1.4 CMake不同版本的区别
-
CMake 2.x与现代CMake
- CMake 2.x版本主要依赖过程式描述,变量(variable)的定义和使用较为普遍。
- 现代CMake(3.x及以后版本)则围绕Target和Property来定义,并竭力避免出现变量的定义。它更像是在遵循面向对象编程(OOP)的规则,通过target来约束link、compile等相关属性的作用域。
-
Target与Property
- 在现代CMake中,一个Target可以看作是一个对象,具有构造函数(如add_executable、add_library)和成员函数(如get_target_property、set_target_properties等)。
- Target中有两个重要概念:Build-Requirements和Usage-Requirements。它们分别包含了构建Target和使用Target所必须的材料。
-
属性传递范围
- 在现代CMake中,可以使用PUBLIC、PRIVATE、INTERFACE等关键字来定义Target属性的传递范围。
- PRIVATE:表示Target的属性只定义在当前Target中,不共享给依赖它的其他Target。
- INTERFACE:表示Target的属性不适用于其自身,而只适用于依赖它的Target。
- PUBLIC:表示Target的属性既是Build-Requirements也是Usage-Requirements,会共享给依赖它的其他Target。
- 在现代CMake中,可以使用PUBLIC、PRIVATE、INTERFACE等关键字来定义Target属性的传递范围。
-
版本兼容性
- CMake的不同版本之间存在差异,某些指令在不同版本的支持程度可能不同。
- 使用cmake_minimum_required()命令可以限制CMake的最低版本需求,确保CMakeLists.txt文件与CMake版本的兼容性。
- 如果CMake运行的版本不在指定的最低和最高版本之间,会报错。
-
策略设置
- CMake还提供了cmake_policy()命令来设置全局策略,以处理不同版本之间的兼容性问题。
- 通过cmake_policy(VERSION)可以指定版本引入的所有策略都将设置为NEW行为或OLD行为。
- 对于特定的策略,可以使用cmake_policy(SET CMP<NNNN> NEW)或cmake_policy(SET CMP<NNNN> OLD)来显式设置其行为。
1.5、CMake的应用场景
CMake广泛应用于各种C/C++项目中,特别是那些需要跨平台构建的项目。它使得开发者可以更加专注于编写代码,而不是花费大量时间在构建系统的配置上。此外,CMake还支持与各种集成开发环境(IDE)的集成,如Visual Studio、Eclipse等,进一步提高了开发效率。
综上所述,CMake是一个功能强大且灵活的跨平台构建工具,它可以帮助开发者更加高效地构建和管理C/C++项目。
二、CMakefile文件
CMake是一个跨平台的自动化构建系统,它使用CMakeLists.txt文件来定义项目的构建规则。CMake的语法相对简单且直观,但功能强大。下面我将对CMake的一些基本语法进行详细说明,并通过例子来展示它们的用法。
2.1 CMake基本语法
- 注释
- 使用
#
字符开始注释,直到该行结束。
- 使用
- 命令
- CMake命令不区分大小写,但参数通常区分大小写。
- 命令由命令名和参数列表组成,参数之间用空格分隔。
- 变量
- CMake中的变量以
${}
包围,例如${CMAKE_CURRENT_SOURCE_DIR}
。 - 使用
set
命令可以定义和修改变量。
- CMake中的变量以
- 列表
- CMake中的列表是由多个字符串组成的,使用分号
;
分隔。 - 列表可以通过
${VAR}
或${VAR[index]}
访问。
- CMake中的列表是由多个字符串组成的,使用分号
2.2 常用命令及示例
CMakeLists.txt不区分函数名字母大小写,一般一个函数都用大写或者都用小写
**#**号用于注释语句
cmake_minimum_required( )指定CMake的最低版本。
PROJECT( ) 定义项目名称。
SET(CMAKE_CXX_FLAGS “-g”)设置C语言标准,此外set(A B)函数还用于赋值将B赋予A。
使用file()函数将所有在src/目录下的.c文件赋给变量SOURCES。
使用include_directories()函数添加头文件目录。
链接库文件路径LINK_LIBRARIES(xxxx.a xxxx.so)
链接库文件LINK_DIRECTORIES(. ./lib/ . ./build/xxx.a . ./build/xxx.so)
最后,使用**add_executable()**函数生成可执行文件,将源文件${SOURCES}执行生成src可执行程序。
生成静态库文件 ADD_LIBRARY(xxx STATIC ${SRC_LIST})生成静态库文件xxx.a
生成动态库 ADD_LIBRARY(xxx SHARED ${SRC_LIST})生成动态库文件xxx.so
-
cmake_minimum_required
- 设置CMake的最低版本要求。
cmake_minimum_required(VERSION 3.10)
-
project
- 指定项目的名称和(可选的)版本号。
project(MyProject VERSION 1.0)
-
add_executable
- 添加一个可执行目标。
add_executable(MyExecutable main.cpp)
-
add_library
- 添加一个库目标(静态库或动态库)。
add_library(MyLibrary SHARED mylib.cpp)
-
target_include_directories
- 为目标添加包含目录。
target_include_directories(MyExecutable PRIVATE include)
target_include_directories(MyLibrary PUBLIC include)
-
target_compile_definitions
- 为目标添加预处理器定义。
target_compile_definitions(MyExecutable PRIVATE MY_DEFINE=1)
-
target_link_libraries
- 链接库到目标。
target_link_libraries(MyExecutable PRIVATE MyLibrary)
-
include_directories
(不推荐在现代CMake中使用)- 为所有后续的目标添加包含目录(这是一个全局设置,不推荐使用,因为会影响所有目标)。
include_directories(include) # 不推荐
-
link_directories
(不推荐在现代CMake中使用)- 为链接器添加库搜索路径(同样是一个全局设置,不推荐使用)。
link_directories(/path/to/libs) # 不推荐
2.3、message()函数
message :为用户显示一条消息
message( [STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR]
"message to display" ...)
# (无) = 重要消息;
# STATUS = 非重要消息;
# WARNING = CMake 警告, 会继续执行;
# AUTHOR_WARNING = CMake 警告 (dev), 会继续执行;
# SEND_ERROR = CMake 错误, 继续执行,但是会跳过生成的步骤;
# FATAL_ERROR = CMake 错误, 终止所有处理过程;
1.输出错误 FATAL_ERROR
message(FATAL_ERROR "
FATAL: In-source builds are not allowed.
You should create a separate directory for build files.
")
2.输出警告 WARNING
message(WARNING "OpenCV requires Android SDK tools revision 14 or newer.")
3.输出正常 STATUS
message(STATUS "Can't detect runtime and/or arch")
4.输出变量的值
在cmake定义了一个变量“USER_KEY”,并打印此变量值。status表示这是一般的打印信息,我们还可以设置为“ERROR”,表示这是一种错误打印信息。
SET(USER_KEY, "Hello World")
MESSAGE( STATUS "this var key = ${USER_KEY}.")
2.4、“ PROJECT_BINARY_DIR ” 和 “ PROJECT_SOURCE_DIR ”
目录结构:
" PROJECT_BINARY_DIR " 是CMake生成一系列文件的目录,包括MakeFile等文件,如果你是让他们生成在build目录中就是 .../helloProject/build
" PROJECT_SOURCE_DIR "是顶级的CMakeLists.txt所在的目录,也就是 .../helloProject 目录
2.5、CMake 同一目录,多个源文件
目录结构:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.5)
# 设置工程名
project (hello_cmake)
# 添加include头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(${PROJECT_SOURCE_DIR}/src DIR_SRCS)
# 指定生成目标
add_executable(hello_cmake ${DIR_SRCS})
2.6、CMake 多个源文件,多个目录
目录结构:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.5)
# 设置工程名
project (hello_cmake)
# 填加文件搜索目录(包含头文件)
aux_source_directory(${PROJECT_SOURCE_DIR} DIR_MAIN_SRCS)
aux_source_directory(${PROJECT_SOURCE_DIR}/hello DIR_HELLO_SRCS)
aux_source_directory(${PROJECT_SOURCE_DIR}/world DIR_WORLD_SRCS)
# 指定生成目标
add_executable(hello_cmake ${DIR_MAIN_SRCS} ${DIR_HELLO_SRCS} ${DIR_WORLD_SRCS})
目录结构:
一共有3个CMakeLists.txt
一个顶级CMakeLists.txt,放在当前工程的根目录下:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.5)
# 设置工程名
project (hello_cmake)
aux_source_directory(. DIR_SRCS)
# 添加头文件路径
include_directories("${PROJECT_SOURCE_DIR}/hello")
include_directories("${PROJECT_SOURCE_DIR}/world")
# 添加 hello 子目录
add_subdirectory(hello)
# 添加 world 子目录
add_subdirectory(world)
# 指定生成目标
add_executable(hello_cmake main.cpp)
# 添加链接库
target_link_libraries(hello_cmake hello world)
hello文件夹中的CMakeLists.txt
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (hello ${DIR_LIB_SRCS})
world文件夹中的CMakeLists.txt
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (world ${DIR_LIB_SRCS})
2.7 NOMINMAX 如何加在CMakeList文件里
在 CMake 中,NOMINMAX
是一个常见的宏定义,用于禁用 Windows 头文件(如 <windows.h>
)中定义的 min
和 max
宏。这些宏可能会导致与标准库中的 std::min
和 std::max
发生冲突。
要在 CMakeLists.txt 文件中添加 NOMINMAX
定义,你可以使用 add_definitions
或 add_compile_definitions
命令。以下是具体的步骤:
使用 add_definitions
add_definitions
命令会为指定的目标添加全局编译器定义。
cmake_minimum_required(VERSION 3.0)
project(MyProject)
# 添加NOMINMAX定义
add_definitions(-DNOMINMAX)
# 添加源文件和目标
add_executable(MyExecutable main.cpp)
使用 add_compile_definitions
add_compile_definitions
命令是 CMake 3.4 及以后版本引入的,可以为目标添加编译定义,而不是全局定义。
使用 add_compile_definitions
add_compile_definitions 命令是 CMake 3.4 及以后版本引入的,可以为目标添加编译定义,而不是全局定义。
选择哪个命令?
-
如果你希望为所有目标添加
NOMINMAX
定义,可以使用add_definitions
。 -
如果你希望仅为特定目标添加
NOMINMAX
定义,推荐使用target_compile_definitions
,因为它提供了更细粒度的控制。
示例
假设你有一个简单的项目结构:
MyProject/
├── CMakeLists.txt
└── main.cpp
你的 CMakeLists.txt
文件可能如下所示:
cmake_minimum_required(VERSION 3.4)
project(MyProject)
# 添加NOMINMAX定义到目标
add_executable(MyExecutable main.cpp)
target_compile_definitions(MyExecutable PRIVATE NOMINMAX)
你的 main.cpp
文件可能如下所示:
#include <iostream>
#include <algorithm> // 包含std::min和std::max
#include <windows.h> // 包含Windows API,可能导致min和max宏冲突
int main() {
int a = 5, b = 10;
std::cout << "Min: " << std::min(a, b) << ", Max: " << std::max(a, b) << std::endl;
return 0;
}
通过这种方式,你可以在 CMake 项目中成功禁用 min
和 max
宏,从而避免与标准库中的 std::min
和 std::max
发生冲突。
2.8 示例:完整CMakeLists.txt文件
下面是一个完整的CMakeLists.txt文件示例,它定义了一个简单的项目,该项目包含一个可执行文件和一个静态库。
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# 添加一个静态库
add_library(MyStaticLib STATIC src/mylib.cpp)
# 为库添加包含目录
target_include_directories(MyStaticLib PUBLIC include)
# 添加一个可执行文件
add_executable(MyExecutable src/main.cpp)
# 链接静态库到可执行文件
target_link_libraries(MyExecutable PRIVATE MyStaticLib)
# 如果需要,还可以添加其他编译选项或链接库
# target_compile_options(MyExecutable PRIVATE -Wall -Wextra)
# target_link_libraries(MyExecutable PRIVATE SomeOtherLib)
在这个例子中,我们定义了一个名为MyProject
的项目,它包含一个名为MyStaticLib
的静态库和一个名为MyExecutable
的可执行文件。静态库MyStaticLib
由src/mylib.cpp
源文件编译而成,并且它的头文件位于include
目录中。可执行文件MyExecutable
由src/main.cpp
源文件编译而成,并且它链接了MyStaticLib
库。
请注意,现代CMake推荐使用target_*
命令来设置目标的属性,而不是全局的include_directories
和link_directories
命令。这是因为全局设置可能会影响所有目标,从而导致构建过程中的不可预测性。
三、CMake使用
3.1、CMake的使用步骤
在Linux下编译命令
mkdir build
cd build
cmake ..
make
windows下建议使用GUI操作
3.2、CMake 在Linux上编译生成动态库和静态库
目录结构:
# 设置CMake最低版本
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 设置目标链接库文件的存放位置
SET(LIBRARY_OUTPUT_PATH "${PROJECT_BINARY_DIR}/lib")
# 添加源文件目录
AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR}/src DIR_SRCS)
# 添加头文件目录
INCLUDE_DIRECTORIES("${PROJECT_SOURCE_DIR}/include")
# 生成动态库
ADD_LIBRARY(hello SHARED ${DIR_SRCS})
# 生成静态库
ADD_LIBRARY(hello_static STATIC ${DIR_SRCS})
3.3、CMake 在Linux上使用动态库和静态库
1、使用动态库
目录结构:
cmake_minimum_required (VERSION 2.6)
project(hello_cmake)
# 头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)
# 生成可执行文件
add_executable(hello_cmake main.cpp)
# 链接库到可执行文件
target_link_libraries(hello_cmake ${PROJECT_SOURCE_DIR}/dynamic/libhello.so)
或者写成
cmake_minimum_required (VERSION 2.6)
project(hello_cmake)
# 头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)
# 添加动态库目录
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/dynamic)
# 生成可执行文件
add_executable(hello_cmake main.cpp)
# 链接库到可执行文件
target_link_libraries(hello_cmake libhello.so)
# 链接库到可执行文件,以下三种写法是相同的:
# target_link_libraries(myProject libhello.so) #这些库名写法都可以。
# target_link_libraries(myProject hello)
# target_link_libraries(myProject -lhello)
2、使用静态库
cmake_minimum_required (VERSION 2.6)
project(hello_cmake)
# 头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)
# 添加静态库目录
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/static)
# 生成可执行文件
add_executable(hello_cmake main.cpp)
# 链接库到可执行文件
target_link_libraries(hello_cmake libhello_static.a)
# 链接库到可执行文件,以下三种写法都是可以的
# target_link_libraries(hello_cmake libhello_static.a)
# target_link_libraries(hello_cmake hello_static.a)
# target_link_libraries(hello_cmake hello_static)
参考: