CMake使用教程(三)

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

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

开发环境:

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

指定编译定义

示例程序地址

在上一步 “系统自检” 中,除了在 TutorialConfig.h 中保存 HAVE_LOGHAVE_EXP 值之外,还有更好的做法吗?对于此示例,我们将尝试使用 target_compile_definitions

首先,从 TutorialConfig.h.in 中删除上一步的定义,在 mysqrt.cxx 中不再包含 TutorialConfig.h,移除上一步在 MathFunctions/CMakeLists.txt 中增加的额外包含。

接下来,我们可以将 HAVE_LOGHAVE_EXP 的检查移至 MathFunctions/CMakeLists.txt,然后添加将这些值指定为 PRIVATE 编译定义。

# does this system provide the log and exp functions?
# 该系统是否提供log和exp函数?
include(CheckSymbolExists)
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_compile_definitions(MathFunctions
                             PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()

完成这些更新后,在项目根目录运行命令编译项目和生成可执行文件:

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

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

./cmake-build-debug/Tutorial 2

终端输出:

Computing sqrt of 2 to be 1.41421 using log and exp
The square root of 2 is 1.41421

添加自定义命令和生成的文件

示例程序地址

假设,出于本教程的目的,我们决定不再使用平台日志和exp函数,而是希望生成一个可在 mysqrt 函数中使用的预计算值表。在本节中,我们将在构建过程中创建表,然后将该表编译到我们的应用程序中。

首先,让我们取消对 MathFunctions/CMakeLists.txt 中的 logexp 函数的检查。然后从 mysqrt.cxx 中删除对 HAVE_LOGHAVE_EXP 的检查。同时,我们可以删除 #include <cmath>

MathFunctions 子目录中,提供了一个名为 MakeTable.cxx 的新源文件来生成表。

// 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
}

我们可以看到生成的表不是简单的文本,而是一段C++代码。并且该文件的文件名是由参数传入决定的。

下一步是将适当的命令添加到 MathFunctions/CMakeLists.txt 文件中,以构建MakeTable 可执行文件,然后在构建过程中运行它。需要一些命令来完成此操作。

首先,在 MathFunctions/CMakeLists.txt 的顶部,添加 MakeTable 的可执行文件,就像添加任何其他可执行文件一样。

# first we add the executable that generates the table
# 首先,我们添加生成表的可执行文件
add_executable(MakeTable MakeTable.cxx)

然后,我们添加一个自定义命令,该命令指定如何通过运行 MakeTable 来产生 Table.h

# 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
)

接下来,我们必须让 CMake 知道 mysqrt.cxx 依赖生成的文件 Table.h。这是通过将生成的 Table.h 添加到库 MathFunctions 的源列表中来完成的。

# add the main library
# 添加主库
add_library(MathFunctions
        mysqrt.cxx
        ${CMAKE_CURRENT_BINARY_DIR}/Table.h
        )

我们还必须将当前的二进制目录添加到包含目录列表中,以便 mysqrt.cxx 可以找到并包含 Table.h

# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# 说明与我们链接的任何人都需要包含当前源目录才能找到 MathFunctions.h,而我们不需要。
# state that we depend on Tutorial_BINARY_DIR but consumers don't, as the
# Table.h include is an implementation detail
# state that we depend on our binary dir to find Table.h
# 声明我们依赖Tutorial_BINARY_DIR但消费者不依赖,因为包含Table.h是一个实现细节,我们依赖二进制目录来查找Table.h
target_include_directories(MathFunctions
        INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
        PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
        )

现在,使用生成的表。首先,修改 mysqrt.cxx 以包含 Table.h 。接下来,我们可以重写 mysqrt 函数以使用该表:

double mysqrt(double x) {
    if (x <= 0) {
        return 0;
    }

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

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

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

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

./cmake-build-debug/Tutorial 2

终端输出:

Use the table to help find an initial value 
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
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-build-debug/Tutorial 12

终端输出:

Computing sqrt of 12 to be 6.5
Computing sqrt of 12 to be 4.17308
Computing sqrt of 12 to be 3.52433
Computing sqrt of 12 to be 3.46462
Computing sqrt of 12 to be 3.4641
Computing sqrt of 12 to be 3.4641
Computing sqrt of 12 to be 3.4641
Computing sqrt of 12 to be 3.4641
Computing sqrt of 12 to be 3.4641
Computing sqrt of 12 to be 3.4641
The square root of 12 is 3.4641

生成安装程序

示例程序地址

接下来,假设我们想将项目分发给其他人,以便他们可以使用它。我们希望在各种平台上提供二进制和源代码分发。这与我们之前在 “安装” 示例进行的安装有些不同,在之前安装中,我们根据源代码构建的二进制文件进行安装。

在此示例中,我们将构建支持二进制安装和程序包管理功能的安装程序包。为此,我们将使用 CPack 创建平台特定的安装程序。具体来说,我们需要在顶级 CMakeLists.txt 文件的底部添加几行。

# setup installer
# 设置安装程序
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)

这就是全部,我们首先包含 InstallRequiredSystemLibraries,该模块将包含项目在当前平台所需的任何运行时库。

接下来,我们将一些项目信息设置给 CPack 变量,比如项目的许可证和版本信息。本示例中 License.txt 内容如下:

This is a License file.

最后,我们包含 CPack 模块,该模块将使用这些变量和当前系统的其他一些属性来设置安装程序。

在项目根目录运行命令编译项目:

cmake -B cmake-build-debug

在项目根目录运行命令构建二进制发行版

cd cmake-build-debug
cpack

在项目根目录下生成了文件:

.
├── ...
├── Tutorial-1.0-Darwin.sh
├── Tutorial-1.0-Darwin.tar.gz
└── ...

注意:要指定生成器,请使用 -G 选项。对于多配置构建,请使用 -C 指定配置。例如:

cpack -G ZIP -C Debug

在项目根目录运行命令构建源代码分发

cd cmake-build-debug
cpack --config CPackSourceConfig.cmake

在项目根目录下生成了文件:

.
├── ...
├── Tutorial-1.0-Source.tar.Z
├── Tutorial-1.0-Source.tar.bz2
├── Tutorial-1.0-Source.tar.gz
├── Tutorial-1.0-Source.tar.xz
└── ...

添加对仪表板的支持

示例程序地址

我们已经在 "测试" 示例中为我们的项目定义了许多测试。现在,我们只需要运行这些测试并将其提交到仪表板即可。为了包括对仪表板的支持,我们在顶层 CMakeLists.txt 中包含了 CTest 模块。

将以下内容:

# enable testing
# 启用测试
enable_testing()

替换为:

# enable dashboard scripting
# 启用仪表板脚本
include(CTest)

CTest 模块将自动调用 enable_testing(),因此我们可以将其从 CMake 文件中删除。我们还需要在顶级目录中创建一个 CTestConfig.cmake 文件,在该文件中我们可以指定项目的名称以及提交仪表板的位置。

set(CTEST_PROJECT_NAME "CMakeTutorial")
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")

set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial")
set(CTEST_DROP_SITE_CDASH TRUE)

CTest 将在运行时读入该文件。

在项目根目录运行命令编译项目:

cmake -B cmake-build-debug

在项目根目录运行命令生成仪表板:

cd cmake-build-debug
ctest –D Experimental
# 或者:ctest -VV –D Experimental

注意:对于多配置生成器(例如Visual Studio),必须指定配置类型:

ctest [-VV] -C Debug –D Experimental

或者从 IDE中 构建 Experimental 目标。

ctest 将构建和测试项目,并将结果提交给Kitware公共仪表板。仪表板的结果将被上传到Kitware的公共仪表板:https://my.cdash.org/index.php?project=CMakeTutorial,如下图所示:

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