菜单
1 前言
2 cmake_minimum_required
3 cmake_policy
4 project
5 add_executable
6 add_library
7 add_definitions
8 target_include_directories
9 include_directories
10 link_directories
11 target_link_directories
12 target_link_libraries
13 target_compile_features
14 set
15 设置可执行文件输出目录
16 add_subdirectory和include
17 打包项目
18 release和debug调用不同的lib库
19 release和debug生成不同的lib名称库
20 多个CMakeLists.txt共同引用一个.cmake文件
21 设置输出目录
22 设置后缀名
23 消息message
24 环境变量
25 option布尔型变量
26 条件控制if
27 操作符优先级
28 foreach 循环范例
29 while 循环范例
30 win32 visual studio设置多线程编译
31 mark_as_advanced
32 add_compile_options
33 文件分组
34 按照文件目录对文件分组
35 项目分组
36 设置启动目录
37 拷贝文件
38 设置MT/MDT
39 设置网络代理
附录1 常用变量

1 前言

CMake指令放入一个名为 CMakeLists.txt 的文件中,CMake命令是不区分大小写的,所以常用的做法是使用小写,参数使用大写。

2 cmake_minimum_required cmake最低版本指定

每个CMakeLists.txt都必须包含cmake_minimum_required作为第一行,设置项目工程依赖CMake的最低版本。如下代码:

cmake_minimum_required(VERSION 3.1)

指定CMake版本为3.1

每个CMakeLists.txt都必须包含cmake_minimum_required作为第一行,设置项目工程依赖CMake的最低版本。

cmake_minimum_required(VERSION 3.7...3.21)

指定CMake版本为3.7到3.21,这意味着这个工程最低可以支持3.7版本,但是也最高在 3.21版本上测试成功过。实际只会设置为3.7版本的特性,因为这些版本处理这个工程没有什么差异。注意:数字3.7...3.21不能有空格。

cmake_minimum_required(VERSION 3.7 FATAL_ERROR)

设置CMake所需的最低版本为3.7,如果使用的CMake版本低于该版本,则会发出致命错误。

3 cmake_policy

CMake版本特性,cmake_minimum_required和cmake_policy搭配使用。

cmake_minimum_required(VERSION 3.7)
if(${CMAKE_VERSION} VERSION_LESS 3.21)
  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
  cmake_policy(VERSION 3.21)
endif()

如果CMake的版本低于3.21,if 块条件为真,CMake 将会被设置为当前版本。如果CMake版本是3.21或者更高,if 块条件为假,CMake会设置3.21版本。

4 project

设置项目名称。

project(MyProject VERSION 1.0
  DESCRIPTION "Very nice project"
  LANGUAGES CXX)

MyProject是.sln文件的名称,如下图所示。

这里的字符串是带引号的,因此内容中可以带有空格。项目名称是这里的第一个参数。所有的关键字参数都可选的。VERSION 设置了一系列变量,例如 MyProject_VERSION和PROJECT_VERSION。

语言可以是 C,CXX,Fortran,ASM,CUDA(CMake3.8+),CSharp(3.8+),SWIFT(CMake3.15+ experimental),默认是CXX。在 CMake 3.9,可以通过DESCRIPTION 关键词来添加项目的描述。

5 add_executable

add_executable指令是生成可执行文件。

add_executable(one two.cpp three.h)

one 既是生成的可执行文件的名称,也是创建的 CMake 目标(target)的名称。紧接着的是源文件的列表,你想列多少个都可以。CMake很聪明,它根据拓展名只编译源文件。在大多数情况下,头文件将会被忽略;列出他们的唯一原因是为了让他们在 IDE 中被展示出来,目标文件在许多 IDE 中被显示为文件夹。

cmake_minimum_required(VERSION 3.7...3.21)

if(${CMAKE_VERSION} VERSION_LESS 3.21)
  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
  cmake_policy(VERSION 3.21)
endif()
                  
project(Test01 VERSION 1.0
  DESCRIPTION "Very nice project"
  LANGUAGES CXX)
                  
add_executable(${PROJECT_NAME} main.cpp test01.h test01.cpp)

6 add_library

生成一个库。

add_library(one STATIC two.cpp three.h)

你可以选择库的类型,可以是 STATIC、SHARED或者MODULE。如果你不选择它,CMake 将会通过 BUILD_SHARED_LIBS 的值来选择构建 STATIC 还是 SHARED 类型的库。

在下面的章节中你将会看到,你经常需要生成一个虚构的目标,也就是说,一个不需要编译的目标。例如,只有一个头文件的库。这被叫做 INTERFACE 库,这是另一种选择,和上面唯一的区别是后面不能有文件名。

你也可以用一个现有的库做一个 ALIAS 库,这只是给已有的目标起一个别名。这么做的一个好处是,你可以制作名称中带有::的库。

如果add_library不添加STATIC、SHARED和MODULE任何参数,默认只生成lib文件,如果添加STATIC也只生成lib文件,如果添加SHARED,则只生成dll文件。

如果我们想同时生成lib和dll文件时,就需要给add_library设置SHARED参数,并设置导出符号__declspec(dllimport) 。

7 add_definitions

添加宏定义

add_definitions(-D宏名称)

-D关键词不能少,且后面紧接着时宏名称,没有空格。

8 target_include_directories

给项目工程加入引用代码目录,也就是引用头文件所在目录。

target_include_directories(one PUBLIC include)

target_include_directories为目标添加了一个目录。PUBLIC 对于一个二进制目标没有什么含义;

但对于库来说,它让CMake知道,任何链接到这个目标的目标也必须包含这个目录。

其他选项还有 PRIVATE(只影响当前目标,不影响依赖),以及 INTERFACE(只影响依赖)。

注意:命令target_include_directories必须放在add_library或者add_executable之后,否则CMake构建工程时报类似“Cannot specify link libraries for target “test“ which is not built by this project.”的错误。

9 include_directories

给项目工程加入引用代码目录,也就是引用头文件所在目录,但是include_directories命令包含其子目录。

给项目工程添加库目录。

注意:link_directories必须放在add_executable指令前。

给项目工程添加库目录。

注意:target_link_directories必须放在add_executable指令后。

使用语法如下:

target_link_directories(${PROJECT_NAME} PUBLIC "../tecio/lib")

连接引用库,也就是链接库。

add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)
target_link_libraries可能是CMake中最有用也最令人迷惑的命令。它指定一个目标,并且在给出目标的情况下添加一个依赖关系。如果不存在名称为one的目标,那他会添加一个链接到你路径中one库(这也是命令叫 target_link_libraries 的原因)。或者你可以给定一个库的完整路径,或者是链接器标志。最后再说一个有些迷惑性的知识:),经典的CMake允许你省略 PUBLIC 关键字,但是你在目标链中省略与不省略混用,那么CMake会报出错误。

只要记得在任何使用目标的地方都指定关键字,那么就不会有问题。

13 target_compile_features

编译特性

target_compile_features(calclib PUBLIC cxx_std_11)

目标calclib使用c++11特性。

14 set

本地变量、缓存变量和环境变量。

声明一个本地变量MY_VARIABLE

set(MY_VARIABLE "value")

取消常规变量MY_VARIABLE

unset(MY_VARIABLE)

变量名通常全部用大写,变量值跟在其后。你可以通过 ${} 来解析一个变量,例如 ${MY_VARIABLE}。CMake 有作用域的概念,在声明一个变量后,你只可以它的作用域内访问这个变量。如果你将一个函数或一个文件放到一个子目录中,这个变量将不再被定义。你可以通过在变量声明末尾添加 PARENT_SCOPE 来将它的作用域置定为当前的上一级作用域。

set(MY_VARIABLE "value" PARENT_SCOPE)

MY_VARIABLE本地变量在父作用域中。

set(MY_LIST "one" "two")
set(MY_LIST "one;two")

列表就是简单地包含一系列变量,可以通过;分隔变量,这和空格的作用是一样的。

有一些和list(进行协同的命令,separate_arguments可以把一个以空格分隔的字符串分割成一个列表。需要注意的是,在CMake中如果一个值没有空格,那么加和不加引号的效果是一样的。这使你可以在处理知道不可能含有空格的值时不加引号。

当一个变量用${}括起来的时候,空格的解析规则和上述相同。对于路径来说要特别小心,路径很有可能会包含空格,因此你应该总是将解析变量得到的值用引号括起来,也就是应该这样"${MY_PATH}" 。

15 设置可执行文件输出目录

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/bin)

上面命令,必须在add_subdirectory的前面。

16 add_subdirectory和include

CMake中项目(project指令)和目标(add_library和add_executable指令),与Visual Studio中是不一样的,CMake 项目相当于Visual Studio解决方案,而CMake目标相当于Visual Studio一个一个的不同类型的项目。Visual Studio一个解决方案,可以包含多个项目。

我们非常关心解决方案的属性和项目的属性,包括 名称、版本、语言、路径、描述和网站。

如果用add_subdirectory命令,在子目录的CMakeLists.txt文件中,也有project命令,那么构建后,都会生成项目

我们用include()命令也可以加载 子目录中的CMakeLists.txt文件,例如:

include(sub/CMakeLists.txt)

虽然include()命令与 add_subdirectory()命令,都会加载CMakeLists.txt文件,但是include()命令是将子目录中CMakeLists.txt文件内容导入到父目录的源文件中,而 add_subdirectory() 命令是将子目录中 CMakeLists.txt 添加到构建中。

所以,有一个比较明显的区别,都执行project命令,如果使用include()那么项目会被覆盖,如果使用add_subdirectory()那么会创建子项目。

17 打包项目

在编译完lib库后,需要手动拷贝lib文件、头文件给别人,做二次开发,这样相当麻烦。可以使用install指令,将生成的lib文件等拷贝到指定的位置。

配置库文件、头文件和执行文件到install的目录下,cmake中的install根目录为CMAKE_INSTALL_PREFIX变量的路径,如果我们要设置配置路径可以使用set命令设置CMAKE_INSTALL_PREFIX变量的值来改变路径。一般默认情况CMAKE_INSTALL_PREFIX变量的值为,在UNIX系统中为:/usr/local,在windows系统中为:c:/Program Files/${PROJECT_NAME}

打包项目指令如下:

# 设置打包目录
set(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR})
# 配置可执行文件到安装路径 CMAKE_INSTALL_PREFIX的bin中
install(TARGETS glew DESTINATION glew-sdk/lib)
# 配置程序的头文件到安装路径 CMAKE_INSTALL_PREFIX的include文件中
install(FILES "include/GL/glew.h"
DESTINATION glew-sdk/include)

18 release和debug调用不同的lib库

使用方法1

target_link_libraries ( app
  debug ${Boost_FILESYSTEM_LIBRARY_DEBUG}
  optimized ${Boost_FILESYSTEM_LIBRARY_RELEASE} )

使用方法2

set( MyFavLib_LIBRARIES 
  debug debug/module1 optimized release/module1
  optimized debug/module2 optimized release/module2 )
target_link_libraries( app ${MyFavLib_LIBRARIES} )

注意,方法2中,module1和module2的lib后缀名要省略,vs会自动补全lib后缀名。

19 release和debug生成不同的lib名称库

使用方法

if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
  set(CMAKE_DEBUG_POSTFIX "_debug")
endif()
if(NOT DEFINED CMAKE_RELEASE_POSTFIX)
  set(CMAKE_RELEASE_POSTFIX "_release")
endif()

注意,该指令必须放在add_library之前,如下图所示。

20 多个CMakeLists.txt共同引用一个.cmake文件

include(${CMAKE_CURRENT_DIR}/../share.cmake)

21 设置输出目录

(1) 设置exe输出目录

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

在Win + VS环境下,会自动在你所设置的目录后面扩展一层 目录,所以最终生成的Debug版本程序会在 ${PROJECT_SOURCE_DIR}/../bin/Debug 目录下,Release版本程序会在 ${PROJECT_SOURCE_DIR}/../bin/Release 目录下.

(2) 设置lib输出目录

SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../lib)

在Win + VS环境下,会自动在你所设置的目录后面扩展一层 目录,所以最终生成的Debug版本库会在 ${PROJECT_SOURCE_DIR}/../lib/Debug 目录下,Release版本库会在 ${PROJECT_SOURCE_DIR}/../lib/Release 目录下. 在Linux + GCC环境下,无论是Debug还是Release,生成的库文件会直接放在你所设置的目录下,不会有差异.

(3) 设置输出目录

#全局设置一下如下
#可执行文件位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#默认存放动态库的位置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
#默认存放静态库的位置,以及MSVC中动态库的lib文件的位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# 单独为某个库设置输出目录:
set_target_properties(${target_name}
        PROPERTIES
        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/libs"
        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/libs" 
        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/libs"
        FOLDER "lib")

(4) 设置debug和release输出目录

用法1

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../bin)

上面两条语句分别设置了Debug版本和Release版本可执行文件的输出目录, 一旦设置上面的属性,在任何环境下生成的可执行文件都将直接放在你所设置的目录.

用法2

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../lib) 
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../lib)

上面两条语句分别设置了Debug版本和Release版本库文件的输出目录, 一旦设置上面的属性,在任何环境下生成的库文件都将直接放在你所设置的目录.

22 设置后缀名

用法1

set(CMAKE_DEBUG_POSTFIX "_d") 
set(CMAKE_RELEASE_POSTFIX "_r") 

上面两条语句分别设置了Debug版本和Release版本下库文件的后缀名

用法2

set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX "_d") 
set_target_properties(${TARGET_NAME} PROPERTIES RELEASE_POSTFIX "_r")

上面两条语句分别设置了Debug版本和Release版本下可执行文件的后缀名

23 message

语法格式

message([<mode>] "message text" ...)

mode 的值包括 FATAL_ERROR、WARNING、AUTHOR_WARNING、STATUS、VERBOSE等。主要使用其中的 2 个——FATAL_ERROR、STATUS。

FATAL_ERROR:产生 CMake Error,会停止编译系统的构建过程;

STATUS:最常用的命令,常用于查看变量值,类似于编程语言中的 DEBUG 级别信息。

"message text"为显示在终端的内容。

24 环境变量ENV

使用set函数 + ENV指令

set(ENV{变量名} 值)

注意:

(1)读环境变量时,需要加上$;写环境变量时,不需要加$;

(2)cmake文件内定义的环境变量仅用于cmake编译过程,不能用于目标程序。

例子如下:

message("myenvvar: $ENV{myenvvar}")
# 定义环境变量myenvvar
set(ENV{myenvvar} "123")
message("myenvvar: $ENV{myenvvar}")

25 option

CMake中的option用于控制编译流程,相当于C语言中的宏条件编译。

options基本格式如下:

option(<variable> "<help_text>" [value])

variable:定义选项名称

help_text:说明选项的含义

value:定义选项默认状态,一般是OFF或者ON,除去ON之外,其他所有值都为认为是OFF。

26 条件控制if

if(expression)
  #...
elseif(expression2)
  #...
else()
  #...
endif()

对于 if(string) 来说:

(1)如果 string 为(不区分大小写)1、ON、YES、TRUE、Y、非 0 的数则表示真

(2)如果 string 为(不区分大小写)0、OFF、NO、FALSE、N、IGNORE、空字符串、以 -NOTFOUND 结尾的字符串则表示假

(3)如果 string 不符合上面两种情况,则 string 被认为是一个变量的名字。变量的值为第二条所述的各值则表示假,否则表示真。

27 操作符优先级

表达式中可以包含操作符,操作符包括:

(1) 一元操作符,例如:EXISTS、COMMAND、DEFINED 等

(2) 二元操作符,例如:EQUAL、LESS、GREATER、STRLESS、STREQUAL、STRGREATER 等

(3) NOT(非操作符)

(4) AND(与操作符)、OR(或操作符)

(5) 操作符优先级:一元操作符 > 二元操作符 > NOT > AND > OR

使用例子1

if (variable)
# 当 variable 不为 空值,1,TRUE、ON时为真

if (NOT variable)
# 当 variable 为 空值,0,FALSE,OFF 或者 NOTFOUND 时为真

if (variable1 AND variable2)
# 当 variable1, variable1 同时不为 空值,0,FALSE,OFF 或者 NOTFOUND 时为真

if (variable1 OR variable2)
# 当 variable1, variable1 有一个不为 空值,0,FALSE,OFF 或者 NOTFOUND 时为真

if (COMMAND command-name)
# 当 command-name 是可调用的命令时为真

if (DEFINED variable)
# 当 variable 已经被设置了值时为真

if (EXISTS file-name)
if (EXISTS directory-name)
# 当指定的文件或者目录时为真

if (IS_DIRECTORY name)
if (IS_ABSOLUTE name)
# 当 name 是目录或者是绝对路径是为真

if (name1 IS_NEWER_THAN name2)
# 当 name1 文件的修改时间比 name2 文件的修改时间要新时为真

if (variable MATCHES regex)
if (string MATCHES regex)
# 当给定的变量或者字符串与给定的正则表达式相匹配时为真

使用例子2

if(NOT expression)
//为真的前提是 expression 为假

if(expr1 AND expr2)
//为真的前提是 expr1 和 expr2 都为真

if(expr1 OR expr2)
//为真的前提是 expr1 或者 expr2 为真

if(COMMAND command-name)
//为真的前提是存在 command-name 命令、宏或函数且能够被调用

if(EXISTS name)
//为真的前提是存在 name 的文件或者目录(应该使用绝对路径)

if(file1 IS_NEWER_THAN file2)
//为真的前提是 file1 比 file2 新或者 file1、file2 中有一个文件不存在(应该使用绝对路径)

if(IS_DIRECTORY directory-name)
//为真的前提是 directory-name 表示的是一个目录(应该使用绝对路径)

if(variable|string MATCHES regex)
//为真的前提是变量值或者字符串匹配 regex 正则表达式

if(variable|string LESS variable|string)
if(variable|string GREATER variable|string)
if(variable|string EQUAL variable|string)
//为真的前提是变量值或者字符串为有效的数字且满足小于(大于、等于)的条件

if(variable|string STRLESS variable|string)
if(variable|string STRGREATER variable|string)
if(variable|string STREQUAL variable|string)
//为真的前提是变量值或者字符串以字典序满足小于(大于、等于)的条件

if(DEFINED variable)
//为真的前提是 variable 表示的变量被定义了。

28 foreach 循环范例

set(VAR a b c)
foreach(f ${VAR})
  message(${f})
endforeach()

29 while 循环范例

set(VAR 5)
while(${VAR} GREATER 0)
 message(${VAR})
 math(EXPR VAR "${VAR} - 1")
endwhile()

30 win32 visual studio设置多线程编译

IF(WIN32 AND NOT ANDROID)

  # GW: no longer required (keep for posterity)
  # GL CORE Profile build (OSG must also be built with it)
  # FIND_PACKAGE(GLCORE)
  #IF(GLCORE_FOUND)
  #    INCLUDE_DIRECTORIES( ${GLCORE_INCLUDE_DIR} )
  #    message(status "Found GLCORE headers at ${GLCORE_INCLUDE_DIR}")
  #ENDIF()    

  IF(MSVC)
        # This option is to enable the /MP switch for Visual Studio 2005 and above compilers
        OPTION(WIN32_USE_MP "Set to ON to build multiprocessor option (/MP)" OFF)
        MARK_AS_ADVANCED(WIN32_USE_MP)
        IF(WIN32_USE_MP)
            SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
        ENDIF(WIN32_USE_MP)

        # More MSVC specific compilation flags
        ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS)
        ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE)
    ENDIF(MSVC)
ENDIF(WIN32 AND NOT ANDROID)

31 mark_as_advanced

mark_as_advanced 将CMake 的缓存变量标记为高级。

mark_as_advanced([CLEAR|FORCE] VAR VAR2 VAR...)

将缓存的变量标记为高级变量。其中,高级变量指的是那些在CMake GUI中,只有当“显示高级选项”被打开时才会被显示的变量。如果CLEAR是第一个选项,参数中的高级变量将变回非高级变量。如果FORCE是第一个选项,参数中的变量会被提升为高级变量。如果两者都未出现,新的变量会被标记为高级变量;如果这个变量已经是高级/非高级状态的话,它将会维持原状。

该命令在脚本中无效。

32 add_compile_options

add_compile_options是设置编译选项指令

(1) 设置utf-8编码

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

(2) 设置使用c++11标准

add_compile_options(-std=c++11)

(3) 设置使用c++17标准

# msvc编译器
if (MSVC_VERSION GREATER_EQUAL "1914")
  add_compile_options("/Zc:__cplusplus") 
endif()

if (MSVC_VERSION GREATER_EQUAL "1900")
  include(CheckCXXCompilerFlag)
  CHECK_CXX_COMPILER_FLAG("/std:c++17" _compiler_supports_cxx17)
  if (_compiler_supports_cxx17)
      add_compile_options("/std:c++17")
  endif()
endif()
# g++编译器
if(CMAKE_COMPILER_IS_GNUCXX)
    include(CheckCXXCompilerFlag)
    CHECK_CXX_COMPILER_FLAG("-std=c++17" _compiler_supports_cxx17)
    if (_compiler_supports_cxx17)
        set(CMAKE_CXX_FLAGS "-std=c++17 ${CMAKE_CXX_FLAGS}")
        message(STATUS "optional:-std=c++17")  
    endif()
endif(CMAKE_COMPILER_IS_GNUCXX)

(4) 取消编译警告提示

if(WIN32)
    add_compile_options(/W4)
    add_compile_options(/wd4828)
    add_compile_options(/wd4100)
    add_compile_options(/wd4251)
endif()

当有一些大型程序需要编译时,由于代码的劣质,会出现成千上万的c4828等编译警告,此时,如果使用的qtcreator编译器,会直接导致qtc编译器卡死或者崩溃。

33 文件分组

有时候,一个项目中,文件很多,我们想让文件看起来整洁一下,就需要将文件进行分组

file(GLOB LOG "log/*.h" "log/*.cpp")
file(GLOB LOGWIN "log/logan-win/*.h" "log/logan-win/*.cpp")
file(GLOB CLOGAN "log/logan-win/clogan/*.h" "log/logan-win/clogan/*.cpp")
file(GLOB MBEDTLS_INCLUDE "log/logan-win/mbedtls/include/mbedtls/*.h" )
file(GLOB MBEDTLS_LIB "log/logan-win/mbedtls/library/*.h" "log/logan-win/mbedtls/library/*.cpp")

set(group group1.h group1.cpp main.cpp)
source_group(group FILES ${group})

source_group(group/log FILES ${LOG})
source_group(group/log/logan-win FILES ${LOGWIN})
source_group(group/log/logan-win/clogan FILES ${CLOGAN})
source_group(group/log/logan-win/mbedtls/include FILES ${MBEDTLS_INCLUDE})
source_group(group/log/logan-win/mbedtls/library FILES ${MBEDTLS_LIB})

add_executable(${PROJECT_NAME}  
${group} ${LOG} ${LOGWIN} ${CLOGAN} ${MBEDTLS_INCLUDE} ${MBEDTLS_LIB})

34 按照文件目录对文件分组

在使用CMake后,手工维护filter是不现实的。CMake也提供了可以生成filter的机制,就是source_group()命令。结合source_group(), file(), string()等命令,我们可以让实现CMake自动按目录结构生成filter。

(1) 在比较顶层的CMakeLists.txt中定义该宏

定义宏代码:

macro(source_group_by_dir source_files)
  if(MSVC)
      set(sgbd_cur_dir ${CMAKE_CURRENT_SOURCE_DIR})
      foreach(sgbd_file ${${source_files}})
          string(REGEX REPLACE ${sgbd_cur_dir}/\(.*\) \\1 sgbd_fpath ${sgbd_file})
          string(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
          string(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
          string(REPLACE "/" "\\" sgbd_group_name ${sgbd_group_name})
          if(sgbd_nogroup)
              set(sgbd_group_name "\\")
          endif(sgbd_nogroup)
          source_group(${sgbd_group_name} FILES ${sgbd_file})
      endforeach(sgbd_file)
  endif(MSVC)
endmacro(source_group_by_dir)

(2) 使用宏

在添加工程(add_library或者add_executable)的CMakeLists.txt文件中调用该宏,调用如下:

source_group_by_dir(all_files)

其中all_files是保存了所有文件名的变量。注意,这里用的是变量名,而没有引用其值。

一般这个文件列表可以用file()或者aux_source_directory()来得到。例如对于C++工程,通常是这样的:

file(GLOB_RECURSE project_headers *.h)
file(GLOB_RECURSE project_cpps *.cpp)
set(all_files ${project_headers} ${project_cpps})
source_group_by_dir(all_files)

35 项目分组

有时候,一个项目中,可能有很多工程,工程多了后,就会显得杂乱无章,此时我们需要对工程进行分组

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "4")    # 分组名称为4

36 VS启动目录

# Visual Studio中的调试工作目录
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/bin/Debug)

如果用cmake构建vs项目工程,直接点击debug按钮,启动程序,会提示找不到程序,此时,需要调用上述代码,设置一下启动目录

37 拷贝文件

# 判断配置文件是否存在
if(EXISTS ${PROJECT_BINARY_DIR}/bin/Debug/config)
    message(DEBUG "style exist")
else()
    file(GLOB CONFIGFILES "${CMAKE_SOURCE_DIR}/config/*")    
    file(COPY ${CONFIGFILES} DESTINATION ${PROJECT_BINARY_DIR}/bin/Debug/config)
endif()

有时候项目需要一些依赖文件,当程序编译好了后,程序运行都要依赖这些文件,此时可以使用上述代码,进行依赖文件拷贝

38 设置MT/MDT

# 当调用lib库时,可能出现 debug调用release库
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")

38 设置代理

# 使用cmake下载github上的库,可能会出现下载不了的情况,可以通过网络代理下载
set(ENV{http_proxy} "http://127.0.0.1:8082")
set(ENV{https_proxy} "http://127.0.0.1:8082")

附录1 常用变量

变量 作用
${CMAKE_CURRENT_SOURCE_DIR} 当前CMakeLists.txt文件所在目录
${CMAKE_CURRENT_BINARY_DIR} 当前CMakeLists.txt构建输出所在的目录
${CMAKE_SOURCE_DIR} 顶级CMakeLists.txt文件所在路径
${CMAKE_BINARY_DIR} 顶级CMakeLists.txt构建输出所在的目录
${PROJECT_SOURCE_DIR} 项目源码顶层目录
${PROJECT_BINARY_DIR} 项目编译输出目录
${PROJECT_NAME} 项目名称
${CMAKE_VERSION} CMake的版本,由 MAJOR、MINOR、PATCH、TWEAK组成
${CMAKE_MAJOR_VERSION} CMake的主要版本,假设CMake的版本是3.21,那么3就表示主要版本,21表示次要版本
${CMAKE_MINOR_VERSION} CMake的次要版本
${CMAKE_PATCH_VERSION} CMake的补丁版本
${CMAKE_TWEAK_VERSION} CMake的微调版本