从 0 到 1 学 CMake:开启高效跨平台构建之旅

开发 开发工具 Linux
无论是初涉编程的新手,还是久经沙场的开发老兵,尤其是那些投身 C++、C 等编译型语言项目的开发者,掌握 CMake 都将成为你提升开发效率、迈向更高层次的关键一步。

在当今软件开发生态系统中,构建工具宛如幕后的 “大管家”,掌控着从源代码到可执行程序或库的复杂流程。它们不仅要应对不同编程语言的特性,还需适配各类操作系统与编译器组合,其重要性不言而喻。今天,我们要聚焦一款在跨平台构建领域熠熠生辉的工具 ——CMake。

你是否曾为在 Windows、Linux 和 macOS 等不同平台上,将代码顺利转化为可运行程序而绞尽脑汁?是否在面对复杂项目中众多源文件、库依赖时,感到构建过程犹如一团乱麻,无从下手?CMake 的诞生,正是为了化解这些棘手难题。它就像一位经验丰富、足智多谋的项目经理,能有条不紊地管理项目构建的全过程,让你的代码跨越平台的界限,在各种环境中都能精准无误地编译和运行。

无论是初涉编程的新手,还是久经沙场的开发老兵,尤其是那些投身 C++、C 等编译型语言项目的开发者,掌握 CMake 都将成为你提升开发效率、迈向更高层次的关键一步。接下来,就让我们一同深入 CMake 的奇妙世界,开启这场从 0 到 1 的探索之旅 。

一、CMake简介

1. 什么是CMake

CMake是个一个开源的跨平台自动化建构系统,用来管理软件建置的程序,并不相依于某特定编译器;并可支持多层目录、多个应用程序与多个库。它用配置文件控制建构过程(build process)的方式和Unix的make相似,只是CMake的配置文件取名为CMakeLists.txt。

CMake并不直接建构出最终的软件,而是产生标准的建构档(如Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是CMake和SCons等其他类似系统的区别之处。它首先允许开发者编写一种平台无关的CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。

显然,CMake 是一个比上述几种 make 更高级的编译配置工具。“CMake”这个名字是"Cross platform MAke"的缩写。虽然名字中含有"make",但是CMake和Unix上常见的“make”系统是分开的,而且更为高端。它可与原生建置环境结合使用,例如:make、苹果的Xcode与微软的Visual Studio。

2. 为什么选择 CMake?

在软件开发的构建领域中,CMake 凭借众多显著优点脱颖而出,成为众多开发者的首选。

(1) 跨平台性:在如今多样化的开发环境下,一款软件往往需要在不同的操作系统上运行,如 Windows、Linux、macOS 等,甚至还可能涉及到移动平台如 Android 。CMake 强大的跨平台特性就像是一位万能的翻译,它允许开发者编写一次构建脚本,然后就能在各种主流操作系统上生成对应的构建文件,确保软件在不同平台上都能顺利编译和运行 。以一个简单的 C++ 项目为例,无论是在 Windows 系统下使用 Visual Studio 编译器,还是在 Linux 系统下使用 GCC 编译器,只需一份相同的 CMakeLists.txt 文件,CMake 就能根据不同的平台环境生成合适的构建脚本,大大提高了开发效率和代码的可移植性。

(2) 模块化:当项目规模逐渐增大,代码量不断增多时,合理的模块划分对于项目的管理和维护至关重要。CMake 支持模块化的项目配置,开发者可以将项目按照功能、模块等维度进行拆分,每个模块都有自己独立的 CMakeLists.txt 文件 。这样一来,各个模块的构建规则和依赖关系都能清晰地定义和管理,不仅方便了开发过程中的协作,也使得项目的结构更加清晰,易于维护和扩展。例如,在一个大型游戏开发项目中,可能会将游戏的渲染模块、逻辑模块、网络模块等分别独立管理,每个模块都可以独立编译、测试和更新,而不会影响到其他模块的正常运行。

(3) 可扩展性:随着项目需求的不断变化和发展,构建系统也需要具备一定的灵活性和可扩展性。CMake 允许开发者编写自定义的模块和宏,以满足项目特定的构建需求 。比如,在一些对编译优化有特殊要求的项目中,开发者可以编写自定义的 CMake 模块,添加特定的编译选项和链接库;在一些需要自动化部署的项目中,开发者可以通过编写宏来实现自动化的打包和发布流程。这种高度的可扩展性使得 CMake 能够适应各种复杂的项目场景,无论是小型的个人项目,还是大型的企业级项目,都能发挥出它的优势。

(4) 自动依赖管理:在项目开发过程中,依赖关系的管理往往是一个繁琐且容易出错的环节。CMake 能够自动检测项目所依赖的库文件和头文件,并在构建过程中自动处理这些依赖关系 。它通过 find_package 等命令,可以在系统中查找指定的依赖库,并设置相应的变量,确保项目在编译和链接时能够正确地找到这些依赖项。例如,当项目依赖于 OpenCV 库时,只需在 CMakeLists.txt 文件中使用 find_package (OpenCV REQUIRED) 命令,CMake 就会自动查找系统中安装的 OpenCV 库,并将其包含路径和库文件路径设置好,开发者无需手动去配置这些复杂的路径信息,大大减少了因依赖管理不当而导致的错误。

(5) 简化构建过程:相较于传统的构建方式,如手动编写 Makefile 文件,CMake 使用一种简洁、直观的语法来描述项目的构建规则 。开发者只需要在 CMakeLists.txt 文件中使用简单的命令,如 project 定义项目名称、add_executable 生成可执行文件、add_library 生成库文件等,就能清晰地定义项目的构建过程。而且,CMake 还提供了丰富的命令和选项,方便开发者进行各种配置,如设置编译选项、添加链接库、定义自定义目标等。这使得构建过程更加自动化和规范化,降低了开发者的学习成本和工作量。

与传统的构建方式相比,CMake 的优势更加明显。在传统的 Makefile 构建方式中,对于不同的平台和编译器,可能需要编写不同的 Makefile 文件,而且Makefile的语法较为复杂,对于复杂的项目依赖关系处理起来也比较困难。而 CMake 通过统一的构建脚本和强大的功能,解决了这些问题,让构建过程变得更加简单、高效和可靠 。

二、CMake使用教程

1. 安装 CMake

CMake 的安装过程相对简单,下面将分别介绍在 Windows、Linux 和 macOS 系统下的安装步骤。

(1) Windows 系统:

  • 首先,前往CMake 官方网站下载 Windows 版本的安装包,通常是.msi 文件。
  • 下载完成后,双击.msi 文件,按照安装向导的指示进行安装。在安装过程中,务必勾选 “Add CMake to the system PATH for all users” 选项,这样可以将 CMake 添加到系统的 PATH 环境变量中,方便后续在命令行中直接使用 cmake 命令。
  • 安装完成后,打开命令提示符(CMD)或 PowerShell,输入cmake --version,如果能正确显示 CMake 的版本信息,说明安装成功。例如:cmake version 3.26.4 。

(2) Linux 系统:不同的 Linux 发行版安装方式略有不同。

(3) Debian 和 Ubuntu 系统:打开终端,输入以下命令进行安装:

sudo apt-get update
sudo apt-get install cmake
  • 1.
  • 2.

(4) Fedora 系统:在终端中执行:

sudo dnf install cmake
  • 1.

(5) Arch Linux 系统:使用以下命令安装:

sudo pacman -S cmake
  • 1.

安装完成后,在终端输入cmake --version验证安装是否成功。

(6) macOS 系统:

通过 Homebrew 安装:如果你的macOS 系统安装了 Homebrew 包管理器,打开终端,执行以下命令即可安装 CMake:

brew install cmake
  • 1.

通过官方安装包:访问CMake官方网站,选择 macOS 版本的.dmg 文件进行下载。下载完成后,运行.dmg 文件,将 CMake 图标拖动到应用程序文件夹。

安装成功后,命令都在/Applications/CMake.app/Contents/bin目录下,需要将环境变量添加到.bash_profile文件中。使用 vim 进行编辑:

vim ~/.bash_profile
  • 1.

将以下内容添加到文件末尾:

export PATH="/Applications/CMake.app/Contents/bin:$PATH"
  • 1.

添加完成后,执行source ~/.bash_profile或者重新启动终端。最后,在终端输入cmake --version确认 CMake 已正确安装。在使用 CMake 构建项目之前,需要了解几个重要的概念:

  • CMakeLists.txt 文件:这是 CMake 构建系统的核心配置文件,每个项目或子目录都可以有一个 CMakeLists.txt 文件。它包含了一系列的 CMake 命令和指令,用于描述项目的构建过程,例如指定项目名称、源文件位置、链接库、编译选项等。通过编写 CMakeLists.txt 文件,开发者可以以一种平台无关的方式定义项目的构建规则,CMake 会根据这些规则生成适合不同平台的构建文件 。
  • 生成器:CMake 本身并不直接构建项目,而是通过生成器(Generator)来生成构建文件。生成器是一种模板驱动的机制,它根据 CMakeLists.txt 文件和用户的配置,生成特定平台和构建工具所需的构建文件,如 Unix 系统下的 Makefile、Windows 系统下 Visual Studio 的项目文件(.sln 和.vcxproj)、Xcode 项目文件(.xcodeproj)等 。不同的生成器适用于不同的开发环境和构建需求,开发者可以根据项目的实际情况选择合适的生成器。
  • 构建目录:构建目录是存放生成的构建文件以及编译过程中产生的中间文件和最终目标文件的地方。为了保持项目源代码的整洁,通常建议将构建目录与源代码目录分开。在构建项目时,首先进入构建目录,然后执行 CMake 命令生成构建文件,最后使用构建工具(如 Make、Ninja 等)在该目录下进行项目的编译和链接 。例如,在一个名为my_project的项目中,可以在项目根目录下创建一个build目录作为构建目录,所有的构建相关文件都将在这个目录中生成和处理。

2. 基础的构建步骤(步骤1)

一个最常用的基础项目是从源码中构建一个可执行文件。对于一个简单的项目两行 CMakeLists.txt 文件就能搞定:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
  • 1.
  • 2.
  • 3.

上面的例子中使用了小写的命令,事实上,CMakeLists.txt 文件并不区分命令的大小写。tutorial.cxx源码是用来计算一个数的算数平方根,下面是其一个简单的版本:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  system("pause");
  return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

译者这里在 16 行附加了一行system("pause");,是为了程序执行完毕后不会立刻关闭窗口。后面的代码的示例中并不会再添加此行,如果用户需要暂停的话,可以在自己的代码中加入该行。

(1) 添加版本号和配置头文件

第一个要添加的特性就是给我们的可执行文件和项目提供一个版本号。你可以在你的源码之外做到这一点,在 CMakeLists.txt 文件中做这些会更加灵活。为了添加一个版本号我们修改我们的 CMakeList.txt 文件如下:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
 
# add the executable
add_executable(Tutorial tutorial.cxx)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

因为配置文件会被写入到生成路径(binary tree) 中,所以我们必须将该文件夹添加到头文件搜索路径中。接下来我们在源码中创建一个包含以下内容的 http://TutorialConfig.h.in 文件:

// the configured options and settings for Tutorial #define Tutorial_VERSION_MAJOR 
@Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
  • 1.
  • 2.

当 CMake 配置这个头文件的时候,@Tutorial_VERSION_MAJOR@ 和 @ Tutorial_VERSION_MINOR@ 就会用CMakeLists.txt 文件中对应的值替换。接下来我们修改 tutorial.cxx 源码包含配置头文件并使用版本号,修改后的源码如下:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
 
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n",
            argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

(2) 构建项目并执行文件

官方并没有给出如何构建项目,这里以 VS 为例介绍如何构建以上项目并编译执行。在该目录下面建立 build 文件夹,并新建 run.cmd 文件,编写内容如下:

echo off
echo build:
cmake -G "Visual Studio 15 2017 Win64" ..
echo compile:
devenv Tutorial.sln /build "Debug|x64"
echo run:
start ./Debug/Tutorial.exe %1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

上面脚本中 echo命令主要是用来输出提示信息,可以忽略。剩下一共有三行代码。第3行代码为使用 CMake 构建工程文件.-G 参数用来指定编译器,如果不写这里会找到一个默认的编译器。我这里默认的编译器就是 VS2017,但是默认构建的程序为 32 位程序,我这里显示的指定使用 VS2017 构建 64 位程序。

第5行代码是使用命令行的形式编译 VS 的 .sln 文件。关于命令行构建 VS 项目这里不做过多介绍,有兴趣可以参考微软官方给出的 Devenv command line switches。当然我们也可以使用 VS 打开 .sln 文件,然后手动点击 生成 。第7行代码为运行程序。

3. 添加一个库文件(步骤2)

现在我们将会给我们的项目添加一个库文件。这个库文件包含了我们自己实现的开方运算。可执行文件使用这个库替代编译器提供的标准开方运算。本教程中我们将其放到 MathFunctions 文件夹下,该文件夹下还有一个包含下面一行代码的 CMakeLists.txt 文件。

add_library(MathFunctions mysqrt.cxx)
  • 1.

mysqrt.cxx 文件只有一个名为 mysqrt 的函数,其提供了和标准库 sqrt 相同的功能。内容如下(官网官方例程中可以找到):

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

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

  double result;
  double delta;
  result = x;

  // do ten iterations
  int i;
  for (i = 0; i < 10; ++i) {
    if (result <= 0) {
      result = 0.1;
    }
    delta = x - (result * result);
    result = result + 0.5 * delta / result;
    fprintf(stdout, "Computing sqrt of %g to be %g\n", x, result);
  }
  return result;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

对应的头文件为 MathFunction.h,其内容如下:

double mysqrt(double x);
  • 1.

为了构建并使用新的库文件,我们需要在顶层 CMakeList.txt 文件添加 add_subdirectory 语句。我们需要添加额外的头文件包含路径,以便将包含函数原型的 MathFunctions/MathFunctions.h 头文件包含进来。最后我们还需要给可执行文件添加库。

最终顶层 CMakeList.txt 文件的最后几行如下所示:

include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions) 
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

现在我们考虑将 MathFunctions 库作为一个可选项。虽然在这里并没有什么必要,但是如果库文件很大或者库文件依赖第三方库你可能就希望这么做了。首先先在顶层 CMakeLists.txt 文件添加一个选项:

# should we use our own math functions?
option (USE_MYMATH 
        "Use tutorial provided math implementation" ON)
  • 1.
  • 2.
  • 3.

这个选项会在 CMake GUI 中显示并会将默认值设置为 ON,用户可以根据需求修改该值。这个设置会本保存下来,所以用户无需在每次运行 CMake 时都去设置。接下来就是将构建和连接 MathFunctions 设置为可选项。修改顶层的 CMakeLists.txt 文件如下所示:

# add the MathFunctions library?
#
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

这里使用 USE_MYMATH 来决定是否编译并使用 MathFunctions 。注意收集可执行文件的可选连接库所使用的变量(这里为 EXTRA_LIBS)的使用方法。这种方法在保持有许多可选组件的大型项目整洁时经常使用。对应的我们修改源码如下:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
 
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n", argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
 
  double inputValue = atof(argv[1]);
 
#ifdef USE_MYMATH
  double outputValue = mysqrt(inputValue);
#else
  double outputValue = sqrt(inputValue);
#endif
 
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

在源码中我们同样使用了 USE_MYMATH 宏。这个宏由 CMake 通过在配置文件 TutorialConfig.h 添加以下代码传递给源码:

#cmakedefine USE_MYMATH
  • 1.

构建、编译和运行使用的代码和上一节相同。

4. 安装和测试(步骤3)

下一步我们将给我们的项目添加安装规则和测试。安装规则简单明了。对于 MathFunctions 库的安装,我们通过在 MathFunction 的 CMakeLists.txt 文件中添加以下两行来设置其库和头文件的安装。

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
  • 1.
  • 2.

对于本文这个应用通过在顶层 CMakeLists.txt 添加以下内容来安装可执行文件和配置的头文件:

# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
         DESTINATION include)
  • 1.
  • 2.
  • 3.
  • 4.

以上就是安装的全部步骤。现在你应该可以编译本教程了。输入 make install(或在 IDE 中编译 install 项目),对应的头文件、库文件和可执行文件就会被安装。CMake 的 CMAKE_INSTALL_PREFIX 参数可以指定安装文件的根目录(之前还可以加上 -D 参数,具体意义可以参考 what does the parameter "-D" mean)。 添加测试过程同样简单明了。

在顶层 CMakeLists.txt 文件的最后我们可以添加一个基础测试数据来验证该应用程序是否正常运行。

include(CTest)

# does the application run
add_test (TutorialRuns Tutorial 25)
# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)
set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")
# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

在编译完成之后,我们可以运行 "ctest" 命令行工具来执行测试。第一个测试简单的验证了程序是否工作,是否有严重错误并且返回0.这是 Ctest 测试的基础。接下来的一些测试都使用了 PASS_REGULAR_EXPRESSION 测试属性(正则表达式)来验证输出中是否包含了特定的字符串。这里验证开方是否正确并且在计算错误时输出输出对应信息。如果你希望添加很多的测试来测试不同的输入值,你可以考虑定义一个像下面这样的宏:

#define a macro to simplify adding tests, then use it
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
 
# do a bunch of result based tests
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

每一次调用 do_test 就会根据指定的信息生成一个新的测试。

该步骤对应的 build 文件夹下的构建和运行脚本 run.cmd 内容如下:

echo off
echo build:
cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=D:\project\cpp\cmake\tutorial\step3\install ..
echo compile:
devenv Tutorial.sln /build "Debug|x64"
echo install:
devenv Tutorial.sln /build "Debug|x64" /project INSTALL
echo test:
devenv Tutorial.sln /build "Debug|x64" /project RUN_TESTS
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

安装位置根据自己的需要进行调整。

5. 添加系统自检(步骤 4)

接下来我们考虑给我们的项目添加一些取决于目标平台是否有一些特性的代码。这里我们将添加一些取决于目标平台是否有 log 和 exp 函数的代码。当然对于大多数平台都会有这些函数,但这里我们认为这并不常见。如果平台有 log 函数我们将在 mysqrt 函数中使用它计算平方根。我们首先在顶层 CMakeLists.txt 文件中使用 CheckFunctionExists 宏测试这些函数是否可用,代码如下:

# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
  • 1.
  • 2.
  • 3.
  • 4.

一定要在使用 configure_file 生成 TutorialConfig.h 之前测试 log 和 exp。因为 configure_file 命令会立刻使用当前 CMake 的设置配置文件。最后根据 log 和 exp 是否在我们的平台上可用我们给 mysqrt 函数提供一个可选的实现,代码如下:

// if we have both log and exp then use them
#if defined (HAVE_LOG) && defined (HAVE_EXP)
  result = exp(log(x)*0.5);
#else // otherwise use an iterative approach
  . . .
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

6. 添加一个生成的文件和生成器(步骤 5)

在这一章节我们将会展示如何在构建一个应用的过程中添加一个生成的源文件。在本例中我们将创建一个预先计算的平方根表作为构建过程的一部分,然后将其编译到我们的应用中。为了做到这一点我们首先需要一个能产生这张表的程序。在 MathFunctions 文件夹下创建一个新的名为 MakeTable.cxx 的文件,内容如下:

// A simple program that builds a sqrt table 
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
int main (int argc, char *argv[])
{
  int i;
  double result;
 
  // make sure we have enough arguments
  if (argc < 2)
    {
    return 1;
    }
  
  // open the output file
  FILE *fout = fopen(argv[1],"w");
  if (!fout)
    {
    return 1;
    }
  
  // create a source file with a table of square roots
  fprintf(fout,"double sqrtTable[] = {\n");
  for (i = 0; i < 10; ++i)
    {
    result = sqrt(static_cast<double>(i));
    fprintf(fout,"%g,\n",result);
    }
 
  // close the table with a zero
  fprintf(fout,"0};\n");
  fclose(fout);
  return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

注意到这张表使用 C++ 代码生成且文件的名字通过输入参数指定。下一步通过在 MathFunctions 的CMakeLists.txt 中添加合适的代码来构建 MakeTable 可执行文件,并将它作为构建的一部分运行。只需要一点代码就能实现这个功能:

# 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
  )
 
# add the binary tree directory to the search path for 
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

首先添加的 MakeTable 可执行文件和其它可执行文件相同。接下来我们添加一个自定义的命令来指定如何通过运行 MakeTable 生成 Table.h 文件。接下来我们必须让 CMake 知道 mysqrt.cxx 依赖于生成的 Table.h 文件。这一点通过将生成的 Table.h 文件添加到 MathFunctions 库的源文件列表实现。

我们同样必须将当前二进制文件路径添加到包含路径中,以保证 Table.h 文件被找到并被 mysqrt.cxx 包含。该项目在构建时会首先构建 MakeTable 可执行文件。接下来会运行该可执行文件并生成 Table.h 文件。最后它将会编译包含 Table.h 的 mysqrt.cxx 文件并生成 MathFunctions 库。此时包含了所有我们添加的特性的顶层 CMakeLists.txt 文件应该像下面这样:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
include(CTest)
 
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
 
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
 
# should we use our own math functions
option(USE_MYMATH 
  "Use tutorial provided math implementation" ON)
 
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories ("${PROJECT_BINARY_DIR}")
 
# add the MathFunctions library?
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})
 
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
         DESTINATION include)
 
# does the application run
add_test (TutorialRuns Tutorial 25)
 
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
  PROPERTIES 
  PASS_REGULAR_EXPRESSION "Usage:.*number"
  )
 
 
#define a macro to simplify adding tests
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )
endmacro (do_test)
 
# do a bunch of result based tests
do_test (4 "4 is 2")
do_test (9 "9 is 3")
do_test (5 "5 is 2.236")
do_test (7 "7 is 2.645")
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
do_test (0.0001 "0.0001 is 0.01")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.

TutorialConfig.h 文件如下:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
 
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

最后 MathFunctions 的 CMakeLists.txt 文件如下:

# 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
  DEPENDS MakeTable
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  )
# add the binary tree directory to the search path 
# for include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
 
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

7. 构造一个安装器(步骤 6)

接下来假设我们想将我们的项目发布给其他人以便供他们使用。我们想提供在不同平台上的二进制文件和源码的发布版本。这一点和我们在之前安装和测试章节(步骤3)略有不同,步骤三安装的二进制文件是我们从源码构建的。这里我们将构建一个支持二进制文件安装的安装包和可以在 cygwin,debian,RPMs 等中被找到的安装管理特性。为了实现这一点我们将使用 CPack 来创建在 Packaging with CPack 章节中介绍过的平台特定安装器(platform specific installers)。我们需要在顶层 CMakeLists.txt 文件添加以下几行内容:

# build a CPack driven installer package
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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

首先我们添加了 InstallRequiredSystemLibraries。该模块会包含我们项目在当前平台所需的所有运行时库(runtime libraries)。接下来我们设置了一些 CPack 变量来指定我们项目的许可文件和版本信息。版本信息使用我们在之前设置的内容。最后我们包含 CPack 模块,它会使用这些变量和其它你安装一个应用程序所需的系统属性。

接下来就是正常编译你的项目然后使用 CPack 运行它,为了编译二进制发布版本你需要运行:

cpack --config CPackConfig.cmake
  • 1.

创建一个源文件发布版本你应该使用下面命令:

cpack --config CPackSourceConfig.cmake
  • 1.

Windows 平台下CMake 默认会使用 NSIS 创建安装包,因此我们在执行上面命令前需要安装该软件。当然我们也可以使用 WiX 包安装工具,只需要在 include(CPack) 之前加上 set(CPACK_GENERATOR WIX) 即可。

8. 添加表盘工具(Dashboard)支持(步骤7)

添加将我们测试结果提交到仪表盘的功能非常简单。在本教程的之前步骤中我们已经给我们的项目定义了一些测试。我们只需要运行这些测试然后提交到仪表盘即可。为了支持仪表盘功能我们需要在顶层 CMakeLists.txt 文件中增加 CTest 模块。

# enable dashboard scripting
include (CTest)
  • 1.
  • 2.

我们同样可以创建一个 CTestConfig.cmake 文件来在表盘工具中指定本项目的名字。

set (CTEST_PROJECT_NAME "Tutorial")
  • 1.

CTest 会在运行时读取该文件。你可以在你的项目上运行 CMake 来创建一个简单的仪表盘,切换目录到二进制文件夹下,然后运行 ctest -DExperimental.你仪表盘的运行结果会上传到 Kitware 的公共仪表盘上 这里。

如果需要上传的话还需要设置 Drop site ,具体细节可以参考官方的 ctest(1) 。

三、CMake常用命令详解

在深入了解 CMake 的世界后,我们来到了一个关键的阶段 —— 学习 CMake 的常用命令。这些命令是我们编写CMakeLists.txt文件的基础,掌握它们,就如同掌握了一门新语言的语法规则,能够让我们自由地构建和管理项目。下面,我们将详细介绍一些 CMake 的常用命令,让你在实际项目中能够灵活运用。

1. 工程管理命令

(1) include_directories:配置头文件路径

在项目中,头文件是不可或缺的部分,它包含了函数、类的声明等重要信息。include_directories命令用于指定头文件的搜索路径,确保编译器在编译源文件时能够找到所需的头文件 。其基本语法如下:

include_directories([AFTER|BEFORE][SYSTEM] dir1 [dir2 ...])
  • 1.

上述示例将include目录添加到头文件搜索路径中,这样在编译时,编译器会在include目录中查找头文件。不过,更推荐使用target_include_directories,它针对特定目标设置包含目录,作用域更明确,可维护性更好 。

(2) add_executable:此命令用于创建一个可执行文件目标

通过指定可执行文件的名称和源文件列表,CMake 会将这些源文件编译链接成一个可执行文件 。在一个简单的 C 项目中,有main.c和utils.c两个源文件,使用add_executable命令可以这样编写:

add_executable(my_program main.c utils.c)
  • 1.

这里my_program是生成的可执行文件的名称,main.c和utils.c是参与编译的源文件。如果源文件较多,也可以将源文件列表放在一个变量中,然后在add_executable中使用该变量 。

(3) add_library:用于创建一个库文件目标,可以生成静态库(STATIC)或动态库(SHARED) 

在一个 C++ 库项目中,有library.cpp源文件,希望生成一个动态库,命令如下:

add_library(my_library SHARED library.cpp)
  • 1.

其中my_library是库的名称,SHARED表示生成动态库,如果要生成静态库,将SHARED改为STATIC即可 。

(4) add_subdirectory:用于包含子目录的 CMakeLists.txt 文件,从而将子目录的项目纳入到整个项目的构建过程中 

在一个大型项目中,可能会有多个模块,每个模块都有自己独立的目录和 CMakeLists.txt 文件,使用add_subdirectory可以方便地管理这些模块的构建 。假设项目目录结构如下:

project/
├── CMakeLists.txt
├── module1/
│   └── CMakeLists.txt
└── module2/
    └── CMakeLists.txt
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

在项目根目录的CMakeLists.txt中,可以这样包含子目录:

add_subdirectory(module1)
add_subdirectory(module2)
  • 1.
  • 2.

这样,module1和module2目录下的 CMakeLists.txt 文件会被执行,其中定义的构建规则和目标会被纳入到整个项目的构建中 。

(5) include:用于引入其他.cmake文件,这些文件通常包含一些自定义的函数、宏或配置信息,通过include可以在当前的 CMakeLists.txt 文件中复用这些内容 。在项目中,有一个自定义的.cmake文件common.cmake,其中定义了一些常用的编译选项和宏,在主 CMakeLists.txt 文件中可以这样引入:

include(common.cmake)
  • 1.

引入后,common.cmake中的内容就可以在当前文件中使用了,这有助于提高代码的复用性和项目的可维护性 。

2. 开关选项命令

(1) option:该命令用于定义一个开关选项,在项目配置时可以通过命令行或 CMake 图形界面来设置这个选项的值,从而控制项目的构建行为 。在一个项目中,可能希望有一个选项来控制是否启用某个功能模块,使用option命令可以这样定义:

option(ENABLE_FEATURE "Enable a specific feature" OFF)
if(ENABLE_FEATURE)
    # 启用功能模块的相关配置
    add_definitions(-DENABLE_FEATURE)
    include_directories(feature_include)
    add_executable(my_program main.c feature.c)
else()
    # 不启用功能模块的配置
    add_executable(my_program main.c)
endif()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

上述示例中,ENABLE_FEATURE是定义的选项名称,"Enable a specific feature"是选项的描述信息,OFF表示该选项的默认值为关闭。在项目配置时,可以通过cmake -DENABLE_FEATURE=ON ..来启用该功能模块 。

(2) add_definition:用于在源码中定义宏,这些宏可以在代码中通过#ifdef等预处理指令进行条件编译 。在一个 C++ 项目中,希望定义一个DEBUG宏来控制调试信息的输出,使用add_definition命令可以这样编写:

add_definitions(-DDEBUG)
  • 1.

这样在编译时,DEBUG宏会被定义,在代码中就可以通过#ifdef DEBUG来判断是否处于调试模式,并进行相应的代码处理 。

3. 调试信息命令

(1) message:用于在 CMake 执行过程中向终端输出信息,它有多种输出模式,不同的模式用于不同的目的 。

(2) STATUS:输出的信息会被发送到 CMake 的状态消息流,通常用于输出构建过程中的状态信息,在命令行上,这些消息会被显示出来,帮助开发者了解构建的进度和当前状态 。

message(STATUS "Building project...")
  • 1.

(3) INFO:输出一般性的信息,类似于STATUS,但没有明确的区分,通常用于输出一些开发者希望关注的信息 。

message(INFO "This is an important information.")
  • 1.

(4) WARNING:输出的信息会被发送到 CMake 的警告消息流,这些消息会被标记为警告,通常用于提示开发者一些可能存在的问题,但不会导致构建失败 。

if(NOT SOME_LIBRARY_FOUND)
    message(WARNING "Some library not found, some features may not be available.")
endif()
  • 1.
  • 2.
  • 3.

(5) FATAL_ERROR:输出的信息会被发送到 CMake 的错误消息流,并立即停止 CMake 的处理过程,通常用于表示严重的错误,如缺少关键的依赖项或配置错误 。

if(NOT REQUIRED_TOOL_FOUND)
    message(FATAL_ERROR "Required tool not found, cannot continue building.")
endif()
  • 1.
  • 2.
  • 3.

4. 其他重要变量

(1) CMAKE_C_FLAGS、CMAKE_CXX_FLAGS:这两个变量分别用于控制 C 和 C++ 的编译选项 。在一个 C++ 项目中,希望设置编译选项为-Wall -Werror,以开启所有警告并将警告视为错误,可以这样设置:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
  • 1.

这样在编译 C++ 源文件时,就会带上-Wall -Werror编译选项 。如果项目中同时有 C 和 C++ 代码,也可以分别设置CMAKE_C_FLAGS和CMAKE_CXX_FLAGS 。

(2) CMAKE_BUILD_TYPE:该变量用于指定构建类型,常见的构建类型有Debug、Release、MinSizeRel(最小尺寸发布,优化目标是减小文件大小)和RelWithDebInfo(发布版本且包含调试信息) 。在项目中,可以通过以下方式设置构建类型:

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
  • 1.
  • 2.
  • 3.

上述示例中,如果没有通过命令行或其他方式指定构建类型,默认设置为Release。在命令行中也可以通过cmake -DCMAKE_BUILD_TYPE=Debug ..来指定构建类型为Debug 。

四、CMake高级应用

1. 自定义构建选项

在实际项目开发中,根据不同的需求和场景进行条件编译是非常常见的操作,而 CMake 提供的option命令就为我们实现这一功能提供了便利。

option命令用于定义一个开关选项,它的基本语法是option(OPTION_NAME "Description" DEFAULT_VALUE),其中OPTION_NAME是选项的名称,"Description"是对该选项的描述信息,方便用户了解其作用,DEFAULT_VALUE则是选项的默认值,可以是ON或OFF。

例如,在一个图像处理项目中,可能希望有一个选项来控制是否启用高级图像算法。可以在CMakeLists.txt文件中这样定义:

option(ENABLE_ADVANCED_ALGORITHM "Enable advanced image algorithm" OFF)
if(ENABLE_ADVANCED_ALGORITHM)
    # 启用高级图像算法的相关配置
    add_definitions(-DENABLE_ADVANCED_ALGORITHM)
    include_directories(advanced_algorithm_include)
    add_executable(image_processing main.cpp advanced_algorithm.cpp)
else()
    # 不启用高级图像算法的配置
    add_executable(image_processing main.cpp basic_algorithm.cpp)
endif()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

在上述示例中,ENABLE_ADVANCED_ALGORITHM是定义的选项名称,"Enable advanced image algorithm"是描述信息,OFF表示默认不启用该选项。当通过cmake -DENABLE_ADVANCED_ALGORITHM=ON ..命令启用该选项时,if条件判断为真,会添加相应的编译定义-DENABLE_ADVANCED_ALGORITHM,并将高级图像算法的源文件advanced_algorithm.cpp包含到可执行文件的构建中;如果不启用该选项,else分支会被执行,使用基本图像算法的源文件basic_algorithm.cpp进行构建。

2. 查找和管理依赖库

在项目开发中,经常会依赖一些外部库来实现特定的功能,而 CMake 的find_package命令就是查找和管理这些依赖库的重要工具。

以查找和使用 Boost 库为例,find_package命令的基本语法是find_package(Boost [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [COMPONENTS [components...]] [OPTIONAL_COMPONENTS [components...]] [CONFIG|NO_MODULE|NO_CONFIG|NO_POLICY_SCOPE] [NAMES name1 [...]])。

假设我们的项目需要使用 Boost 库的文件系统和线程组件,并且要求找到的 Boost 库版本至少为 1.70,可以在CMakeLists.txt文件中这样编写:

find_package(Boost 1.70 REQUIRED COMPONENTS filesystem thread)
if(Boost_FOUND)
    message(STATUS "Boost include dir: ${Boost_INCLUDE_DIRS}")
    message(STATUS "Boost libraries: ${Boost_LIBRARIES}")
    include_directories(${Boost_INCLUDE_DIRS})
    target_link_libraries(your_target_name ${Boost_LIBRARIES})
else()
    message(FATAL_ERROR "Boost library not found.")
endif()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

上述代码中,find_package(Boost 1.70 REQUIRED COMPONENTS filesystem thread)表示查找 Boost 库,要求版本至少为 1.70,并且必须找到文件系统和线程组件。如果找到 Boost 库,Boost_FOUND变量会被设置为真,然后通过message命令输出 Boost 库的包含目录和库文件路径,再使用include_directories将 Boost 库的包含目录添加到项目中,使用target_link_libraries将 Boost 库链接到项目的目标(your_target_name)上;如果没有找到 Boost 库,Boost_FOUND为假,通过message(FATAL_ERROR "Boost library not found.")输出错误信息并终止构建过程。

3. 生成安装目标

在项目开发完成后,通常需要将生成的可执行文件、库文件、配置文件等安装到指定的目录中,以便用户能够方便地使用和部署。CMake 的install命令就提供了这样的功能,它可以将各种文件和目标安装到指定的位置。

(1)安装可执行文件:

add_executable(my_app main.cpp)
install(TARGETS my_app RUNTIME DESTINATION bin)
  • 1.
  • 2.

上述代码中,add_executable创建了一个名为my_app的可执行文件,install命令将my_app的运行时文件(即生成的可执行文件)安装到bin目录下。这里的bin目录可以是绝对路径,也可以是相对于CMAKE_INSTALL_PREFIX的相对路径,CMAKE_INSTALL_PREFIX是 CMake 的一个预定义变量,用于指定安装路径的前缀,默认情况下在 Unix 系统中是/usr/local,在 Windows 系统中是C:/Program Files (x86)/项目名称 。如果希望安装到自定义的绝对路径,如/home/user/my_project/bin,可以将DESTINATION设置为/home/user/my_project/bin 。

(2)安装库文件:

add_library(my_lib SHARED lib.cpp)
install(TARGETS my_lib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static)
  • 1.
  • 2.

这里add_library创建了一个共享库my_lib,install命令将共享库文件安装到lib目录下,将归档文件(通常是静态库文件,如果有的话)安装到lib/static目录下。同样,lib和lib/static可以是相对路径或绝对路径 。

(3)安装普通文件:

install(FILES config.ini DESTINATION etc)
  • 1.

该示例将config.ini文件安装到etc目录下,config.ini是相对于CMakeLists.txt文件所在目录的路径 。

(4)安装目录:

install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN "*.h")
  • 1.

上述代码会将include目录下所有扩展名为.h的头文件安装到include目录下,这里的两个include目录可以根据实际需求进行修改 。如果希望安装整个include目录及其所有内容,包括子目录,可以将FILES_MATCHING相关部分去掉 。

五、常见问题及解决方案

在使用 CMake 的过程中,开发者可能会遇到各种各样的问题,下面将列举一些常见问题,并提供相应的解决方案。

1. 找不到依赖库

在编译项目时,CMake 可能会提示找不到某个依赖库,这是一个比较常见的问题。比如在编译一个使用 OpenCV 库的项目时,可能会出现 “Could not find a package configuration file provided by “OpenCV” with any of the following names: OpenCVConfig.cmake, opencv-config.cmake” 这样的错误提示 。

原因分析:造成这个问题的原因可能有多种。首先,可能是依赖库没有正确安装,例如没有安装 OpenCV 库或者安装路径不正确;其次,CMake 可能无法自动检测到依赖库的位置,这可能是因为库的安装路径没有被添加到系统的环境变量中,或者 CMake 的搜索路径设置不正确 。

解决方案:针对这些问题,可以采取以下解决措施。首先,确保已经正确安装了相应的依赖库。如果已经安装了依赖库,但 CMake 仍然找不到它,可以通过设置 CMake 变量来指定库的路径 。例如,使用set()命令设置CMAKE_PREFIX_PATH变量,将库的安装路径添加到其中:

set(CMAKE_PREFIX_PATH "/path/to/library" ${CMAKE_PREFIX_PATH})
  • 1.

另外,还可以使用find_path()或find_library()函数,并明确指定依赖库的搜索路径 。例如:

find_package(Boost REQUIRED PATHS /path/to/boost)
  • 1.

如果依赖是在系统级安装的,确认相关的环境变量(如LD_LIBRARY_PATH或PYTHONPATH)已经设置正确 。对于预打包的库,检查操作系统是否有对应的软件包,并使用系统的包管理工具安装 。此外,查阅依赖库的官方文档,了解特定的 CMake 配置步骤也是很有必要的 。

2. 编译选项错误

在 CMakeLists.txt 文件中设置编译选项时,可能会因为设置错误而导致编译失败。比如在设置 C++ 编译选项时,将CMAKE_CXX_FLAGS变量设置错误,可能会出现 “unrecognized command line option” 这样的错误提示 。

原因分析:这种错误通常是由于对编译选项的不熟悉或者拼写错误导致的。不同的编译器支持的编译选项可能会有所不同,如果设置了不支持的编译选项,就会出现此类错误 。

解决方案:解决这个问题,首先要仔细检查 CMakeLists.txt 文件中编译选项的设置,确保拼写正确,并且是目标编译器支持的选项 。例如,如果希望设置 C++ 编译选项为-Wall -Werror,以开启所有警告并将警告视为错误,可以这样设置:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
  • 1.

如果不确定某个编译选项是否被支持,可以查阅目标编译器的官方文档 。另外,在设置编译选项时,可以使用条件判断来根据不同的编译器或构建类型设置不同的选项 。例如:

if(CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
elseif(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX")
endif()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

上述代码中,根据编译器类型分别设置了不同的编译选项,对于 GCC 编译器,设置-Wall -Werror选项,对于 MSVC 编译器,设置/W4 /WX选项 。

3. 头文件路径设置错误

在项目中,头文件路径设置不正确也会导致编译错误,例如出现 “fatal error: xxx.h: No such file or directory” 这样的错误提示 。

原因分析:这通常是因为在 CMakeLists.txt 文件中没有正确设置头文件的搜索路径,或者设置的路径与实际头文件的位置不一致 。

解决方案:可以使用include_directories()命令将头文件所在的路径添加到编译过程中。例如:

include_directories(include)
  • 1.

上述示例将include目录添加到头文件搜索路径中 。不过,更推荐使用target_include_directories(),它针对特定目标设置包含目录,作用域更明确,可维护性更好 。例如:

add_executable(my_program main.c)
target_include_directories(my_program PRIVATE include)
  • 1.
  • 2.

这里PRIVATE表示该包含目录仅对my_program目标可见,如果头文件路径是公共的也可以使用PUBLIC关键字 。

4. 构建类型设置错误

在项目中,如果构建类型设置错误,可能会导致生成的可执行文件或库文件不符合预期,例如在需要生成 Release 版本的可执行文件时,却生成了 Debug 版本 。

原因分析:这种问题通常是由于没有正确设置CMAKE_BUILD_TYPE变量,或者在命令行中没有正确指定构建类型导致的 。

解决方案:可以在 CMakeLists.txt 文件中设置CMAKE_BUILD_TYPE变量的默认值 。例如:

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
  • 1.
  • 2.
  • 3.

上述示例中,如果没有通过命令行或其他方式指定构建类型,默认设置为Release 。在命令行中也可以通过cmake -DCMAKE_BUILD_TYPE=Debug ..来指定构建类型为Debug 。

六、CMake在Clion中的配置

1. Ubuntu 下 Clion 的安装

clion 及相关工具安装:

# clion 所用到的工具链:gcc(C),g++C++),make(连接),cmake(跨平台建构系统), gdb(debug)
sudo apt install gcc
sudo apt install g++
sudo apt install make
sudo apt install cmake
sudo apt install gdb

# Install using the Toolbox App or Standalone installation
sudo tar -xzf jetbrains-toolbox* -C /opt

# Standalone installation
sudo tar xvzf CLion-*.tar.gz -C /opt/
sh /opt/clion-*/bin/clion.sh
#  create a desktop entry, do one of the following:
- On the Welcome screen, click Configure | Create Desktop Entry
- From the main menu, click Tools | Create Desktop Entry
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

如果没有桌面快捷方式,尝试用如下方法解决:

sudo vim /usr/share/applications/clion.desktop

# 插入如下内容,保存退出即可,在 search 里面就可以找到 clion 了
[Desktop Entry]
Encoding=UTF-8
Name=CLion
Comment=clion-2020.1.2
Exec=/opt/clion-2020.1.2/bin/clion.sh
Icnotallow=/opt/clion-2020.1.2/bin/clion.svg
Categories=Application;Development;Java;IDE
Versinotallow=2020.1.2
Type=Application
#Terminal=1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

2. 如何在 clion 运行多个 cpp 文件 ?

直接修改 CMakeLists.txt 即可。

新建 CPP 文件时注意:把默认勾选的 Add to targerts 去掉(如下图);在项目处右击,选择 Reload CMake Project,在重新加载完之后可以看到运行框列表有了对应的运行选项

cmake_minimum_required(VERSION 3.16)
project(cpp14)

set(CMAKE_CXX_STANDARD 14)


# 递归遍历项目当前目录下所有的 .cpp 文件
file(GLOB_RECURSE files *.cpp)
foreach (file ${files})
    string(REGEX REPLACE ".+/(.+)\\..*" "\\1" exe ${file})  # 正则匹配,取出文件名前缀
    add_executable(${exe} ${file})  # exe 文件名, file 为文件绝对路径
    message("src file name is: " ${exe})
    message(STATUS "src file path is: " ${file})
endforeach (file)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

责任编辑:赵宁宁 来源: 深度Linux
相关推荐

2019-12-13 09:00:58

架构运维技术

2022-07-13 11:17:00

大数据规划

2022-06-13 07:02:02

Zadig平台自动化

2016-01-14 13:07:20

美团压测工具工具

2017-08-10 09:11:38

规则引擎构建

2024-05-28 07:58:08

2016-11-28 16:23:23

戴尔

2023-04-10 07:40:50

BI 体系数据中台

2022-05-09 08:35:43

面试产品互联网

2022-07-06 07:27:52

32Core树莓派集群

2017-01-18 09:33:07

数据构建价值

2023-05-10 10:45:06

开源工具库项目

2016-03-01 22:21:26

IBM

2022-10-14 16:25:50

数据可视化大屏搭建BI平台

2024-02-26 07:31:26

WindowsLinuxmacOS

2023-06-28 10:48:09

平台框架高性能

2021-02-20 16:29:26

用户画像数据收集流程

2021-03-10 09:21:00

Spring开源框架Spring基础知识

2021-07-01 07:03:32

开发Webpack代码

2023-03-06 11:35:55

经营分析体系
点赞
收藏

51CTO技术栈公众号