0


【一学就会】(一)C++编译工具链——基于VSCode的CMake、make与g++简单理解与应用示例

一、CMake、make与g++

1、名词辨析

CMake、make与g++分别是项目构建生成工具、项目构建工具和项目编译链接工具的代表之一。他们的关系可以概括为由项目构建生成工具CMake生成项目构建工具make所需的文件,项目构建工具make利用这些文件编译链接项目;而项目编译链接工具g++可以独立完成项目构建生成工具CMake和项目构建工具make的工作。

所谓Makefile文件,即是make编译、链接的规则性说明文件,作用相当于CMakeLists.txt之于CMake.

2、孰优孰劣

  • 既然g++可以完成CMake和make两人的工作,为什么不直接使用g++?

因为直接使用g++的话,面对复杂的工程,多项目、多参数和各种依赖关系,命令代码是很复杂的,而且难以理清工程各种源文件之间的编译顺序,不便于项目管理与维护。对于简单项目,g++自然可以应付的来了。

  • make也可以独立完成项目的编译和链接,为什么还需要CMake的帮助?

一句话,CMake的出现就是为了简化和升华make的工作的。Makefile的编码更加严格,语法规则更加复杂,况且Makefile针对不同的操作系统和编译器,编写内容是不同的,平台移植性差。CMake能够更改好的跨平台。一般地说,支持跨平台的原因是有自己独立运行的机制、检测不同平台的能力和针对不同平台生成对应对象的能力。当然,再能够支持第三方库和工具,则更具有可扩展性。CMake便是如此。他使用一套抽象的编译器和操作系统,能够检测不同的操作系统和编译器,并针对它们生成对应的Makefile文件。同样,CMake支持第三方库和工具。这样,便更有利于开发。

二、应用示例

1、工具类安装与配置

1)VSCode安装与配置

这个不要多说,推荐使用免安装版,因为安装版不可选安装路径,占据C盘空间。

官网:Download Visual Studio Code - Mac, Linux, Windows

2)CMake下载与安装

官网:Download CMake

版本:安装版可以自动环境变量配置。

安装步骤:

第一步,直接next

第二步,agree,继续next

第三步,选择加入系统环境变量,方便起见,为所有用户安装,无需创建桌面图标。

第四步,改变安装路径,继续next

第五步,install,等待安装完成。

第六步,验证。win键唤出开始菜单,敲cmd,打开控制台,输入

cmake -version

3)MinGW-W64下载与安装

说明:MinGW-W64就是充当make的工具,对应于Linux的make构建工具,作用基本相同。在下文会说明使用CMake指定生成MinGW编译器的makefile文件。

官网:MinGW-w64

版本:这里我们直接下载mingw-w64,其他工具不做多介绍。主页点击Downloads。

可以看到由很多版本可以选择,我们直接选择右侧sources

选择SourcesFrogs,下载最新版本就好。

点击files栏

A、科学上网法

下拉之后,选择在线安装程序,点击下载之后,跳转等5秒即可开始下载,下载可能有点慢,稍等即可。

第一步,next

第二步,做出如下选择

version:版本选最新的

Architecture:系统架构,64位选择x86_64;32位选择i686

Threads:Windows系统选择win32;跨平台系统选择posix

Exception:异常处理,不管是32位还是64位的,sjlj都是比较老的,选择第一项最新的即可。

Build revision:没得选,0;若有得选,建议默认即可。

第三步,更改安装位置,选择创建开始菜单。然后科学上网,再点next,等待安装完成。

B、无需科学上网法

在线安装程序的下方,就是各种离线包。依据:

version:版本选最新的

Architecture:系统架构,64位选择x86_64;32位选择i686

Threads:Windows系统选择win32;类Unix系统选择posix

Exception:异常处理,不管是32位还是64位的,sjlj都是比较老的,选择第一项最新的即可。

Build revision:没得选,0;若有得选,建议默认即可。

我的话,就选择x86_64-win32-seh这一项,等待下载完成。

下载完成解压提取文件到新建的文件夹下。建议简化解压目录,剪切mingw64到外层文件夹即可。

设置系统环境变量。

win键唤起开始菜单,输入环境变量,打开编辑系统环境变量。

双击path,新建->浏览,

定位到解压目录即可。

验证:win键唤起开始菜单,输入cmd,输入代码,g++也可以。

gcc -v

弹出版本信息。输入代码

where gcc

显示安装位置。

这样一来,我们已经可以使用cmake和make来编译我们的项目啦。为了使VSCode更好使用,我们安装一些插件。

4)VSCode推荐插件

打开VSCode,快捷键F1,选择extension

A、c/c++编译环境配置

扩展搜索框搜索:c/c++

安装C/C++ Extension Pack,会附带安装4个扩展,方便快捷。


有一个设置弹窗,可以进行设置


显示在侧边栏,方便一些


B、Fitten Code

这是一个类GitHub Copilot插件,可代码补全、代码解释、生成代码以及对话等。就是需要注册一个账号,介意的话,就不要使用了。

2、CMake使用示例

寄语:希望我能做到你不用动手就能获取使用经验

本教程有官方示例,可参阅网址:CMake Tutorial — Mastering CMake

官方文档虽然很正式,但是毕竟是全英文,而且不如结合VSCode实际操作来得直观。本教程还是有一定价值的。我们就参考着官网文档和源码从零开始一步一步展示cmake示例。

官网操作源码地址:CMake Tutorial — CMake 3.29.1 Documentation

点击如下链接即可。

第一步 创建简单项目

A、新建并打开项目文件夹

新建一个空白文件夹(无中文路径是基本默认的)作为我们项目文件夹,打开VSCode,打开文件夹。


B、新建所需文件

新建文件CMakeLists.txt、tutorial.cpp和TutorialConfig.h.in,注意后缀一起当文件名填上,文件大小写是敏感的,这样规范。


C、点击tutorial.cpp文件

输入代码

// A simple program that computes the square root of a number
#include <iostream>
#include <string>
#include "TutorialConfig.h"

#include "MathFunctions.h"

#include <math.h>

int main(int argc, char* argv[])
{
  if (argc < 2) {
    // report version
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  // convert input to double
  const double inputValue = std::stod(argv[1]);

  const double outputValue = mathfunctions::sqrt(inputValue);

  std::cout << "The square root of " << inputValue << " is " << outputValue
            << std::endl;
  return 0;
}


D、点击CMakeLists.txt 文件

输入代码

# 用于设定项目所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 设置项目名称和版本号
project(Tutorial VERSION 1.0)

# 设置c++标准为23
set(CMAKE_CXX_STANDARD 23)
# 设置c++标准为23时,要求编译器必须支持该标准
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置文件,在构建项目时由前者生成后者。
configure_file(TutorialConfig.h.in TutorialConfig.h)

# CMake使用源文件 tutorial.cpp 创建一个名为 "Tutorial" 的可执行文件
add_executable(Tutorial tutorial.cpp)

# 设置头文件搜索路径
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

通俗点说

  • cmake_minimum_required()指定cmake的最低版本,低于指定版本就不行。
  • project()设定最后生成的可执行文件叫什么名字和目前的版本号。
  • set(CMAKE_CXX_STANDARD 23)设置c++标准为23
  • set(CMAKE_CXX_STANDARD_REQUIRED True)设置c++标准为23时,要求编译器必须支持该标准
  • configure_file(TutorialConfig.h.in TutorialConfig.h)设置文件,用于存放cmake的一些设置信息,在构建项目时由前者生成后者。后者默认存放于构建项目生成路径,因而,需要target_include_directories()设置项目包含的头文件所在的路径。
  • add_executable()指示编译哪些源文件,最后生成对应名称的可执行文件。若有多也不多个源文件,如两三个,则用空格隔开就行,如add_executable(mytest a.cpp b.cpp),即是编译a.cpp和b.cpp,生成mytest.exe的可执行文件。还可以这么做
  • set(SRC_LIST a.cpp b.cpp c.cpp)add_executable(${PROJECT_NAME} ${SRC_LIST})
  • set 命令指定 SRC_LIST 变量来表示多个源文件,用 ${SRC_LIST} 获取变量的值。
  • target_include_directories()设置我们的项目应该在哪里找到构建项目的文件。下文会进行外部构建,生成的构建项目的文件将放在build文件夹中,cmake将会自动探测并定位到./build路径。.表示当前路径目录,..表示上一级路径目录。public即是构建文件的可见性为最高级,大家都能见到。

E、点击TutorialConfig.h.in文件

输入代码

// 定义宏,表示项目的版本号,由CMakeLists.txt文件中的set(Tutorial_VERSION_MAJOR 1)和set(Tutorial_VERSION_MINOR 0)命令设置。
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

项目版本号的配置文件。一大一小。编译后,会在build构建文件下生成TutorialConfig.h头文件。


F、构建和运行

打开控制台,顶部栏Terminal->new Terminal,输入代码创建build文件夹

mkdir build


build文件夹将是我们cmake存放生成构建项目文件的文件夹。由 TutorialConfig.h.in文件通过cmake构建生成的TutorialConfig.h就将存放于此。上文

target_include_directories(Tutorial PUBLIC

                       "${PROJECT_BINARY_DIR}"

                       )

PROJECT_BINARY_DIR就将是./build文件目录。这由cmake自动完成探测。

然后,使用控制台cd到文件目录build。即是在当前目录更进一步到build目录。

cd build


1)cmake不指定编译器

准备生成构建项目文件。

  • 此次构建未指定编译器。
  • Windows系统下,如果安装了Visual Studio,默认使用MSVC编译器。后续构建时使用MinGW编译器将会报错。
cmake ..

因为项目的顶级CMakeLists.txt 文件在当前文件目录build的上一级,所以是cmake ..,两个英文句号。一个英文句号.表示当前目录。目录下一定要有CMakeLists.txt 文件。


可以看到,生成的配置文件 TutorialConfig.h。当然,还没有进行编译。这将调用默认编译器进行编译。build不是我们新建的build文件夹哦。

cmake --build .

此时将对项目进行编译。

运行生成的可执行文件,位于./build/Debug文件夹下。

第一次运行控制台少了数字报错错误,看 tutorial.cpp代码即可知道为什么。./Debug/Tutorial 为可执行文件的路径。由于当前在bulid目录,因此用.代替即可。


2)mingw32-make副本改名make

若我们使用MinGW编译器编译文件,将会报错。

mingw32-make
  • 为什么是mingw32-make,而不是Linux常见的make?
  • 请打开mingw64的安装位置,可以找到mingw32-make.exe文件。我们就是使用它编译的。
  • 若想简单点,复制mingw32-make.exe文件,然后粘贴到当前文件夹,系统自动创建副本,然后改名为make.exe,就能使用make了。


3)cmake指定Mingw编译器及各编译器参数

下面我们删除build下的所有文件,使用cmake指定编译器进行生成MakeFiles构建文件。

cmake -G"MinGW Makefiles" ..
  • cmake命令,-G指定使用哪种编译器。MinGW编译器对应的值为“MinGW Makefiles”,同样的,两个英文句号..表示顶层CMakeLists.txt文件在上一级。
  • 其他编译器对应的值为(别问我怎么得到的,MinGW Makefiles打成了MinGW MakeFiles,说明大小写敏感)
  • Visual Studio 17 2022 = Generates Visual Studio 2022 project files. Use -A option to specify architecture. Visual Studio 16 2019 = Generates Visual Studio 2019 project files. Use -A option to specify architecture. Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 12 2013 [arch] = Deprecated. Generates Visual Studio 2013 project files. Optional [arch] can be "Win64" or "ARM". Visual Studio 9 2008 [arch] = Deprecated. Generates Visual Studio 2008 project files. Optional [arch] can be "Win64" or "IA64". Borland Makefiles = Generates Borland makefiles. NMake Makefiles = Generates NMake makefiles. NMake Makefiles JOM = Generates JOM makefiles. MSYS Makefiles = Generates MSYS makefiles. MinGW Makefiles = Generates a make file for use with mingw32-make. Green Hills MULTI = Generates Green Hills MULTI files (experimental, work-in-progress). Unix Makefiles = Generates standard UNIX makefiles. Ninja = Generates build.ninja files. Ninja Multi-Config = Generates build-<Config>.ninja files. Watcom WMake = Generates Watcom WMake makefiles. CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files (deprecated). CodeBlocks - NMake Makefiles = Generates CodeBlocks project files (deprecated). CodeBlocks - NMake Makefiles JOM = Generates CodeBlocks project files (deprecated). CodeBlocks - Ninja = Generates CodeBlocks project files (deprecated). CodeBlocks - Unix Makefiles = Generates CodeBlocks project files (deprecated). CodeLite - MinGW Makefiles = Generates CodeLite project files (deprecated). CodeLite - NMake Makefiles = Generates CodeLite project files (deprecated). CodeLite - Ninja = Generates CodeLite project files (deprecated). CodeLite - Unix Makefiles = Generates CodeLite project files (deprecated). Eclipse CDT4 - NMake Makefiles = Generates Eclipse CDT 4.0 project files (deprecated). Eclipse CDT4 - MinGW Makefiles = Generates Eclipse CDT 4.0 project files (deprecated). Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files (deprecated). Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files (deprecated). Kate - MinGW Makefiles = Generates Kate project files (deprecated). Kate - NMake Makefiles = Generates Kate project files (deprecated). Kate - Ninja = Generates Kate project files (deprecated). Kate - Ninja Multi-Config = Generates Kate project files (deprecated). Kate - Unix Makefiles = Generates Kate project files (deprecated). Sublime Text 2 - MinGW Makefiles = Generates Sublime Text 2 project files (deprecated). Sublime Text 2 - NMake Makefiles = Generates Sublime Text 2 project files (deprecated). Sublime Text 2 - Ninja = Generates Sublime Text 2 project files (deprecated). Sublime Text 2 - Unix Makefiles = Generates Sublime Text 2 project files (deprecated).


然后使用make编译(已经创建mingw32-make.exe的副本改名了)。

make


这时候就看到了区别,可执行文件直接在build文件夹下。而MSVC编译器却在./Debug/下。

然后运行试试。./不能少,指示文件路径。


4)内部构建和外部构建

上述使用的是外部构建,在指定文件夹下生成构建文件和可执行文件;内部构建是直接在下面目录下生成构建文件和可执行文件。显然,推荐使用外部构建,不会让项目目录显得很乱,也方便管理,可随时删除删除的构建文件夹并重建。


第二步 添加库

A、创建子文件夹和库文件

控制台代码创建子文件夹,注意是项目路径,不是在build下。build文件夹只负责构建项目。

mkdir MathFunctions

MathFunctions文件夹下,创建MathFunctions.h文件、MathFunctions.cpp文件、mysqrt.h文件、mysqrt.cpp文件以及CMakeLists.txt文件。


B、点击MathFunctions.h文件

写入代码

#pragma once

namespace mathfunctions
{
    double sqrt(double x);
}


C、点击mysqrt.h文件

输入代码

#pragma once

namespace mathfunctions
{
    namespace detail
    {
        double mysqrt(double x);
    }
}


D、点击MathFunctions.cpp文件

输入代码

#include "MathFunctions.h"
#include <cmath>
#include "mysqrt.h"

namespace mathfunctions
{
  double sqrt(double x)
  {
    // which square root function should we use?
      return detail::mysqrt(x);
  }
}


E、点击mysqrt.cpp文件

输入代码

#include "mysqrt.h"

#include <iostream>

namespace mathfunctions
{
  namespace detail
  {
    // a hack square root calculation using simple operations
    double mysqrt(double x)
    {
      if (x <= 0)
      {
        return 0;
      }

      double result = x;

      // do ten iterations
      for (int i = 0; i < 10; ++i)
      {
        if (result <= 0)
        {
          result = 0.1;
        }
        double delta = x - (result * result);
        result = result + 0.5 * delta / result;
        std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
      }
      return result;
    }
  }
}


F、点击子CMakeLists.txt文件

与官网不同,其实,MathFunctions.cpp和mysqrt.cpp都需要加入库里的。

# 添加一个名为MathFunctions的库,并包含MathFunctions.cpp文件、mysqrt.cpp文件
add_library(MathFunctions MathFunctions.cpp mysqrt.cpp)

  • add_library(MathFunctions MathFunctions.cpp mysqrt.cpp)#就是创建一个名为MathFunctions的库,源文件是MathFunctions.cpp和mysqrt.cpp。这个库可以链接到项目的其他部分,以提供MathFunctions.cpp和mysqrt.cpp中实现的功能。

G、点击顶层CMakeLists.txt文件

在顶层CMakeLists.txt文件中,把MathFunctions库加进去。此时,顶层CMakeLists.txt文件全部代码为:后续各代码的功能我不再赘述了,看代码里的注释就可以了。我会注释的清楚些。

# 用于设定项目所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 设置项目名称和版本号
project(Tutorial VERSION 1.0)

# 设置c++标准为23
set(CMAKE_CXX_STANDARD 23)
# 设置c++标准为23时,要求编译器必须支持该标准
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置文件,在构建项目时由前者生成后者
configure_file(TutorialConfig.h.in TutorialConfig.h)

# 添加一个子目录,包含数学函数的源文件,并将其编译成库文件MathFunctions.lib
# 库文件命名为 "MathFunctions",并添加到当前项目中,使得当前项目可以调用数学函数库中的函数
add_subdirectory(MathFunctions)

# CMake使用源文件 tutorial.cpp 创建一个名为 "Tutorial" 的可执行文件
add_executable(Tutorial tutorial.cpp)

# 将MathFunctions库链接到Tutorial可执行文件中,使得可执行文件可以调用数学函数库中的函数
# 这里使用了target_link_libraries命令,将MathFunctions库添加到Tutorial可执行文件中,
# 并指定链接类型为PUBLIC,表示该库对Tutorial可执行文件可见,其他项目也可以使用该库,
# 而不用链接到该库。
target_link_libraries(Tutorial PUBLIC MathFunctions)

# 将当前项目的源文件目录、二进制目录和MathFunctions库的源文件目录添加到Tutorial可执行文件中,
# 使得Tutorial可执行文件可以找到MathFunctions库中的源文件。
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           "${PROJECT_SOURCE_DIR}/MathFunctions"
                           )


H、将库设为可选项

对于复杂的大型项目,可能需要在不同的情况下,同一代码位置调用不同的库,这就需要将库设置为可选的,这个功能还是很有必要的,工作中也会比较常用到。所以,我单独拿来说,不与G步骤合并。

向顶层CMakeLists.txt文件添加如下代码:

# 设置项目的可选选项,并提供默认值,用户可以根据需要选择是否开启
# 这里的选项表示是否使用本教程提供的数学函数库,中间参数为描述信息,最后一个参数为默认值
option(USE_MYMATH "Use tutorial provided math implementation" ON)


如果MathFunctions库变为可选的了,那么,涉及到MathFunctions库的代码就不能理所当然的直接添加MathFunctions库到当前项目了。

  • if语句用于判断是否使用本教程提供的数学函数库,如果使用,则添加MathFunctions子目录,并将MathFunctions库添加到EXTRA_LIBS列表中,并将MathFunctions库的源文件目录添加到EXTRA_INCLUDES列表中。这里的MathFunctions库的源文件目录是通过add_subdirectory命令添加的,并将MathFunctions目录添加到当前项目的源文件目录中。最后,将MathFunctions库的源文件目录添加到Tutorial可执行文件中,使得Tutorial可执行文件可以找到MathFunctions库中的源文件。这样,Tutorial项目就可以调用MathFunctions库中的函数了。注意,如果不使用本教程提供的数学函数库,则MathFunctions目录及其源文件目录不会被添加到项目中。另外,如果不使用本教程提供的数学函数库,则EXTRA_LIBS列表和EXTRA_INCLUDES列表也不会被添加到Tutorial可执行文件中。这样,Tutorial项目就只能调用系统提供的数学函数库中的函数了。list命令用于将列表中的元素连接成一个字符串,并将结果赋值给变量。这里的${PROJECT_SOURCE_DIR}变量用于获取当前项目的源文件目录。append命令用于将元素添加到列表末尾。这里${PROJECT_SOURCE_DIR}/MathFunctions变量用于获取MathFunctions库的源文件目录。EXTRA_LIBS列表用于保存MathFunctions库的名称,EXTRA_INCLUDES列表用于保存MathFunctions库的源文件目录。

  • 显然,我们已经不能直接使用MathFunctions这个名称了。因为在把Mathfuntions设为可选时,若USE_MYTH为off,则不会定义MathFuntions。在可选代码中,若可选则,MathFuntions添加到EXTRA_LIBS中;若不可选,则不会添加。而EXTRA_LIBS本身是CMake的关键字,故,链接的库应当使用${EXTRA_LIBS}。

  • 同样的,MathFuntions库的路径添加或者不添加进EXTRA_INCLUDES里,取决于USE_MYMATH的值,故,头文件路径使用EXTRA_INCLUDES代替。

最后,顶层CMakeLists.txt文件的代码为:

# 用于设定项目所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 设置项目名称和版本号
project(Tutorial VERSION 1.0)

# 设置c++标准为23
set(CMAKE_CXX_STANDARD 23)
# 设置c++标准为23时,要求编译器必须支持该标准
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置项目的可选选项,并提供默认值,用户可以根据需要选择是否开启
# 这里的选项表示是否使用本教程提供的数学函数库,中间参数为描述信息,最后一个参数为默认值
option(USE_MYMATH "Use tutorial provided math implementation" ON)

# 设置文件,在构建项目时由前者生成后者
configure_file(TutorialConfig.h.in TutorialConfig.h)

# 根据USE_MYMATH选项的值,决定是否添加MathFunctions库作为依赖项
# 添加EXTRA_LIBS额外库,即MathFunctions库
# 添加EXTRA_INCLUDES额外头文件目录,即MathFunctions库的源文件目录
if(USE_MYMATH)
  add_subdirectory(MathFunctions)
  list(APPEND EXTRA_LIBS MathFunctions)
  list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()

# CMake使用源文件 tutorial.cpp 创建一个名为 "Tutorial" 的可执行文件
add_executable(Tutorial tutorial.cpp)

# 将EXTRA_LIBS中的库添加到Tutorial可执行文件中,使得可执行文件可以调用这些库中的函数。
# 这里使用了target_link_libraries命令,将EXTRA_LIBS中的库添加到Tutorial可执行文件中,
# 并指定链接类型为PUBLIC,表示这些库对Tutorial可执行文件可见。
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})      
                           
# 将EXTRA_INCLUDES中的头文件目录添加到Tutorial可执行文件中                    
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           ${EXTRA_INCLUDES}
                           )

不止顶层CMakeLists.txt文件需要更改,用到MathFuntions.h头文件的

tutorial.cpp

源文件同样需要更改。

  • 同样的,MathFuntions.h是否使用,取决于USE_MYMATH的值。


最后,tutorials.cpp的代码为

// A simple program that computes the square root of a number
#include <iostream>
#include <string>
// 如果定义了USE_MYMATH,则使用MathFunctions.h中的函数,否则使用系统自带的sqrt函数
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

#include "TutorialConfig.h"
#include <math.h>

int main(int argc, char* argv[])
{
  if (argc < 2) {
    // report version
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  // convert input to double
  const double inputValue = std::stod(argv[1]);

#ifdef USE_MYMATH
  const double outputValue = mysqrt(inputValue);
#else
  const double outputValue = sqrt(inputValue);
#endif

  std::cout << "The square root of " << inputValue << " is " << outputValue
            << std::endl;
  return 0;
}

项目设置文件TutorialConfig.h.in添加代码:

#cmakedefine USE_MYMATH
  • 按教程添加#cmakedefine USE_MYMATH,VSCode会报错,不用管它,因为我没有找到解决方案。但它害我耽误了几十分钟时间,实在可恨,后续有解决方案再更新,以免耽误本文进度。
  • #cmakedefine与#define是不能相互替换的。因为如果用#define定义宏,是一定会定义的;使用#cmakedefine定义时,会根据宏在CMakeLists.txt文件中的值决定定不定义。
  • 我们更改USE_MYMATH的值,在顶层CMakeLists.txt文件option()下使用这个代码
  • # 通过cache变量方式修改option值(正确方式,力推荐)set(USE_MYMATH ON CACHE BOOL "构建示例模块" FORCE)
  • 注意,option()相当于申明USE_MYMATH,先申明才能使用。同样的,在源文件中,要先#include "TutorialConfig.h"再// 如果定义了USE_MYMATH,则使用MathFunctions.h中的函数,否则使用系统自带的sqrt函数#ifdef USE_MYMATH#include "MathFunctions.h"#endif
  • 因为在配置文件TutorialConfig.h.in中,#cmakedefine USE_MYMATH,生存在TutorialConfig.h文件中,即相当于在TutorialConfig.h文件中声明的USE_MYMATH。注意与CMakeLists.txt文件中的option()声明区分,源文件是引用"TutorialConfig.h"头文件的定义。
  • 现在,我们来验证USE_MYMATH为OFF时,TutorialConfig.h文件会怎样“声明”USE_MYMATH。直接#注销修改USE_MYMATH值的代码,即
  • # 通过cache变量方式修改option值(正确方式,力推荐)#set(USE_MYMATH ON CACHE BOOL "构建示例模块" FORCE)然后,我们只需删除build文件夹下的CMakeCache.txt文件,使用代码
  • rm CMakeCache.txtCMakeCache.txt文件在build文件夹下,所以先cd到build文件夹
  • 然后,重新build
  • cmake -G"MinGW Makefiles" ..此时,我们可以在CMakeCache.txt下看到USE_MYMATH的值。当然,CMakeCache.txt里边还有其他内容。
  • 再看TutorialConfig.h中的定义,已经无定义了。
  • make一下,运行代码,为系统默认函数,而非我们的库MathFunctions中的函数。

现在,让我们设置USE_MYMATH值为ON,按照上面几步重新删除CMakeCache.txt文件,重新编译,运行测试,直接出结果了。

可以看到调用的是MathFuntions库的自定义函数。


第三步 调教库

既然我使用了你这库,把你添加进了我的使用范围,那你是不是该管好你自己?我喊你名字,你答应就行了,怎么还让我去找你的小弟cpp源文件呢?所以,你自己管好你的小弟,我只需喊你就行了。

于是,MathFunctions/CMakeLists.txt就要改改了。向里面添加代码,

# 设置MathFunctions库的头文件搜索路径
# 这里的${CMAKE_CURRENT_SOURCE_DIR}表示当前CMakeLists.txt文件所在的目录
target_include_directories(MathFunctions
          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
          )


目的就是库自己可以找到自己的源文件在哪。

那么,库被调教的可以自己找源文件在哪了,我自然就不需要再把你库的源文件路径写进我里面了呀。所以,在顶层CMakeLists.txt文件里删除相应代码。

  • 假如,我们的文件夹长这样:
  • 我把mysqrt.h和mysqrt.cpp移动到MathFunctions/test文件夹下,那么,MathFunctions/CMakeLists.txt就应该这样写:
  • 和官方教程的对比,可以看出一些不同,这样可以应对不同的情况。
  • 后续我将还原。

最后,顶层CMakeLists.txt长这样:

# 用于设定项目所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 设置项目名称和版本号
project(Tutorial VERSION 1.0)

# 设置c++标准为23
set(CMAKE_CXX_STANDARD 23)
# 设置c++标准为23时,要求编译器必须支持该标准
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置项目的可选选项,并提供默认值,用户可以根据需要选择是否开启
# 这里的选项表示是否使用本教程提供的数学函数库,中间参数为描述信息,最后一个参数为值,不写最后一个参数默认为OFF
option(USE_MYMATH "Use tutorial provided math implementation" OFF)

# 通过cache变量方式修改option值(正确方式,力推荐)
set(USE_MYMATH ON CACHE BOOL "构建示例模块" FORCE)

# 设置文件,在构建项目时由前者生成后者
configure_file(TutorialConfig.h.in TutorialConfig.h)

# 根据USE_MYMATH选项的值,决定是否添加MathFunctions库作为依赖项
# 添加EXTRA_LIBS额外库,即MathFunctions库
if(USE_MYMATH)
  add_subdirectory(MathFunctions)
  list(APPEND EXTRA_LIBS MathFunctions)
endif()

# CMake使用源文件 tutorial.cpp 创建一个名为 "Tutorial" 的可执行文件
add_executable(Tutorial tutorial.cpp)

# 将EXTRA_LIBS中的库添加到Tutorial可执行文件中,使得可执行文件可以调用这些库中的函数。
# 这里使用了target_link_libraries命令,将EXTRA_LIBS中的库添加到Tutorial可执行文件中,
# 并指定链接类型为PUBLIC,表示这些库对Tutorial可执行文件可见。
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})      
                           
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

当然,官方教程提供了其他方法,可供参考。

  • target_compile_definitions
  • target_compile_options
  • target_include_directories
  • target_link_libraries

第四步 安装与调试

A、安装法则

现在MathFunctions/CMakeLists.txt文件下,添加代码

# 安装MathFunctions库和头文件
# MathFunctions库安装到lib目录下,将MathFunctions.h文件安装到include目录下
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)


顶层CMakeLists.txt文件下,添加代码

# 把Tutorial可执行文件和TutorialConfig.h头文件安装到系统目录中, 并指定安装路径,
# 这里Tutorial可执行文件安装到bin目录和TutorialConfig.h头文件安装到include目录中
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
        DESTINATION include
        )

现在,删除CMakeCache.txt文件,重新编译。注意,每次打开控制台要cd到build。

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..
make

以上都是比较熟悉的步骤了,然后,运行install命令,需要管理员权限,以管理员身份运行VSCode即可。默认安装位置C:/Program Files (x86)/Tutorial/bin,这不好。

cmake --insall .

所以,我们选择自定义安装位置的命令

cmake --install . --prefix "../installdir"

  • 这里我强调一下,我们构建项目的文件存储在build,因此,一定要cd到build文件夹下
  • 路径前的两个英文句号,“..”表示build文件夹的上一级,一个句号表示当前即build文件夹。

看结果

运行结果

../installdir/bin/Tutorial 8


B、调试法则

只和顶层CMakeLists.txt有关,添加如下代码,注释很清晰,可以理解的。更改文件别忘了保存哦。

# 表示项目启用测试功能
enable_testing()

# 添加一个测试,测试Tutorial可执行文件是否能正确计算平方根
add_test(NAME Runs COMMAND Tutorial 25)

# 添加一个测试,测试Tutorial可执行文件是否能正确输出使用方法
# 这里使用了set_tests_properties命令,将Usage测试的描述信息设置为"Usage:.*number",
# 并将PASS_REGULAR_EXPRESSION设置为"Usage:.*number",表示测试输出中必须包含Usage:和数字
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
  PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
  )

# 定义一个do_test函数,用于生成一系列测试,并设置测试的描述信息和期望结果
# 这里的测试使用了do_test函数,并将Tutorial可执行文件作为参数,测试输入和期望结果作为参数
# 期望结果使用正则表达式来表示,表示测试输出中必须包含数字和期望结果
# 这里的测试使用了add_test命令,将测试名称设置为Comp${arg},表示测试输入为arg,
# 并将测试命令设置为${target} ${arg},表示测试可执行文件和输入参数
# 这里的测试使用了set_tests_properties命令,将测试的描述信息设置为${result}, 
function(do_test target arg result)
  add_test(NAME Comp${arg} COMMAND ${target} ${arg})
  set_tests_properties(Comp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )
endfunction(do_test)

# 调用do_test函数,生成一系列测试
# 测试输入为数字,期望结果为数字的平方根
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")

现在,删除CMakeCache.txt文件,重新编译。注意,每次打开控制台要cd到build。

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..
make

输入代码

ctest -N

打印

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> ctest -N
Test project E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
  Test #1: Runs
  Test #2: Usage
  Test #3: Comp9
  Test #4: Comp5
  Test #5: Comp7
  Test #6: Comp25
  Test #7: Comp-25
  Test #8: Comp0.0001

Total Tests: 8

输入

ctest -VV

打印的信息很详细。

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> ctest -VV
UpdateCTestConfiguration  from :E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/DartConfiguration.tcl
UpdateCTestConfiguration  from :E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/DartConfiguration.tcl
Test project E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
Constructing a list of tests
Done constructing a list of tests
Updating test list for fixtures
Added 0 tests to meet fixture requirements
Checking test dependency graph...
Checking test dependency graph end
test 1
    Start 1: Runs

1: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "25"
1: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
1: Test timeout computed to be: 10000000
1: Computing sqrt of 25 to be 13
1: Computing sqrt of 25 to be 7.46154
1: Computing sqrt of 25 to be 5.40603
1: Computing sqrt of 25 to be 5.01525
1: Computing sqrt of 25 to be 5.00002
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: The square root of 25 is 5
1/8 Test #1: Runs .............................   Passed    0.01 sec
test 2
    Start 2: Usage

2: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe
2: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
2: Test timeout computed to be: 10000000
2: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Tutorial.exe Version 1.0
2: Usage: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Tutorial.exe number
2/8 Test #2: Usage ............................   Passed    0.01 sec
test 3
    Start 3: Comp9

3: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "9"
3: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
3: Test timeout computed to be: 10000000
3: Computing sqrt of 9 to be 5
3: Computing sqrt of 9 to be 3.4
3: Computing sqrt of 9 to be 3.02353
3: Computing sqrt of 9 to be 3.00009
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: The square root of 9 is 3
3/8 Test #3: Comp9 ............................   Passed    0.01 sec
test 4
    Start 4: Comp5

4: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "5"
4: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
4: Test timeout computed to be: 10000000
4: Computing sqrt of 5 to be 3
4: Computing sqrt of 5 to be 2.33333
4: Computing sqrt of 5 to be 2.2381
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: The square root of 5 is 2.23607
4/8 Test #4: Comp5 ............................   Passed    0.01 sec
test 5
    Start 5: Comp7

5: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "7"
5: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
5: Test timeout computed to be: 10000000
5: Computing sqrt of 7 to be 4
5: Computing sqrt of 7 to be 2.875
5: Computing sqrt of 7 to be 2.65489
5: Computing sqrt of 7 to be 2.64577
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: The square root of 7 is 2.64575
5/8 Test #5: Comp7 ............................   Passed    0.01 sec
test 6
    Start 6: Comp25

6: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "25"
6: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
6: Test timeout computed to be: 10000000
6: Computing sqrt of 25 to be 13
6: Computing sqrt of 25 to be 7.46154
6: Computing sqrt of 25 to be 5.40603
6: Computing sqrt of 25 to be 5.01525
6: Computing sqrt of 25 to be 5.00002
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: The square root of 25 is 5
6/8 Test #6: Comp25 ...........................   Passed    0.01 sec
test 7
    Start 7: Comp-25

7: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "-25"
7: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
7: Test timeout computed to be: 10000000
7: The square root of -25 is 0
7/8 Test #7: Comp-25 ..........................   Passed    0.01 sec
test 8
    Start 8: Comp0.0001

8: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "0.0001"
8: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
8: Test timeout computed to be: 10000000
8: Computing sqrt of 0.0001 to be 0.50005
8: Computing sqrt of 0.0001 to be 0.250125
8: Computing sqrt of 0.0001 to be 0.125262
8: Computing sqrt of 0.0001 to be 0.0630304
8: Computing sqrt of 0.0001 to be 0.0323084
8: Computing sqrt of 0.0001 to be 0.0177018
8: Computing sqrt of 0.0001 to be 0.0116755
8: Computing sqrt of 0.0001 to be 0.0101202
8: Computing sqrt of 0.0001 to be 0.0100007
8: Computing sqrt of 0.0001 to be 0.01
8: The square root of 0.0001 is 0.01
8/8 Test #8: Comp0.0001 .......................   Passed    0.01 sec

100% tests passed, 0 tests failed out of 8

Total Test time (real) =   0.07 sec

输入

ctest -C Debug -VV

打印信息同样很详细

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> ctest -C Debug -VV
UpdateCTestConfiguration  from :E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/DartConfiguration.tcl
UpdateCTestConfiguration  from :E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/DartConfiguration.tcl
Test project E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
Constructing a list of tests
Done constructing a list of tests
Updating test list for fixtures
Added 0 tests to meet fixture requirements
Checking test dependency graph...
Checking test dependency graph end
test 1
    Start 1: Runs

1: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "25"
1: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
1: Test timeout computed to be: 10000000
1: Computing sqrt of 25 to be 13
1: Computing sqrt of 25 to be 7.46154
1: Computing sqrt of 25 to be 5.40603
1: Computing sqrt of 25 to be 5.01525
1: Computing sqrt of 25 to be 5.00002
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: Computing sqrt of 25 to be 5
1: The square root of 25 is 5
1/8 Test #1: Runs .............................   Passed    0.01 sec
test 2
    Start 2: Usage

2: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe
2: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
2: Test timeout computed to be: 10000000
2: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Tutorial.exe Version 1.0
2: Usage: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Tutorial.exe number
2/8 Test #2: Usage ............................   Passed    0.01 sec
test 3
    Start 3: Comp9

3: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "9"
3: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
3: Test timeout computed to be: 10000000
3: Computing sqrt of 9 to be 5
3: Computing sqrt of 9 to be 3.4
3: Computing sqrt of 9 to be 3.02353
3: Computing sqrt of 9 to be 3.00009
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: Computing sqrt of 9 to be 3
3: The square root of 9 is 3
3/8 Test #3: Comp9 ............................   Passed    0.01 sec
test 4
    Start 4: Comp5

4: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "5"
4: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
4: Test timeout computed to be: 10000000
4: Computing sqrt of 5 to be 3
4: Computing sqrt of 5 to be 2.33333
4: Computing sqrt of 5 to be 2.2381
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: Computing sqrt of 5 to be 2.23607
4: The square root of 5 is 2.23607
4/8 Test #4: Comp5 ............................   Passed    0.01 sec
test 5
    Start 5: Comp7

5: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "7"
5: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
5: Test timeout computed to be: 10000000
5: Computing sqrt of 7 to be 4
5: Computing sqrt of 7 to be 2.875
5: Computing sqrt of 7 to be 2.65489
5: Computing sqrt of 7 to be 2.64577
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: Computing sqrt of 7 to be 2.64575
5: The square root of 7 is 2.64575
5/8 Test #5: Comp7 ............................   Passed    0.01 sec
test 6
    Start 6: Comp25

6: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "25"
6: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
6: Test timeout computed to be: 10000000
6: Computing sqrt of 25 to be 13
6: Computing sqrt of 25 to be 7.46154
6: Computing sqrt of 25 to be 5.40603
6: Computing sqrt of 25 to be 5.01525
6: Computing sqrt of 25 to be 5.00002
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: Computing sqrt of 25 to be 5
6: The square root of 25 is 5
6/8 Test #6: Comp25 ...........................   Passed    0.01 sec
test 7
    Start 7: Comp-25

7: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "-25"
7: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
7: Test timeout computed to be: 10000000
7: The square root of -25 is 0
7/8 Test #7: Comp-25 ..........................   Passed    0.01 sec
test 8
    Start 8: Comp0.0001

8: Test command: E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build\Tutorial.exe "0.0001"
8: Working Directory: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
8: Test timeout computed to be: 10000000
8: Computing sqrt of 0.0001 to be 0.50005
8: Computing sqrt of 0.0001 to be 0.250125
8: Computing sqrt of 0.0001 to be 0.125262
8: Computing sqrt of 0.0001 to be 0.0630304
8: Computing sqrt of 0.0001 to be 0.0323084
8: Computing sqrt of 0.0001 to be 0.0177018
8: Computing sqrt of 0.0001 to be 0.0116755
8: Computing sqrt of 0.0001 to be 0.0101202
8: Computing sqrt of 0.0001 to be 0.0100007
8: Computing sqrt of 0.0001 to be 0.01
8: The square root of 0.0001 is 0.01
8/8 Test #8: Comp0.0001 .......................   Passed    0.01 sec

100% tests passed, 0 tests failed out of 8

Total Test time (real) =   0.07 sec

基本属于两种方式,根据需要选择即可。


第五步 设置系统日志和异常处理(系统内省)

主要针对算法源文件,毕竟算法出bug很正常的。

在MathFunctions/CMakeLists.txt文件中,加入有关log日志和exp异常处理的代码。m库为防止有些系统不存在log和exp函数。

# 导入CheckSymbolExists模块,用于检查系统是否存在log和exp函数
# 如果系统不存在log和exp函数,则尝试链接m库
# 如果链接成功,则设置MathFunctions库的链接库为m
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
  unset(HAVE_LOG CACHE)
  unset(HAVE_EXP CACHE)
  set(CMAKE_REQUIRED_LIBRARIES "m")
  check_symbol_exists(log "math.h" HAVE_LOG)
  check_symbol_exists(exp "math.h" HAVE_EXP)
  if(HAVE_LOG AND HAVE_EXP)
    target_link_libraries(MathFunctions PRIVATE m)
  endif()
endif()

有了双重保险之后,在后面添加代码:

# 如果系统存在log和exp函数,则设置MathFunctions库的编译定义HAVE_LOG和HAVE_EXP
if(HAVE_LOG AND HAVE_EXP)
  target_compile_definitions(MathFunctions
                             PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()

然后,在需要使用log和exp函数的代码文件中,使用它们。这里主要是mysqrt.cpp文件,做如下更改:

      // use log and exp if available
#if defined(HAVE_LOG) && defined(HAVE_EXP)
      double result = exp(log(x) * 0.5);
      std::cout << "Computing sqrt of " << x << " to be " << result
                << " using log and exp" << std::endl;
#else
      double result = x;
#endif

还有头文件

#include <cmath>

现在,cd到build文件夹,只需要make一下即可。因为顶层CMakeLists.txt文件并没有更改,一切还是按照它的构建来的,包括MathFunctions/CMakeLists.txt也是它的构建,make的时候,会把这个字库重新编译的。

make

运行

./Tutorial 9

结果(包括make的步骤)

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> make
-- Looking for log
-- Looking for log - found
-- Looking for exp
-- Looking for exp - found
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
[ 20%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/MathFunctions.cpp.obj
[ 40%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cpp.obj
[ 60%] Linking CXX static library libMathFunctions.a
[ 60%] Built target MathFunctions
[ 80%] Linking CXX executable Tutorial.exe
[100%] Built target Tutorial
PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> ./Tutorial 9                              
Computing sqrt of 9 to be 3 using log and exp
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
The square root of 9 is 3

第六步 添加自定义命令和生成文件

我们按照官方教程来,但不完全按照教程来,log和exp就保留着吧。但是,mysqrt.cpp的log和exp代码注释掉,恢复原来的样子。添加的代码,后面会说。


在MathFunctions文件夹下,新建文件MakeTable.cpp

添加代码

// A simple program that builds a sqrt table
#include <cmath>
#include <fstream>
#include <iostream>

int main(int argc, char* argv[])
{
  // make sure we have enough arguments
  if (argc < 2) {
    return 1;
  }

  std::ofstream fout(argv[1], std::ios_base::out);
  const bool fileOpen = fout.is_open();
  if (fileOpen) {
    fout << "double sqrtTable[] = {" << std::endl;
    for (int i = 0; i < 10; ++i) {
      fout << sqrt(static_cast<double>(i)) << "," << std::endl;
    }
    // close the table with a zero
    fout << "0};" << std::endl;
    fout.close();
  }
  return fileOpen ? 0 : 1; // return 0 if wrote the file
}

在MathFunctions/CMakeLists.txt文件最上边,添加代码

# 添加一个名为MakeTable的可执行文件,并包含MakeTable.cpp文件
add_executable(MakeTable MakeTable.cpp)

这是一部分实时画面。


然后这么做

# 添加一个自定义命令,用于生成Table.h文件
# 自定义命令的输出文件为${CMAKE_CURRENT_BINARY_DIR}/Table.h
# 自定义命令的命令为MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
# 自定义命令的依赖文件为MakeTable
add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
)

记得保存文件。Ctrl+S


修改添加的库代码,记得删除旧代码。


下一步,添加部分代码到mysqrt.cpp

      if (x >= 1 && x < 10)
      {
        std::cout << "Use the table to help find an initial value " << std::endl;
        result = sqrtTable[static_cast<int>(x)];
      }

并引入如下代码,此时会报错,莫慌,按步骤往下走,make之后就没事了。

#include "Table.h"

部分实时画面为


没有动顶层CMakeLists.txt,所以make一下,别忘了cd到build,如果需要的话。

cd build
make

运行

 ./Tutorial 9
PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> ./Tutorial 9      
Use the table to help find an initial value 
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
Computing sqrt of 9 to be 3
The square root of 9 is 3
  • 解释一 Table.h在哪里?
  • 编译之后,在bulid/MathFunctions下。请看大屏幕。
  • 解释二 Table.h的内容是什么?为什么?
  • 文件找到了,点击文件,来看内容。请看大屏幕。
  • 是不是很熟悉啊,其实是19的开平方值。10对应0,表示结束。有MathTable.cpp代码产生。这是主要代码,生成了一个文件,就是"Table.h",内容就是上边19的开平方值。
  • std::ofstream fout(argv[1], std::ios_base::out); const bool fileOpen = fout.is_open(); if (fileOpen) { fout << "double sqrtTable[] = {" << std::endl; for (int i = 0; i < 10; ++i) { fout << sqrt(static_cast<double>(i)) << "," << std::endl; } // close the table with a zero fout << "0};" << std::endl; fout.close();
  • 解释三 Table.h的名称和生成位置这么控制的?
  • 就是配置的MathFunctions/CMakeLists.txt文件,只要代码是
  • # 添加一个自定义命令,用于生成Table.h文件# 自定义命令的输出文件为${CMAKE_CURRENT_BINARY_DIR}/Table.h# 自定义命令的命令为MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h# 自定义命令的依赖文件为MakeTableadd_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h DEPENDS MakeTable)

第七步 创建安装包

在第四步的基础上,我们在顶层CMakeLists.txt文件的最下方添加一些代码。

# 生成安装包
# 导入InstallRequiredSystemLibraries模块,用于生成安装包时自动检测系统依赖库
# 设置CPACK_RESOURCE_FILE_LICENSE变量,用于指定安装包的license文件
# 设置CPACK_PACKAGE_VERSION_MAJOR、CPack_PACKAGE_VERSION_MINOR变量,用于指定安装包的版本号
# 并将Tutorial_VERSION_MAJOR、Tutorial_VERSION_MINOR变量的值作为CPack_PACKAGE_VERSION_MAJOR、CPack_PACKAGE_VERSION_MINOR的值
# 这里使用了include(CPack)命令,将CPack模块包含进来
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)

部分实时画面:记得Ctrl+S保存


然后,我们在项目文件夹下新建文件License.txt


因为动了顶层CMakeLists.txt文件,所以。。。

现在,删除CMakeCache.txt文件,重新编译。注意,每次打开控制台要cd到build

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..
make

然后

cpack

然后就报错了

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> cpack
CPack Error: Cannot find NSIS compiler makensis: likely it is not installed, or not in your PATH
CPack Error: Could not read NSIS registry value. This is usually caused by NSIS not being installed. Please install NSIS from http://nsis.sourceforge.net  
CPack Error: Cannot initialize the generator NSIS

缺少NSIS,安装就好了。

NSIS Wiki (sourceforge.io) 这是官网,下载最新的。

跳转之后等5秒。点开三点,点保留。

前面一直默认,然后更改安装目录

一个不勾选,Finish。

再次

cpack

结果

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> cpack
CPack: Create package using NSIS
CPack: Install projects
CPack: - Run preinstall target for: Tutorial
CPack: - Install project: Tutorial []
CPack: Create package
CPack: - package: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Tutorial-1.0-win64.exe generated.


第八步 添加dashboard支持

还是第四步的基础上,更改一些顶层CMakeLists.txt文件的代码。

按官网解释,CTest模块会自动调用测试功能的。


在项目顶层目录新建文件CTestConfig.cmake

写入代码

# 设置CTEST的基本配置信息,包括项目名称、测试时间、测试结果上报方式、CDash地址等。
set(CTEST_PROJECT_NAME "CMakeTutorial")
# 设置测试时间为每天的0点,即每天的0时0分0秒开始测试。
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")
# 设置测试结果上报方式为CDash,并设置CDash地址。
set(CTEST_DROP_METHOD "http")
# 设置CDash地址为http://my.cdash.org/submit.php?project=CMakeTutorial
set(CTEST_DROP_SITE "my.cdash.org")
# 设置项目名称为CMakeTutorial
set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial")
# 设置测试结果上报时是否显示CDash地址。
set(CTEST_DROP_SITE_CDASH TRUE)


因为更改了顶层CMakeLists.txt,所以

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..

这次不要make了。到此为止就好。

然后

用于有多种配置(Debug、Relese、X86平台等等吧)的编译器。

ctest [-VV] -C Debug -D Experimental

或者

ctest [-VV] -D Experimental

输入指令之后,直接enter就好

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\build> ctest [-VV] -C Debug -D Experimental
>>    
   Site: LAPTOP-M5M90PSH
   Build name: Win32-mingw32-make
Create new tag: 20240410-1118 - Experimental
Configure project
   Each . represents 1024 bytes of output
    . Size of output: 0K
Build project
   Each symbol represents 1024 bytes of output.
   '!' represents an error and '*' a warning.
    . Size of output: 0K
   0 Compiler errors
   0 Compiler warnings
Test project E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build
    Start 1: Runs
1/8 Test #1: Runs .............................   Passed    0.01 sec
    Start 2: Usage
2/8 Test #2: Usage ............................   Passed    0.01 sec
    Start 3: Comp9
3/8 Test #3: Comp9 ............................   Passed    0.01 sec
    Start 4: Comp5
4/8 Test #4: Comp5 ............................   Passed    0.01 sec
    Start 5: Comp7
5/8 Test #5: Comp7 ............................   Passed    0.01 sec
    Start 6: Comp25
6/8 Test #6: Comp25 ...........................   Passed    0.01 sec
    Start 7: Comp-25
7/8 Test #7: Comp-25 ..........................   Passed    0.01 sec
    Start 8: Comp0.0001
8/8 Test #8: Comp0.0001 .......................   Passed    0.01 sec

100% tests passed, 0 tests failed out of 8

Total Test time (real) =   0.06 sec
Performing coverage
 Cannot find any coverage files. Ignoring Coverage request.
Submit files
   SubmitURL: http://my.cdash.org/submit.php?project=CMakeTutorial
   Uploaded: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Testing/20240410-1118/Configure.xml
   Uploaded: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Testing/20240410-1118/Build.xml
   Uploaded: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Testing/20240410-1118/Test.xml
   Uploaded: E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/build/Testing/20240410-1118/Done.xml
   Submission successful

结果就上传到这个网址了,CMakeTutorial (cdash.org)

下面是我的结果。


第九步 创建共享库

在这一步,我们旨在让MathFunctions库成为真正的库以共享出去。

后续要改的代码很多,因此,到此之后,我另存了一份项目。

在顶层CMakeLists.txt文件增加代码,再修改部分代码:

# 设置输出目
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
# 设置库文件输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
# 设置可执行文件输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

# 设置项目的可选选项,并提供默认值,用户可以根据需要选择是否开启
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

最后长这样

# 用于设定项目所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 设置项目名称和版本号
project(Tutorial VERSION 1.0)

# 设置c++标准为23
set(CMAKE_CXX_STANDARD 23)
# 设置c++标准为23时,要求编译器必须支持该标准
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置输出目
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
# 设置库文件输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
# 设置可执行文件输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

# 设置项目的可选选项,并提供默认值,用户可以根据需要选择是否开启
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

# 设置文件,在构建项目时由前者生成后者
configure_file(TutorialConfig.h.in TutorialConfig.h)

# 添加MathFunctions库作为依赖项
add_subdirectory(MathFunctions)

# CMake使用源文件 tutorial.cpp 创建一个名为 "Tutorial" 的可执行文件
add_executable(Tutorial tutorial.cpp)

# 并指定MathFunctions链接类型为PUBLIC,表示这些库对Tutorial可执行文件可见。
target_link_libraries(Tutorial PUBLIC MathFunctions)      
                           
# 将EXTRA_INCLUDES中的头文件目录添加到Tutorial可执行文件中                    
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

# 把Tutorial可执行文件和TutorialConfig.h头文件安装到系统目录中, 并指定安装路径,
# 这里Tutorial可执行文件安装到bin目录和TutorialConfig.h头文件安装到include目录中
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
        DESTINATION include
        )

# 导入CTest模块,用于生成测试用例
include(CTest)

# 添加一个测试,测试Tutorial可执行文件是否能正确计算平方根
add_test(NAME Runs COMMAND Tutorial 25)

# 添加一个测试,测试Tutorial可执行文件是否能正确输出使用方法
# 这里使用了set_tests_properties命令,将Usage测试的描述信息设置为"Usage:.*number",
# 并将PASS_REGULAR_EXPRESSION设置为"Usage:.*number",表示测试输出中必须包含Usage:和数字
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
  PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
  )

# 定义一个do_test函数,用于生成一系列测试,并设置测试的描述信息和期望结果
# 这里的测试使用了do_test函数,并将Tutorial可执行文件作为参数,测试输入和期望结果作为参数
# 期望结果使用正则表达式来表示,表示测试输出中必须包含数字和期望结果
# 这里的测试使用了add_test命令,将测试名称设置为Comp${arg},表示测试输入为arg,
# 并将测试命令设置为${target} ${arg},表示测试可执行文件和输入参数
# 这里的测试使用了set_tests_properties命令,将测试的描述信息设置为${result}, 
function(do_test target arg result)
  add_test(NAME Comp${arg} COMMAND ${target} ${arg})
  set_tests_properties(Comp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )
endfunction(do_test)

# 调用do_test函数,生成一系列测试
# 测试输入为数字,期望结果为数字的平方根
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")

# 生成安装包
# 导入InstallRequiredSystemLibraries模块,用于生成安装包时自动检测系统依赖库
# 设置CPACK_RESOURCE_FILE_LICENSE变量,用于指定安装包的license文件
# 设置CPACK_PACKAGE_VERSION_MAJOR、CPack_PACKAGE_VERSION_MINOR变量,用于指定安装包的版本号
# 并将Tutorial_VERSION_MAJOR、Tutorial_VERSION_MINOR变量的值作为CPack_PACKAGE_VERSION_MAJOR、CPack_PACKAGE_VERSION_MINOR的值
# 这里使用了include(CPack)命令,将CPack模块包含进来
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)

在MathFunctions/CMakeLists.txt文件下,修改代码:

# 添加一个名为MakeTable的可执行文件,并包含MakeTable.cpp文件
add_executable(MakeTable MakeTable.cpp)

# 添加一个自定义命令,用于生成Table.h文件
# 自定义命令的输出文件为${CMAKE_CURRENT_BINARY_DIR}/Table.h
# 自定义命令的命令为MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
# 自定义命令的依赖文件为MakeTable
add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
)

# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)

  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")

  # first we add the executable that generates the table
  add_executable(MakeTable MakeTable.cxx)

  # add the command to generate the source code
  add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    DEPENDS MakeTable
    )

  # library that just does sqrt
  add_library(SqrtLibrary STATIC
              mysqrt.cxx
              ${CMAKE_CURRENT_BINARY_DIR}/Table.h
              )

  # state that we depend on our binary dir to find Table.h
  target_include_directories(SqrtLibrary PRIVATE
                             ${CMAKE_CURRENT_BINARY_DIR}
                             )
  # state that SqrtLibrary need PIC when the default is shared libraries
  set_target_properties(SqrtLibrary PROPERTIES
                        POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
                        )
  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()

# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")

# install rules
set(installable_libs MathFunctions)
if(TARGET SqrtLibrary)
  list(APPEND installable_libs SqrtLibrary)
endif()

# 安装MathFunctions库和头文件
# MathFunctions库安装到lib目录下,将MathFunctions.h文件安装到include目录下
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)

# 导入CheckSymbolExists模块,用于检查系统是否存在log和exp函数
# 如果系统不存在log和exp函数,则尝试链接m库
# 如果链接成功,则设置MathFunctions库的链接库为m
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
  unset(HAVE_LOG CACHE)
  unset(HAVE_EXP CACHE)
  set(CMAKE_REQUIRED_LIBRARIES "m")
  check_symbol_exists(log "math.h" HAVE_LOG)
  check_symbol_exists(exp "math.h" HAVE_EXP)
  if(HAVE_LOG AND HAVE_EXP)
    target_link_libraries(MathFunctions PRIVATE m)
  endif()
endif()

# 如果系统存在log和exp函数,则设置MathFunctions库的编译定义HAVE_LOG和HAVE_EXP
if(HAVE_LOG AND HAVE_EXP)
  target_compile_definitions(MathFunctions
                             PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()

然后,重写MathFunctions/MathFunctions.h

#pragma once
#if defined(_WIN32)
#  if defined(EXPORTING_MYMATH)
#    define DECLSPEC __declspec(dllexport)
#  else
#    define DECLSPEC __declspec(dllimport)
#  endif
#else // non windows
#  define DECLSPEC
#endif

namespace mathfunctions
{
    double DECLSPEC sqrt(double x);
}

修改tutorial.cpp文件,去掉#ifdef相关代码:

// A simple program that computes the square root of a number
#include <iostream>
#include <string>
#include "TutorialConfig.h"

#include "MathFunctions.h"
#include <math.h>

int main(int argc, char* argv[])
{
  if (argc < 2) {
    // report version
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  // convert input to double
  const double inputValue = std::stod(argv[1]);

  const double outputValue = mathfunctions::sqrt(inputValue);

  std::cout << "The square root of " << inputValue << " is " << outputValue
            << std::endl;
  return 0;
}

然后,记得管理员权限打开VSCode

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..
make

结果


第十步 添加生成器表达式

修改顶层CMakeLists.txt文件,记得删除旧代码。


然后, 在顶层CMakeLists.txt文件中添加代码

# 设置编译器选项
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
# 设置msvc_cxx为MSVC编译器
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
# 提供编译器选项
target_compile_options(tutorial_compiler_flags INTERFACE
  "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
  "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

然后,

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..
make

生成器表达式可用于启用条件链接、编译时使用的条件定义、条件包含目录等。这些条件可能基于生成配置、目标属性、平台信息或任何其他可查询信息。也就是说,不同的生成系统通过配置生成器表达式将产生不同的生成配置信息。


第十一步 添加导出配置

修改MathFunctions/CMakeLists.txt文件


在顶层CMakeLists.txt文件底部添加代码

# 生成MathFunctions的安装包
install(EXPORT MathFunctionsTargets
  FILE MathFunctionsTargets.cmake
  DESTINATION lib/cmake/MathFunctions
)

修改MathFunctions/CMakeLists.txt文件代码


此时,我们已经将所需的目标信息正确打包,但我们仍然需要生成一个MathFunctionsConfig.cmake,以便 CMake find_package 命令可以找到我们的项目。因此,让我们继续将一个新文件添加到名为 Config.cmake.in 的项目的顶层 。

向Config.cmake.in添加代码。

@PACKAGE_INIT@

include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

然后,为了设置和安装文件,顶层CMakeLists.txt文件底部还需要添加代码:

# 生成MathFunctions的配置文件
# 这里使用了CMakePackageConfigHelpers模块,用于生成配置文件
include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
# 设置配置文件的名称为MathFunctionsConfig.cmake
# 设置配置文件的安装路径为lib/cmake/MathFunctions
# 没有设置和检查宏和没有检查必需组件宏
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION "lib/cmake/example"
  NO_SET_AND_CHECK_MACRO
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
  )
# generate the version file for the config file
# 设置配置文件的版本号为${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}
# 设置配置文件的兼容性为AnyNewerVersion
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
  COMPATIBILITY AnyNewerVersion
)

# install the configuration file
install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
  DESTINATION lib/cmake/MathFunctions
  )

此时,我们已经为项目生成了一个可重定位的 CMake 配置,可以在安装或打包项目后使用。如果我们希望我们的项目也从构建目录中使用,我们只需要将以下内容添加到顶层CMakeLists.txt的底部:

# 输出MathFunctionsTargets.cmake文件
export(EXPORT MathFunctionsTargets
  FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)

然后,

cd build
rm CMakeCache.txt
cmake -G"MinGW Makefiles" ..
make

结果


第十二步 Debug和Release打包

在顶层CMakeLists.txt文件顶部,添加代码:

# 设置Debug模式下编译器的后缀
set(CMAKE_DEBUG_POSTFIX d)
# 添加一个接口库,用于设置编译器的特性
add_library(tutorial_compiler_flags INTERFACE)

在顶层CMakeLists.txt文件add_executable(Tutorial tutorial.cpp)下一行添加代码:

# 设置调试后缀
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})

MathFunctions/CMakeLists.txt

中,设置版本属性:

# 设置MathFunctions库的版本号和SO版本号
set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")

在项目目录下,新建debug和release文件夹

然后,先生成debug

cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .

结果:

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug> cmake --build .
适用于 .NET Framework MSBuild 版本 17.9.5+33de0b227

  1>Checking Build System
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/MathFunctions/CMakeLists.txt
  MakeTable.cpp
  MakeTable.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\MakeTable.exe
  Generating Table.h
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/MathFunctions/CMakeLists.txt
  mysqrt.cpp
  SqrtLibrary.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\SqrtLibraryd.lib
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/MathFunctions/CMakeLists.txt
  MathFunctions.cpp
    正在创建库 E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Debug/MathFunctionsd.lib 和对象 E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/D
  ebug/MathFunctionsd.exp
  MathFunctions.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\MathFunctionsd.dll
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/CMakeLists.txt
  tutorial.cpp
E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\TutorialConfig.h(1,1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存
为 Unicode 格式以防止数据丢失 [E:\jet
Brains\Project\C++\cmaketutorial\tutorialrun\debug\Tutorial.vcxproj]
  (编译源文件“../tutorial.cpp”)

  Tutorial.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\Tutoriald.exe
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/CMakeLists.txt

中间那一块”以防止数据丢失“的原因是 E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\TutorialConfig.h有中文注释,删去即可。记得连同项目目录下的TutorialConfig.h.in文件的注释,因为症结是这。

正常结果:

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug> cmake --build .
适用于 .NET Framework MSBuild 版本 17.9.5+33de0b227

  MakeTable.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\MakeTable.exe
  SqrtLibrary.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\SqrtLibraryd.lib
  MathFunctions.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\MathFunctionsd.dll
  tutorial.cpp
  Tutorial.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\debug\Debug\Tutoriald.exe

然后,Release,".."两点表示上一级目录,因为现在在与release同级的debug文件夹。

cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
  • 解释
  • cmake -DCMAKE_BUILD_TYPE=Release ..:两个点是因为cmake创建构建项目的文件是根据顶层CMakeLists.txt文件,在release上一级。
  • cmake --build .:一个点是因为cmake --build的文件是MakeFiles的文件,也就是在release文件夹下生成的文件,所以,是一个点表示本级目录。

结果:

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\release> cmake --build .
适用于 .NET Framework MSBuild 版本 17.9.5+33de0b227

  1>Checking Build System
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/MathFunctions/CMakeLists.txt
  MakeTable.cpp
  MakeTable.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\release\Debug\MakeTable.exe
  Generating Table.h
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/MathFunctions/CMakeLists.txt
  mysqrt.cpp
  SqrtLibrary.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\release\Debug\SqrtLibraryd.lib
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/MathFunctions/CMakeLists.txt
  MathFunctions.cpp
    正在创建库 E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/release/Debug/MathFunctionsd.lib 和对象 E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/relea
  se/Debug/MathFunctionsd.exp
  MathFunctions.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\release\Debug\MathFunctionsd.dll
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/CMakeLists.txt
  tutorial.cpp
  Tutorial.vcxproj -> E:\jetBrains\Project\C++\cmaketutorial\tutorialrun\release\Debug\Tutoriald.exe
  Building Custom Rule E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/CMakeLists.txt

注意:此示例对单配置生成器有效,不适用于多配置生成器(例如 Visual Studio)。

默认情况下,CMake 的模型是构建目录仅包含单个配置,无论是 Debug、Release、MinSizeRel 还是 RelWithDebInfo。但是,可以设置 CPack 以捆绑多个生成目录并构造包含同一项目的多个配置的包。

首先,我们希望确保调试和发布版本对将要安装的可执行文件和库使用不同的名称。让我们使用 d 作为调试可执行文件和库的后缀。

所以,最后这个不是单配置生成器的,就可以不用试了,直接跳过。

然后,最后在本例中,我们为了同时安装 debug 和 release,在项目目录下,新建文件

MultiCPackConfig.cmake

MultiCPackConfig.cmake中,添加代码
include("release/CPackConfig.cmake")
# Add debug and release configurations to the CPACK_INSTALL_CMAKE_PROJECTS variable
set(CPACK_INSTALL_CMAKE_PROJECTS
    "debug;Tutorial;ALL;/"
    "release;Tutorial;ALL;/"
    )

cd到项目目录下

cd ..

执行命令

cpack --config MultiCPackConfig.cmake

结果

PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun> cpack --config MultiCPackConfig.cmake
>>
CPack: Create package using NSIS
CPack: Install projects
CPack: - Install project: Tutorial []
CMake Error at debug/cmake_install.cmake:39 (file):
  file INSTALL cannot find
  "E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Release/Tutorial.exe":
  File exists.

CMake Error at E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/MathFunctions/cmake_install.cmake:51 (file):
  file INSTALL cannot find
  "E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Release/MathFunctions.dll":
  File exists.
Call Stack (most recent call first):
  debug/cmake_install.cmake:118 (include)

CMake Error at E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/MathFunctions/cmake_install.cmake:87 (file):
  file INSTALL cannot find
  "E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Release/SqrtLibrary.lib":
  File exists.
Call Stack (most recent call first):
  debug/cmake_install.cmake:118 (include)

CPack Error: Error when generating package: Tutorial
PS E:\jetBrains\Project\C++\cmaketutorial\tutorialrun> cpack --config MultiCPackConfig.cmake
>>
CPack: Create package using NSIS
CPack: Install projects
CPack: - Install project: Tutorial []
CMake Error at debug/cmake_install.cmake:39 (file):
  file INSTALL cannot find
  "E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Release/Tutorial.exe":
  File exists.

CMake Error at E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/MathFunctions/cmake_install.cmake:51 (file):
  file INSTALL cannot find
  "E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Release/MathFunctions.dll":
  File exists.
Call Stack (most recent call first):
  debug/cmake_install.cmake:118 (include)

CMake Error at E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/MathFunctions/cmake_install.cmake:87 (file):
  file INSTALL cannot find
  "E:/jetBrains/Project/C++/cmaketutorial/tutorialrun/debug/Release/SqrtLibrary.lib":
  File exists.
Call Stack (most recent call first):
  debug/cmake_install.cmake:118 (include)

CPack Error: Error when generating package: Tutorial

到此CMake使用教程,结束。

3、g++使用教程

本文将侧重于CMake教程示例。

使用g++很难做到CMake和make结合起来的效果,因此,就简单列出一些命令:

A、编译单个源文件并生成可执行文件program:

g++ main.cpp -o program

B、编译多个源文件并生成可执行文件program:

g++ a.cpp b.cpp -o program

C、指定 C++ 编译版本生成可执行文件:

g++ -std=c++23 main.cpp -o program

D、添加头文件和库文件的搜索路径:

g++ -Iinclude -Llib -lmylib main.cpp -o program

E、生成调试信息:

g++ -g main.cpp -o program

F、启用警告并将视为错误:

g++ -Wall -Werror main.cpp -o program

全文完,共53250字。

标签: c++ vscode cmake

本文转载自: https://blog.csdn.net/weixin_46248871/article/details/137500744
版权归原作者 一度雨 所有, 如有侵权,请联系我们删除。

“【一学就会】(一)C++编译工具链——基于VSCode的CMake、make与g++简单理解与应用示例”的评论:

还没有评论