ardupilot开发 --- cmake 篇

0. 官方文档

首页:CMake: A Powerful Software Build System
开发文档CMake Reference Documentation

1. 一些概念

  • CMake是构建C++代码的标准;

  • 支持构建多平台的C++代码,如unix,windows,ios…
    在这里插入图片描述

  • 以linux系统为例,make使用Makefile来构建代码,cmake使用CMakeLists.txt来构建Makefile文件,因此,学习cmake就是学习如何编写 CMakeLists.txt !以及如何使用命令行工具cmake!(有哪些参数?如何使用?)

  • cmake允许每个源代码树具有多个构建树:
    在这里插入图片描述

  • CMake支持许多流行的C++IDE系统以及命令行构建工具如:Visual Studio、Xcode、ninja、make和VSCode等;

  • cmake除了可以构建库文件和可执行文件外,CMake还允许在构建时运行任意cmd命令。

  • 尽管CMake支持大写、小写和混合大小写命令,但小写命令是首选命;

  • CMake的发展历史:https://cmake.org/history/

  • CMake News and Updates

  • Forum、FAQs、Support、Issue、Documentation
    CMake Discourse Forum
    CMake FAQs
    Advanced Support
    Issue Tracker
    CMake Reference Documentation

  • 如何阅读文档
    (1)将不懂的指令或函数放入搜索框中搜索阅读。
    在这里插入图片描述
    (2)有哪些工具行命令?用法?:Command-Line Tools;如cmake、cpack。
    (3)有哪些函数?用法?:cmake-commands(7);如set。
    (4)有哪些变量?用法?:cmake-variables(7);如CMAKE_CXX_STANDARD。

  • 只有包含main函数的xx.cpp才可以被cmake构建成可执行程序!其余的会被构建成库文件xx.so。

  • 构建、编译、链接
    构建 = 编译 + 链接
    构建:Build,指生成可执行文件的全过程,包括预编译、编译、和链接等。
    编译:Compile,将main.cpp, main.h编译成目标文件main.o;将xx.cpp, xx.h编译成库文件xx.so
    链接:Link,将库文件xx.so链接到目标文件main.o得到最终的可执行文件main.exe

  • 构建可执行程序的过程
    在这里插入图片描述

  • 顶层CMakeLists.txt和子层CMakeLists.txt
    cmake的对象是顶层CMakeLists.txt文件。因此要么在顶层CMakeLists.txt所在目录下执行cmake命令,要么cmake时使用相对路径。

    • 顶层CMakeLists.txt的作用
      • 解决依赖问题:findpakage()
      • 告知cmake要编译哪些cpp源文件
        1)编译默认路径下的源文件,即与顶层CMakeLists.txt同级路径下的源文件。
        2)编译通过add_subdirectory()指定的目录下的源文件。如,add_subdirectory(subdirectoryName)
      • 告知cmake如何编译cpp源文件
        1)将哪些cpp文件构建成可执行文件xx.exe:add_executable()
        2)将哪些cpp文件编译成库文件xx.so:add_library(xxso_name xx1.cpp xx2.cpp),其中xx1.cpp和xx2.cpp可以是相互调用的关系也可以毫无关系。
      • 告知cmake如何将库文件xx.so链接到目标文件main.o以得到最后的可执行文件main.exe
        1)告知cmake该库的.h文件所在:target_include_directories()
        2)告知cmake该库的.so文件所在:target_link_libraries()
      • 其他
        声明项目名称:project()
        声明cmake版本要求:cmake_minimum_required()
        C++版本要求:
        CMAKE_CXX_STANDARD
        CMAKE_CXX_STANDARD_REQUIRED
        set()
    • 子层CMakeLists.txt的作用
      将同级目录中的xx.h, xx.cpp编译成库文件libxx.so。
      add_library(libxx )
  • 在cmake中,[[...]][*[...]*]表示换行符,其中*表示某个任意字符。

  • 在CMake中的路径分隔符总是应当使用/,因为CMake会对字符串中的\转义,CMake在对接VS时会自动处理路径分隔符的替换问题。

  • 在较复杂的项目中,我们可以在不同的子目录下使用多个CMakeLists.txt,在根目录下的CMakeLists.txt是最顶级的,例如可以使用add_subdirectory(source)命令,进入source文件夹,然后自动执行source目录下的CMakeLists.txt,执行完毕后返回上一级,还可以继续前往其它子目录执行相应的CMakeLists.txt。

2. CMakeLists进阶历程

2.1 创建一个最简单的CMake项目:1 xx.cpp + 1 CMakeLists.txt

  • 使用场景:项目结构 = 1 xx.cpp + 1 CMakeLists.txt

  • 目录结构(文件路径):CMakeLists.txt是否必要与写有main函数的cpp文件同一目录?不一定,但一般一个CMakeLists.txt与它负责的xx.cpp、xx.h文件会放在同一个目录下。如果没放在同一个目录,则在CMakeLists.txt中要写相对路径!

  • 学习目标:语法、变量、命令

  • 最小 CMakeLists.txt:至少包含3行代码(命令)

    • 1)使用CMake_minimum_required() 指定最低CMake版本。
      项目的最高层CMakeLists.txt都必须通过使用CMake_minimum_required() 来指定最低CMake版本。
    • 2)使用 project() 设置项目名称、语言、版本号等。
      每个项目都需要此调用,并且应在cmake_minimum_required()之后立即调用。
    • 3)使用 add_executable() 命令告诉CMake将哪些.cpp构建成可执行程序。
      注意:只有包含main函数的xx.cpp才可以被cmake编译成可执行程序!
  • 如何编写项目的顶层CMakeLists.txt ?
    例子:项目结构 = 1 xx.cpp + 1 CMakeLists.txt
    目录结构(文件路径):CMakeLists.txt 与 xx.cpp 放在同一目录下。
    CMakeLists.txt的写法如下:

    cmake_minimum_required(VERSION 3.10)#cmake的最低版本
    project(Tutorial)#cmake项目名称
    add_executable(Tutorial tutorial.cpp)#将源文件tutorial.cpp编译成可执行程序Tutorial 
    

    注意:只有包含main函数的xx.cpp才可以被cmake编译成可执行程序!add_executable的更多用法请参考:cmake-commands

  • 编译(构建)项目
    写完CMakeLists.txt后如何用cmake命令构建项目?

    • 1)创建一个build目录:
      mkdir Step1_build
      cd Step1_build
    • 2)根据CMakeLists.txt构建Makefile:
      格式:cmake <顶层CMakeLists.txt的路径>
      如:cmake ../Step1
      其中../Step1是项目最顶层CMakeLists.txt的所在路径。
    • 3)编译:
      格式: cmake --build <Makefile所在路径>
      如:cmake --build .
      或用 make命令进行编译。
      其中 .是Makefile的所在目录

2.2 指定 C++ 标准

  • 学习目标:1. cmake变量;2. 使用set函数设置cmake变量;
  • cmake中的变量:CMake中的系统变量如CMAKE_CXX_STANDARDCMAKE_CXX _STANDARD_REQUIRED可以用于指定构建项目所需的C++标准。
    CMake机制中许多变量以CMAKE_开头。
  • 使用场景:某.cpp文件中使用了C++11的语法,CMakeLists.txt 中必须要指定构建程序时使用C++11标准。C++其他版本则同理。
  • 涉及命令:
    CMAKE_CXX_STANDARD
    CMAKE_CXX_STANDARD_REQUIRED
    set()
  • CMakeLists.txt的写法:
    在add_executable()之前添加代码:
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED True)
  • 编译方法同理。

2.3 显示编译版本号

  • 使用场景:有时,在源代码中希望可以使用CMakelists.txt文件中定义的变量。例如,我们希望根据每次编译来作为项目版本的依据。
  • 涉及命令
    <PROJECT-NAME>_VERSION_MAJOR
    <PROJECT-NAME>_VERSION_MINOR
    configure_file()
    target_include_directories()
  • 其本质是.cpp文件与cmake变量之间的交互!
  • 非重点,待续…
  • 详细请参考:Exercise 3 - Adding a Version Number and Configured Header File

2.4 使用库来构建可执行程序

  • 参考:Adding a Library
  • 使用场景:一个项目,包含多个目录、子目录,源文件被放在不同的目录中,顶层的cpp代码引用了子层的cpp,或互引用。
  • 说明:这种场景下,包含main函数的cpp所在目录即项目的顶层目录,顶层目录拥有一个CMakeLists.txt文件,称为顶层CMakeLists,其他层级的目录也拥有属于自己的CMakeLists.txt文件,称为子层CMakeLists,它们文件名相同但路径不同!功能上也略有不同。
  • 子层CMakeLists在构建程序时会将该层级的cpp编译成库文件xx.o(linux平台),顶层CMakeLists负责将整个项目代码编译成最后的可执行程序,在顶层CMakeLists文件中,使用add_subdirectory命令将子目录添加到构建中,最后通过target_include_directoriestarget_link_libraries将库文件xx.o 链接(link) 到最后的可执行程序。
  • 涉及命令:
    add_library()
    add_subdirectory()
    target_include_directories()
    target_link_libraries()
    PROJECT_SOURCE_DIR
  • 例子:
    顶层目录: 1 CMakeLists.txt + 1 tutorial.cpp(入口函数main所在的cpp)
    库目录(顶层目录的子目录):CMakeLists.txt + 2 xx.cpp + 2 xx.h
    调用关系:顶层目录中的代码调用了库目录中的代码(函数)。
    子层CMakeLists写法:
    1)创建一个名为MathFunctions的库文件(编译结果应该是MathFunctions.o),库文件包含这些MathFunctions.cxx mysqrt.cxx源文件的代码。
    add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
    顶层CMakeLists写法:
    1)添加要被编译成库的子目录(名称):
    add_subdirectory(MathFunctions)
    2)将库链接到可执行程序:
    target_link_libraries(Tutorial PUBLIC MathFunctions)
    3)使用这些库的必要依赖信息(指定库的头文件位置):
target_include_directories(Tutorial PUBLIC
                          "${PROJECT_SOURCE_DIR}/MathFunctions"
                          )

如果链接的库比较多时,还可以这样写:

# 添加要被编译成库都的子目录
add_subdirectory(MathFunctions1)
add_subdirectory(MathFunctions2)
list(APPEND EXTRA_INCLUDES1 "${PROJECT_SOURCE_DIR}/MathFunctions1")
list(APPEND EXTRA_INCLUDES2 "${PROJECT_SOURCE_DIR}/MathFunctions2")
target_link_libraries(Tutorial PUBLIC MathFunctions1)
target_link_libraries(Tutorial PUBLIC MathFunctions2)
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           ${EXTRA_INCLUDES1}
                           ${EXTRA_INCLUDES2}
                           )
  • 完整的CMakeLists

顶层CMakeLists:

cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
# TODO 2: Use add_subdirectory() to add MathFunctions to this project
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
# TODO 3: Use target_link_libraries to link the library to our executable
target_link_libraries(Tutorial PUBLIC MathFunctions)
# TODO 4: Add MathFunctions to Tutorial's target_include_directories()
# Hint: ${PROJECT_SOURCE_DIR} is a path to the project source. AKA This folder!
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           "${PROJECT_SOURCE_DIR}/MathFunctions"
                           )

子层CMakeLists:

# TODO 1: Add a library called MathFunctions with sources MathFunctions.cxx
# and mysqrt.cxx
# Hint: You will need the add_library command
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)

2.5 cmake自定义变量、编译定义

  • 使用场景:在使用cmake编译代码时需要输入自定义选项来控制代码块的选择性编译,如cmake -DTHIS_MYOPTION=ON
  • 涉及命令
    if()
    option()
    target_compile_definitions()
  • 学习目标:
    • 添加cmake自定义选项
      option(USE_MYOPTION "my diy option" ON)
      什么是自定义选项?cmake -DUSE_MYOPTION =ON中的 -DUSE_MYOPTION就是自定义选项!其中选项的默认前缀为-D
    • 添加编译定义
      target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
      什么是编译定义?原名词为 compile definition,可以理解为编译某块代码的使能,如 xx.cpp、xx.h 中:
      #ifdef MY_ COMPILE_DEFINITION
      #else
      #endif

      其中的 MY_ COMPILE_DEFINITION 就是编译定义(compile definition)!!
    • 条件编译语句
      如 xx.cpp、xx.h 中:
      #ifdef USE_MYMATH
      #else
      #endif
  • 用法、语法
    • 添加自定义选项
      对于cmake命令行工具来说是选项;对于CMakeLists.txt来说是变量
      1)在CMakeLists.txt中添加一个变量:option(USE_MYOPTION "my diy option" ON)
      2)该变量USE_MYOPTION的赋值可以通过命令行选项的形式输入,如cmake -DUSE_MYOPTION =OFF
      3)然后就可以在同一个CMakeLists.txt中使用该变量(选项),如
      if (USE_MYMATH)
      target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
      endif()
    • 为库添加编译定义
      在xx.cpp、xx.h中,除了可以用#define来添加(声明)编译定义之外,还可以在CMakeLists.txt中用target_compile_definitions添加,如:
      target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
      其中第一个参数的含义是要为哪个target添加,target可以是库(Library),也可以是可执行程序;第二个参数是作用域;第三个参数是编译定义的名称,并且"USE_MYMATH"与USE_MYMATH、-DUSE_MYMATH都会被认定为名称为USE_MYMATH !

2.6 库的使用依赖(usage requirement)

  • usage requirement 表示使用某个库时必须要包含的依赖(如:库的源码路径等信息)。
  • 解决的痛点:
    在之前,任何想要链接到某个库如MathFunctions的target都需要被告知该库的源码路径信息,例如可执行程序Tutorial要链接库MathFunctions,在顶层CMakeLists.txt中必须包含这些代码:
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           "${PROJECT_SOURCE_DIR}/MathFunctions"
                           )

其中${PROJECT_BINARY_DIR}是可执行程序Tutorial的源码目录,${PROJECT_SOURCE_DIR}/MathFunctions是库MathFunctions的源码目录;
为了节省这些不必要的源码路径声明,可以使用INTERFACE依赖项来实现!

  • 用法
    • 为库添加依赖项(usage requirement)
      1)为库添加 INTERFACE usage requirement
      在该库的CMakeLists.txt中添加:
      target_include_directories(MathFunctions
        INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
      )
      其中,CMAKE_CURRENT_SOURCE_DIR是该库所在的绝对路径。
      这样就为该库指定了一个INTERFACE usage requirement。
      INTERFACE usage requirement 表示调用者需要而库本身不必须得东西。
      usage requirement 被称为“使用依赖”,而 INTERFACE 是其中一项。
      2)在调用层的 CMakeLists.txt 删除该库的依赖信息
      list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
      target_include_directories(Tutorial PUBLIC
        ${EXTRA_INCLUDES}
      )
      或:
      target_include_directories(Tutorial PUBLIC
        "${PROJECT_SOURCE_DIR}/MathFunctions"
      )
      3)这样,在调用层的CMakeLists.txt仅这几行代码,需要就可以实现对某库的调用(链接):
      add_subdirectory(MathFunctions)
      target_link_libraries(Tutorial PUBLIC MathFunctions)

2.7 为库指定C++标准

  • 使用场景:为不同的库指定不同的C++版本。
  • 技术路线:使用接口库(interface library)。
  • 用法
    • (1)在顶层CMakeLists.txt中,删除全局配置:
      set(CMAKE_CXX_STANDARD 11)
      set(CMAKE_CXX_STANDARD_REQUIRED True)
    • (2)创建一个接口库,并为接口库添加特性(指定C++版本)
      add_library(tutorial_compiler_flags INTERFACE)
      target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
    • (3)将可执行程序、库、接口库链接到一起
      target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
      target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
      target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)

2.8 添加生成器表达式

2.9 Installing and Testing

  • 使用场景:通常,仅仅构建一个可执行文件是不够的,它还应该是可安装的。使用CMake,我们可以使用 install 命令指定安装规则。在CMake中支持构建的本地安装通常很简单,只需指定安装位置以及要安装的目标和文件即可。
  • 学习目标:安装可执行程序Tutorial 以及库 MathFunctions
  • 涉及语法:install
  • 条件:cmake3.15或以上版本;老版本的安装只支持make install
  • 关于安装路径
    可以在CMakeLists.txt中用变量 CMAKE_INSTALL_PREFIX 指定安装路径,也用--prefix选项来指定,如:cmake --install . --prefix "/home/myuser/installdir", --prefix将会覆盖CMAKE_INSTALL_PREFIX的值!
  • 待续…
  • 参考:Installing and Testing

2.10 Selecting Static or Shared Libraries

2.11 Selecting Static or Shared Libraries

待续…

3. cmake命令用法

  • 参考:cmake(1)
  • 功能
    • Generate a Project Buildsystem
      cmake [<options>] -B <path-to-build> [-S <path-to-source>]
      cmake [<options>] <path-to-source | path-to-existing-build>
    • Build a Project
      cmake --build <dir> [<options>] [-- <build-tool-options>]
    • Install a Project
      cmake --install <dir> [<options>]
    • Open a Project
      cmake --open <dir>
    • Run a Script
      cmake [-D <var>=<value>]... -P <cmake-script-file>
    • Run a Command-Line Tool
      cmake -E <command> [<options>]
    • Run the Find-Package Tool
      cmake --find-package [<options>]
    • Run a Workflow Preset
      cmake --workflow [<options>]
    • View Help
      cmake --help[-<topic>]
  • 什么是Project Buildsystem
    例如,Project Buildsystem可以是与make一起使用的Makefile文件,也可以是某IDE的项目文件,总之,它描述了如何使用构建工具(如make)从源代码编译成最终的可执行文件或可被调用的库文件。
  • 如何Generate a Project Buildsystem
    • 标准语法
      cmake [<options>] -B <path-to-build> [-S <path-to-source>]
      cmake [<options>] <path-to-source | path-to-existing-build>
      解析:
      cmake [可选选项] -B <编译路径> -S <源码路径>
      其中:
      [可选选项] :可在中查阅;cmake-options
      <编译路径>:生成makefile、CMakeCache.txt 的路径,若不存在则会自动创建,支持绝对路径或相对路径。
      <源码路径>顶层 CMakeLists.txt文件所在的路径,支持绝对路径或相对路径。
    • 用法1:源码路径、编译路径都指定
      cmake -S src -B buildcmake -S ./src -B ./build
      cd ./build
      make -j
    • 用法2:只指定源码路径,编译路径缺省,-S缺省
      编译路径缺省,默认当前目录为编译路径,并且选项-S可以缺省!!
      mkdir build
      cd build
      cmake ../src
    • 用法3:在已存在的编译目录下重新编译(缓存文件CMakeCache.txt已存在)
      针对缓存文件CMakeCache.txt已存在的情况
      例如已知CMakeCache.txt的路径为./build
      cd ./build
      cmake .
    • 用法4:缺省-S -B的情况下自动判定源码路径、编译路径
      在这里插入图片描述
      其中,cwd 表示当前目录 current working directory 。
    • 构建Project Buildsystem后如何编译?如何安装?
      如:
      make
      make install

4. 文档阅读方式

授人以鱼不如授人以渔。
一开始cmake的目的只是单纯的为生成Makefile而生。
到如今的cmake强大到可以为不通的IDE生成项目文件,如Visual Studio、Xcode等。
根据不同的使用cmake目的来阅读不同的帮助文档,例如:

5. 知识点列表

构建、编译、链接

构建、编译、链接即 build、compile、link。
构建 = 预编译+编译 + 链接。
构建:Build,指生成可执行文件的全过程,主要包括预编译、编译、和链接等过程。
编译:Compile,将main函数.cpp编译成目标文件main.o;将xx.cpp, xx.h编译成库文件xx.so
链接:Link,将依赖库libxx.so链接到xx.so;将库文件xx.so、依赖库libxx.so链接到目标文件main.o得到最终的可执行文件main.exe

顶层、父层、子层CMakeLists.txt

父层CMakeLists.txt中的add_subdirectory()会告知cmake有哪些子层CMakeLists.txt。
父层CMakeLists.txt执行到add_subdirectory()时就会进入子层目录执行子层CMakeLists.txt??
顶层一定是父层,但父层不一定是顶层。
顶层CMakeLists.txt一般用于遍历、全局变量定义、全局环境配置等。
父层CMakeLists.txt一般用于构建可执行文件
子层CMakeLists.txt一般用于构建库文件
在这里插入图片描述

CMake_minimum_required()

指定cmake版本。

project()

指定项目名。适用于顶层目录或子目录。

add_executable()

指定要将哪些.cpp构建成可执行文件。
一般出现在顶层或父层CMakeLists.txt。

add_library()

指定要将哪些.cpp构建成库文件。
一般出现在子层CMakeLists.txt。

cmake变量

  • 和其他语言一样,cmake可以看做是一种编程语言,他也有变量和函数。

  • 所有变量的值只能是字符串;

  • 当变量值不包括空格时,可以不使用双引号;

  • 使用空格或者分号作为字符串的分隔符;

  • 采用${var}形式来获取变量值;

  • 可以使用未定义的变量,如果使用了未定义的变量,那么它的值是一个空字符串;

  • 变量的值可以包含换行,也可以包含引号,不过需要转义。

  • 分类

    • 一般变量
      • 系统变量:系统变量通常以CMAKE_开头。作用域为全局。
      • 自定义变量:作用域在定义时指定为当前CMakeCache.txt域或父CMakeCache.txt域。
    • 列表变量:若对一个一般变量赋了多个值,则这个变量是一个列表变量。作用域、用法与一般变量相同。
    • 环境变量:作用域为全局且仅在cmake过程中有效,而非操作系统级环境变量。
    • 缓存变量:上一次运行cmake时缓存变量的值会被缓存在CMakeCache.txt 中,所以当再次运行cmake时可以从中获取上一次的值,而不是重新去评估。作用域为全局。
  • 变量定义的格式

    set(<variable> <value>... [PARENT_SCOPE])
    
  • 变量的析构

    set(variable)
    unset(variable)
    
  • 定义一般变量(自定义变量)

    # 作用域在当前CMakeLists.txt区域
    set(varName "*.cpp")
    # 作用域在父CMakeLists.txt区域
    set(varName "*.cpp" PARENT_SCOPE)
    
  • 定义列表变量

    # 作用域在当前CMakeLists.txt区域
    set(varName "aaa.cpp" "bbb.cpp")
    # 作用域在父CMakeLists.txt区域
    set(varName "aaa.cpp" "bbb.cpp" PARENT_SCOPE)
    # 等价于
    set(varName "aaa.cpp;bbb.cpp" PARENT_SCOPE)
    
  • 定义cmake环境变量

    set(ENV{varName} "/opt/myDir") 
    

    引用:$ENV{varName}

  • 定义缓存变量

    set(varName value... CACHE type ["description"] [FORCE])
    

    BOOL类型的缓存变量除了可以使用set来定义外,还可以使用option:

    option(varName helpString [initialValue])
    # 等价于:
    set(varName initialValue CACHE BOOL helpString)
    # 不过上述两个命令定义缓存变量是有一点区别的,option()命令没有FORCE关键字。
    

    其中:

    • CACHE是固定参数,必填。
    • "description"记录变量的描述,可填。
    • FORCE表示每次运行都强制重新评估该变量的值,而不是从缓存CMakeCache.txt 中读取该变量值。如果缺省则会从缓存中取值。可填。
    • type表示变量类型。类型取值范围如下:
      • BOOL
        布尔型。
        ON、TRUE、1 或 OFF、FALSE、0
      • FILEPATH
        文件路径 类型,表示这个变量存放的是文件路径。
      • STRING
        字符串 类型。
      • INTERNAL
        内部缓存变量 类型。内部变量一般不会对用户可见。内部缓存变量默认是FORCE的。
  • 使用场景
    什么场景使用一般变量?什么场景使用缓存变量?
    一般变量适用于变量的值相对固定,而且只在某一个很小的作用域生效的场景。
    缓存变量适用于其可以随时更改,作用域作为全局的情况,经常在cmake中定义缓存变量,给一个默认值,如果用户想要更改缓存变量的值,可以通过cmake -D的形式去更改。

  • 变量值的修改
    在CMakeLists.txt中可以通过set函数来修改所有类型变量的值。
    也可以在执行cmake命令时通过选项-DvarName=xxx动态地修改变量的值,同样适用于所有类型的变量

  • 常用系统变量
    cmake-variables.
    cmake系统变量通常以CMAKE_开头。
    如CMAKE_CXX_STANDARD、CMAKE_CXX _STANDARD_REQUIRED用于指定C++标准。

变量的作用域

Cmake里面有三种作用域,全局层的,目录层的,函数层的。

  • 全局层:cache变量,在整个项目范围之内可见,一般在用set定义变量的时候指定CACHE参数就能定义为cache的变量。
  • 目录层:在当前目录CMakeLists.txt中定义,及在该文件中包含进来的(通过include或者macro引进的)其他的cmake源文件中定义的变量属于目录层这一级的作用域。
  • 函数层:在命令函数中定义的变量,属于函数作用域内的变量。

全局层 < 目录层 < 函数层。
如果修改时通过set命令明确指定PARENT_SCOPE参数,修改的变量作用域就是在上一层作用域,而不是当前作用域。

cmake -Dxx=xx

可以动态地为所有类型的变量赋值。
如果该变量没在CMakeLists.txt中被定义不会报错但会有警告信息。

set()

set可以实现变量定义或赋值。
如果变量不存在,定义并初始化;如果变量已存在则仅赋值。

option()

定义、初始化BOOL类型的缓存变量。

block()

在任意位置产生新的作用域。
待续…

execute_process()

  • 执行cmd命令序列,并且多个命令是通过管道方式执行,上一个命令的标准输出通过管道传递,作为下一个命令的标准输入。
  • 命令可以大写EXECUTE_PROCESS。
  • 命令格式
    其中[]表示可选、<>表示必选、COMMAND是固定参数。
    execute_process(COMMAND <cmd1> [<arguments>]
                    [COMMAND <cmd2> [<arguments>]]...
                    [WORKING_DIRECTORY <directory>]
                    [TIMEOUT <seconds>]
                    [RESULT_VARIABLE <variable>]
                    [RESULTS_VARIABLE <variable>]
                    [OUTPUT_VARIABLE <variable>]
                    [ERROR_VARIABLE <variable>]
                    [INPUT_FILE <file>]
                    [OUTPUT_FILE <file>]
                    [ERROR_FILE <file>]
                    [OUTPUT_QUIET]
                    [ERROR_QUIET]
                    [COMMAND_ECHO <where>]
                    [OUTPUT_STRIP_TRAILING_WHITESPACE]
                    [ERROR_STRIP_TRAILING_WHITESPACE]
                    [ENCODING <name>]
                    [ECHO_OUTPUT_VARIABLE]
                    [ECHO_ERROR_VARIABLE]
                    [COMMAND_ERROR_IS_FATAL <ANY|LAST>])
    
  • 例子
    # 格式:
    execute_process(COMMAND <cmd1> [<arguments>]
                   	COMMAND <cmd2> [<arguments>]
                   	COMMAND <cmd2> [<arguments>]
    )
    # 示例:
    EXECUTE_PROCESS(COMMAND ls -al
     				COMMAND grep "cmake"
     				COMMAND awk "{print $9}"
    )
    # 等价于:
    ls -al | grep "cmake" | awk "{print $9}"
    
  • 参数选项
    • WORKING_DIRECTORY
      指定命令的工作目录。
    • TIMEOUT
      命令的超时。
    • RESULT_VARIABLE
      管道最后一个命令的结果、返回码、描述字符串。
    • RESULTS_VARIABLE
      所有子命令执行结果存入该变量中,子结果以分号连接。
      注意与RESULT_VARIABLE的区别!!
    • OUTPUT_VARIABLE、ERROR_VARIABLE
      将标准输出和标准错误管道内容分别写入OUTPUT_VARIABLE和ERROR_VARIABLE指定的变量中,如果指定的是同一个变量,那么会按照执行的顺序合并输出。
    • INPUT_FILE
      指定文件作为第一个命令的标准输入 。
    • OUTPUT_FILE
      指定文件作为最后一个命令的标准输出。
    • ERROR_FILE
      所有命令运行错误结果存储的文件。
    • OUTPUT_QUIET, ERROR_QUIET
      忽略标准输出或者标准错误。
    • COMMAND_ECHO
      将运行的命令echo到STDOUT(标准输出)、STDERR(标准错误)、NONE,也可以通过设置变量CMAKE_EXECUTE_PROCESS_COMMAND_ECHO来控制默认值。
    • ENCODING
      指定命令执行的输出用何种方式解码,只在Windows下生效。
    • ECHO_OUTPUT_VARIABLE, ECHO_ERROR_VARIABLE
      将标准输出或标准错误复制一份到指定的变量(例如通过OUTPUT_VARIABLE指定的变量),而不是重定向。这意味着标准输出或者标准错误仍然会有效。
    • COMMAND_ERROR_IS_FATAL
      指定当遇到错误时的命令行为。

ADD_DEFINITIONS()

已被add_compile_definitions()、include_directories() 、add_compile_options() 代替。
添加编译定义(宏定义)、编译选项、cmake变量。

ADD_DEPENDENCIES()

ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)

如果两个targets有依赖关系,虽然可以通过target_link_xxx解决。add_dependencies 可以在编译当前target时,自动检查它所依赖的target是否已经编译,如果为否则先编译依赖的target,然后再编译当前target,最后 link depend target。若只有一个targets有依赖关系,一般选择使用 target_link_libraries。

find_package()

  • 功能:搜索外部库,将结果赋值给cmake系统变量。
  • 基本语法:
    find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
                 [REQUIRED] [[COMPONENTS] [components...]]
                 [OPTIONAL_COMPONENTS components...]
                 [REGISTRY_VIEW  (64|32|64_32|32_64|HOST|TARGET|BOTH)]
                 [GLOBAL]
                 [NO_POLICY_SCOPE]
                 [BYPASS_PROVIDER])
    
    其中:
    [version] 表示版本号;
    [EXACT] 表示版本号精确匹配;
    [QUIET] 表示禁止提示信息;
    [REQUIRED] 表示如果包(库)没有被找到的话,cmake过程会终止,并输出警告信息;
    [[COMPONENTS] [components...] 表示与该库相关的部件清单;
    待续…
  • 搜索原理
    cmake本身不提供任何搜索库的便捷方法,搜索库并给变量赋值的所有操作必须由该库提供的FindXXX.cmakeXXXConfig.cmake来完成。一般,库的作者通常会提供这两个文件,以方便使用者调用。
  • 搜索模式
    • Module模式
      默认模式。
      CMAKE_MODULE_PATH变量指定的路径下搜索FindXXX.cmake文件,并执行该文件从而找到XXX库。
    • Config模式
      如果Module模式未找到库,才会采取Config模式。
      先去变量XXX_DIR指定的路径搜索XXXConfig.cmake文件,执行该文件从而找到XXX库。XXX_DIR可以是操作系统级别的环境变量也可以是cmake级别的环境变量也可以是cmake变量。若找不到,再去/usr/local/lib/cmake/XXX/中找。
      例如:XXX库安装时没有安装到系统目录,因此无法自动找到XXXConfig.cmake,可以在CMakeLists.txt中定义一个XXX_DIR缓存变量:
      set(XXX_DIR /home/wjg/projects/XXX/build CACHE PATH "Directory that contains XXXConfig.cmake")
      
  • 搜索结果
    无论是Module模式还是Config模式,搜索结果都会被保存在XXX_INCLUDE_DIRSXXX_LIBRARIES两个变量中。这两个变量在FindXXX.cmake或XXXConfig.cmake中被定义。
  • 使用示例
    以引用第三方库 BZip2 为例。
    project(helloworld)
    add_executable(helloworld hello.cpp)
    find_package (BZip2)
    if (BZIP2_FOUND)
    	include_directories(${BZIP_INCLUDE_DIRS})
    	target_link_libraries (helloworld ${BZIP2_LIBRARIES})
    endif (BZIP2_FOUND)
    
  • xxx.cmake、cmake模块
    为了能支持各种常见的库和包的搜索,CMake自带了很多模块,每个模块对应一个xxx.cmake。
    可以使用命令 cmake --help-module-list来查看所有模块名称。
    或者使用ls /usr/share/cmake-xx/Modules/命令查看所有模块。
    使用cmake --help-module Findxxx查看Findxxx.cmake模块中有哪些可用变量。如:
    在这里插入图片描述

注释

支持#开头的单行注释,支持#[[开头,]]结尾的多行注释。
在这里插入图片描述

操作符优先级

  • 优先级
    () > 一元操作符 > 二元操作符 > 逻辑运算

    操作符类型操作符名称
    一元EXISTS, COMMAND, DEFINED
    二元EQUAL, LESS, LESS_EQUAL, GREATER, GREATER_EQUAL, STREQUAL,STRLESS, STRLESS_EQUAL, STRGREATER, STRGREATER_EQUAL, VERSION_EQUAL, VERSION_LESS, VERSION_LESS_EQUAL, VERSION_GREATER, VERSION_GREATER_EQUAL, MATCHES
    逻辑NOT, AND, OR

if条件语句

if(表达式)
  # 要执行的命令块
elseif(表达式2)
  # 要执行的命令块
else(表达式)
  # 要执行的命令块
endif(表达式)

while循环

while(表达式)
  # 要执行的命令块
endwhile(表达式)

foreach循环遍历

#用法1
foreach(item "A" "B" "C")
  message("${item}")
endforeach(item)

#用法2
foreach(循环变量 RANGE total)
  # 要执行的命令块
endforeach(循环变量)

#用法3
foreach(循环变量 RANGE  start stop [step])
  # 要执行的命令块
endforeach(循环变量)

#用法4
# 支持对列表的遍历
set(list_var "1;2;3;4")
foreach(x IN LISTS list_var)
  message("${x}")
endforeach(x)

命令(函数)

系统命令如set、message、if、while、foreach等。
cmake中的命令(command)本质上就是函数。
自定义函数命令:

function(<name> [<arg1> ...])
# 要执行的命令块
# 不需要返回值!
endfunction()

注意:如果不指定参数列表,则函数可以接受任意的参数,ARGC内置变量表明传人参数的个数,ARGV0, ARGV1, ARGV2, …内置变量可以获得对应传入的参数,ARGV内置变量可以获得整个参数列表。
例如:

function(foo x y z)
  message("Calling function 'foo':")
  message("  x = ${x}")
  message("  y = ${y}")
  message("  z = ${z}")
  message("ARGC = ${ARGC} arg1 = ${ARGV0} arg2 = ${ARGV1} arg3 = ${ARGV2} all args = ${ARGV}")
endfunction(foo)

foo("1" "2" "3")

用宏macro来定义函数:

macro(foo [<arg1> ...])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
endmacro(foo)

foo("1" "2" "3")

宏和函数的基本上是一样的,只是说函数命令有自己的作用域,宏命令的作用域和调用者的作用域一样。

多核编译加速 -j4

make -j4
cmake --build build -j4

编译目录build结构

默认情况下:

  • 对于Linux,非常简单直观:可执行文件被放在bin;静态库(.a)和动态库(.so)都被放在lib。
  • 对于windows(MSVC),非常繁琐:可执行文件(.exe)和动态库的主要部分(.dll)被放在bin;静态库(.lib)和动态库的辅助部分(.lib)被放在lib。
  • 对于windows(Mingw),效果也类似:可执行文件(.exe)和动态库的主要部分(.dll)被放在bin;静态库(.a)和动态库的辅助部分(.dll.a)被放在lib。

自定义不同模式下,编译后的输出目录:

# 设置不同模式下,编译后的输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib")

编译模式

CMake支持四种编译模式:DebugReleaseRelWithDebInfoMinSizeRel
由系统变量 CMAKE_BUILD_TYPE决定。
如:

cmake .. -DCMAKE_BUILD_TYPE=Release

宏定义、编译定义、编译选项、cmake变量

  • 宏定义、编译定义表示同一个概念。只不过在xx.cpp, xx.h中被叫做宏定义(macro);在CMakeLists.txt被叫做编译定义(ompile definitions)而已。
  • 编译选项、cmake变量也表示同一个概念。只不过cmake把编程语言中的变量称为编译选项(compile options)而已。
  • 宏定义
    CMakeLists.txtxx.cpp, xx.h中定义,而在xx.cpp, xx.h中使用!
    例如:
    • CMakeLists.txt中定义宏USE_PYTHON2 :
      target_compile_definitions(targetName PRIVATE USE_PYTHON2)
      
    • 或在xx.cpp, xx.h中定义宏:
      #define USE_PYTHON2 
      
    • xx.cpp, xx.h中使用该宏 :
      #ifdef USE_PYTHON2 
      	//代码块1被编译
      #else
      	//代码块2被编译
      #endif
      
  • cmake变量
    只在 CMakeLists.txt 中定义和使用!可通过cmake -Dxx=xx来动态地修改变量的值!
    例如:
    待续…

什么是target

target 即 “构建目标”。
cmake中的target有以下几种:

  • 可执行文件target(executable target)
  • 静态库target(static library target)
  • 动态库target(shared library target)
  • 其他库:(1)由.o文件组成的OBJECT库;(2)由头文件组成的INTERFACE库(header-only)。

编译库文件

  • 静态、动态库分别用关键字STATIC | SHARED来区分。
  • add_library() 可缺省 STATIC | SHARED 参数,此时默认为STATIC全部生成静态库,但是也可以通过指定 BUILD_SHARED_LIBS为真,修改默认值为SHARED全部生成动态库。
  • 编译静态库
    add_library(static_fun STATIC)
    target_sources(static_fun PRIVATE static_fun.cpp) #库的cpp文件
    # 指明该库编译时和使用时都需要使用的头文件搜索路径:
    target_include_directories(static_fun PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
  • 编译动态库
    add_library(shared_fun SHARED)
    target_sources(shared_fun PRIVATE shared_fun.cpp) #库的cpp文件
    # 指明该库编译时和使用时都需要使用的头文件搜索路径:
    target_include_directories(shared_fun PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
  • 为target取别名
    add_library(demo SHARED)
    add_library(Demo::demo ALIAS demo)
    
  • 对以上代码中PRIVATE参数的解释请看下文。

编译可执行文件

add_executable(test)
target_sources(test PRIVATE test.cpp)
#或者:
add_executable(test PRIVATE test.cpp)

设置target属性

对于一个target,我们引入以下两个概念:

  • build-requirements: 为了正确编译这个target我们需要的一切。
  • usage-requirements: 其它的target为了正确使用当前target所需要的一切,会自动被它的使用方target获取,而build-requirements则不会传播给别的target。

target通常需要设置如下属性:

  • 源文件
  • 头文件搜索路径
  • 链接的库
  • 库搜索路径
  • 宏定义(编译定义compile definitions)
  • 编译选项(cmake变量、compile options)
  • 链接选项
  • 其它编译特点,例如指定C++标准

我们使用如下三个修饰符来指定这些属性的作用范围:

  • PRIVATE: 私有的,表示只用于build-requirements,不用于usage-requirements.
  • INTERFACE: 接口化的,只用于usage-requirements,不用于build-requirements.
  • PUBLIC: 公开的,既用于usage-requirements,又用于build-requirements.

例如:

#编译一个动态库target
add_library(shared_fun SHARED)

#编译target之前需要设置一些必要的属性:
#(1)源文件
target_sources(<target> PRIVATE <source-file>...)
#(2)头文件搜索路径
target_include_directories(<target> PRIVATE <include-search-dir>...)
#(3)预处理的宏定义
target_compile_definitions(<target> PRIVATE <macro-definitions>...)
#(4)编译选项
target_compile_options(<target> PRIVATE <compile-option>...)
#(5)链接相关的库
target_link_libraries(<target> PRIVATE <dependency>...)
#(6)库搜索路径
target_link_directories(<target> PRIVATE <linker-search-dir>...)
#(7)链接选项
target_link_options(<target> PRIVATE <linker-option>...)
# (8)其它编译特点,例如指定C++标准
target_compile_features(<target> PRIVATE <feature>...)

这里的PRIVATE换成INTERFACE和PUBLIC均可。建议总是加上这些修饰符,虽然有时候省略也是合法的语法,但不是modern cmake推荐的用法。target_link_libraries 既支持链接到CMake构建的target,也支持连接到一个已经安装在某目录下的库。
除了使用target_xxx来设置target的属性外,还可以使用get_target_propertyset_target_properties命令,请参考https://zhuanlan.zhihu.com/p/653279430

get_target_property 和 set_target_properties

除了使用target_xxx来设置target的属性外,还可以使用get_target_propertyset_target_properties命令,请参考https://zhuanlan.zhihu.com/p/653279430
例如:

set_target_properties(demo PROPERTIES INTERFACE_INCLUDE_DIRECTORIES src/include)
set_target_properties(demo PROPERTIES INCLUDE_DIRECTORIES src/include)
# 分别等价于:
target_include_directories(demo INTERFACE src/include)
target_include_directories(demo PRIVATE src/include)

添加源文件

  • 用法1
    add_executable(targetXX PRIVATE main.cpp a.cpp b.cpp)
    
  • 用法2
    add_executable(targetXX)
    target_sources(targetXX PRIVATE main.cpp a.cpp b.cpp)
    
  • 用法3
    先将搜索结果存储到变量varXX,再添加到target。
    file(GLOB varXX src/*.cpp include/*.h)
    target_sources(targetXX PRIVATE ${varXX})
    
    这里其实没必要添加头文件,只是便于VS项目的生成,因为如果不添加头文件,则生成VS项目时会把头文件直接排除出去。
  • 用法4
    使用关键字GLOB_RECURSE递归查找源文件。
    file(GLOB_RECURSE CONFIGURE_DEPENDS varXX src/*.cpp include/*.h)
    target_sources(targetXX PRIVATE ${varXX})
    
  • 用法5
    使用aux_source_directory命令自动搜集添加当前目录下的源文件。
    add_executable(targetXX)
    aux_source_directory(. varXX)
    aux_source_directory(./src varXX)
    target_sources(targetXX ${varXX})
    

编译结果后缀

请参考:https://cgold.readthedocs.io/en/latest/index.html
略…

INTERFACE库、OBJECT库、IMPORTED目标

请参考:https://cgold.readthedocs.io/en/latest/index.html
略…

6. CMake多文件项目示例

这里是一个多文件项目示例,包含多层级CMakeLists,这里暂不涉及对第三方库的依赖,不涉及一些编译的复杂逻辑,不涉及安装。项目在Linux平台上进行。

  • Demo项目的文件结构如下:
    在这里插入图片描述

  • Demo项目包括:

    • 单独生成可执行文件test1。
    • 首先生成静态库static_fun,然后生成可执行文件test2,test2调用静态库static_fun。
    • 首先生成动态库shared_fun,然后生成可执行文件test3,test3调用动态库shared_fun。
  • 两个库的头文件分别为:

    // static_fun.h
    void static_function();
    
    // shared_fun.h
    void shared_function();
    
  • 两个库的源文件依次为:

    // static_fun.cpp
    #include "static_fun.h"
    #include <cstdio>
    
    void static_function(){
        printf("this is static_function\n");
        return;
    }
    
    // shared_fun.cpp
    #include "shared_fun.h"
    #include <cstdio>
    
    void shared_function(){
        printf("this is shared_function\n");
        return;
    }
    
  • 三个可执行文件的源文件依次为:

    // test1.cpp
    #include <cstdio>
    
    int main(){
        printf("这是单文件测试\nhello,world\n");
        return 0;
    }
    
    // test2.cpp
    #include "static_fun.h"
    #include <cstdio>
    
    int main(){
        printf("这是main函数, 调用静态库测试\n");
        static_function();
    
        return 0;
    }
    
    // test3.cpp
    #include "shared_fun.h"
    #include <cstdio>
    
    int main(){
        printf("这是main函数, 调用动态库测试\n");
        shared_function();
    
        return 0;
    }
    
  • 项目根目录下的CMakeLists.txt如下:

    # CMakeLists(0)
    
    cmake_minimum_required(VERSION 3.10)
    
    project(Demo VERSION 0.1)
    
    # 设置不同模式下,编译后的输出目录
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/bin")
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/bin")
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib")
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib")
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib")
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib")
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib")
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib")
    
    add_subdirectory(source)
    
  • 各级子目录下的CMakeLists.txt依次为:

    # CMakeLists(1)
    add_subdirectory(test1)
    add_subdirectory(test2)
    add_subdirectory(test3)
    
    # CMakeLists(2)
    add_executable(test1)
    target_sources(test1 PRIVATE test1.cpp)
    
    # CMakeLists(3)
    add_subdirectory(static_fun)
    add_executable(test2)
    target_sources(test2 PRIVATE test2.cpp)
    target_link_libraries(test2 PUBLIC static_fun)
    
    # CMakeLists(4)
    add_subdirectory(shared_fun)
    add_executable(test3)
    target_sources(test3 PRIVATE test3.cpp)
    target_link_libraries(test3 PUBLIC shared_fun)
    
    # CMakeLists(5)
    add_library(static_fun STATIC)
    target_sources(static_fun PRIVATE static_fun.cpp)
    target_include_directories(static_fun PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
    # CMakeLists(6)
    add_library(shared_fun SHARED)
    target_sources(shared_fun PRIVATE shared_fun.cpp)
    target_include_directories(shared_fun PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
  • 生成构建系统,编译完成后,我们会得到两个库和三个可执行文件,分别存放在bin和lib目录:
    在这里插入图片描述

  • 参考:https://zhuanlan.zhihu.com/p/653279430

参考文献

【0】https://cmake.org/cmake/help/latest/index.html
【1】https://blog.csdn.net/m0_38036750/article/details/131490392
【2】https://blog.csdn.net/FL1768317420/article/details/137001232
【3】https://preshing.com/20170522/learn-cmakes-scripting-language-in-15-minutes/
【4】https://zhuanlan.zhihu.com/p/653279430
【5】https://cgold.readthedocs.io/en/latest/index.html
【6】https://www.jianshu.com/p/1ec2b5602b03



在这里插入图片描述

点赞、收藏、关注哟!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值