CMake
是一种跨平台的免费开源软件工具,用于使用与编译器无关的方法来管理软件的构建过程。在 Android Studio
上进行 NDK
开发默认就是使用 CMake
管理 C/C++
代码,所以在学习 NDK
以前最好对 CMake
有必定的了解。html
本文主要以翻译 CMake
的官方教程文档为主,加上本身的一些理解,该教程涵盖了 CMake
的常见使用场景。因为能力有限,翻译部分采用机翻+人工校对,翻译有问题的地方,说声抱歉。ios
开发环境:c++
示例程序地址git
在本节中,咱们将展现如何使用 BUILD_SHARED_LIBS
变量来控制 add_library
的默认行为,并容许控制构建没有显式类型 (STATIC/SHARED/MODULE/OBJECT)
的库。github
为此,咱们须要将 BUILD_SHARED_LIBS
添加到顶级 CMakeLists.txt
。咱们使用 option
命令,由于它容许用户有选择地选择该值是 On
仍是 Off
。shell
接下来,咱们将重构 MathFunctions
使其成为使用 mysqrt
或 sqrt
封装的真实库,而不是要求调用代码执行此逻辑。这也意味着 USE_MYMATH
将不会控制构建 MathFuctions
,而是将控制此库的行为。express
第一步是将顶级 CMakeLists.txt
的开始部分更新为:windows
# 设置运行此配置文件所需的CMake最低版本
cmake_minimum_required(VERSION 3.15)
# set the project name and version
# 设置项目名称和版本
project(Tutorial VERSION 1.0)
# specify the C++ standard
# 指定C ++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
# 控制静态和共享库的构建位置,以便在Windows上咱们无需修改运行可执行文件的路径
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 a header file to pass the version number only
# 配置头文件且仅传递版本号
configure_file(TutorialConfig.h.in TutorialConfig.h)
# 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}"
)
复制代码
如今咱们将始终使用 MathFunctions
库,咱们须要更新该库的逻辑。所以,在 MathFunctions/CMakeLists.txt
中,咱们须要建立一个 SqrtLibrary
,当启用 USE_MYMATH
时有条件地对其进行构建。如今,因为这是一个教程,咱们将明确要求 SqrtLibrary
是静态构建的。bash
最终结果是 MathFunctions/CMakeLists.txt
应该以下所示:ide
# add the library that runs
# 添加运行时库
add_library(MathFunctions MathFunctions.cxx)
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# 说明与咱们连接的任何人都须要包括当前源目录才能找到MathFunctions.h,而咱们不须要。
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
# 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
# 只包含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
# 声明咱们依靠二进制目录找到Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif ()
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
# 定义标记在Windows上构建时使用declspec(dllexport)
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
# install rules
# 安装规则
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
复制代码
接下来在 MathFunctions
文件目录下, 新建一个 mysqrt.h
文件,内容以下:
namespace mathfunctions {
namespace detail {
double mysqrt(double x);
}
}
复制代码
接下来在 MathFunctions
文件目录下, 新建一个 MathFunctions.cxx
文件,内容以下:
#include "MathFunctions.h"
#ifdef USE_MYMATH
# include "mysqrt.h"
#else
# include <cmath>
#endif
namespace mathfunctions {
double sqrt(double x) {
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
}
}
复制代码
接下来,更新 MathFunctions/mysqrt.cxx
以使用 mathfunctions
和 detail
命名空间:
#include <iostream>
#include "mysqrt.h"
// include the generated table
#include "Table.h"
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
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;
}
}
}
复制代码
咱们还须要在 tutorial.cxx
中进行一些更改,以使其再也不使用 USE_MYMATH
:
MathFunctions.h
mathfunctions::sqrt
cmath
移除 TutorialConfig.h.in
中关于 USE_MYMATH
的定义,最后,更新 MathFunctions/MathFunctions.h
以使用 dll
导出定义:
#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);
}
复制代码
此时,若是您构建了全部内容,则会注意到连接会失败,由于咱们将没有位置的静态库代码库与具备位置的代码库相结合。解决方案是不管构建类型如何,都将 SqrtLibrary
的 POSITION_INDEPENDENT_CODE
目标属性显式设置为 True
。
# state that SqrtLibrary need PIC when the default is shared libraries
# 声明默认为共享库时,SqrtLibrary须要PIC
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
复制代码
使用 cmake-gui
构建项目,勾选 BUILD_SHARED_LIBS
在项目根目录运行命令生成可执行文件:
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-gui
从新构建项目,取消勾选 BUILD_SHARED_LIBS
在项目根目录运行命令生成可执行文件:
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
复制代码
在构建系统生成期间会评估生成器表达式,以生成特定于每一个构建配置的信息。
在许多目标属性的上下文中容许使用生成器表达式,例如 LINK_LIBRARIES
、INCLUDE_DIRECTORIES
、 COMPILE_DEFINITIONS
等。当使用命令填充这些属性时,也可使用它们,例如 target_link_libraries()
、target_include_directories()
、target_compile_definitions()
等。
生成器表达式可用于启用条件连接、编译时使用的条件定义、条件包含目录等。这些条件能够基于构建配置、目标属性、平台信息或任何其余可查询信息。
生成器表达式有不一样类型,包括逻辑,信息和输出表达式。
逻辑表达式用于建立条件输出,基本的表达式是0和1表达式,即布尔表达式。$<0:…>
表明冒号前的条件为假,表达式的结果为空字符串。 $<1:…>
表明冒号前的条件为真,表达式的结果为“…”的内容
生成器表达式的一个常见用法是有条件地添加编译器标志,例如语言级别或警告标志。一个好的模式是将此信息与容许传播此信息的 INTERFACE
目标相关联。让咱们开始构建 INTERFACE
目标,并指定所需的 C++
标准级别11,而不是使用 CMACHYCXXY
标准。
因此下面的代码:
# specify the C++ standard
# 指定C ++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
复制代码
将被替换为:
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
复制代码
接下来,咱们为项目添加所需的编译器警告标志。因为警告标志根据编译器的不一样而不一样,所以咱们使用 COMPILE_LANG_AND_ID
生成器表达式来控制在给定一种语言和一组编译器 ID
的状况下应应用的标志,以下所示:
# add compiler warning flags just when building this project via
# the BUILD_INTERFACE genex
# 仅当经过BUILD_INTERFACE生成此项目时添加编译器警告标志
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
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>>"
)
复制代码
咱们能够看到警告标志封装在 BUILD_INTERFACE
条件内。这样作是为了让已安装项目的使用者不会继承咱们的警告标志。
修改 MathFunctions/CMakeLists.txt
文件,使全部的目标都增长一个调用 tutorial_compiler_flags
的 target_link_libraries
。
在本教程的 “安装” 一节,咱们增长了 CMake
安装库和项目头的能力。在 "生成安装程序“ 一节,咱们添加了打包此信息的功能,以便将其分发给其余人。
下一步是添加必要的信息,以便其余 CMake
项目可使用咱们的项目,不管是构建目录、本地安装仍是打包。
第一步是更新咱们的 install(TARGETS)
命令,不只要指定 DESTINATION
,还要指定 EXPORT
。EXPORT
关键字将生成并安装一个CMake文件,该文件包含用于从安装树中导入 install
命令中列出的全部目标的代码。经过更新 MathFunctions/CMakeLists.txt
中的 install
命令,显式导出 MathFunctions
库,以下所示:
# install rules
# 安装规则
install(TARGETS MathFunctions tutorial_compiler_flags
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
复制代码
如今咱们已经导出了 MathFunctions
,咱们还须要显式安装生成的 MathFunctionsTargets.cmake
文件。这是经过将如下内容添加到顶级 CMakeLists.txt
的底部来完成的:
# install the configuration targets
# 安装配置目标
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
复制代码
此时,您应该尝试运行 CMake
。若是一切设置正确,您将看到 CMake
将生成以下错误:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
复制代码
CMake
想说的是,在生成导出信息的过程当中,它将导出一个与当前机器有内在联系的路径,而且在其余机器上无效。解决方案是更新 MathFunctions
的 target_include_directories
,让 CMake
理解在从生成目录和安装/打包中使用时须要不一样的接口位置。这意味着将 MathFunctions
调用的 target_include_directories
转换为以下所示:
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# 说明与咱们连接的任何人都须要包括当前源目录才能找到MathFunctions.h,而咱们不须要。
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
复制代码
更新后,咱们能够从新运行 CMake
并查看是否再也不发出警告。
至此,咱们已经正确地包装了 CMake
所需的目标信息,但仍然须要生成 MathFunctionsConfig.cmake
,以便 CMake find_package
命令能够找到咱们的项目。所以,咱们将添加新文件 Config.cmake.in
到项目的顶层,其内容以下:
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
复制代码
而后,要正确配置和安装该文件,请在顶级 CMakeLists
的底部添加如下内容:
# install the configuration targets
# 安装配置目标
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
# 生成包含导出的配置文件
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
# 生成配置文件的版本文件
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
的底部:
# generate the export targets for the build tree
# needs to be after the install(TARGETS ) command
# 在install(TARGETS)命令以后生成生成树的导出目标
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
复制代码
经过此导出调用咱们将生成一个 Targets.cmake
,容许在构建目录中配置的 MathFunctionsConfig.cmake
由其余项目使用,而无需安装它。