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
由其餘項目使用,而無需安裝它。