Cmake从入门到精通

背景:本文是笔者第一次使用cmake来生成工程文件,从开始学习到完成一个中小型软件的所有CMakeLists.txt的编写,大约耗时2~3周的时间,所以该文档既可以解决大部分入门Cmake的读者可能面临的一些疑惑,也可供一些有Cmake基础的读者参考;当然由于笔者也是第一次接触Cmake,并且以下的所有总结都来自于个人的领悟(即没有经过正统的学习和交流),所以有些理解可能会有误差或者更好的解决方案;不过,这并不会影响该文档做为“Cmake从入门到精通”的指导手册。

Cmake第一行怎么写?

        做为第一次接触Cmake的同学而言,动手写下第一行Cmake语句,非常重要!多的不说,直接上代码(第一个CMakeLists.txt文件的开端):

#set Cmake Mini Version
CMAKE_MINIMUM_REQUIRED(VERSION 3.6)
#set PROJECT_NAME and version
PROJECT(***** VERSION 0.1 LANGUAGES CXX)

        第一句话,设置Cmake最低要求的版本,为何设置这个呢?因为随着Cmake版本的升级,会加入一些新的特性,如果使用的版本过低,可能就不支持某些特性,这个其实和C++语言的版本是同一个效果,如果你的CMakeLists.txt里面使用的指令比较简单,都是一些最基础的操作,这一行倒也不重要,等你需要使用一些较高版本才能使用的特性时,你会想到有这一行代码可以对最低版本进行约束就好~

        第二句话,*****的内容就是你的工程名,也就是使用VS打开工程时,所看到的名称;后面的VERSION 0.1指代版本号,做为新手,这个并不重要;LANGUAGES CXX指代的是编程语言为C++;

备注:在CMakeLists.txt中,使用#进行注释,等价于C++里面的//;另外,Cmake语句是不区分大小写的,即PROJECT也可以使用project,当然我习惯统一使用大写,后面的所有代码也都会使用大写,个人建议大小写最好统一,方便查阅

如何添加本地头文件,源文件?

        本小节介绍的是如何将源代码文件组织起来。比如我们在一个src文件夹下有很多.cpp和.h文件,那么工程如何包含这些源代码文件呢?

        首先,在src文件夹下,创建一个CMakeLists.txt文件,然后里面写如下语句:

FILE(GLOB_RECURSE project_headers ${CMAKE_CURRENT_SOURCE_DIR}/*.h)

FILE(GLOB_RECURSE project_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

此时,project_headers就是一个指代该目录下所有.h文件的一个变量,后面生成动/静态库或可执行文件时,使用project_headers就可以了,project_sources就是一个指代该目录下所有.cpp文件的一个变量。在这两行代码中,还涉及到一个变量CMAKE_CURRENT_SOURCE_DIR,这个变量的含义看它的名字就可以理解了,它指代的是当前CMakeLists.txt文件所在的目录;在Cmake中,要使用一个变量的形式是${变量名};所以当我们要使用project_headers或者project_sources变量时,也需要使用${project_sources}这种形式;细心的朋友可能会注意到,CMAKE_CURRENT_SOURCE_DIR是直接拿来使用的,对的,在Cmake中,有一些预定义了的变量,我们可以直接拿来使用,后面我还会介绍一些其他的常用变量的含义;而project_headers这个是我们定义的变量,上述的代码即是给project_headers/project_sources赋值;既然是自定义的变量,也就是你可以使用任意你喜欢的名字来定义它,并不是一定要使用笔者定义名字;

写到这里,突然想介绍另外一个指令:MESSAGE,这个指令类似于C++语言中的printf,可以将变量的内容输出到控制台,方便我们查BUG和了解变量当前的内容;使用方法如下:

MESSAGE(STATUS "This is BINARY dir " ${CMAKE_CURRENT_SOURCE_DIR})

其中,STATUS的作用是在控制台输出时前面加个“-”,方便查看,可以删除;

        另外,还有两个单词没有解释,FILE和GLOB_RECURSE;这两个也都是Cmake自带的,FILE是Cmake的一个文件操作指令,GLOB_RECURSE的作用是告诉FILE查找该目录以及子目录下所有满足查询条件的文件,如果不想查找子目录,使用GLOB;

        当然,添加源文件并不是只有FILE这一种指令可以完成,Cmake中定义一个变量最常用的指令就是使用SET,比如SET(project_sources main.c),这句话的意思就是设置变量project_sources,并赋值为main.c;如果源文件比较少的情况下,可以直接使用SET进行设置,不过,非常不建议;因为如果这样设置了,当添加或删除了源文件时,Cmake需要同步进行修改,而使用FILE指令,可以查找当前文件夹下所有的.h和.cpp,不管是添加源文件还是修改源文件的名称,都不需要修改CMakeLists.txt;

附:在Cmake中,经常会用到SET指令,SET指令可用于显示的定义任何变量        

        上文中我们只提到了.h和.cpp类型的文件,有些读者可能会问了,其他后缀的文件也可以使用这种方式吗?毕竟源文件不仅只有这两种后缀了,还会有.xcc/.rc/.c/hpp等等;当然可以了,定义方式同上,如:

FILE(GLOB_RECURSE SORCE_RC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/*.rc)

如何引用外部头文件?

        上面介绍了一个工程下面所有的头文件和源文件的添加方式,但是我们在开发的过程中,通常不止一个工程,比如有两个工程,分别为ProjectA,ProjectB,在ProjectB中需要引用ProjectA中的头文件,那么此时该如何添加呢?在使用CMake之前,我们的配置方式是,右键工程名,选择属性,在属性页的C/C++→常规项下,有一个附件包含目录的设置,在这里输入需要包含的头文件路径即可;那么在Cmake中如何配置呢?使用INCLUDE_DIRECTORIES,如下:

INCLUDE_DIRECTORIES("头文件路径")

这里的INCLUDE_DIRECTORIES指令就等价于在VS的附件包含目录中所做的配置。当然了,你可能会说,我在代码里面引用的时候,通过相对路径进行包含也是一样的,没必要配置这个,但是当你引用第三方库时,会有很多头文件,或者文件路径比较复杂时,都用这种相对路径去查找的话,你会认输的。所以老老实实用最简单的方式吧。        

如何引用外部库文件?

        一个成熟的软件必然会使用其他的第三方库,在使用CMake之前,我们的配置方式是:右键工程名,选择属性,在属性页的链接器→常规项下,有一个附件库目录的设置,在这里输入需要包含的库文件的路径,然后在属性页的链接器→输入项下,有一个附加依赖项的设置,在这里输入需要包含的三方库名称即可;那么在Cmake中如何引入第三方库呢?

        如工程配置一样,Cmake引入三方库时,也分为两步;第一步,添加库文件的路径,如下:

LINK_DIRECTORIES("库文件路径")

第二步,引用三方库,如下:

TARGET_LINK_LIBRARIES(${PROJECT_NAME} "三方库库名称")

       上面的代码段中使用了Cmake中的两个指令:LINK_DIRECTORIESTARGET_LINK_LIBRARIESLINK_DIRECTORIES指令的作用就是向工程加入非标准的库文件搜索路径;TARGET_LINK_LIBRARIES指令的作用是向工程加入库链接;另外,在代码段中,还涉及到一个变量的引用${PROJECT_NAME},PROJECT_NAME的变量值是什么呢?可以把注意力转移到本文刚开始介绍的“Cmake第一行怎么写”这个章节,我们使用PROJECT指令定义了我们的工程名之后,就可以使用${PROJECT_NAME}来进行引用了,而PROJECT_NAME的值就是我们使用PROJECT指令定义的工程名。另外,需要注意的时,在填写三方库名称时,是不用带后缀的,例如opencv.lib,直接写opencv就可以了。

如何添加预处理器宏定义?

        这个章节的内容非常容易被忽视,但却是一个成熟的CMakeLists.txt文件必备的内容。同样的,我还是准备先介绍如何在VS编译器下面配置预处理宏;在使用CMake之前,我们的配置方式是:右键工程名,选择属性,在属性页的C/C++→预处理器选项下,有一个预处理器定义的设置,在这里输入需要定义的宏即可;在Cmake中使用ADD_DEFINITIONS指令就可以了,如下:

ADD_DEFINITIONS(-DUNICODE -D_UNICODE)

        这行代码中,-D是固定的格式,-D后面就是宏名称,上面我们定义了两个宏,UNICODE和_UNICODE,需要定义更多的宏,在后面追加就可以了。

        这里定义的宏其实和我们代码中定义的常量宏并不是一个作用,在C++代码中,我们通常会将一个常用的且不会改变的常量定义成一个宏,比如:

#define PI 3.1415

        在程序运行时,会将所有PI字符替换为3.1415进行计算,而在CMakeLists.txt文件中定义的宏,主要用于在编译期起作用。比如我们在创建一个导出库时,通常会有这样的定义:

#ifdef  EXPORT_DLL
#define CLASSNAME_EX_IM_PORT _declspec(dllexport)
#else
#define CLASSNAME_EX_IM_PORT _declspec(dllimport)
#endif

       上述代码通过是否定义EXPORT_DLL决定被CLASSNAME_EX_IM_PORT修饰的类是否为一个导出类,在CMakeLists.txt文件中使用ADD_DEFINITIONS(-DEXPORT_DLL),就可以产生一个导出类(在类的定义时,使用的格式为class CLASSNAME_EX_IM_PORT className),此时,外部程序可以通过引入导出类的库从而直接使用被CLASSNAME_EX_IM_PORT修饰的类的接口,如果在编译期并没有添加该宏定义,但是程序中引用了类的某些接口,那么编译时会提示找不到相关的标识。很多初级程序员在这里会犯错,以为引入了相关的头文件就可以使用头文件下的所有接口,然后编译报错提示无法识别的符号时会不知所措。

因为Cmake的内容还比较多,我不准备一次写完,后面还有几个章节,后续慢慢进行补充,有需要或有疑问的朋友也可以在下方留言,我看到了都会及时回复。

后面的内容包括但不仅限于:

1.生成动/静态库

2.生成可执行文件

3.编写CMakeLists.txt文件后,如何生成工程文件?

4.安装打包

        码字不易,喜欢的朋友可以给博主点个赞,您的支持就是我输出优秀文章最大的动力~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值