CMake进阶使用

本文很大程度参考了小彭老师的课程,倒不如说就是 小彭老师课程的笔记 虽然以往都会补充,但 本次因为一些原因并没有补充额外的东西,更多信息可以参考cmake的官网。

[ 注 ]  CMake是区分大小写的,更准确地说,指令不区分大小但是其他的所有内容是区分大小写的。

我在之前的笔记中记录了程序是怎么从.cpp文件变为可执行文件,同时也知道了所谓build其实就是就是把别人已经写好的子模块源代码经过编译成库文件,然后为自己所使用。也许有人会问,那为什么我们不直接把别人的代码直接加到我们的项目里面?这样做也是可以的,但是这往往取决于你所使用的第三方库,如果说你的引入的库是“Head Only”的也就是只有头文件(.h .hpp),那你就只需要include进来相关的头文件即可,一般这种库,会有一个总的入口头文件,include这一个就行。如果说一个库既有.h 也有.cpp 那么此时你就应该将其编译成库文件。这主要是取决于你所使用的第三方库。

CMake2.x和CMake3.x

 mkdir -p build 
 cd build 
 cmake .. _DCMAKE_BUILD_TYPE=Release   // 设置Build Type为Release
 make -j4   // 用4个进程并行的构建
 make install  // 让本地的构建系统执行安装
 cd ..

[ Release 和 Debug 版本的区别 ]:

Debug 是所谓的调试版本,也就是程序中带有调试信息,即你可以打断点查内存。g++ -g 就是生成具有调试信息的文件。同时 Debug版本编译器是不会使用优化的。也就是你的程序就是他原本的样子

Release是发行版本,是交付给用户的版本,编译器会对Release版本进行各种优化,以至于代码底层执行顺序已经被改变。而C++的重要课题就是“如何让编译器更好的为你的程序提供优化”,毕竟不开优化就不同用C++

 cmake -B build -_DCMAKE_BUILD_TYPE=Release
 cmake --build build --parallel 4
 cmake --build build --target install

CMake程序构建的步骤:

  1. cmake -B build, 配置阶段(configure):这个阶段检测环境并生成构建规则 也就是makefile。使用VS,它会生成sln vcxproj的文件,这就是相当于Win下的makefile,你如果不想使用VS打开,你可以找到Win下的msbuild(记得添加msbuild到环境变量.\bin)

  2. cmake --build build, 构建阶段(build):此时编译器通过-D来设hi在缓存变量。同时-D的配置依旧会被保留:

    cmake -B build -DCMAKE_INSTALL_PREFIX= …/xxx/xxx

    cmake -B build -DCMAKE_BUILD_TYPE=Release

    cmake -B build 即便 你没有对 -DCMAKE_INSTALL_PREFIX,-DCMAKE_BUILD_TYPE这两个缓存变量进行配置,它也会默认之前的选择

还有一种相对常用的参数 -G : cmake -B build -G Ninja

实际上 -G 的选项很多,Linux平台下默认生成的是 Unix Makefiles。

[ Ninja ]: Ninja 是Google的一名程序员推出的注重速度的构建工具,一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度。Ninja > Makefile > msbuild

添加源文件

方法一:

 add_executable(main main.cpp) // 第一个参数是名字,后续的参数是文件列表

方法二:

 add_executable(main) 
 target_sources(main PUBLIC test.cpp) // 后续往main中添加

方法三:

 add_executable(main) 
 set(sources main.cpp other.cpp other.h) // 创建一个变量,  .h不加也行 最好加上
 target_sources(main PUBLIC ${sources})  // 访问一个变量

方法二和三,如果文件太对一个一个打就很麻烦

方法四:

 add_executable(main) 
 file(GLOB source CONFIGURE_DEPENDS *.cpp *.h) // 让source包括所有.cpp .h
 target_sources(main PUBLIC ${sources})

如果没有CONFIGURE_DEPENDS 则添加或删除新文件cmake不会更新

如果代码在子文件里面则需要把子文件夹都写进去

 add_executable(main) 
 file(GLOB source CONFIGURE_DEPENDS *.cpp *.h subfile/*.cpp subfile/*.h) 
 target_sources(main PUBLIC ${sources})

还是老问题一个一个手打很烦

可以把GLOB换成GLOB_RECURSE,这样会自动包含所有的.cpp .h文件, 但是这个GLOB_RECURSE 会把build下的临时文件也加入进来

[ 注 ]: cmake为了测试编译器会生成临时文件

此时 建议把源码都放在 src 文件夹中,意思是src和build是平级的文件夹

方法五:

 add_executable(main) 
 aux_source_directory(. source)
 aux_source_directory(subfile source)
 target_sources(main PUBLIC ${sources})

aux_source_directory(. source) 中的 . 表示:当前目录

项目配置变量

-DCMAKE_BUILD_TYPE 除了能选择 Debug还是Release以外还可以有其他选择, 默认Debug

  • Debug : -O0 -g
  • Release : -O3 -DNDEBUG
  • MinSizeRel :最小体积发布,按照最小体积进行优化,所以体积比Release更小 : -Os DNDEBUG
  • RelWithDebInfo : 带有调式信息的Release,生成的体积>Release。方便后续返回 :-O2 -g -DNDEBUG

若定义了NDEBUG会使得assert被除去

如果希望默认值为 Release

 if(NOT CMAKE_BUILD_TYPE) // 若CMAKE_BUILD_TYPE未定义
     set(CMAKE_BUILD_TYPE Release) // 则CMAKE_BUILD_TYPE定义为 Release
 endif()

project:初始化项目信息

project:会初始化项目信息,并且把当前CMakeList.txt所在位置作为根目录

简单来说 project(xxxx) 就是给项目起名字

 PROJECT_NAME:当前项目名
 CMAKE_PROJECT_NAME:根项目名
 PROJECT_SOURCE_DIR:当前项目的源码位置 -存main.cpp的地方
 PROJECT_BINARY_DIR:当前项目的输出位置 -存main.exe的地方
 CMAKE_CURRENT_SOURCE_DIR:表示当前源码目录的位置 -存main.cpp的地方
 CMAKE_CURRENT_BINARY_DIR:表示输出目录的位置 -存main.exe的地方
 PROJECT_IS_TOP_LEVEL:BOOL类型,表示当前目录是否是项目的最顶层 => 可以用来检测是否作为子模块

PROJECT_SOURCE_DIR,PROJECT_BINARY_DIR 和 CMAKE_CURRENT_SOURCE_DIR,CMAKE_CURRENT_BINARY_DIR的区别在于:当作为子模块时,PROJUECT_ 是表示最近一次调用projectde的CMakeLists.txt的位置,而CMAKE_ 则表示最外层的CMakeLists.txt的目录。如果使用CMAKE_CURRENT_SOURCE_DIR 会让你的程序无法作为子模块使用

LANGUAGES 字段

 project(testcmakeproject LANGUAGES C CXX)
 project(testcmakeproject LANGUAGES NONE)
 enable_language(CXX)

设置C++标准

 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)   // 当编译器不支持C++17时报错
 set(CMAKE_CXX_EXTENSIONS OFF)  // 不会启用GCC的特性,保证可移植性
 project(testcmakeproject LANGUAGES C CXX)

虽然可以手动手动输入命令来启用C++17 : -std=c++17 但是这个编译器flag只对g++有效,对于MSVC则不支持

VERSION 字段

VERSION字段 表示版本号:project(name VERSION x.y.z) ,其中 x 是主版本号, y 是次版本号, z是补丁版本号

  • PROJECT_VERSION_MAJOR:获取主版本号
  • PROJECT_VERSION_MINOR:获取次版本号
  • PROJECT_VERSION_PATCH:获取补丁版本号

CMake的 ${ } 可以嵌套

 projectname_VERSION = ${${PROJECT_NAME}_VERSION}
 projectname_SOURCE_DIR = ${${PROJECT_NAME}_SOURCE_DIR}
 projectname_BINARY_DIR = ${${PROJECT_NAME}_BINARY_DIR}

cmake_minimum_required 指定最低CMake版本

cmake_minimum_required(VERSION 3.15)

 CMAKE_VERSION: 查询cmake版本
 CMAKE_MINIMUM_REQUIRED_VERSION: 查询cmake最低版本 

一个标准的CMakeLists.txt模板

 cmake_minimum_required(VERSION 3.15)
 ​
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 ​
 project(projectname LANGUAGE C CXX)
 ​
 if(PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR)
     message(WARNING "The binary directory of CMake cannot be the same as source directory")
 endif()
 ​
 if(NOT CMAKE_BUILD_TYPE)
     set(CMAKE_BUILD_TYPE Release)
 endif()
 ​
 if(WIN32)
     add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES)
 endif()
 ​
 if(NOT MSVC)
     find_program(CCACHE_PROGRAM ccache)
     if(CCACHE_PROGRAM)
         message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
         set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
         set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PROGRAM})
     endif()
 endif()

链接库

add_executable(main main.cpp libtest.cpp) // 直接链接
add_library(libtest STATIC libtest.cpp)  // 指定为静态链接库
add_library(libtest SHARED libtest.cpp)  // 指定为动态库或者叫共享库
add_library(libtest OBJECT libtest.cpp)  // 指定为对象链接库
add_executable(main main.cpp)
target_link_libraries(main PUBLIC libtest)  // 链接静态链接库

所谓对象库:不生成.a文件 只是由CMake记住该库生成了哪些对象文件。同时能绕开编译器,从而实现跨平台。但是对象库仅仅作为代码组织的方式,而实际生成的可执行文件只有一个,减轻部署的困难

例如:

//CMakeLists
add_library(libtest STATIC libtest.cpp)
// libtest.cpp:
#include <cstdio>
static int a = printf("静态初始化!");
// main.cpp:
#include <cstdio>
int main(){
        printf("main");    
}

以上代码libtest.cpp会先于main执行,此时gcc看到没有文件引用a 于是gcc就会删除libtest.o 但是我们需要这个静态初始化。所以此时使用对象库就能避开gcc的这个特性。

默认情况下CMake会根据 BUILD_SHARED_LIBS变量来确定是共享库还是静态库,ON是SHARED,OFF是STATIC。BUILD_SHARED_LIBS默认是OFF

动态库无法链接静态库

add_library(libstatic STATIC libstatic.cpp)
add_library(libshared SHARED libshared.cpp)

target_link_libraries(libshared PUBLIC libstatic)

因为CMake以为你的静态库要插入程序中,但是你插入共享库中,共享库是运行时链接,静态库插入的时候共享库还没有分配内存。静态库不想修改地址,而动态需要修改地址。更具体地来说,因为共享库中由fpic这个字段用来表示相对地址,如果你要把动态库链接静态库,要么将静态库改变为对象库,要么给静态库也生成表示相对地址的代码

  • set(CMAKE_POSITION_INDEPENDENT_CODE ON) 全局修改
  • set_property(TARGET libstatic PROPERTY POSITION_INDEPENDENT_CODE ON) 只针对 libstatic 这一个库

对象属性

set_property(TARGET name PROJECT xxx)

set_property(TARGET mian PROPECT CXX_STANDARD 17)            # 采用C++17编译(默认11)
set_property(TARGET mian PROPECT CXX_STANDARD_REQUIRED ON)   # 若编译器不支持C++17 则报错(默认OFF)
set_property(TARGET mian PROPECT WIN32_EXECUTABLE ON)        # 在Windows系统中,运行时不启动控制台窗口,只有GUI界面(默认OFF)
set_property(TARGET mian PROPECT LINK_WHAT_YOU_USE ON)       # 告诉编译器不要自动剔除没有引用符号的链接库(默认OFF)
set_property(TARGET mian PROPECT LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)  # 设置动态链接库输出路径(默认${CMAKE_SOURCE_DIR})
set_property(TARGET mian PROPECT ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)  # 设置静态链接库输出路径(默认${CMAKE_SOURCE_DIR})
set_property(TARGET mian PROPECT RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)  # 设置可执行文件输出路径(默认${CMAKE_SOURCE_DIR})

set_target_properties(TARGET name PROJECTIES xxx xxx xxx)

set_target_properties(TARGET name PROJECTIES
        PROPECT CXX_STANDARD 17
        CXX_STANDARD_REQUIRED ON
        WIN32_EXECUTABLE ON
        LINK_WHAT_YOU_USE ON
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib
        ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib
)

全局设置

set_property(CXX_STANDARD 17)            # 采用C++17编译(默认11)
set_property(CXX_STANDARD_REQUIRED ON)   # 若编译器不支持C++17 则报错(默认OFF)
set_property(WIN32_EXECUTABLE ON)        # 在Windows系统中,运行时不启动控制台窗口,只有GUI界面(默认OFF)
set_property(LINK_WHAT_YOU_USE ON)       # 告诉编译器不要自动剔除没有引用符号的链接库(默认OFF)
set_property(LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)  # 设置动态链接库输出路径(默认${CMAKE_SOURCE_DIR})
set_property(ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)  # 设置静态链接库输出路径(默认${CMAKE_SOURCE_DIR})
set_property(RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)  # 设置可执行文件输出路径(默认${CMAKE_SOURCE_DIR})
add_executable(main main.cpp)

Windows下的动态链接库

// .cpp
#include <cstdio>
#ifdef _MSC_VER
__declspec(dllexport)
#endif
void hello(){  printf("hello");  }
// .h
#pragma once
#ifdef _MSC_VER
__declspec(dllimport)
#endif
void hello();

在Windows下,如果exe和dll不在同一目录下且不在PATH中,则会出错,因为Win不会递归的找,只会查找exe的当前路径和PATH

在CMake中,我们可以设置

set_property(TARGET libtest PROPERTY RUN_OUT_DIRECTORY ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY ARCHIVE_OUT_DIRECTORY ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY LIBRARY_OUT_DIRECTORY ${PROJECT_DINARY_DIR}) 
set_property(TARGET libtest PROPERTY RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_DINARY_DIR})
set_property(TARGET libtest PROPERTY LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_DINARY_DIR})

链接第三方库

需要注意一个问题:在Linux下有一个专门存放库文件的路径。但是在Windows下并没有。所以在Windows下链接可能是需要进一步指定地址。

在CMake中统一使用‘ / ’即便是Windows下也是‘ / ’,CMake会自动转换。

target_link_libraries(main PUBLIC lib_path) # lib_path 即链接库的路径

以上方式不能跨平台,更通用使用find_package,以TBB库为例:

find_package(TBB REQUIRED)   # 查找./user/lib/cmake/TBB/TBBConfig.cmake这个配置文件,创建TBB::tbb这伪对象
target_link_libraries(main PUBLIC TBB::tbb)  # PUBLIC属性会给TBB::tbb指向的libtbb.so链接它对象的带上一些flag
  # 具体来说,TBB::tbb的头文件,包括tbb本身使用的别的库,也会自动链接上不用手动加

实际上,find_package()还可以增加CONFIG选项。添加CONFIG选项后 会优先查找 TBBConfig.cmake,而不是Find TBB.cmake 。

这个Findxxx.cmake一般是项目作者写的,并把它放进cmake/ 目录里面并添加到CMAKE_MOUDEL_PATH。

而xxxConfig.cmake一般是操作系统自带的

有的老库是没有xxxConfig.cmake,只有Findxxx.cmake

find_package( ) 会生成 包名::组件名 的伪对象,可以在find_package( )中使用 COMPOENENTS选项,后面跟随一个列表表示需要调用的组件

find_package(TBB COMPOENENTS tbb tbbmalloc tbbmalloc_proxy REQUIRED)
target_link_libraries(main PUBLIC TBB::tbb TBB::tbbmalloc TBB::tbbmalloc_proxy)

由于Windows没有固定的路径去安装 lib (Linux有user/lib) 所以可能出现 系统找不到xxxConfig.cmake文件。此时,你找到你的下载路径。在这个安装的路径中找到xxxConfig.cmake这个文件

  1. 自己设置CMAKE_MOUDEL_PATH: set(CMAKE_MOUDEL_PATH ${CMAKE_MOUDEL_PATH} your_path) 注意在Windows下也要用’ / ’
  2. 法1的一种全局的方法,所有的库都会去这个路径下找xxxConfig.cmake。我们针对不同的库专门设置一个路径:set(XXX_DIR your_path)。
  3. 也可以通过命令行 -Dxx_DIR=”your path”来指定 : cmake -B build -Dxx_DIR=”your path”
  4. 通过设置环境变量 xxx_DIR : export xxx_DIR=”your path” 这样对Windows用户比较困难

如果不指定REQUIRED,找不到也不会报错,仅仅是将TBB_FOUND 设置为FALSE:

find_package(TBB)
if(TBB_FOUND)
        message(STATUS "TBB found at: ${TBB_DIR}")
        target_link_libraries(main PUBLIC TBB::tbb)
        target_compile_definitions(main PUBLIC WITH_TBB)
else()
        message(WARNING "TBB not found!")
endif()

在.cpp中可以判断WITH_TBB宏,找不到就....(自己处理)

#include <cstdio>
#ifdef WITH_TBB
#include <tbb/parallel_for.h>
#endif

int main(){
#ifdef WITH_TBB
    tbb::parallel_for(0, 4, [&](int i){
#else
        for(int i=0; i<4; i++){
#endif
            printf("!!");
#ifdef WITH_TBB
    })
#else
    }
#endif
    return 0;
}

或者,我们可以通过检测受否有TBB::tbb这个伪对象来实现同样的效果

find_package(TBB)
if(TARGET TBB::tbb) # 这里面可以使用各种布尔运算: NOT TARGET TBB::tbb AND TARGET Eigen3::eigen
        message(STATUS "TBB found at: ${TBB_DIR}")
        target_link_libraries(main PUBLIC TBB::tbb)
        target_compile_definitions(main PUBLIC WITH_TBB)
else()
        message(WARNING "TBB not found!")
endif()

输出与变量

cmake -B build 打印字符串

message(”string”) : 在控制台直接输出,用于调试信息

message(STATUS ”string”) : 在控制台输出时增加 -- 前缀,表示状态信息

message(WARNING ”string”) : 在控制台输出时变为黄色字体,表示警告信息

message(AUTHOR_WARNING ”string”) : 在控制台输出时变为黄色字体,表示警告信息,但仅仅给项目作者才能看到,可以通过-Wno-dev关闭

message(FATAL_ERROR ”string”) : 表示错误信息(红字),会终止CMake运行

message(SEND_ERROR ”string”) : 表示错误信息(红字),不会终止CMake运行

针对”string”也可以单独设置一个变量:

set(msg “msg”)

message(”string : ${msg}”)

[ 补充 ] set(msg “a;b”) 等价于 set(msg a b) 没有引号和分号

变量与缓存

cmake会将检测编译器等信息放入一个缓存文件中,第二次在此检测时cmake就会去查找这个缓存文件,以便加速。但这里面有一个问题:当你外部的环境变化了,但cmake仍然还在使用缓存文件。 “cmake 99%的问题都可以通过删除 build 来解决”(rm -rf build)。

如果删除 build 那么会将很多中间结果删除,导致我们编译变慢。我们其实只需要清除缓存, 找CMakeCache.txt删除即可

设置缓存变量

set(var “hello” CACHE STRING “This is the docsrting”) # CACHE是选项, STRING是类型

message(”var is : ${var}”)

更新缓存变量

  1. cmake -B build -Dxxxx=xxxx

  2. ccmake -B build 在可视化界面中更新

    Windows下使用 cmake-gui -B build

  3. 直接用编辑器打开build/CMakeCache.txt修改保存

案例:添加一个BOOL值,控制某种特性的开启与关闭

add_executable(main main.cpp)
set(WITH_TBB ON CACHE BOOL "set to ON to enable TBB, OFF to disable TBB")
if(WITH_TBB)
        target_compile_definitions(main PUBLIC WITH_TBB)
        fing_package(TBB REQUIRED)
        target_link_libraries(main PUBLIC TBB::tbb)
endif()

如果是设置BOOL值,则可以用option(变量名 “描述” 变量值)

option(WITH_TBB "set to ON to enable TBB, OFF to disable TBB" ON)

修改需要用上述方式修改cmake -B build -DWITH_TBB=ON

跨平台与编译器

在CMake中给.cpp定义宏

#include <cstdio>
int main(){
#ifdef MY_MACRO

#else

#endif
}

target_compile_definitions(main PUBLIC MY_MACRO=1)

根据不同系统修改宏

if(CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(main PUBLIC MY_MACRO="Bill Gates")
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(main PUBLIC MY_MACRO="Linus Torvalds")
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") # APPLE 叫这个
    target_compile_definitions(main PUBLIC MY_MACRO="Steve Jobs")
endif()

简写:

if(WIN32) # WIN32 WIN64都可以
    target_compile_definitions(main PUBLIC MY_MACRO="Bill Gates")
elseif(UNIX AND NOT APPLE) # 类Unix系统,所以要出去APPLE
    target_compile_definitions(main PUBLIC MY_MACRO="Linus Torvalds")
elseif(APPLE)
    target_compile_definitions(main PUBLIC MY_MACRO="Steve Jobs")
endif()

使用生成器表达式,简化:

target_compile_definitions(main PUBLIC
    $<$<PLATFROM_ID:Windows>:MY_MACRO="Bill Gates"> # 只有当PLATFROM_ID=Windows时生效
    $<$<PLATFROM_ID:Linux>:MY_MACRO="Linus Torvalds">
    $<$<PLATFROM_ID:Darwin>:MY_MACRO="Steve Jobs">
)
target_compile_definitions(main PUBLIC
    $<$<PLATFROM_ID:Windows>:MY_MACRO="Bill Gates"> # 只有当PLATFROM_ID=Windows时生效
    $<$<PLATFROM_ID:Linux,Darwin>:MY_MACRO="Unix-like">
)

类似的,你也可以用CMAKE_CXX_COMPILER_ID来检测编译器

if(CMAKE_CXX_COMPILER_ID MATCHES "GUN")
    target_compile_definitions(main PUBLIC MY_MACRO="gcc")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "NVIDIA")
    target_compile_definitions(main PUBLIC MY_MACRO="nvcc")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    target_compile_definitions(main PUBLIC MY_MACRO="clang")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
    target_compile_definitions(main PUBLIC MY_MACRO="msvc")
endif()

可以在官网(cmake-generator-expressions(7) — CMake 3.23.2 Documentation)上查找更多

在命令行设置编译器

cmake -B build DCMAKE_CXX_COMPILER=”your_path”

分支与判断

我们知道想访问一个变量 需要使用,但是则不需要。可以直接写这是因为的出现比​{}还要早,所以你不用,会自动帮你替换。你如果在里加了​{}会出现一个问题:

set(hello World)
set(var hello)
if(${var} MATCHES "hello")
    message("YES")
else()
    message("NO")
endif()

这里我们希望它能显示YES 但是它却显示NO。这是因为var通过展开之后,变成,但是又会用的语法再次替换一次变成,这就和我们的希望不一样。那我不希望它替换,但是想用​{},那你可以加”” : if(”${var}” MATCHES "hello")

变量与作用域

变量传播规则:父传子,子不传父。父模块的变量能给子模块,但是子模块定义的变量不能给父模块去用

同时 如果父子模块中有变量重名,父模块也不会感觉到。如果想让父模块察觉,则需要在子模块的变量中使用PARENT_SCOPE选项,让其往上传播。set(var ON PARENT_SCOPE)

include中的 XXX.cmake没有独立作用域

add_subdirectory的CMakeLists.txt有独立作用域

macro没有独立作用域

function有独立作用域

变量的访问

$ENV{PATH} 获取PATH的值

$CACHE{xx} 获取缓存中的变量

环境变量和缓存变量是父子模块共有的,所有没有作用域的概念

if(DEFINED var) 判断var是否有被定义 [ 注 ] : 空字符串不等于没有定义

if(DEFINED var)
    message("YES")
else()
    message("NO")
endif()

[ 注 ] : 在if和set里面的使用ENV不要加$ Ex: set(ENV{var} “hello”) , if(DEFINED ENV{var})

  • if 之前说过是历史原因
  • set 是因为 本来就是要设置变量var,你再使用$去展开也没用

CACHE也是同理

CCache 编译加速

gcc -c main.cpp -o main.cpp ⇒ ccache -c main.cpp -o main.cpp 即可

在CMake启用ccache加速:

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM ccache)
    message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
    set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PROGRAM})
endif()

伪目标的添加

run伪目标

add_executable(main main.cpp)
add_custom_target(run COMMAND $<TARGET_FILE:main>) # 会让run自动依赖于main

当你生成好伪目标时,可以在命令行运行 cmake --build build --target run 来启动main.exe。这样就会用根据平台手动的写 build/main or build\main.exe

configure伪目标

add_custom_target(run COMMAND $<TARGET_FILE:main>)
if(CMAKE_EDIT_COMMAND)
    add_custom_target(configure COMMAND ${CMAKE_EDIT_COMMAND} -B ${CMAKE_BINARY_DIR})

cmake --build build --target configure 来启动ccmake修改缓存,用于可视化地修改缓存变量

在Linux上相当于 : ccmake -B build

在Windows上相当于 : cmake-gui -B build

cmake·c++
789 views
Comments
登录后评论
Sign In