CMake使用教程(一)

CMake 是一种跨平台的免费开源软件工具,用于使用与编译器无关的方法来管理软件的构建过程。在 Android Studio 上进行 NDK 开发默认就是使用 CMake 管理 C/C++ 代码,因此在学习 NDK 之前最好对 CMake 有一定的了解。

本文主要以翻译 CMake官方教程文档为主,加上自己的一些理解,该教程涵盖了 CMake 的常见使用场景。由于能力有限,翻译部分采用机翻+人工校对,翻译有问题的地方,说声抱歉。

开发环境:

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

基础项目

示例程序地址

最基础的项目是单个源代码文件构建的可执行文件。

本示例提供的源代码文件是 tutorial.cxx,可用于计算数字的平方根。代码如下:

// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double inputValue = atof(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}

对于简单的项目,只需三行内容的 CMakeLists.txt 文件,这将是本教程的起点。在项目根目录下创建一个 CMakeLists.txt 文件,其内容如下:

# 设置运行此配置文件所需的CMake最低版本
cmake_minimum_required(VERSION 3.15)

# set the project name
# 设置项目名称
project(Tutorial)

# add the executable
# 添加一个可执行文件
add_executable(Tutorial tutorial.cxx)

请注意,此示例在 CMakeLists.txt 文件中使用小写命令。CMake 支持大写,小写和大小写混合命令。

当前项目结构如下:

.
├── CMakeLists.txt
└── tutorial.cxx

在项目根目录运行命令生成编译中间文件以及 makefile 文件:

cmake .

命令执行后会在项目根目录下生成文件,项目结构如下:

.
├── CMakeCache.txt
├── CMakeFiles
├── CMakeLists.txt
├── Makefile
├── cmake_install.cmake
└── tutorial.cxx

这样源文件和生成的文件都混在一起,不方便管理,建议使用一个专门的目录管理这些生成的文件。这里使用 CLion 默认生成文件目录 cmake-build-debug,在项目根目录运行编译命令并指定生成文件目录:

cmake -B cmake-build-debug

项目结构如下:

.
├── CMakeLists.txt
├── cmake-build-debug
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── Makefile
│   └── cmake_install.cmake
└── tutorial.cxx

在项目根目录运行命令生成可执行文件:

cmake --build cmake-build-debug

命令执行后生成了可执行文件 Tutorial,项目结构如下:

 .
 ├── CMakeLists.txt
 ├── cmake-build-debug
 │   ├── CMakeCache.txt
 │   ├── CMakeFiles
 │   ├── Makefile
+│   ├── Tutorial
 │   └── cmake_install.cmake
 └── tutorial.cxx

在项目根目录运行生成的可执行文件且不携带参数:

./cmake-build-debug/Tutorial

终端输出:

Usage: ./cmake-build-debug/Tutorial number

在项目根目录运行生成的可执行文件并携带参数:

./cmake-build-debug/Tutorial 2

终端输出:

The square root of 2 is 1.41421

添加版本号和配置头文件

示例程序地址

我们添加的第一个功能是为我们的可执行文件和项目提供版本号。虽然我们可以仅在源代码中执行此操作,但是使用 CMakeLists.txt 可以提供更大的灵活性。

首先,修改 CMakeLists.txt 文件以设置版本号。

project(Tutorial VERSION 1.0)

然后,配置头文件以将版本号传递给源代码:

# configure a header file to pass some of the CMake settings
# to the source code
# 配置头文件以将某些CMake设置传递给源代码
configure_file(TutorialConfig.h.in TutorialConfig.h)

由于已配置的文件将被写入二进制目录,因此我们必须将该目录添加到路径列表中以搜索包含文件。将以下行添加到CMakeLists.txt文件的末尾:

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
# 将二进制目录添加到包含文件的搜索路径中,以便我们找到TutorialConfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        )

使用您喜欢的编辑器,在源目录中使用以下内容创建 TutorialConfig.h.in

// the configured options and settings for Tutorial
// 教程的配置选项和设置
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

CMake 配置此头文件时,会在二进制目录下生成一个文件 TutorialConfig.h,会把 TutorialConfig.h.in 中的内容拷贝到里面,只是把 @Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@ 替换成在 CMakeLists.txt 的配置的 1 和 0。

这里的 1 和 0 是怎么和 Tutorial_VERSION_MAJORTutorial_VERSION_MINOR关联上的? 在 project() 中指定了 VERSION 后,CMake 会把版本信息存储在以下变量中:

  • PROJECT_VERSION, <PROJECT-NAME>_VERSION
  • PROJECT_VERSION_MAJOR, <PROJECT-NAME>_VERSION_MAJOR
  • PROJECT_VERSION_MINOR, <PROJECT-NAME>_VERSION_MINOR
  • PROJECT_VERSION_PATCH, <PROJECT-NAME>_VERSION_PATCH
  • PROJECT_VERSION_TWEAK, <PROJECT-NAME>_VERSION_TWEAK.

MAJORMINORPATCHTWEAK 分别代表着版本号的四位,比如版本号 1.2.3.4MAJOR=1MINOR=2PATCH=3TWEAK=4。版本号不一定非得是4位,可以只有1位,只是最大为4位。

这里 PROJECT-NAME 值为 Tutorial,所以能从 Tutorial_VERSION_MAJORTutorial_VERSION_MINOR 中读取到版本信息。

当从顶层 CMakeLists.txt 调用 project() 命令时,该版本也存储在变量 CMAKE_PROJECT_VERSION 中。

接下来,修改 tutorial.cxx 以包含配置的头文 件TutorialConfig.h 和打印出版本号,如下所示:

// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

+ #include "TutorialConfig.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 = atof(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}

在项目根目录运行命令编译项目和生成可执行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug

在项目根目录运行生成的可执行文件且不携带参数:

./cmake-build-debug/Tutorial

终端输出:

./cmake-build-debug/Tutorial Version 1.0
Usage: ./cmake-build-debug/Tutorial number

指定C++标准

示例程序地址

CMake 中启用对特定 C ++ 标准的支持的最简单方法是使用 CMAKE_CXX_STANDARD 变量。对于本教程,请将 CMakeLists.txt 文件中的 CMAKE_CXX_STANDARD 变量设置为11,并将 CMAKE_CXX_STANDARD_REQUIRED 设置为 True

# specify the C++ standard
# 指定C ++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

接下来,通过在 tutorial.cxx 中用 std :: stod 替换 atof,将一些 C ++ 11 功能添加到我们的项目中。同时,删除 #include <cstdlib>

// A simple program that computes the square root of a number
#include <cmath>
- #include <cstdlib>
#include <iostream>
#include <string>

#include "TutorialConfig.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 = atof(argv[1]);
+    const double inputValue = std::stod(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}

在项目根目录运行命令编译项目和生成可执行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug

在项目根目录运行生成的可执行文件:

./cmake-build-debug/Tutorial 2

终端输出:

The square root of 2 is 1.41421

添加库

示例程序地址

现在,我们将添加一个库到我们的项目中,该库用于计算数字的平方根,可执行文件可以使用此库,而不是使用编译器提供的标准平方根函数。该库有两个文件:

  • MathFunctions.h

    double mysqrt(double x);
    
  • mysqrt.cxx

    源文件有一个 mysqrt 的函数,该函数提供与编译器的 sqrt 函数类似的功能。

    #include <iostream>
    
    #include "MathFunctions.h"
    
    // 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;
    }
    

在项目根目录下创建一个文件夹 MathFunctions ,把该库放在其下,在其下创建一个 CMakeLists.txt 文件,内容如下:

add_library(MathFunctions mysqrt.cxx)

为了使用新库,我们将在顶层 CMakeLists.txt 文件中添加 add_subdirectory 调用,以便构建该库。我们将新库添加到可执行文件,并将 MathFunctions 添加为包含目录,以便可以找到 mqsqrt.h 头文件。顶级 CMakeLists.txt 文件的最后几行现在应如下所示:

# add the MathFunctions library
# 添加 MathFunctions 库
add_subdirectory(MathFunctions)

# add the executable
# 添加一个可执行文件
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC MathFunctions)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
# 将二进制目录添加到包含文件的搜索路径中,以便我们找到TutorialConfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        "${PROJECT_SOURCE_DIR}/MathFunctions"
        )

修改 tutorial.cxx 使用引入的库,其内容如下:

// A simple program that computes the square root of a number
- #include <cmath>
#include <iostream>
#include <string>

#include "TutorialConfig.h"
+ #include "MathFunctions.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]);

    // calculate square root
-    const double outputValue = sqrt(inputValue);
+    const double outputValue = mysqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}

在项目根目录运行命令编译项目和生成可执行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug

在项目根目录运行生成的可执行文件:

./cmake-build-debug/Tutorial 2

终端输出:

Computing sqrt of 2 to be 1.5
Computing sqrt of 2 to be 1.41667
Computing sqrt of 2 to be 1.41422
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
The square root of 2 is 1.41421

提供选项

示例程序地址

现在让我们将 MathFunctions 库设为可选。虽然对于本教程而言确实不需要这样做,但是对于大型项目来说,这是很常见的。第一步是向顶级 CMakeLists.txt 文件添加一个选项:

# should we use our own math functions
# 我们应该使用自己的数学函数吗
option(USE_MYMATH "Use tutorial provided math implementation" ON)

此选项将显示在 CMake GUIccmake 中,默认值ON可由用户更改。此设置将存储在缓存中,因此用户无需在每次在构建目录上运行CMake时都设置该值。

下一个是使建立和链接 MathFunctions 库成为条件。为此,我们将顶级 CMakeLists.txt 文件的结尾更改为如下所示:

# add the MathFunctions library
# 添加 MathFunctions 库
if (USE_MYMATH)
    add_subdirectory(MathFunctions)
    list(APPEND EXTRA_LIBS MathFunctions)
    list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif ()

# add the executable
# 添加一个可执行文件
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
# 将二进制目录添加到包含文件的搜索路径中,以便我们找到TutorialConfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        ${EXTRA_INCLUDES}
        )

请注意,这里使用变量 EXTRA_LIBS 来收集所有可选库,以供以后链接到可执行文件中。变量 EXTRA_INCLUDES 类似地用于可选的头文件。当处理许多可选组件时,这是一种经典方法,我们将在下一步中介绍现代方法。

对源代码的相应更改非常简单。首先,根据需要在 tutorial.cxx 中决定包含 MathFunctions 头还是 包含 <cmath>

// should we include the MathFunctions header?
#ifdef USE_MYMATH
#include "MathFunctions.h"
#else
#include <cmath>
#endif

然后,在同一文件中,使用 USE_MYMATH 来确定使用哪个平方根函数:

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

由于源代码现在需要 USE_MYMATH,因此可以使用以下行将其添加到 TutorialConfig.h.in 中:

#cmakedefine USE_MYMATH

download 上根据自己的平台下载对应版本的 cmake-gui,安装后打开软件,选择源代码目录和生成文件,如下图所示:

点击左下角 Generate 按钮,软件会弹出的选择项目生成器的弹窗,这里默认就好,点击点击 Done 按钮,cmake-gui 开始编译项目,生成中间文件,并且可以在软件看到我们为用户提供的选项:

这个时候 cmake-build-debug/TutorialConfig.h 的内容如下:

// the configured options and settings for Tutorial
// 教程的配置选项和设置
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
#define USE_MYMATH

在项目根目录运行命令生成可执行文件:

cmake --build cmake-build-debug

在项目根目录运行生成的可执行文件:

./cmake-build-debug/Tutorial 2

终端输出:

Computing sqrt of 2 to be 1.5
Computing sqrt of 2 to be 1.41667
Computing sqrt of 2 to be 1.41422
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
Computing sqrt of 2 to be 1.41421
The square root of 2 is 1.41421

取消 cmake-gui 中的 USE_MYMATH 的勾选,点击 Generate 按钮重新编译项目,这个时候 cmake-build-debug/TutorialConfig.h 的内容如下:

// the configured options and settings for Tutorial
// 教程的配置选项和设置
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
/* #undef USE_MYMATH */

在项目根目录运行命令生成可执行文件:

cmake --build cmake-build-debug

在项目根目录运行生成的可执行文件:

./cmake-build-debug/Tutorial 2

终端输出:

The square root of 2 is 1.41421

CMake使用教程系列文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335