深入浅出之CMake工具及CMakefile文件

一、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,可以决定工程文件生成二进制文件、动态库、静态库文件,以及链接各种库文件。

  • 构建过程

    1. cmake步骤:CMake首先扫描计算机以查看一些默认设置,这些设置包括库的位置以及在系统上安装软件的位置。然后,它使用CMakeLists.txt文件来配置项目。

    2. make步骤:接下来,使用系统上的make命令(在Linux上是GUN Make,在其他系统上可能是不同的make工具)来编译程序。这个过程通常是将人类可读的源代码转换成机器语言。

    3. make install步骤:最后,在make install一步中,那些编译过的文件将被拷贝到计算机上合适的位置。

1.3、CMake的进阶使用

  • 变量:CMake支持多种类型的变量,包括预定义变量、环境变量、系统信息等。这些变量可以在CMakeLists.txt文件中使用,以控制构建过程。

  • 条件控制和循环语句:CMake还提供了if条件控制和while、foreach等循环语句,以及macro宏定义和function函数等高级功能,使得CMakeLists.txt文件可以更加灵活和强大。

  • 查找库和包含目录:在构建过程中,CMake可以查找指定的库文件和包含目录,并设置链接库搜索目录和target需要链接的库。这使得构建过程更加自动化和便捷。

1.4 CMake不同版本的区别

  1. CMake 2.x与现代CMake

    • CMake 2.x版本主要依赖过程式描述,变量(variable)的定义和使用较为普遍。
    • 现代CMake(3.x及以后版本)则围绕Target和Property来定义,并竭力避免出现变量的定义。它更像是在遵循面向对象编程(OOP)的规则,通过target来约束link、compile等相关属性的作用域。
  2. Target与Property

    • 在现代CMake中,一个Target可以看作是一个对象,具有构造函数(如add_executable、add_library)和成员函数(如get_target_property、set_target_properties等)。
    • Target中有两个重要概念:Build-Requirements和Usage-Requirements。它们分别包含了构建Target和使用Target所必须的材料。
  3. 属性传递范围

    • 在现代CMake中,可以使用PUBLIC、PRIVATE、INTERFACE等关键字来定义Target属性的传递范围。
      • PRIVATE:表示Target的属性只定义在当前Target中,不共享给依赖它的其他Target。
      • INTERFACE:表示Target的属性不适用于其自身,而只适用于依赖它的Target。
      • PUBLIC:表示Target的属性既是Build-Requirements也是Usage-Requirements,会共享给依赖它的其他Target。
  4. 版本兼容性

    • CMake的不同版本之间存在差异,某些指令在不同版本的支持程度可能不同。
    • 使用cmake_minimum_required()命令可以限制CMake的最低版本需求,确保CMakeLists.txt文件与CMake版本的兼容性。
    • 如果CMake运行的版本不在指定的最低和最高版本之间,会报错。
  5. 策略设置

    • 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基本语法

  1. 注释
    • 使用#字符开始注释,直到该行结束。
  2. 命令
    • CMake命令不区分大小写,但参数通常区分大小写。
    • 命令由命令名和参数列表组成,参数之间用空格分隔。
  3. 变量
    • CMake中的变量以${}包围,例如${CMAKE_CURRENT_SOURCE_DIR}
    • 使用set命令可以定义和修改变量。
  4. 列表
    • CMake中的列表是由多个字符串组成的,使用分号;分隔。
    • 列表可以通过${VAR}${VAR[index]}访问。

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
  1. cmake_minimum_required

    • 设置CMake的最低版本要求。
    cmake_minimum_required(VERSION 3.10)
  2. project

    • 指定项目的名称和(可选的)版本号。
    project(MyProject VERSION 1.0)
  3. add_executable

    • 添加一个可执行目标。
    add_executable(MyExecutable main.cpp)
  4. add_library

    • 添加一个库目标(静态库或动态库)。
    add_library(MyLibrary SHARED mylib.cpp)
  5. target_include_directories

    • 为目标添加包含目录。
    target_include_directories(MyExecutable PRIVATE include)
    target_include_directories(MyLibrary PUBLIC include)
  6. target_compile_definitions

    • 为目标添加预处理器定义。
    target_compile_definitions(MyExecutable PRIVATE MY_DEFINE=1)
  7. target_link_libraries

    • 链接库到目标。
    target_link_libraries(MyExecutable PRIVATE MyLibrary)
  8. include_directories(不推荐在现代CMake中使用)

    • 为所有后续的目标添加包含目录(这是一个全局设置,不推荐使用,因为会影响所有目标)。
    include_directories(include) # 不推荐
  9. 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的可执行文件。静态库MyStaticLibsrc/mylib.cpp源文件编译而成,并且它的头文件位于include目录中。可执行文件MyExecutablesrc/main.cpp源文件编译而成,并且它链接了MyStaticLib库。

请注意,现代CMake推荐使用target_*命令来设置目标的属性,而不是全局的include_directorieslink_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)

参考:

  1. Cmake入门(一文读懂)
  2. cmake的基本用法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值