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_LOG
和 HAVE_EXP
值之外,还有更好的做法吗?对于此示例,我们将尝试使用 target_compile_definitions
。
首先,从 TutorialConfig.h.in
中删除上一步的定义,在 mysqrt.cxx
中不再包含 TutorialConfig.h
,移除上一步在 MathFunctions/CMakeLists.txt
中增加的额外包含。
接下来,我们可以将 HAVE_LOG
和 HAVE_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
中的 log
和 exp
函数的检查。然后从 mysqrt.cxx
中删除对 HAVE_LOG
和 HAVE_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使用教程系列文章
-
CMake使用教程(一)
- 基础项目
- 添加版本号和配置头文件
- 指定C++标准
- 添加库
- 提供选项
-
CMake使用教程(二)
- 添加“库”的使用要求
- 安装
- 测试
- 系统自检
-
CMake使用教程(三)
- 指定编译定义
- 添加自定义命令和生成的文件
- 生成安装程序
- 添加对仪表板的支持
-
CMake使用教程(四)
- 混合静态和共享
- 添加生成器表达式
- 添加导出配置