CMake使用教程(四)

CMake 是一種跨平臺(tái)的免費(fèi)開源軟件工具,用于使用與編譯器無關(guān)的方法來管理軟件的構(gòu)建過程。在 Android Studio 上進(jìn)行 NDK 開發(fā)默認(rèn)就是使用 CMake 管理 C/C++ 代碼,因此在學(xué)習(xí) NDK 之前最好對(duì) CMake 有一定的了解。

本文主要以翻譯 CMake官方教程文檔為主,加上自己的一些理解,該教程涵蓋了 CMake 的常見使用場景。由于能力有限,翻譯部分采用機(jī)翻+人工校對(duì),翻譯有問題的地方,說聲抱歉。

開發(fā)環(huán)境:

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

混合靜態(tài)和共享

示例程序地址

在本節(jié)中,我們將展示如何使用 BUILD_SHARED_LIBS 變量來控制 add_library 的默認(rèn)行為,并允許控制構(gòu)建沒有顯式類型 (STATIC/SHARED/MODULE/OBJECT) 的庫。

為此,我們需要將 BUILD_SHARED_LIBS 添加到頂級(jí) CMakeLists.txt。我們使用 option 命令,因?yàn)樗试S用戶有選擇地選擇該值是 On 還是 Off。

接下來,我們將重構(gòu) MathFunctions 使其成為使用 mysqrtsqrt 封裝的真實(shí)庫,而不是要求調(diào)用代碼執(zhí)行此邏輯。這也意味著 USE_MYMATH 將不會(huì)控制構(gòu)建 MathFuctions,而是將控制此庫的行為。

第一步是將頂級(jí) CMakeLists.txt 的開始部分更新為:

# 設(shè)置運(yùn)行此配置文件所需的CMake最低版本
cmake_minimum_required(VERSION 3.15)

# set the project name and version
# 設(shè)置項(xiàng)目名稱和版本
project(Tutorial VERSION 1.0)

# specify the C++ standard
# 指定C ++標(biāo)準(zhǔn)
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
# 控制靜態(tài)和共享庫的構(gòu)建位置,以便在Windows上我們無需修改運(yùn)行可執(zhí)行文件的路徑
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
# 配置頭文件且僅傳遞版本號(hào)
configure_file(TutorialConfig.h.in TutorialConfig.h)

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

# add the executable
# 添加一個(gè)可執(zhí)行文件
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
# 將二進(jìn)制目錄添加到包含文件的搜索路徑中,以便我們找到TutorialConfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        )

現(xiàn)在我們將始終使用 MathFunctions 庫,我們需要更新該庫的邏輯。因此,在 MathFunctions/CMakeLists.txt 中,我們需要?jiǎng)?chuàng)建一個(gè) SqrtLibrary ,當(dāng)啟用 USE_MYMATH 時(shí)有條件地對(duì)其進(jìn)行構(gòu)建?,F(xiàn)在,由于這是一個(gè)教程,我們將明確要求 SqrtLibrary 是靜態(tài)構(gòu)建的。

最終結(jié)果是 MathFunctions/CMakeLists.txt 應(yīng)該如下所示:

# add the library that runs
# 添加運(yùn)行時(shí)庫
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.
# 說明與我們鏈接的任何人都需要包括當(dāng)前源目錄才能找到MathFunctions.h,而我們不需要。
target_include_directories(MathFunctions
        INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
        )

# should we use our own math functions
# 我們是否使用自己的數(shù)學(xué)函數(shù)
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
    # 首先,我們添加生成表的可執(zhí)行文件
    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
    # 聲明我們依靠二進(jìn)制目錄找到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
# 定義標(biāo)記在Windows上構(gòu)建時(shí)使用declspec(dllexport)
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")

# install rules
# 安裝規(guī)則
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)

接下來在 MathFunctions 文件目錄下, 新建一個(gè) mysqrt.h 文件,內(nèi)容如下:

namespace mathfunctions {
    namespace detail {
        double mysqrt(double x);
    }
}

接下來在 MathFunctions 文件目錄下, 新建一個(gè) MathFunctions.cxx 文件,內(nèi)容如下:

#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 以使用 mathfunctionsdetail 命名空間:

#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 中進(jìn)行一些更改,以使其不再使用 USE_MYMATH

  1. 始終包含 MathFunctions.h
  2. 始終使用 mathfunctions::sqrt
  3. 不包含 cmath

移除 TutorialConfig.h.in 中關(guān)于 USE_MYMATH 的定義,最后,更新 MathFunctions/MathFunctions.h 以使用 dll 導(dǎo)出定義:

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

此時(shí),如果您構(gòu)建了所有內(nèi)容,則會(huì)注意到鏈接會(huì)失敗,因?yàn)槲覀儗]有位置的靜態(tài)庫代碼庫與具有位置的代碼庫相結(jié)合。解決方案是無論構(gòu)建類型如何,都將 SqrtLibraryPOSITION_INDEPENDENT_CODE 目標(biāo)屬性顯式設(shè)置為 True。

# state that SqrtLibrary need PIC when the default is shared libraries
# 聲明默認(rèn)為共享庫時(shí),SqrtLibrary需要PIC
set_target_properties(SqrtLibrary PROPERTIES
        POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
        )
        
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)

使用 cmake-gui 構(gòu)建項(xiàng)目,勾選 BUILD_SHARED_LIBS

在項(xiàng)目根目錄運(yùn)行命令生成可執(zhí)行文件:

cmake --build cmake-build-debug

在項(xiàng)目根目錄運(yùn)行生成的可執(zhí)行文件:

./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 重新構(gòu)建項(xiàng)目,取消勾選 BUILD_SHARED_LIBS

在項(xiàng)目根目錄運(yùn)行命令生成可執(zhí)行文件:

cmake --build cmake-build-debug

在項(xiàng)目根目錄運(yùn)行生成的可執(zhí)行文件:

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

添加生成器表達(dá)式

示例程序地址

在構(gòu)建系統(tǒng)生成期間會(huì)評(píng)估生成器表達(dá)式,以生成特定于每個(gè)構(gòu)建配置的信息。

在許多目標(biāo)屬性的上下文中允許使用生成器表達(dá)式,例如 LINK_LIBRARIESINCLUDE_DIRECTORIES、 COMPILE_DEFINITIONS 等。當(dāng)使用命令填充這些屬性時(shí),也可以使用它們,例如 target_link_libraries()、target_include_directories()、target_compile_definitions()等。

生成器表達(dá)式可用于啟用條件鏈接、編譯時(shí)使用的條件定義、條件包含目錄等。這些條件可以基于構(gòu)建配置、目標(biāo)屬性、平臺(tái)信息或任何其他可查詢信息。

生成器表達(dá)式有不同類型,包括邏輯,信息和輸出表達(dá)式。

邏輯表達(dá)式用于創(chuàng)建條件輸出,基本的表達(dá)式是0和1表達(dá)式,即布爾表達(dá)式。$<0:…> 代表冒號(hào)前的條件為假,表達(dá)式的結(jié)果為空字符串。 $<1:…> 代表冒號(hào)前的條件為真,表達(dá)式的結(jié)果為“…”的內(nèi)容

生成器表達(dá)式的一個(gè)常見用法是有條件地添加編譯器標(biāo)志,例如語言級(jí)別或警告標(biāo)志。一個(gè)好的模式是將此信息與允許傳播此信息的 INTERFACE 目標(biāo)相關(guān)聯(lián)。讓我們開始構(gòu)建 INTERFACE 目標(biāo),并指定所需的 C++ 標(biāo)準(zhǔn)級(jí)別11,而不是使用 CMACHYCXXY 標(biāo)準(zhǔn)。

所以下面的代碼:

# specify the C++ standard
# 指定C ++標(biāo)準(zhǔn)
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)

接下來,我們?yōu)轫?xiàng)目添加所需的編譯器警告標(biāo)志。由于警告標(biāo)志根據(jù)編譯器的不同而不同,因此我們使用 COMPILE_LANG_AND_ID 生成器表達(dá)式來控制在給定一種語言和一組編譯器 ID 的情況下應(yīng)應(yīng)用的標(biāo)志,如下所示:

# add compiler warning flags just when building this project via
# the BUILD_INTERFACE genex
# 僅當(dāng)通過BUILD_INTERFACE生成此項(xiàng)目時(shí)添加編譯器警告標(biāo)志
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>>"
        )

我們可以看到警告標(biāo)志封裝在 BUILD_INTERFACE 條件內(nèi)。這樣做是為了讓已安裝項(xiàng)目的使用者不會(huì)繼承我們的警告標(biāo)志。

修改 MathFunctions/CMakeLists.txt 文件,使所有的目標(biāo)都增加一個(gè)調(diào)用 tutorial_compiler_flagstarget_link_libraries。

添加導(dǎo)出配置

示例程序地址

在本教程的 “安裝” 一節(jié),我們?cè)黾恿?CMake 安裝庫和項(xiàng)目頭的能力。在 "生成安裝程序“ 一節(jié),我們添加了打包此信息的功能,以便將其分發(fā)給其他人。

下一步是添加必要的信息,以便其他 CMake 項(xiàng)目可以使用我們的項(xiàng)目,無論是構(gòu)建目錄、本地安裝還是打包。

第一步是更新我們的 install(TARGETS) 命令,不僅要指定 DESTINATION,還要指定 EXPORTEXPORT 關(guān)鍵字將生成并安裝一個(gè)CMake文件,該文件包含用于從安裝樹中導(dǎo)入 install 命令中列出的所有目標(biāo)的代碼。通過更新 MathFunctions/CMakeLists.txt 中的 install 命令,顯式導(dǎo)出 MathFunctions庫,如下所示:

# install rules
# 安裝規(guī)則
install(TARGETS MathFunctions tutorial_compiler_flags
        DESTINATION lib
        EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)

現(xiàn)在我們已經(jīng)導(dǎo)出了 MathFunctions,我們還需要顯式安裝生成的 MathFunctionsTargets.cmake 文件。這是通過將以下內(nèi)容添加到頂級(jí) CMakeLists.txt 的底部來完成的:

# install the configuration targets
# 安裝配置目標(biāo)
install(EXPORT MathFunctionsTargets
        FILE MathFunctionsTargets.cmake
        DESTINATION lib/cmake/MathFunctions
        )

此時(shí),您應(yīng)該嘗試運(yùn)行 CMake。如果一切設(shè)置正確,您將看到 CMake 將生成如下錯(cuò)誤:

Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:

  "/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"

which is prefixed in the source directory.

CMake 想說的是,在生成導(dǎo)出信息的過程中,它將導(dǎo)出一個(gè)與當(dāng)前機(jī)器有內(nèi)在聯(lián)系的路徑,并且在其他機(jī)器上無效。解決方案是更新 MathFunctionstarget_include_directories,讓 CMake 理解在從生成目錄和安裝/打包中使用時(shí)需要不同的接口位置。這意味著將 MathFunctions 調(diào)用的 target_include_directories 轉(zhuǎn)換為如下所示:

# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# 說明與我們鏈接的任何人都需要包括當(dāng)前源目錄才能找到MathFunctions.h,而我們不需要。
target_include_directories(MathFunctions
        INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
        $<INSTALL_INTERFACE:include>
        )

更新后,我們可以重新運(yùn)行 CMake 并查看是否不再發(fā)出警告。

至此,我們已經(jīng)正確地包裝了 CMake 所需的目標(biāo)信息,但仍然需要生成 MathFunctionsConfig.cmake,以便 CMake find_package 命令可以找到我們的項(xiàng)目。因此,我們將添加新文件 Config.cmake.in 到項(xiàng)目的頂層,其內(nèi)容如下:

@PACKAGE_INIT@

include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

然后,要正確配置和安裝該文件,請(qǐng)?jiān)陧敿?jí) CMakeLists 的底部添加以下內(nèi)容:

# install the configuration targets
# 安裝配置目標(biāo)
install(EXPORT MathFunctionsTargets
        FILE MathFunctionsTargets.cmake
        DESTINATION lib/cmake/MathFunctions
        )

include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
# 生成包含導(dǎo)出的配置文件
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
        )

至此,我們?yōu)轫?xiàng)目生成了可重定位的 CMake 配置,可以在安裝或打包項(xiàng)目后使用它。如果我們也希望從構(gòu)建目錄中使用我們的項(xiàng)目,則只需將以下內(nèi)容添加到頂級(jí) CMakeLists 的底部:

# generate the export targets for the build tree
# needs to be after the install(TARGETS ) command
# 在install(TARGETS)命令之后生成生成樹的導(dǎo)出目標(biāo)
export(EXPORT MathFunctionsTargets
        FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
        )

通過此導(dǎo)出調(diào)用我們將生成一個(gè) Targets.cmake,允許在構(gòu)建目錄中配置的 MathFunctionsConfig.cmake 由其他項(xiàng)目使用,而無需安裝它。

CMake使用教程系列文章

  • CMake使用教程(一)
    • 基礎(chǔ)項(xiàng)目
    • 添加版本號(hào)和配置頭文件
    • 指定C++標(biāo)準(zhǔn)
    • 添加庫
    • 提供選項(xiàng)
  • CMake使用教程(二)
    • 添加“庫”的使用要求
    • 安裝
    • 測試
    • 系統(tǒng)自檢
  • CMake使用教程(三)
    • 指定編譯定義
    • 添加自定義命令和生成的文件
    • 生成安裝程序
    • 添加對(duì)儀表板的支持
  • CMake使用教程(四)
    • 混合靜態(tài)和共享
    • 添加生成器表達(dá)式
    • 添加導(dǎo)出配置
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容