一個不錯的Modern CMake的入門教程



CMake使用教程(一)

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

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

開發環境:

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

基礎項目

示例程序地址

最基礎的項目是單個源代碼文件構建的可執行文件。

本示例提供的源代碼文件是 tutorial.cxx,可用於計算數字的平方根。代碼如下:

// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double inputValue = atof(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
復制代碼

對於簡單的項目,只需三行內容的 CMakeLists.txt 文件,這將是本教程的起點。在項目根目錄下創建一個 CMakeLists.txt 文件,其內容如下:

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

# set the project name
# 設置項目名稱
project(Tutorial)

# add the executable
# 添加一個可執行文件
add_executable(Tutorial tutorial.cxx)
復制代碼

請注意,此示例在 CMakeLists.txt 文件中使用小寫命令。CMake 支持大寫,小寫和大小寫混合命令。

當前項目結構如下:

.
├── CMakeLists.txt
└── tutorial.cxx
復制代碼

在項目根目錄運行命令生成編譯中間文件以及 makefile 文件:

cmake .
復制代碼

命令執行后會在項目根目錄下生成文件,項目結構如下:

.
├── CMakeCache.txt
├── CMakeFiles
├── CMakeLists.txt
├── Makefile
├── cmake_install.cmake
└── tutorial.cxx
復制代碼

這樣源文件和生成的文件都混在一起,不方便管理,建議使用一個專門的目錄管理這些生成的文件。這里使用 CLion 默認生成文件目錄 cmake-build-debug,在項目根目錄運行編譯命令並指定生成文件目錄:

cmake -B cmake-build-debug
復制代碼

項目結構如下:

.
├── CMakeLists.txt
├── cmake-build-debug
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── Makefile
│   └── cmake_install.cmake
└── tutorial.cxx
復制代碼

在項目根目錄運行命令生成可執行文件:

cmake --build cmake-build-debug
復制代碼

命令執行后生成了可執行文件 Tutorial,項目結構如下:

 .
 ├── CMakeLists.txt
 ├── cmake-build-debug
 │   ├── CMakeCache.txt
 │   ├── CMakeFiles
 │   ├── Makefile
+│   ├── Tutorial
 │   └── cmake_install.cmake
 └── tutorial.cxx
復制代碼

在項目根目錄運行生成的可執行文件且不攜帶參數:

./cmake-build-debug/Tutorial
復制代碼

終端輸出:

Usage: ./cmake-build-debug/Tutorial number
復制代碼

在項目根目錄運行生成的可執行文件並攜帶參數:

./cmake-build-debug/Tutorial 2
復制代碼

終端輸出:

The square root of 2 is 1.41421
復制代碼

添加版本號和配置頭文件

示例程序地址

我們添加的第一個功能是為我們的可執行文件和項目提供版本號。雖然我們可以僅在源代碼中執行此操作,但是使用 CMakeLists.txt 可以提供更大的靈活性。

首先,修改 CMakeLists.txt 文件以設置版本號。

project(Tutorial VERSION 1.0)
復制代碼

然后,配置頭文件以將版本號傳遞給源代碼:

# configure a header file to pass some of the CMake settings
# to the source code
# 配置頭文件以將某些CMake設置傳遞給源代碼
configure_file(TutorialConfig.h.in TutorialConfig.h)
復制代碼

由於已配置的文件將被寫入二進制目錄,因此我們必須將該目錄添加到路徑列表中以搜索包含文件。將以下行添加到CMakeLists.txt文件的末尾:

# 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}"
        )
復制代碼

使用您喜歡的編輯器,在源目錄中使用以下內容創建 TutorialConfig.h.in

// the configured options and settings for Tutorial
// 教程的配置選項和設置
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
復制代碼

CMake 配置此頭文件時,會在二進制目錄下生成一個文件 TutorialConfig.h,會把 TutorialConfig.h.in 中的內容拷貝到里面,只是把 @Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@ 替換成在 CMakeLists.txt 的配置的 1 和 0。

這里的 1 和 0 是怎么和 Tutorial_VERSION_MAJORTutorial_VERSION_MINOR關聯上的? 在 project() 中指定了 VERSION 后,CMake 會把版本信息存儲在以下變量中:

  • PROJECT_VERSION, _VERSION
  • PROJECT_VERSION_MAJOR, _VERSION_MAJOR
  • PROJECT_VERSION_MINOR, _VERSION_MINOR
  • PROJECT_VERSION_PATCH, _VERSION_PATCH
  • PROJECT_VERSION_TWEAK, _VERSION_TWEAK.

MAJORMINORPATCHTWEAK 分別代表着版本號的四位,比如版本號 1.2.3.4MAJOR=1MINOR=2PATCH=3TWEAK=4。版本號不一定非得是4位,可以只有1位,只是最大為4位。

這里 PROJECT-NAME 值為 Tutorial,所以能從 Tutorial_VERSION_MAJORTutorial_VERSION_MINOR 中讀取到版本信息。

當從頂層 CMakeLists.txt 調用 project() 命令時,該版本也存儲在變量 CMAKE_PROJECT_VERSION 中。

接下來,修改 tutorial.cxx 以包含配置的頭文 件TutorialConfig.h 和打印出版本號,如下所示:

// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

+ #include "TutorialConfig.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
+        // report version
+        std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
+                  << Tutorial_VERSION_MINOR << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double inputValue = atof(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
復制代碼

在項目根目錄運行命令編譯項目和生成可執行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
復制代碼

在項目根目錄運行生成的可執行文件且不攜帶參數:

./cmake-build-debug/Tutorial
復制代碼

終端輸出:

./cmake-build-debug/Tutorial Version 1.0
Usage: ./cmake-build-debug/Tutorial number
復制代碼

指定C++標准

示例程序地址

CMake 中啟用對特定 C ++ 標准的支持的最簡單方法是使用 CMAKE_CXX_STANDARD 變量。對於本教程,請將 CMakeLists.txt 文件中的 CMAKE_CXX_STANDARD 變量設置為11,並將 CMAKE_CXX_STANDARD_REQUIRED 設置為 True

# specify the C++ standard
# 指定C ++標准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
復制代碼

接下來,通過在 tutorial.cxx 中用 std :: stod 替換 atof,將一些 C ++ 11 功能添加到我們的項目中。同時,刪除 #include

// A simple program that computes the square root of a number
#include <cmath>
- #include <cstdlib>
#include <iostream>
#include <string>

#include "TutorialConfig.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
        // report version
        std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
                  << Tutorial_VERSION_MINOR << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
-    const double inputValue = atof(argv[1]);
+    const double inputValue = std::stod(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
復制代碼

在項目根目錄運行命令編譯項目和生成可執行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
復制代碼

在項目根目錄運行生成的可執行文件:

./cmake-build-debug/Tutorial 2
復制代碼

終端輸出:

The square root of 2 is 1.41421
復制代碼

添加庫

示例程序地址

現在,我們將添加一個庫到我們的項目中,該庫用於計算數字的平方根,可執行文件可以使用此庫,而不是使用編譯器提供的標准平方根函數。該庫有兩個文件:

  • MathFunctions.h

    double mysqrt(double x);
    復制代碼
    
  • mysqrt.cxx

    源文件有一個 mysqrt 的函數,該函數提供與編譯器的 sqrt 函數類似的功能。

    #include <iostream>
    
    #include "MathFunctions.h"
    
    // a hack square root calculation using simple operations
    double mysqrt(double x) {
        if (x <= 0) {
            return 0;
        }
    
        double result = 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;
    }
    復制代碼
    

在項目根目錄下創建一個文件夾 MathFunctions ,把該庫放在其下,在其下創建一個 CMakeLists.txt 文件,內容如下:

add_library(MathFunctions mysqrt.cxx)
復制代碼

為了使用新庫,我們將在頂層 CMakeLists.txt 文件中添加 add_subdirectory 調用,以便構建該庫。我們將新庫添加到可執行文件,並將 MathFunctions 添加為包含目錄,以便可以找到 mqsqrt.h 頭文件。頂級 CMakeLists.txt 文件的最后幾行現在應如下所示:

# 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}"
        "${PROJECT_SOURCE_DIR}/MathFunctions"
        )
復制代碼

修改 tutorial.cxx 使用引入的庫,其內容如下:

// A simple program that computes the square root of a number
- #include <cmath>
#include <iostream>
#include <string>

#include "TutorialConfig.h"
+ #include "MathFunctions.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
        // report version
        std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
                  << Tutorial_VERSION_MINOR << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double inputValue = std::stod(argv[1]);

    // calculate square root
-    const double outputValue = sqrt(inputValue);
+    const double outputValue = mysqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
復制代碼

在項目根目錄運行命令編譯項目和生成可執行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
復制代碼

在項目根目錄運行生成的可執行文件:

./cmake-build-debug/Tutorial 2
復制代碼

終端輸出:

Computing sqrt of 2 to be 1.5
Computing sqrt of 2 to be 1.41667
Computing sqrt of 2 to be 1.41422
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
復制代碼

提供選項

示例程序地址

現在讓我們將 MathFunctions 庫設為可選。雖然對於本教程而言確實不需要這樣做,但是對於大型項目來說,這是很常見的。第一步是向頂級 CMakeLists.txt 文件添加一個選項:

# should we use our own math functions
# 我們應該使用自己的數學函數嗎
option(USE_MYMATH "Use tutorial provided math implementation" ON)
復制代碼

此選項將顯示在 CMake GUIccmake 中,默認值ON可由用戶更改。此設置將存儲在緩存中,因此用戶無需在每次在構建目錄上運行CMake時都設置該值。

下一個是使建立和鏈接 MathFunctions 庫成為條件。為此,我們將頂級 CMakeLists.txt 文件的結尾更改為如下所示:

# add the MathFunctions library
# 添加 MathFunctions 庫
if (USE_MYMATH)
    add_subdirectory(MathFunctions)
    list(APPEND EXTRA_LIBS MathFunctions)
    list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif ()

# add the executable
# 添加一個可執行文件
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})

# 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}"
        ${EXTRA_INCLUDES}
        )
復制代碼

請注意,這里使用變量 EXTRA_LIBS 來收集所有可選庫,以供以后鏈接到可執行文件中。變量 EXTRA_INCLUDES 類似地用於可選的頭文件。當處理許多可選組件時,這是一種經典方法,我們將在下一步中介紹現代方法。

對源代碼的相應更改非常簡單。首先,根據需要在 tutorial.cxx 中決定包含 MathFunctions 頭還是 包含 ``:

// should we include the MathFunctions header?
#ifdef USE_MYMATH
#include "MathFunctions.h"
#else
#include <cmath>
#endif
復制代碼

然后,在同一文件中,使用 USE_MYMATH 來確定使用哪個平方根函數:

#ifdef USE_MYMATH
  const double outputValue = mysqrt(inputValue);
#else
  const double outputValue = sqrt(inputValue);
#endif
復制代碼

由於源代碼現在需要 USE_MYMATH,因此可以使用以下行將其添加到 TutorialConfig.h.in 中:

#cmakedefine USE_MYMATH
復制代碼

download 上根據自己的平台下載對應版本的 cmake-gui,安裝后打開軟件,選擇源代碼目錄和生成文件,如下圖所示:

img

點擊左下角 Generate 按鈕,軟件會彈出的選擇項目生成器的彈窗,這里默認就好,點擊點擊 Done 按鈕,cmake-gui 開始編譯項目,生成中間文件,並且可以在軟件看到我們為用戶提供的選項:

img

這個時候 cmake-build-debug/TutorialConfig.h 的內容如下:

// the configured options and settings for Tutorial
// 教程的配置選項和設置
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
#define USE_MYMATH
復制代碼

在項目根目錄運行命令生成可執行文件:

cmake --build cmake-build-debug
復制代碼

在項目根目錄運行生成的可執行文件:

./cmake-build-debug/Tutorial 2
復制代碼

終端輸出:

Computing sqrt of 2 to be 1.5
Computing sqrt of 2 to be 1.41667
Computing sqrt of 2 to be 1.41422
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 中的 USE_MYMATH 的勾選,點擊 Generate 按鈕重新編譯項目,這個時候 cmake-build-debug/TutorialConfig.h 的內容如下:

// the configured options and settings for Tutorial
// 教程的配置選項和設置
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
/* #undef USE_MYMATH */
復制代碼

在項目根目錄運行命令生成可執行文件:

cmake --build cmake-build-debug
復制代碼

在項目根目錄運行生成的可執行文件:

./cmake-build-debug/Tutorial 2
復制代碼

終端輸出:

The square root of 2 is 1.41421
復制代碼

CMake使用教程系列文章

  • CMake使用教程(一)
    • 基礎項目
    • 添加版本號和配置頭文件
    • 指定C++標准
    • 添加庫
    • 提供選項
  • CMake使用教程(二)
    • 添加“庫”的使用要求
    • 安裝
    • 測試
    • 系統自檢
  • CMake使用教程(三)
    • 指定編譯定義
    • 添加自定義命令和生成的文件
    • 生成安裝程序
    • 添加對儀表板的支持
  • CMake使用教程(四)
    • 混合靜態和共享
    • 添加生成器表達式
    • 添加導出配置

關注下面的標簽,發現更多相似文章

CMake使用教程(二)

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

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

開發環境:

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

添加“庫”的使用要求

示例程序地址

使用要求可以更好地控制庫或可執行文件的鏈接和包含行,同時還可以更好地控制 CMake 內部目標的傳遞屬性。利用使用要求的主要命令是:

  • target_compile_definitions

    給指定目標添加編譯定義。

  • target_compile_options

    給指定目標添加編譯選項。

  • target_include_directories

    給指定目標添加包含目錄。

  • target_link_libraries

    指定鏈接給定目標或其依賴項時要使用的庫或標志。

控制 CMake 內部目標的傳遞屬性有三種類型:

  • PRIVATE

    屬性只應用到本目標,不應用到鏈接本目標的目標。即生產者需要,消費者不需要。

  • PUBLIC

    屬性既應用到本目標也應用到鏈接目標的目標。即生產者和消費者都需要。

  • INTERFACE

    屬性不應用到本目標,應用到鏈接本目標的目標。即生產者不需要,消費者需要。

讓我們重構代碼“提供選項”項目的代碼,以使用現代 CMake 的使用要求方法。我們首先聲明,鏈接到 MathFunctions 的任何人都需要包含當前源目錄,而 MathFunctions 本身不需要。因此,這里使用 INTERFACE

將以下行添加到 MathFunctions/CMakeLists.txt 的末尾:

# 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}
        )
復制代碼

現在,我們已經指定了 MathFunction 的使用要求,我們可以安全地從頂級 CMakeLists.txt 中刪除對 EXTRA_INCLUDES變量的使用:

if(USE_MYMATH)
  add_subdirectory(MathFunctions)
  list(APPEND EXTRA_LIBS MathFunctions)
endif()

target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        )
復制代碼

在項目根目錄運行命令編譯項目和生成可執行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
復制代碼

在項目根目錄運行生成的可執行文件:

./cmake-build-debug/Tutorial 2
復制代碼

終端輸出:

Computing sqrt of 2 to be 1.5
Computing sqrt of 2 to be 1.41667
Computing sqrt of 2 to be 1.41422
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
復制代碼

安裝

示例程序地址

安裝規則非常簡單:對於 MathFunctions ,我們要安裝庫和頭文件,對於應用程序,我們要安裝可執行文件和配置的頭文件。

因此,在 MathFunctions/CMakeLists.txt 的末尾添加:

# install rules
# 安裝規則
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
復制代碼

並在頂級 CMakeLists.txt 的末尾添加:

# add the install targets
# 添加安裝規則
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
        DESTINATION include
        )
復制代碼

這就是本地安裝所需的全部。

在項目根目錄運行命令編譯項目和生成可執行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
復制代碼

在項目根目錄運行命令安裝可執行文件:

 cmake --install cmake-build-debug
復制代碼

CMake 從3.15開始使用 cmake --install 安裝文件。CMake 變量 CMAKE_INSTALL_PREFIX 用於確定文件的安裝根目錄。如果使用 cmake --install,則可以通過 --prefix 參數指定自定義安裝目錄。對於多配置工具,請使用 --config 參數指定配置。

終端輸出:

-- Install configuration: ""
-- Installing: /usr/local/lib/libMathFunctions.a
-- Installing: /usr/local/include/MathFunctions.h
-- Installing: /usr/local/bin/Tutorial
-- Installing: /usr/local/include/TutorialConfig.h
復制代碼

在項目根目錄執行命令:

Tutorial 2
復制代碼

終端輸出:

Computing sqrt of 2 to be 1.5
Computing sqrt of 2 to be 1.41667
Computing sqrt of 2 to be 1.41422
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 文件,而是安裝到 /usr/local/bin 目錄下的 Tutorial 文件。我們可以通過命令查看一下 Tutorial 的位置:

where Tutorial
復制代碼

終端輸出:

/usr/local/bin/Tutorial
復制代碼

測試

示例程序地址

接下來,測試我們的應用程序。在頂級 CMakeLists 文件的末尾,我們可以啟用測試,然后添加一些基本測試以驗證應用程序是否正常運行。

# enable testing
# 啟用測試
enable_testing()

# does the application run
# 測試應用程序是否運行
add_test(NAME Runs COMMAND Tutorial 25)

# does the usage message work?
# 測試消息是否工作?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
        PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
        )

# define a function to simplify adding tests
# 定義一個函數以簡化添加測試
function(do_test target arg result)
    add_test(NAME Comp${arg} COMMAND ${target} ${arg})
    set_tests_properties(Comp${arg}
            PROPERTIES PASS_REGULAR_EXPRESSION ${result}
            )
endfunction(do_test)

# do a bunch of result based tests
# 做一堆基於結果的測試
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
復制代碼

第一個測試只是驗證應用程序正在運行,沒有段錯誤或其他崩潰,並且返回值為零。這是 CTest 測試的基本形式。

下一個測試使用 PASS_REGULAR_EXPRESSION 測試屬性來驗證測試的輸出是否包含某些字符串。在這種情況下,驗證在提供了錯誤數量的參數時是否打印了用法消息。

最后,我們有一個名為 do_test 的函數,該函數運行應用程序並驗證所計算的平方根對於給定輸入是否正確。對於 do_test 的每次調用,都會基於傳遞的參數將另一個測試添加到項目中,該測試具有名稱,輸入和預期結果。

在項目根目錄運行命令編譯項目和生成可執行文件:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
復制代碼

在項目根目錄運行命令測試應用程序:

cd cmake-build-debug
ctest
復制代碼

終端輸出:

Test project /Users/taylor/Project/Taylor/C/Study/cmake-tutorial/cmake-test/cmake-build-debug
    Start 1: Runs
1/9 Test #1: Runs .............................   Passed    0.00 sec
    Start 2: Usage
2/9 Test #2: Usage ............................   Passed    0.00 sec
    Start 3: Comp4
3/9 Test #3: Comp4 ............................   Passed    0.00 sec
    Start 4: Comp9
4/9 Test #4: Comp9 ............................   Passed    0.00 sec
    Start 5: Comp5
5/9 Test #5: Comp5 ............................   Passed    0.00 sec
    Start 6: Comp7
6/9 Test #6: Comp7 ............................   Passed    0.00 sec
    Start 7: Comp25
7/9 Test #7: Comp25 ...........................   Passed    0.00 sec
    Start 8: Comp-25
8/9 Test #8: Comp-25 ..........................   Passed    0.00 sec
    Start 9: Comp0.0001
9/9 Test #9: Comp0.0001 .......................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 9

Total Test time (real) =   0.03 sec
復制代碼

系統自檢

示例程序地址

讓我們考慮向我們的項目中添加一些代碼,這些代碼取決於目標平台可能不具備的功能。

對於此示例,我們將添加一些代碼,具體取決於目標平台是否具有 logexp 函數。當然,幾乎每個平台都具有這些功能,但對於本教程而言,假定它們並不常見。

如果平台具有 logexp ,那么我們將使用它們來計算 mysqrt 函數中的平方根。我們首先在頂級 CMakeList 中使用 CheckSymbolExists.cmake 宏測試這些功能的可用性。

# 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)
復制代碼

TutorialConfig.hconfigure_file 命令之前完成對 logexp 的測試非常重要,configure_file 命令使用 CMake 中的當前設置立即配置文件,所以 check_symbol_exists 命令應該放在 configure_file 之前。

現在,將這些定義添加到 TutorialConfig.h.in 中,以便我們可以從 mysqrt.cxx中使用它們:

// does the platform provide exp and log functions?
// 平台是否提供log和exp函數?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
復制代碼

更新 MathFunctions/CMakeLists.txt 文件,以便 mysqrt.cxx知道此文件的位置:

target_include_directories(MathFunctions
          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
          PRIVATE ${CMAKE_BINARY_DIR}
          )
復制代碼

修改 mysqrt.cxx 以包含 cmathTutorialConfig.h。接下來,在 mysqrt函數的同一文件中,我們可以使用以下代碼(如果在系統上可用)提供基於 logexp 的替代實現(在返回結果前不要忘記 #endif !):

我們將在 TutorialConfig.h.in 中使用新定義,因此請確保在配置該文件之前進行設置。

#if defined(HAVE_LOG) && defined(HAVE_EXP)
    double result = exp(log(x) * 0.5);
    std::cout << "Computing sqrt of " << x << " to be " << result
              << " using log and exp" << std::endl;
#else
    double result = 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;
    }
#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
復制代碼

CMake使用教程系列文章

  • CMake使用教程(一)
    • 基礎項目
    • 添加版本號和配置頭文件
    • 指定C++標准
    • 添加庫
    • 提供選項
  • CMake使用教程(二)
    • 添加“庫”的使用要求
    • 安裝
    • 測試
    • 系統自檢
  • CMake使用教程(三)
    • 指定編譯定義
    • 添加自定義命令和生成的文件
    • 生成安裝程序
    • 添加對儀表板的支持
  • CMake使用教程(四)
    • 混合靜態和共享
    • 添加生成器表達式
    • 添加導出配置

關注下面的標簽,發現更多相似文章

CMake使用教程(三)

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_LOGHAVE_EXP 值之外,還有更好的做法嗎?對於此示例,我們將嘗試使用 target_compile_definitions

首先,從 TutorialConfig.h.in 中刪除上一步的定義,在 mysqrt.cxx 中不再包含 TutorialConfig.h,移除上一步在 MathFunctions/CMakeLists.txt 中增加的額外包含。

接下來,我們可以將 HAVE_LOGHAVE_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 中的 logexp 函數的檢查。然后從 mysqrt.cxx 中刪除對 HAVE_LOGHAVE_EXP 的檢查。同時,我們可以刪除 #include

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的公共儀表板:my.cdash.org/index.php?p…,如下圖所示:

![img](data:image/svg+xml;utf8, )

CMake使用教程系列文章

  • CMake使用教程(一)
    • 基礎項目
    • 添加版本號和配置頭文件
    • 指定C++標准
    • 添加庫
    • 提供選項
  • CMake使用教程(二)
    • 添加“庫”的使用要求
    • 安裝
    • 測試
    • 系統自檢
  • CMake使用教程(三)
    • 指定編譯定義
    • 添加自定義命令和生成的文件
    • 生成安裝程序
    • 添加對儀表板的支持
  • CMake使用教程(四)
    • 混合靜態和共享
    • 添加生成器表達式
    • 添加導出配置

關注下面的標簽,發現更多相似文章

CMake使用教程(四)

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

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

開發環境:

  • macOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

混合靜態和共享

示例程序地址

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

為此,我們需要將 BUILD_SHARED_LIBS 添加到頂級 CMakeLists.txt。我們使用 option 命令,因為它允許用戶有選擇地選擇該值是 On 還是 Off

接下來,我們將重構 MathFunctions 使其成為使用 mysqrtsqrt 封裝的真實庫,而不是要求調用代碼執行此邏輯。這也意味着 USE_MYMATH 將不會控制構建 MathFuctions,而是將控制此庫的行為。

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

# 設置運行此配置文件所需的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 是靜態構建的。

最終結果是 MathFunctions/CMakeLists.txt 應該如下所示:

# 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使用教程系列文章

  • CMake使用教程(一)
    • 基礎項目
    • 添加版本號和配置頭文件
    • 指定C++標准
    • 添加庫
    • 提供選項
  • CMake使用教程(二)
    • 添加“庫”的使用要求
    • 安裝
    • 測試
    • 系統自檢
  • CMake使用教程(三)
    • 指定編譯定義
    • 添加自定義命令和生成的文件
    • 生成安裝程序
    • 添加對儀表板的支持
  • CMake使用教程(四)
    • 混合靜態和共享
    • 添加生成器表達式
    • 添加導出配置

關注下面的標簽,發現更多相似文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM