当前位置: 首页 > news >正文

CMake基础介绍

1、CMake 概述

CMake 是一个项目构建工具,并且是跨平台的;关于项目构建目前比较流行的还有 Makefile(通过 Make 命令进行项目的构建),

大多 IDE 软件都集成了 make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake 等,若自己动手写 makefile,会发现

makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时容易出错

而 CMake 恰好能解决上述问题,其允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的 Makefile

和工程文件,最后用户只需 make 编译即可,所以可以把 CMake 看成一款自动生成 Makefile 的工具,其编译流程如下:

画板

1、蓝色虚线代表 makefile 构建项目过程

2、红色实线表示使用 cmake 构建项目的过程

CMake 有如下优点:

跨平台

能够管理大型项目

简化编译构建过程和编程过程

可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能

2、CMake 的使用

2.1、CMake 工具安装

1、 下载 cmake,官网地址Download CMake。根据系统环境选择相对应的安装包。

双击安装到对应路径,将 bin 的路径添加到环境变量中。

安装完成后,在 cmd 中输入 cmake --version,若显示版本信息,则安装成功;

2、下载 MinGW,下载地址MinGW。

下载完安装包后,双击打开,需安装 C/C++编译器及 mingw32-make。

安装完成后,需将安装目录 MinGW 下 bin 文件下的 mingw32-make.exe 改名成 make.exe。并将该路径添加到系统环境变量中

在 cmd 窗口中输入 gcc -v 及 make -v;若出现相对应的版本号,则代表安装成功;

2.2、CMake 的使用

1、注释

CMake 使用#进行 行注释,可放在任何位置,#[[]] 形式为块注释。

2、单目录工程测试

工程目录下包含文件如下:

1、 源文件编写:

#include <stdio.h>
#include "head.h"int main()
{int a = 5;int b = 10;printf("a = %d, b = %d\n", a, b);printf("a+b = %d\n", add(a, b));return 0;
}
#include <stdio.h>
#include "head.h"int add (int a, int b)
{return a+b;
}
#pragma onceextern int add (int a, int b);

2、 编写 CMakeLists.txt 文件

#CMake最低版本号要求
cmake_minimum_required(VERSION 3.1)
#项目信息
project(demo)
#指定生成目标
add_executable(app main.c;add.c)

CMakeLists.txt 由命令、注释、空格组成,其中命令是不区分大小写。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔;

本例中 CMakeLists.txt 文件的命令详细解释如下:

cmake_minimum_required:指定使用的 cmake 的最低版本(该命令可选,非必须,若不加可能会有警告)

project:定义工程名称,并可指定工程的版本、工程描述、web 主页地址、支持的语言,若这些不需要可忽略,只需指定工程名字即可;

add_executable:定义工程生成一个可执行程序;源文件可以是一个或多个,若有多个可用空格或;间隔。

# 样式1
add_executable(app main.c;add.c)
# 样式2
add_executable(app main.c add.c)

3、 执行 CMake 命令

新建一个 build 文件夹,然后进入 build文件夹下使用 cmake 命令,如下图:

-G 后面指定具体的生成器类型,MinGW 表示 windows,Unix 表示 unix 或 linux 环境下的生成器

DCMAKE_C_COMPILER表示C代码的编译工具,DCMAKE_CXX_COMPILER表示C++代码的编译工具;

“…”表示到上一级目录寻找"CMakeLists.txt",等同于“…/”。

有关生成器详细参见手册:

https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#manual:cmake-generators(7)

build 文件夹下会生成相应的 Makefile 文件,如下图:

4、执行 make 命令

在 build 文件下执行 make 会生成相应的 app.exe 可执行程序;如下图:

3、多目录多文件工程测试

基本所有项目的源文件中都包含多个目录文件,以实现功能代码分层;

如下图工程目录:

src 文件下包含相应函数的.c 文件,inc 文件夹下包含头文件;

1、CMAKE 定义变量

在 cmake 里定义变量需要使用 set

VAR:变量名 VALUE:变量值

2、指定输出的路径

在 cmake 中使用宏 EXECUTABLE_OUTPUT_PATH 指定可执行程序的输出路径:

若该路径中的子目录不存在,会自动生成,无需自己手动创建;

若可执行程序生成路径使用的是相对路径./xxx/xx,那这个路径中的./对应的是 makefile 文件所在的目录

3、搜索文件

若在项目里边的源文件很多,在编写 CMakeLists.txt 文件的时候可以使用 aux_source_directory 命令或者 file

命令搜索文件

aux_source_directory 命令可以查找某个路劲下的所有源文件,命令格式如下:

dir:要搜索的目录

variable:将从 dir 目录下搜索到的源文件列表存储到该变量中

file 命令搜索所有源文件,命令格式如下:

GLOB:将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中;如下例子

在 cmake 中设置要包含的目录可以使用 include_directories 命令:

完整的 CMakeLIsts.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)#添加一个变量指向main.c
set(MAIN main.c)#指定可执行程序输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/build/bin)#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/inc)#包含源文件
#aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LIST)
file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/*.c)add_executable(app ${SRC_LIST} ${MAIN})

工程中的 build.bat 脚本内容如下:

:: 删除build文件夹,“/S”表示递归删除,“/Q” 表示静默,可通过在cmd下执行“help rmdir”查看参数具体功能
rmdir /S /Q build
mkdir build
cd build:: -G后面指定具体的生成器类型,MinGW表示windows,Unix表示unix或linux环境下的生成器。
:: 有关生成器详细参见手册:  https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#manual:cmake-generators(7)
:: DCMAKE_C_COMPILER表示C代码的编译工具,DCMAKE_CXX_COMPILER表示C++代码的编译工具;
:: “..”表示到上一级目录寻找"CMakeLists.txt",等同于“../”。
cmake -G "MinGW Makefiles" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..:: 在cmake生成了makefile文件,并指定了C/C++源码编译工具后,使用make命令编译makefile定义的工程。
makepause

运行 build.bat 脚本完成程序编译结果如下:

4、制作动态库或静态库

在 cmake 中,若要制作静态库,需要使用的命令如下:

静态库名字分为三部分:lib + 库名字 + .a;如下一个例子目录,需要将 src 目录中的源文件编译成静态库:

根据上面的目录结构,CMakeLists.txt 具体内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)
#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/inc)
#指定静态库输出路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
#包含源文件
file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/*.c)
#生成静态库
add_library(math STATIC ${SRC_LIST})

最终生成如下对应的静态库文件

在 cmake 中制作动态库,需要使用的命令如下:

以上述目录为例,只要修改下 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.0)
project(test)
#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/inc)
#指定动态库输出路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
#包含源文件
file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/*.c)
#生成静态库
add_library(math SHARED ${SRC_LIST})
5、包含库文件

1、链接静态库

在 cmake 中,链接静态库的命令如下:

link_libraries(<static lib> [<static lib>...])
参数1:指定出要链接的静态库的名字(全名libxxx.a/可以是去头lib去尾.a之后的名字xxx
参数2:要链接的其它静态库的名字

2、链接动态库

target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <item>... [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
target:指定要加载的库的文件的名字(该文件可能是源文件/动态库/静态库/可执行文件
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。target_link_libraries(A B C)target_link_libraries(D A)
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。

动态库的链接和静态库是完全不同的:

静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。

动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数

的时候,动态库才会被加载到内存

因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后;

2.3、CMake 基本命令

1、日志命令

在 CMake 中可以用用户显示一条消息,该命令的名字为 message:

(无):重要消息

STATUS:非常重要消息

WARNING:CMake 警告,会继续执行

AUTHOR_WARNING:CMake 警告(dev),会继续执行

SEND_ERROR:CMake 错误,继续执行,但是会跳过生成的步骤

FATAL_ERROR:CMake 错误,终止所有处理过程

CMake 的命令工具会在 stdout 上显示 STATUS 消息,在 stderr 上显示其他所有消息,CMake 的 GUI 会在它的 log

区域显示所有消息。如下所示:

#CMake最低版本号要求
cmake_minimum_required (VERSION 2.8)
#项目信息
project (demo)message(STATUS "source path: ${PROJECT_SOURCE_DIR}")message(WARNING "source path: ${PROJECT_SOURCE_DIR}")message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

2、变量操作

cmake 语法中有两种方法可以用来追加变量,set/list。

使用 set 拼接,对应的命令格式如下:

上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中

若第一个变量参数中原本有数据会对原数据进行覆盖。示例如下:

cmake_minimum_required(VERSION 3.0)
project(test)
set (TEMP "hello,cmake")
file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/*.c)
#追加(拼接)
set(SRC_LIST ${SRC_LIST} ${TEMP})
message(STATUS "message: ${SRC_LIST}")

使用 list 进行字符串拼接,对应的命令格式如下:

对于 list 命令的功能比 set 要强大,字符串拼接只是它子功能之一。

APPEND 表示数据追加,后边的参数和 set 就一样;示例如下:

cmake_minimum_required(VERSION 3.0)
project(test)
set (TEMP "hello,cmake")
file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/*.c)
#追加(拼接)
list(APPEND SRC_LIST ${SRC_LIST} ${TEMP})
message(STATUS "message: ${SRC_LIST}")

list 命令的字符串移除,对应的命令格式如下:

示例如下:

cmake_minimum_required(VERSION 3.0)
project(test)
set (TEMP "hello,cmake")
file(GLOB SRC_LIST ${PROJECT_SOURCE_DIR}/src/*.c)
#移除前日志
message(STATUS "message: ${SRC_LIST}")
#移除 sub.c
list(REMOVE_ITEM SRC_LIST ${PROJECT_SOURCE_DIR}/src/sub.c)
#移除后日志
message(STATUS "message: ${SRC_LIST}")

关于 list 命令还有其它功能,如下:

1、获取list长度
list(LENGTH <list> <output variable>)
LENGTH:子命令LENGTH用于读取列表长度 
<list>当前操作列表 
<output variable>新创变量,用于存储列表长度2、读取列表中指定索引的元素,可以指定多个索引
list(GET <list> <element index> [<element index> ...] <output variable>)
<list>当前操作列表
<element index>:列表元素的索引
从0开始编号,索引0的元素为列表中的第一个元素;
索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推
当索引(不管是正还是负)超过列表的长度,运行会报错
<output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表3、将列表中的元素用连接符(字符串)连接起来组成一个字符串
list (JOIN <list> <glue> <output variable>)
<list>:当前操作的列表
<glue>:指定的连接符(字符串)
<output variable>:新创建的变量,存储返回的字符串4、查找列表是否存在指定的元素,若未找到,返回-1
list(FIND <list> <value> <output variable>)
<list>:当前操作的列表
<value>:需要再列表中搜索的元素
<output variable>:新创建的变量
如果列表<list>中存在<value>,那么返回<value>在列表中的索引
如果未找到则返回-1。5、将元素追加到列表中
list (APPEND <list> [<element> ...])6、在list中指定的位置插入若干元素
list(INSERT <list> <element_index> <element> [<element> ...])7、将元素插入到列表的0索引位置
list (PREPEND <list> [<element> ...])8、将列表中最后元素移除
list (POP_BACK <list> [<out-var>...])9、将列表中第一个元素移除
list (POP_FRONT <list> [<out-var>...])10、将指定的元素从列表中移除
list (REMOVE_ITEM <list> <value> [<value> ...])11、将指定索引的元素从列表中移除
list (REMOVE_AT <list> <index> [<index> ...])12、移除列表中重复元素
list (REMOVE_DUPLICATES <list>)13、列表翻转
list(REVERSE <list>)14、列表排序
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
COMPARE:指定排序方法。有如下几种值可选:STRING:按照字母顺序进行排序,为默认的排序方法FILE_BASENAME:如果是一系列路径名,会使用basename进行排序NATURAL:使用自然数顺序排序CASE:指明是否大小写敏感。有如下几种值可选:SENSITIVE: 按照大小写敏感的方式进行排序,为默认值INSENSITIVE:按照大小写不敏感方式进行排序ORDER:指明排序的顺序。有如下几种值可选:ASCENDING:按照升序排列,为默认值DESCENDING:按照降序排列
3、宏定义

CMake 中常用的宏如下:

PROJECT_SOURCE_DIR 使用cmake命令后紧跟的目录,一般是工程的根目录

PROJECT_BINARY_DIR 执行cmake命令的目录

CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径

CMAKE_CURRENT_BINARY_DIR target 编译目录

EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置

LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置

PROJECT_NAME 返回通过PROJECT指令定义的项目名称

CMAKE_BINARY_DIR 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

在 CMake 中可以使用命令 add_definitions 添加代码中的宏定义,如下一段代码:

#include <stdio.h>#define NUMBER          3   int main()
{int a = 10;
#ifdef DEBUGprintf("这是一个测试打印...\n");
#endiffor(int i = 0; i < NUMBER; ++i){printf("hello, GCC!\n");}return 0;
}

对于上面的源文件编写的 CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)
#添加自定义宏
add_definitions(-DDEBUG)
add_executable(app ./main.c)

通过上述这种方式,上述代码中的第八行日志就能够被输出出来;

http://www.xdnf.cn/news/287479.html

相关文章:

  • D. Pythagorean Triples 题解
  • 手机打电话时由对方DTMF响应切换多级IVR语音应答(一)
  • \documentclass[lettersize,journal]{IEEEtran}什么意思
  • 机器人强化学习入门学习笔记(二)
  • DeepSeek-Prover-V2:数学定理证明领域的新突破
  • Dify网页版 + vllm + Qwen
  • Matlab自学笔记五十三:保存save和载入load
  • 杨校老师竞赛课之C++备战蓝桥杯初级组省赛
  • Python爬虫实战:获取优美图库各类高清图片,为用户提供设计素材
  • 洛谷 P9007 [入门赛 #9] 最澄澈的空与海 (Hard Version)
  • 【从零开始学习微服务 | 第一篇】单体项目到微服务拆分实践
  • 本地MySQL连接hive
  • ASP.NET Core 请求限速的ActionFilter
  • 算法中的数学:质数(素数)
  • 30天通过软考高项-第十一天
  • CodeBlocks25配置wxWidgets3.2
  • 004-nlohmann/json 快速认识-C++开源库108杰
  • 地埋式燃气泄漏检测装置与地下井室可燃气体检测装置有什么区别
  • 专业课复习笔记 4
  • Vue中的过滤器参数:灵活处理文本格式化
  • 5月5日日记
  • 基于 HTML5 Canvas 实现图片旋转与下载功能
  • linux tar命令详解。压缩格式对比
  • Java IO流核心处理方式详解
  • 论高并发下的高可用
  • LeetCode 热题 100 46. 全排列
  • 【PostgreSQL数据分析实战:从数据清洗到可视化全流程】5.1 描述性统计分析(均值/方差/分位数计算)
  • 代码随想录算法训练营Day45
  • 一个电商场景串联23种设计模式:创建型、结构型和行为型
  • Cordova开发自定义插件的方法