CMake学习笔记

CMake官网 https://cmake.org/

前言

CMake是一个跨平台项目构建工具,主要对标C/C++项目。其师出于automake,有其独有的DSL,详情请移步到CMake官网

安装CMake

对于Linux,这个挺简单的,CentOS执行 yum install cmake,Ubuntu执行 apt-get install cmake,如果觉得Linux发行版安装的cmake版本太旧,那么从官网上下载源码编译吧,当然,到cmake download page上下载也是个不错的途径

而对于Windows,个人是建议直接到cmake download page上下载二进制版本,将其添加到系统环境变量path上即可。电脑上安装了Msys2的话,也可以通过pacman -S cmake来安装;安装了cmder的话,在 .../vendor/init.bat 里插入 set path=%path%;xxx:/xxx/cmake-xxx/bin; 也是可以的,如下图所示:

/assets/2018-06-25/09-1529930004000.png

入门教程

cmake由于其语法丰富,比较适合管理大型的跨平台c/c++工程,而在轻量级的项目里,premake、xmake、qmake都是不错的方案呢。在终端上输入 cmake -h 后,可以看到cmake 3.12.0中已经支持了vs、codeblocks、codelite、eclipse等IDE和高级文本编辑器了

至于cmake的入门教程,由于网上相关资料已经汗牛充栋了,这里就不重复了,读者可以直接移步到《CMake 入门实战》中文《cmake-tutorial》官方英文,先过一遍中文教程,心里有底之后 ,再过一遍cmake官方教程即可入门了,剩下的知识点在项目需要用到时再学吧,毕竟只是个项目管理工具而已

下面整理了一些常用的操作命令说明:

函数 作用
project() 指定工程名称,在vs中就是.sln的名字
add_subdirectory() 设置子CMakeLists的所在路径
configure_file() 设置配置文件,一般用来预处理项目版本以及配合find_package、find_library、option等命令控制某些功能是否开启用的
add_executable() 添加一个可执行二进制项目,对应vs里的cli、gui项目
add_library() 添加一个动/静态链接库库项目,SHARED是动态链接库,STATIC是静态链接库
set() 设置变量,如果想多个CMakeLists.txt共用某个变量,那么需要将其指定为CACHE,如set(contribdir ${contribdir} ${CMAKE_CURRENT_SOURCE_DIR}/stb CACHE INTERNAL "contrib library" )
unset() 删除变量,通常用来删除临时变量
option() 设置操作项,可通过ON/OFF来控制开关,这个配置项在cmake-gui中以复选框的形式显示,通常与configure_file结合使用
message() 设置项目生成时的日志输出,甚至可以控制cmake的执行,比如FATAL_ERROR可终止cmake的往下执行
source_group() 将文件列表设为某个子群组,对应vs里的include、src等目录,如source_group("contrib/include" ${CURRENT_HEADERS})
file() 快速搜索文件,将其存放在某个变量里,支持多路径搜索,多用于搜索头文件、资源文件什么的,配合source_group将之添加到工程里
aux_source_directory() 搜索指定路径下的源文件,通常是.cpp、.c、.cxx等后缀名的文件,结果存放在某个变量里
add_definitions() 添加预处理宏,针对当前CMakeLists下的所有项目,如果只想为某个项目添加特定的预处理宏,那么需要使用set_tests_properties,如set_tests_properties(person_dll PROPERTIES COMPILE_DEFINITIONS "DEMO_USE_DLL")
add_custom_command() 添加自定义命令,可以设置执行时机(编译前、链接中、编译后),比如生成了.dll后,想将其复制到主项目的可执行文件所在目录中,就需要它了,还有各种自定义生成指令也是用到了它
target_link_libraries() 设置要链接的库文件
include_directories() 添加头文件的搜索路径,只对当前CMakeLists.txt生效
link_directories() 添加链接库的搜索路径,只对当前CMakeLists.txt生效
find_package() 查找第三方包,比如某项目依赖了第三方开源库curl来实现一个下载器,那么就可以通过这条指令来判断开发者是否安装了curl,如果 没开启,则禁用下载功能,通常与CMAKE_MODULE_PATH组合使用,如SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_path() 搜索某路径下的文件,多用于搜索头文件、链接库,以此来控制功能的开关,举个例子,某个项目集成了一个未开源的第三方库svgload,这个第三方库时以显式调用的方式提供,也就是说,提供了.h、.dll和.lib这三个文件,但是不给源码,此时就可以通过这条指令来尝试搜索项目,看有没有这个svgload开发包了,如果没有的话,通过预编译关闭掉svg的加载功能,当然,也可以采用find_package()来实现
find_library() 搜索某路径下的链接库,find_pat其实也可以替代它,两者用法相似

非官方例子

由于工作中用到的东西有点多,脑子记不过来,因此在此特意记下几个常用的例子,这里以图片转换器为例:

基本功能:加载常见的图片格式,如jpg、png、bmp、svg等,将其转换成其它图片格式,目前仅提供CLI版本用作演示

技术选型如下:

1
2
3
4
5
6
7
                +-------->ImageLoadModule(gdi+、stb_image.h、libgif、naosvg、skia、freeimage等),加载磁盘上的图片资源到内存中
                |
                |
ImageConverter--|--------->AutoCompleteModule(linenoise、readline等),为CLI程序提供命令自动补全的功能
                |
                |
                +-------->ImageSaveModule(gdi+、stb_image_write.h、freeimage等),将内存中的图片数据保存到磁盘

ImageConverter 0.1

这只是一个初始版,仅仅是搜索项目下的源文件,将其添加到VS上,可以编译执行了,附件:ImageConverter0.1.zip

1
2
3
4
5
6
7
8
# 指定cmake的最小版本
cmake_minimum_required(VERSION 2.8)
# 指定工程名称,ImageConverter.sln
project(ImageConverter)
# 搜索当前目录下的源码文件,用srcs存储起来
aux_source_directory(. srcs)
# 配置一个可执行文件项目,ImageConverterDemo.vcxproj
add_executable(ImageConverterDemo ${srcs})

ImageConverter 0.2

接下来搭建这个项目的基础部分,定好各种组件的接口,这里先以gdi+作为图片的编解码器,附件:ImageConverter0.2.zip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 指定cmake的最小版本
cmake_minimum_required(VERSION 2.8)

# 指定工程名称,ImageConverter.sln
project(ImageConverter)

# 搜索目录下的源码文件
aux_source_directory(. srcs)

FILE(GLOB_RECURSE coresrcs 
    ./core/*.cpp
)

FILE(GLOB_RECURSE miscsrcs 
    ./misc/*.cpp
)

# 搜索目录下的头文件
FILE(GLOB_RECURSE coreheaders 
    ./core/*.h
)

FILE(GLOB_RECURSE mischeaders 
    ./misc/*.h
)

# 添加到项目筛选器上
source_group("src" FILES ${srcs})
source_group("src\\core" FILES ${coresrcs})
source_group("src\\misc" FILES ${miscsrcs})

source_group("include\\misc" FILES ${mischeaders}) 
source_group("include\\core" FILES ${coreheaders}) 

# 添加头文件搜索路径
include_directories(./core)
include_directories(./misc)

# 配置一个可执行文件项目,ImageConverterDemo.vcxproj
add_executable(ImageConverterDemo ${srcs} ${coresrcs} ${miscsrcs} ${coreheaders} ${mischeaders})

ImageConverter 0.5

这里调用了第三方类库nanosvg来支持svg文件的读取,主要演示了如何通过Git来管理第三方类库,以及如何在cmake里面整合第三方类库源码的过程,简略描述如下: 借助option来配置svg的功能开启,如果为ON,则这里以SUPPORT_SVG_TYPE作为开启的条件,通过add_definitions(-DSUPPORT_SVG_TYPE)来添加预处理宏,源码里以SUPPORT_SVG_TYPE作为预编译宏,整理所有与svg相关的代码逻辑,另外,也要在CMakeLists.txt中,以if(SUPPORT_SVG_TYPE)判断是否执行追加相关头文件搜索路径,添加相关文件名到工程等命令 借助configure_file来配置一个外部的头文件,用来传入程序的版本号,当然,也可以配合option来做其他事情 附件:ImageConverter0.5.zip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 指定cmake的最小版本
cmake_minimum_required(VERSION 2.8)

# 指定工程名称,ImageConverter.sln
project(ImageConverter)

# 配置版本号
set (APP_VERSION_MAJOR 1)
set (APP_VERSION_MINOR 0)

# 配置是否开启SVG的支持
option (SUPPORT_SVG_TYPE "support .svg file" ON)  

# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_SOURCE_DIR}/config.h"
 )

aux_source_directory(. srcs)
source_group("src" FILES ${srcs})

FILE(GLOB_RECURSE coresrcs 
    ./core/*.cpp
)
source_group("src\\core" FILES ${coresrcs})

FILE(GLOB_RECURSE miscsrcs 
    ./misc/*.cpp
)
source_group("src\\misc" FILES ${miscsrcs})

FILE(GLOB_RECURSE coreheaders 
    ./core/*.h
)
source_group("include\\core" FILES ${coreheaders}) 

FILE(GLOB_RECURSE mischeaders 
    ./misc/*.h
)
source_group("include\\misc" FILES ${mischeaders}) 

if(SUPPORT_SVG_TYPE)
FILE(GLOB_RECURSE nanosvgheaders 
    ./modules/nanosvg/src/*.h
    ./modules/nanosvg/example/*.h
)
source_group("modules\\include\\nanosvg" FILES ${nanosvgheaders}) 
include_directories(./modules/nanosvg/src)
include_directories(./modules/nanosvg/example)
add_definitions(-DSUPPORT_SVG_TYPE)
endif(SUPPORT_SVG_TYPE)

# 添加头文件搜索路径
include_directories(./core)
include_directories(./misc)

# 配置一个可执行文件项目,ImageConverterDemo.vcxproj
add_executable(ImageConverterDemo ${srcs} ${coresrcs} ${miscsrcs} ${coreheaders} ${mischeaders} ${nanosvgheaders})

ImageConverter 0.6

目前发现Gdiplus里面的图片编码、解码的功能还是太弱了,支持的图片类型太少,比如无法正常加载psd类型,因此打算采用FreeImage来加强这个功能。由于FreeImage是以一个动态库的开发包形式分发的,因此需要更多的指令来控制依赖FreeImage库的工程生成,其中包括项目生成之后的dll文件拷贝、根据.dll、.lib的文件是否存在来判断是否生成ForFreeImage的工程等等,用到的关键指令如下所示:

函数 作用
FIND_PACKAGE() 用来查看cmake目录下的xxx.cmake文件,里面定义了这个模块的源码、开发包的搜索方式,注意,xxx.cmake里定义的参数可以传递到CMakeLists.txt里面哦
set_target_properties() 为指定项目配置特有属性,通常用来添加项目特有的宏定义,而add_definitions定义的宏会添加到所有生成的项目中,注意这个的区别哦
target_link_libraries() 让指定项目链接静态库/导入库,不是作用到所有项目哦
add_custom_command() 自定义生成命令,在此是为了让FreeImage项目生成后,自动拷贝FreeImage开发包下的.dll文件到.exe所在目录下,注意,这里面还用set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) 来重定向输出目录了哦

附件:ImageConverter0.6.7z

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
### 这里仅仅截取CMakeLists.txt的FreeImage配置相关代码段
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

FIND_PACKAGE(FREEIMAGEX32)
IF(FREEIMAGEX32_FOUND)
    include_directories(${FREEIMAGEX32_INCLUDE_DIR})
    FILE(GLOB_RECURSE freeimageheaders 
        ${FREEIMAGEX32_INCLUDE_DIR}/*.h
    )
    source_group("include\\freeimage" FILES ${freeimageheaders}) 
    if (FREEIMAGEX32_STATICLIB)
        add_executable(ImageConverterForFreeImage 
            ${srcs} 
            ${coresrcs} 
            ${miscsrcs} 
            ${coreheaders} 
            ${mischeaders} 
            ${freeimageheaders}
            ${nanosvgheaders})

        set_target_properties(ImageConverterForFreeImage PROPERTIES COMPILE_DEFINITIONS "SUPPORT_FREEIMAGE")
        target_link_libraries(ImageConverterForFreeImage ${FREEIMAGEX32_STATICLIB})
        add_custom_command(TARGET ImageConverterForFreeImage POST_BUILD        # Adds a post-build event to MyTest
            COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake - E copy_if_different..."
                "${FREEIMAGEX32_SHAREDLIB}"      # <--this is in-file
                "${PROJECT_SOURCE_DIR}/bin/$<CONFIGURATION>"
                )
    endif(FREEIMAGEX32_STATICLIB)
else(FREEIMAGEX32_FOUND)    
    MESSAGE(status "没有找到FreeImage哦,不生成此工程啦") 
ENDIF(FREEIMAGEX32_FOUND)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
### FindFREEIMAGEX32.cmake,此文件是配合Find_Package指令来使用的,目的是搜索指定路径
### 看FreeImage开发包是否完整,一般而言,FindXXX.cmake是会主动生成一些静态库、动态库项目的
### 当然,这是由源码的前提了,这里是借助了第三方提供的工具包,因此显得有点绕
# 设置查找路径
set (search_header  ${PROJECT_SOURCE_DIR}/modules/FreeImage/x32)
set (search_staticlib ${PROJECT_SOURCE_DIR}/modules/FreeImage/x32)
set (search_sharedlib ${PROJECT_SOURCE_DIR}/modules/FreeImage/x32)

# 搜索头文件
FIND_PATH(FREEIMAGEX32_INCLUDE_DIR FreeImage.h ${search_header})

# 搜索链接库
FILE(GLOB_RECURSE FREEIMAGEX32_STATICLIB ${search_staticlib}/FreeImage.lib)

# 搜索dll
FILE(GLOB_RECURSE FREEIMAGEX32_SHAREDLIB ${search_sharedlib}/FreeImage.dll)

IF (FREEIMAGEX32_INCLUDE_DIR AND FREEIMAGEX32_STATICLIB AND FREEIMAGEX32_SHAREDLIB)
    SET(FREEIMAGEX32_FOUND TRUE)
ENDIF (FREEIMAGEX32_INCLUDE_DIR AND FREEIMAGEX32_STATICLIB AND FREEIMAGEX32_SHAREDLIB)

# 删除临时变量
unset(search_header)
unset(search_staticlib)
unset(search_sharedlib)

配置编译参数

在VS中,需要配置编译参数,如将Debug下的/MDd=>/MTd,Release下的/MD=>/MT可以如下配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
if (MSVC)
    set(CompilerFlags
        CMAKE_CXX_FLAGS
        CMAKE_CXX_FLAGS_DEBUG
        CMAKE_CXX_FLAGS_RELEASE
        CMAKE_C_FLAGS
        CMAKE_C_FLAGS_DEBUG
        CMAKE_C_FLAGS_RELEASE
        )
    foreach(CompilerFlag ${CompilerFlags})
        string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
    endforeach()
endif(MSVC)

FAQ

不熟悉xxx命令,如何快速检索相关资料呢

对于不熟悉的cmake command,有两种方法可以快速找到其使用说明,这里以find_path为例

  • 直接在终端上输入cmake -h find_path来查询即可,如下图所示:
  • 直接问度娘或谷歌,输入cmake find_path来搜索即可,通常搜索引擎前三就是cmake官方的文档,后面的搜索结果是网友的一些博客了

/assets/2018-06-25/09-1530522434000.png

自2023年之后,可以借助ChatGpt4、Github Copilot之类的问答类大模型来快速查找相关函数用法,这个比搜索引擎更加便捷

SublimeText、VSCode等编辑器应该安装什么插件来快速编辑cmake呢

  • SublimeText—-CMakeEditor
  • VSCode—-CMakeTool+CMake

如何调用cmake来编译工程

1
2
cd build/
cmake --build .

参考资料:

0%