CMake使用教程(四)

CMake 是一種跨平臺的免費開源軟件工具,用於使用與編譯器無關的方法來管理軟件的構建過程。在 Android Studio 上進行 NDK 開發默認就是使用 CMake 管理 C/C++ 代碼,所以在學習 NDK 以前最好對 CMake 有必定的瞭解。html

本文主要以翻譯 CMake官方教程文檔爲主,加上本身的一些理解,該教程涵蓋了 CMake 的常見使用場景。因爲能力有限,翻譯部分採用機翻+人工校對,翻譯有問題的地方,說聲抱歉。ios

開發環境:c++

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

混合靜態和共享

示例程序地址git

在本節中,咱們將展現如何使用 BUILD_SHARED_LIBS 變量來控制 add_library 的默認行爲,並容許控制構建沒有顯式類型 (STATIC/SHARED/MODULE/OBJECT) 的庫。github

爲此,咱們須要將 BUILD_SHARED_LIBS 添加到頂級 CMakeLists.txt。咱們使用 option 命令,由於它容許用戶有選擇地選擇該值是 On 仍是 Offshell

接下來,咱們將重構 MathFunctions 使其成爲使用 mysqrtsqrt 封裝的真實庫,而不是要求調用代碼執行此邏輯。這也意味着 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 以使用 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 中進行一些更改,以使其再也不使用 USE_MYMATH

  1. 始終包含 MathFunctions.h
  2. 始終使用 mathfunctions::sqrt
  3. 不包含 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);
}
複製代碼

此時,若是您構建了全部內容,則會注意到連接會失敗,由於咱們將沒有位置的靜態庫代碼庫與具備位置的代碼庫相結合。解決方案是不管構建類型如何,都將 SqrtLibraryPOSITION_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_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_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_flagstarget_link_libraries

添加導出配置

示例程序地址

在本教程的 「安裝」 一節,咱們增長了 CMake 安裝庫和項目頭的能力。在 "生成安裝程序「 一節,咱們添加了打包此信息的功能,以便將其分發給其餘人。

下一步是添加必要的信息,以便其餘 CMake 項目可使用咱們的項目,不管是構建目錄、本地安裝仍是打包。

第一步是更新咱們的 install(TARGETS) 命令,不只要指定 DESTINATION,還要指定 EXPORTEXPORT 關鍵字將生成並安裝一個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 想說的是,在生成導出信息的過程當中,它將導出一個與當前機器有內在聯繫的路徑,而且在其餘機器上無效。解決方案是更新 MathFunctionstarget_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 由其餘項目使用,而無需安裝它。

CMake使用教程系列文章

相關文章
相關標籤/搜索