CMakeLists.txt 怎么写

写程序的大体步骤就是:首先用编辑器编写源代码,如.c文件;然后经过预处理、编译和汇编生成可重定位目标文件,也就是.o(Unix下)文件;最后通过链接器将所有的.o以及用到的库文件链接成可执行文件。
但是当源文件越来越多时,一个个地编译就会特别麻烦,于是人们设计了一种批量处理源文件的工具,也就是Make,一种自动化编译工具,可以通过一条命令实现完全编译。为了实现以上功能,需要编写一个规则文件,也就是Makefile。

而对于一个大型程序,编写Makefile也是一件复杂的事情,于是人们设计了一个工具,自动生成Makefile,也就是CMake工具。另外,CMake不仅可以生成Unix环境下的GNU Make,也可以生成QT的qmake,微软的MS nmake,BSD Make(pmake),Makepp,等等。也就是说,CMake是一种跨平台的Make maker,它根据平台无关的CMakeLists.txt文件来指定整个编译过程,再根据平台生成所需的Makefile或project文件,本文就简单介绍一下Linux下CMakeLists.txt文件的编写。

1 一个简单的例子

假设我们只有一个源文件,如下所示:

#include <stdio.h>int main()
{printf("Hello World!\n");return 0;
}
~                                                                               "main.c"

我们在源文件所在的目录下写一个CMakeLists.txt文件,如下所示,其中:

  • cmake_minimum_required(VERSION 3.3):指定此文件运行的所需的cmake最低版本是3.3;
  • project(test):表示该项目的名字为test;
  • add_executable(test main.c):表示将源文件main.c编译生成test;
# Check the version of cmake
cmake_minimum_required(VERSION 3.3)# Project name
project(test)# Generate project
add_executable(test main.c)

这个时候,我们执行以下指令(新建一个build目录用于生成一系列产物很有必要,因为cmake的执行过程中会生成很多的中间产物和文件,这些文件并不属于工程,放在build目录下便于管理):

$ mkdir build && cd build && cmake ../
-- The C compiler identification is GNU 4.4.7
-- The CXX compiler identification is GNU 4.4.7
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/……/build

这个时候我们发现build目录下生成了多个文件,其中就有Makefile,这个时候我们执行以下指令,可以看到输出了打印结果。可以看到,通过在CMakeLists.txt中简单的几行代码,就可以构建代码工程。

$ make && ./test
Scanning dependencies of target test
[ 50%] Building C object CMakeFiles/test.dir/main.c.o
[100%] Linking C executable test
[100%] Built target test
Hello World!

值得注意的是,在cmake中,所有的内置命令是不区分大小写的;但是cmake的变量是区分大小写的!

2 多个源文件

2.1 指定源文件

当同一目录下有多个源文件时,我们可以有多种方式,譬如均写在add_executable后:

add_executable(test main.c a.c b.c)

但是当源文件太多时,这样写明显不方便,这时有多种方式解决。

2.1.1 aux_source_directory命令

aux_source_directory命令形式如下,表示收集指定目录下(不包括子目录)所有源文件的名称,并将链表存储在中,我测试的时候发现,后缀为.cpp、.c和.cc的源文件都会被收录进来。

aux_source_directory(<dir> <variable>)

这个时候,我们只需要将CMakeLists.txt修改成以下即可:

# Check the version of cmake
cmake_minimum_required(VERSION 3.3)# Project name
project(test)# Collect all source file in current directory
aux_source_directory(. SRC)# Generate project
add_executable(test ${SRC})

“.”表示当前目录,即CMakeLists.txt所在的目录,也可以用CMAKE_SOURCE_DIR(cmake定义了很多的常用变量,大家可以去cmake的文档中查找,很好用);和shell脚本、Makefile规则一样,“$”符号用作取变量值。

2.1.2 file命令

file命令用于文件操作,功能强大,大家可以通过查看cmake官方手册查看。在这里,我们用到file命令查找的功能,即:

file(GLOB <variable>[LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS][<globbing-expressions>...])
file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS][LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS][<globbing-expressions>...])

file命令以上的用法是生成match正则表达式的文件链表,并将其储存在变量中。LIST_DIRECTORIES、RELATIVE和CONFIGURE_DEPENDS都是标志,这里不表。我们可以使用以下的命令生成在该文件夹下后缀为.c的源文件list(如果还想要子文件夹下的源文件,将关键字替换为GLOB_RECURSE),并存入SRC变量中。

# Check the version of cmake
cmake_minimum_required(VERSION 3.3)# Project name
project(test)# Collect all source file in current directory
file(GLOB SRC *.c)# Generate project
add_executable(test ${SRC})

值得注意的是,在file的文档中,有以下说明:

Note We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate.

大意就是官方不建议使用file的GLOB指令来收集工程的源文件,因为当收集的源文件增加或删除,且CMakeLists.txt没有发生修改时,CMake不能识别这些文件。其实,在aux_source_directory命令的说明中,也有如下同样的一段,表达相同的意思,如下。所以我们使用这两者进行文件收集时,应当注意在文件发生增删时,需重新使用cmake命令。

aux_source_directory(<dir> <variable>)
...
It is tempting to use this command to avoid writing the list of source files for a library or executable target. While this seems to work, there is no way for CMake to generate a build system that knows when a new source file has been added. Normally the generated build system knows when it needs to rerun CMake because the CMakeLists.txt file is modified to add a new source. When the source is just added to the directory without modifying this file, one would have to manually rerun CMake to generate a build system incorporating the new file.

2.1.3 set命令

set命令是cmake最常用的命令之一,用于将变量设置为给定值,常用的原型如下:

set(<variable> <value>... [PARENT_SCOPE])

即用于将值赋给当前,作用域默认在当前函数或目录,但是当PARENT_SCOPE标志设置后,作用域将扩大到上一级目录或范围。我们可以改写CMakeLists.txt如下:

# Check the version of cmake
cmake_minimum_required(VERSION 3.3)# Project name
project(test)# Collect all source file in current directory
set(SRC ./main.c ./a.c ./b.c)# Generate project
add_executable(test ${SRC})

以上三种方式其实大同小异,都是利用变量存储源文件,最后利用add_executable函数生成可执行文件。

2.2 多目录下引用、链接路径

大多数时候工程源代码都会分布在不同的目录下,这个时候的源文件的查找下使用2.1中方法进行查找,但是切不可在最高目录(一般CMakeLists.txt在此)使用GLOB_RECURSE关键字的file命令,这会将build目录下由CMake生成的源文件也包含进去,其中在build/CMakeFiles/3.18.0-rc3/CompilerIdC/CMakeCCompilerId.c文件中,还包含main函数,会造成multiple definition of `main’的错误。
在多目录的工程下,需要指定头文件和库文件的引用目录,分别类似于Makefile文件中“-I”和“-L”的用法。

2.2.1 include_directories命令

在CMake中,使用include_directories命令来指定头文件的路径,类似于Makefile中使用-I指定路径。命令将给定目录添加到编译器用于搜索包含文件的目录中,相对路径被解释为相对于当前源目录。

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

这里需要注意,CMake中的include命令类似于C语言的include命令,用于载入其它模块的CMake代码,和include_directories的区别需厘清。

2.2.2 link_directories命令

和include_directories一样,link_directories命令用于指定链接库文件的搜索路径,类似于Makefile中使用-L,用于添加链接器应在其中搜索库的路径。赋予此命令的相对路径被解释为相对于当前源目录。

3 库文件的链接

3.1 link_libraries命令

如果说link_directories类似于gcc中的“-L”,那么link_libraries就像gcc中的“-L… -l…”,不仅指定了链接文件的路径,还指定了链接文件名。命令应该在add_executable命令前执行。

link_libraries([item1 [item2 [...]]][[debug|optimized|general] <item>] ...)

3.2 target_link_libraries命令

其形式如下:

target_link_libraries(<target> ... <item>... ...)

相比于link_libraries,CMake更加推荐使用target_link_libraries,如下,大意就是target_link_libraries不需要指定路径,除非是自己定义的私有库。

Note The target_link_libraries() command should be preferred whenever possible. Library dependencies are chained automatically, so directory-wide specification of link libraries is rarely needed.

如果我们需要链接某个库文件,可能有以下两种方法,如果是标准库,使用target_link_libraries就显得更方便了。

cmake_minimum_required(VERSION 3.3)project(test)link_libraries("/libpath/libhello.so")add_executable(test main.c)
cmake_minimum_required(VERSION 3.3)project(test)link_directories("/libpath/")add_executable(test main.c)target_link_libraries(test libhello.so)
# or target_link_libraries(test hello)

4 添加子目录的方法

在大型工程中,如果按照以上方法一一添加源文件会显得很麻烦,我们也可以将各个子文件夹中的内容编译成子模块,并利用add_subdirectory命令来添加该子模块。

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

而在子模块中,我们将源文件编译为静态库,这时无需指定链接路径。如下例子,在主文件夹下(即最高目录)的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.3)project(test)include_directories(./source_Dir1./source_Dir2
)add_subdirectory(source_Dir1)
add_subdirectory(source_Dir2)add_executable(test main.c)target_link_libraries(test aaa bbb m)

在source_Dir1和source_Dir2的子文件夹下,其CMakeLists.txt分别如下:

cmake_minimum_required(VERSION 3.3)aux_source_directory(. DIR_LIB_SRCS)add_library(aaa STATIC ${DIR_LIB_SRCS})
cmake_minimum_required(VERSION 3.3)aux_source_directory(. DIR_LIB_SRCS)add_library(bbb STATIC ${DIR_LIB_SRCS})

基本按照以上的形式构建一个工程就差不多搞定了。编译动态库时将以上STATIC关键字改成SHARED即可。
值得注意的是,对于有些需要库文件(譬如libaaa.a)需要完全编译到最后的可执行代码中的情况,需要在target_link_libraries命令下做如下处理(一行写也行,我是为了看起来直观),-Wl,–whole-archive和-Wl,–no-whole-archive是gcc的链接选项,可以在CMake中直接使用。

target_link_libraries(test-Wl,--whole-archiveaaa-Wl,--no-whole-archivebbbm)

5 小结

CMakeLists.txt的简单写法就介绍到这里,还有关其很多的关键字和内置变量的用法这里就不一一介绍了,百度的资料良莠不齐,因此去官网学习和搜索是一种很好的学习习惯。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/595727.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

K8S Dashboard登录Token过期问题处理

整体思路 用户访问一个页面&#xff0c;在该页面中设置一个超链接&#xff0c;点击跳转至K8S Dashboard&#xff1b;跳转后&#xff0c;使用剪贴板上已复制的Token粘贴到Dashboard页面中的输入框登录即可。 写个定时任务将Token复制到页面上&#xff0c;过期了重新再登…

Linux第1步_VMware软件安装

1、双击“VMware-workstation-full-15.5.0-14665864”&#xff0c;得到下面的界面&#xff1a; 2、等待几分钟&#xff0c;得到下面的界面&#xff1a; 3、点击“下一步” 4、勾选“我接受许可协议中的条款(A)”&#xff0c;见下图&#xff1a; 5、点击“下一步”&#xff0c;得…

阿里云Alibaba Cloud Linux 3镜像版本大全特性说明

Alibaba Cloud Linux阿里云打造的Linux服务器操作系统发行版&#xff0c;Alibaba Cloud Linux完全兼容完全兼容CentOS/RHEL生态和操作方式&#xff0c;目前已经推出Alibaba Cloud Linux 3&#xff0c;阿里云百科aliyunbaike.com分享Alibaba Cloud Linux 3版本特性说明&#xff…

使用通用MCU实现无人机飞行任务的快速二次开发

使用通用MCU实现无人机飞行任务的快速二次开发 ---TIDronePilot外部控制offboard模式介绍 无名小哥 2024年1月1日 传统飞控二次开发方法和主要存在的问题简介 通过对前面几讲中《零基础竞赛无人机积木式编程指南》系列开发教程的学习可知&#xff0c;在以往TI电赛真题的学习…

K8S学习指南(59)-K8S核心组件ETCD简介

文章目录 前言一、设计思路1.1 一致性与可靠性1.2 分布式键值存储 二、在K8S集群中的主要功能2.1 配置存储2.2 选主与高可用2.3 服务发现 三、内部工作原理3.1 Raft一致性算法3.2 快照和日志3.3 分布式事务 四、与其他NoSQL产品的比较4.1 优势4.1.1 一致性4.1.2 性能 4.2 劣势4…

RK3568驱动指南|第九篇 设备模型-第101章 总线注册流程理论分析实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

力扣hot100 二叉树展开为链表 递归 特殊遍历

&#x1f468;‍&#x1f3eb; 题目地址 &#x1f469;‍&#x1f3eb; 参考题解 &#x1f60b; 将左子树插入到右子树上 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* …

C/C++动态内存分配 malloc、new、vector(简单讲述)

路虽远&#xff0c;行则将至 事虽难&#xff0c;做则必成 今天来主要讲C中动态内存分配 其中会穿插一些C的内容以及两者的比较 如果对C语言中的动态内存分配还不够理解的同学 可以看看我之前的博客:C语言动态分配 在讲解C的动态内存分配之前 我们先讲一下C内存模型 &#xff1…

CSS免费在线字体格式转换器 CSS @font-face 生成器

今天竟意外发现的一款免费的“网页字体生成器”&#xff0c;功能强大又好用~ 工具地址&#xff1a;https://transfonter.org/ 根据你设置生成后的文件预览&#xff1a; 支持TTF、OTF、WOFF、WOFF2 或 SVG字体格式转换生成&#xff0c;每个文件最大15MB。转换完成以后还会生成一…

宏集PC Runtime软件助推食品行业生产线数字化革新

一、前言 近年来&#xff0c;中国食品行业发展迅速且灵活多变&#xff0c;在当前经济下行的情形下&#xff0c;食品行业正面临着日益激烈的竞争&#xff0c;导致企业利润下降。 为了保持企业市场竞争力&#xff0c;国内某top10食品企业采用宏集SCADA解决方案—PC Runtime软件…

数学公式编译器MathType下载与安装

下载网址&#xff1a;下载 MathType - WIRIS Store 1.点击【下载MathType for Windows】 2、点击中文版 3.找到所下载的目录&#xff1a; 右击-->以管理员身份运行 4、新建word文档 点击文件->账户->关于word 5.点击【文件】、【选项】&#xff0c;❶点击【加载项】…

有趣的数学 为什么素数在密码学中很重要?

这里我们将探讨为什么素数在密码学中很重要。我们将根据特定的密码系统&#xff08; RSA 算法&#xff09;来进行深入了解。 一、素数的特殊性 每个数字都可以分解为它的素数。一般来说&#xff0c;找到一个数的因数是非常困难的。要找到一个自然数的所有素因数&#xff0c;必…

基于51单片机的激光竖琴设计

一、摘要 激光竖琴是一种将激光技术与音乐相结合的新型乐器&#xff0c;具有独特的音色和视觉效果。本文设计了一种基于51单片机的激光竖琴&#xff0c;通过对激光发射器的控制&#xff0c;实现对激光束的调制&#xff0c;从而产生不同的音高。系统主要包括51单片机控制模块、…

Zookeeper 分布式服务协调治理框架介绍入门

文章目录 为甚么需要Zookeeper一、Zookeeper 介绍1.1 介绍1.2 Zookeeper中的一些概念1.2.1 集群角色1.2.2 会话 session1.2.3 数据节点 Znode1.2.4 版本1.2.5 事件监听器 Watcher1.2.6 ACL 权限控制表(Access Control Lists) 二、 Zookeeper的系统模型2.1.1 ZNode节点2.1.2 ZNo…

C#的Char 结构的方法之IsLetterOrDigit()

目录 一、Char 结构 二、Char.IsLetterOrDigit 方法 1.定义 2.重载 3.示例 4.IsLetterOrDigit(Char) 5.IsLetterOrDigit(String, Int32) 一、Char 结构方法 CompareTo(Char)将此实例与指定的 Char 对象进行比较&#xff0c;并指示此实例在排序顺序中是位于指定的 Char …

基于ssm的班级事务管理系统+vue论文

摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对班级事务信息管理的提升&#x…

【LeetCode:114. 二叉树展开为链表 | 二叉树 + 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

STM32 基础知识(探索者开发板)--135讲 ADC转换

ADC定义&#xff1a; ADC即模拟数字转换器&#xff0c;英文详称 Analog-to-digital converter&#xff0c;可以将外部的模拟信号转换 ADC数模转换中一些常用函数&#xff1a; 1. HAL_ADC_Init 函数 HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc); 初始化ADC 形参&…

洛谷 P1293 班级聚会

题目链接 分析 枚举每一个城市&#xff0c;并计算以他做聚会地点的价钱&#xff0c;取最小&#xff0c;如果相同则取最靠后的&#xff0c;时间复杂度为 Θ ( n 2 ) \Theta(n^2) Θ(n2)。 其实可以发现将城市 i i i 换到城市 i 1 i1 i1&#xff0c;那么 i i i 之前的包括…

数据库中的存储过程和函数

1、定义 存储过程和函数是事先经过编译并存储在数据库中的一段 SQL 语句的集合。 存储过程和函数是在数据库中预先定义并编译好的可复用代码块&#xff0c;可以用于完成特定的任务&#xff0c;如计 算、查询和变换等。 2、好处 提高代码的复用性。 减少数据在数据库和应用…