CMake 入門實戰

原文鏈接:https://www.jianshu.com/p/6e6569ba2237?tdsourcetag=s_pcqq_aiomsg

什麼是 CMake

cmake.jpg

CMake是個一個開源的跨平臺自動化建構系統,用來管理軟件建置的程序,並不相依於某特定編譯器。

並可支持多層目錄、多個應用程序與多個庫。 它用配置文件控制建構過程(build process)的方式和Unix的make相似,只是CMake的配置文件取名爲CMakeLists.txt。

CMake並不直接建構出最終的軟件,而是產生標準的建構檔(如Unix的Makefile或Windows Visual C++的projects/workspaces),然後再依一般的建構方式使用。

這使得熟悉某個集成開發環境(IDE)的開發者可以用標準的方式建構他的軟件,這種可以使用各平臺的原生建構系統的能力是CMake和SCons等其他類似系統的區別之處。

它首先允許開發者編寫一種平臺無關的CMakeList.txt 文件來定製整個編譯流程,然後再根據目標用戶的平臺進一步生成所需的本地化 Makefile 和工程文件,如 Unix的 Makefile 或 Windows 的 Visual Studio 工程。從而做到“Write once, run everywhere”。顯然,CMake 是一個比上述幾種 make 更高級的編譯配置工具。

“CMake”這個名字是"Cross platform MAke"的縮寫。雖然名字中含有"make",但是CMake和Unix上常見的“make”系統是分開的,而且更爲高端。 它可與原生建置環境結合使用,例如:make、蘋果的Xcode與微軟的Visual Studio。

CMake維基百科地址

文中代碼github地址

本文采用 JetBrains CLion 與 CMake 作爲 開發與編譯工具,和大家一起走進CMake,瞭解CMake。

單個源文件

首先創建項目,選擇如下圖:

20190121093117.png

項目創建侯如下圖所示目錄:

 

20190121093324.png

20190121093307.png

對於簡單的項目,只需要寫幾行代碼就可以了。現在我們的項目中只有一個源文件 main.cc,該程序的用途是計打印出Hello, World!

編寫 CMakeLists.txt

CMakeLists.txt 文件,保存在與 main.cc源文件同個目錄下:

# CMake 最低版本號要求
cmake_minimum_required(VERSION 3.10.2)
# 項目信息
project(CMakeDemo C)
# 設置C語言標準
set(CMAKE_C_STANDARD 99)
# 指定生成目標
add_executable(CMakeDemo main.c)

CMakeLists.txt 的語法比較簡單,由命令、註釋和空格組成,其中命令是不區分大小寫的。符號 # 後面的內容被認爲是註釋。命令由命令名稱、小括號和參數組成,參數之間使用空格進行間隔。

對於上面的 CMakeLists.txt 文件,依次出現了幾個命令:

  1. cmake_minimum_required:指定運行此配置文件所需的 CMake 的最低版本;

  2. project:參數值是 CMakeDemo,該命令表示項目的名稱是 `CMakeDemo。

  3. add_executable: 將名爲 main.cc 的源文件編譯成一個名稱爲 CMakeDemo的可執行文件。

運行程序,看打印結果:

20190121094108.png

我們還可以手動編譯項目(下面示例皆可以此方法編譯。Linux環境下,Windows配置較爲麻煩)

在當前目錄執行 cmake . ,得到 Makefile 後再使用 make 命令編譯得到 CMakeDemo可執行文件。

cmakemake.png

大家可以看到相關編譯過程與運行結果。

多個源文件

同一目錄,多個源文件

下面我們新建MathUtils.c,將一些常用數學運算加入其中,並在main.c中調用。

cmaketwo.png

唯一的改動只是在 add_executable 命令中增加了一個 MathUtils.c 源文件。這樣寫當然沒什麼問題,但是如果源文件很多,把所有源文件的名字都加進去將是一件煩人的工作。更省事的方法是使用 aux_source_directory 命令,該命令會查找指定目錄下的所有源文件,然後將結果存進指定變量名。其語法如下:

aux_source_directory(<dir> <variable>)

cmakethree.png

這樣,CMake 會將當前目錄所有源文件的文件名賦值給變量 DIR_SRCS ,再指示變量 DIR_SRCS 中的源文件需要編譯成一個名稱爲 CMakeDemo的可執行文件。

多個目錄,多個源文件

一,CMake添加動態鏈接庫

現在進一步將 MathUtils.h 和 MathUtils.c文件移動到 math 目錄下。

cmakefour.png

運行報錯,我們未將轉移的C和H文件添加到CMakeLists.txt

對於這種情況,需要分別在項目根目錄 CMakeDemo和 math 目錄裏各編寫一個 CMakeLists.txt 文件。爲了方便,我們可以先將 math 目錄裏的文件編譯成靜態庫再由 main 函數調用。

根目錄中的 CMakeLists.txt :

cmakefive.png

cmake_minimum_required(VERSION 3.13)
project(CMakeDemo C)

set(CMAKE_C_STANDARD 99)

# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)
# 指定生成目標
add_executable(CMakeDemo ${DIR_SRCS})

# 添加 math 子目錄
add_subdirectory(math)

# 添加鏈接庫
target_link_libraries(CMakeDemo MathUtils)

math/CMakeLists.txt如下:

# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_LIB_SRCS 變量
aux_source_directory(. DIR_LIB_SRCS)
# 指定生成 MathUtils 鏈接庫
add_library (MathUtils ${DIR_LIB_SRCS})#命令 add_library 將 src 目錄中的源文件編譯爲靜態鏈接庫。

該文件添加了下面的內容: 使用命令 add_subdirectory 指明本項目包含一個子目錄 math,這樣 math 目錄下的 CMakeLists.txt 文件和源代碼也會被處理 。使用命令 target_link_libraries 指明可執行文件 main 需要連接一個名爲 MathUtils 的鏈接庫 。

二,add_executable添加相對路徑(單個簡單項目推薦,不需要再配置其他CMakeLists)

# CMake 最低版本號要求
cmake_minimum_required(VERSION 3.10.2)
# 項目信息
project(CMakeDemo C)
# 設置C語言標準
set(CMAKE_C_STANDARD 99)
# 指定生成目標和源碼路徑
add_executable(CMakeDemo main.c math/MathUtils.c math/MathUtils.h)

cmakesix.png

三,添加其他第三方開源庫(以cjson爲示例)

cmakeeight.png

set(CJSON_LIBRARY "-cJSON")
find_package(cJSON REQUIRED)
include_directories(${cJSON_INCLUDE_DIR})
target_link_libraries(CCodes ${CJSON_LIBRARIES})
char *post_str = NULL;
    cJSON *root = cJSON_CreateObject();

cJSON_AddStringToObject(root, "user", "爲所欲爲");
    cJSON_AddStringToObject(root, "pwd", "hkcw3cjbc");
    post_str = cJSON_Print(root);
    cJSON_Delete(root);
    root = NULL;
    printf("post_str is %s \n",post_str);

自定義編譯選項

CMake 允許爲項目增加編譯選項,從而可以根據用戶的環境和需求選擇最合適的編譯方案。

例如,可以將 MathUtils 庫設爲一個可選的庫,如果該選項爲 ON ,就使用該庫定義的數學函數來進行運算。否則就調用標準庫中的數學函數庫。

修改 CMakeLists 文件

我們要做的第一步是在頂層的 CMakeLists.txt 文件中添加該選項:

cmake_minimum_required(VERSION 3.13)

project(CMakeDemo C)

set(CMAKE_C_STANDARD 99)

# 加入一個配置頭文件,用於處理 CMake 對源碼的設置
configure_file (
        "${PROJECT_SOURCE_DIR}/config.h.in"
        "${PROJECT_BINARY_DIR}/config.h"
)

# 是否使用自己的 MathUtils 庫
option (USE_MATH_LIB
        "Use provided math implementation" ON)
# 是否加入 MathUtils 庫
if (USE_MATH_LIB)
    include_directories ("${PROJECT_SOURCE_DIR}/math")
    add_subdirectory (math)
    set (EXTRA_LIBS ${EXTRA_LIBS} MathUtils)
endif (USE_MATH_LIB)


# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)
# 指定生成目標
add_executable(CMakeDemo ${DIR_SRCS})

target_link_libraries (CMakeDemo  ${EXTRA_LIBS})

其中:

  1. configure_file 命令用於加入一個配置頭文件 config.h ,這個文件由 CMake 從 config.h.in生成,通過這樣的機制,將可以通過預定義一些參數和變量來控制代碼的生成。
  2. option 命令添加了一個 USE_MATH_LIB選項,並且默認值爲ON
  3. 根據 USE_MATH_LIB變量的值來決定是否使用我們自己編寫的 MathUtils庫。

修改 main.c 文件

之後修改 main.c文件,讓其根據 USE_MYMATH 的預定義值來決定是否調用標準庫還是 MathUtils庫:

#include <stdio.h>
#include <config.h>

#ifdef USE_MATH_LIB

#include <MathUtils.h>

#else
#include <math.h>
#endif

int main(int argc, char *argv[]) {

#ifdef USE_MATH_LIB
    printf("Now we use our own Math library. \n");
    hello();
#else
    printf("Now we use the standard library. \n");
     printf("Hello, World !\n");
#endif
    printf("Hello, World Ending!\n");
    return 0;
}

編寫 config.h.in文件

上面的程序值得注意的是第2行,這裏引用了一個 config.h 文件,這個文件預定義了 USE_MATH_LIB的值。但我們並不直接編寫這個文件,爲了方便從 CMakeLists.txt 中導入配置,我們編寫一個 config.h.in文件,內容如下:

#cmakedefine USE_MATH_LIB

這樣 CMake 會自動根據 CMakeLists 配置文件中的設置自動生成 config.h 文件。

cmakeseven.png

安裝和測試

CMake 也可以指定安裝規則,以及添加測試。這兩個功能分別可以通過在產生 Makefile 後使用 make install 和 make test 來執行。在以前的 GNU Makefile 裏,你可能需要爲此編寫 install 和 test 兩個僞目標和相應的規則,但在 CMake 裏,這樣的工作同樣只需要簡單的調用幾條命令。

首先先在 math/CMakeLists.txt 文件裏添加下面兩行:

# 指定 MathUtils 庫的安裝路徑
install (TARGETS MathUtils DESTINATION bin)
install (FILES MathUtils.h DESTINATION include)

指明 MathUtils 庫的安裝路徑。之後同樣修改根目錄的 CMakeLists 文件,在末尾添加下面幾行:

# 指定安裝路徑
install (TARGETS CMakeDemo DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
         DESTINATION include)

通過上面的定製,生成的 CMakeDemo文件和 MathUtils 函數庫 libMathUtils .o 文件將會被複制到 /usr/local/bin 中,而 MathUtils .h 和生成的 config.h 文件則會被複制到 /usr/local/include 中。我們可以驗證一下

順帶一提的是,這裏的 /usr/local/ 是默認安裝到的根目錄,可以通過修改 CMAKE_INSTALL_PREFIX 變量的值來指定這些文件應該拷貝到哪個根目錄。

[G490@ubuntu CMakeDemo]$ sudo make install
[ 50%] Built target MathUtils
[100%] Built target CMakeDemo
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/CMakeDemo
-- Installing: /usr/local/include/config.h
-- Installing: /usr/local/bin/MathUtils.a
-- Up-to-date: /usr/local/include/MathUtils.h
[G490@ubuntu CMakeDemo]$ ls /usr/local/bin
Demo  libMathUtils .a
[G490@ubuntu CMakeDemo]$ ls /usr/local/include
config.h  MathUtils .h

添加環境檢查

有時候可能要對系統環境做點檢查,例如要使用一個平臺相關的特性的時候。在這個例子中,我們檢查系統是否自帶 pow 函數。如果帶有 pow 函數,就使用它;否則使用我們定義的 power 函數。

添加 CheckFunctionExists 宏

首先在頂層 CMakeLists 文件中添加 CheckFunctionExists.cmake 宏,並調用 check_function_exists 命令測試鏈接器是否能夠在鏈接階段找到 pow 函數。

# 檢查系統是否支持 pow 函數
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (pow HAVE_POW)
#include <stdio.h>
#include <config.h>

#ifdef USE_MATH_LIB

#include <MathUtils.h>
#include <stdlib.h>

#else
#include <math.h>
#endif

int main(int argc, char *argv[]) {


#ifdef USE_MATH_LIB
    printf("Now we use our own Math library. \n");
    hello(argc);
#else
    printf("Now we use the standard library. \n");
     printf("Hello, World !\n");
#endif
    printf("Hello, World Ending!\n");


#ifdef HAVE_POW
    double result = power(7, 8);
    printf("Now we use our own Math library. Result is %f\n", result);
#else
    double result = power(6, 9);
    printf("Now we use our own Math library. Result is %f\n", result);

#endif
    return 0;
}

添加版本號

給項目添加和維護版本號是一個好習慣,這樣有利於用戶瞭解每個版本的維護情況,並及時瞭解當前所用的版本是否過時,或是否可能出現不兼容的情況。

首先修改頂層 CMakeLists 文件,在 project 命令之後加入如下兩行:

set(VERSIONCODEMAJOR  1)
set(VERSIONCODEMINOR  0)

分別指定當前的項目的主版本號和副版本號。

之後,爲了在代碼中獲取版本信息,我們可以修改 config.h.in 文件,添加兩個預定義變量:

// the configured options and settings for Tutorial
#define VERSIONCODEMAJOR @VERSIONCODEMAJOR@
#define VERSIONCODEMINOR @VERSIONCODEMINOR@
 printf("%s Version %d.%d \n",argv[0],VERSIONCODEMAJOR,VERSIONCODEMINOR);

這樣就可以直接在代碼中打印版本信息了:

/home/shanlovana/CLionProjects/DailyCode/CMakeDemo/cmake-build-debug/CMakeDemo Version 1.0  

生成安裝包

本節將學習如何配置生成各種平臺上的安裝包,包括二進制安裝包和源碼安裝包。爲了完成這個任務,我們需要用到 CPack ,它同樣也是由 CMake 提供的一個工具,專門用於打包。

首先在頂層的 CMakeLists.txt 文件尾部添加下面幾行:

# 構建一個 CPack 安裝包
include (InstallRequiredSystemLibraries)
SET(CPACK_CMAKE_GENERATOR "Unix Makefiles")
SET(CPACK_GENERATOR "STGZ;TGZ;TZ")

set (CPACK_RESOURCE_FILE_LICENSE
        "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")

SET(CPACK_PACKAGE_NAME "CMakeDemo")
SET(CPACK_PACKAGE_VENDOR "Kitware")
SET(CPACK_PACKAGE_VERSION "2.5.0")
SET(CPACK_PACKAGE_VERSION_MAJOR "2")
SET(CPACK_PACKAGE_VERSION_MINOR "5")
SET(CPACK_PACKAGE_VERSION_PATCH "0")

SET(CPACK_SYSTEM_NAME "Linux-i686")
SET(CPACK_TOPLEVEL_TAG "Linux-i686")

include (CPack)

上面的代碼做了以下幾個工作:

  1. 導入 InstallRequiredSystemLibraries 模塊,以便之後導入 CPack 模塊;
  2. 設置一些 CPack 相關變量,包括版權信息和版本信息,其中版本信息用了上一節定義的版本號;
  3. 導入 CPack 模塊。

接下來的工作是像往常一樣構建工程,並執行 cpack 命令。

  • 生成二進制安裝包:

    cpack -C CPackConfig.cmake
    
  • 生成源碼安裝包:

`cpack -C CPackSourceConfig.cmake`

此次並未成功生成安裝包,後續再進行調試,不多做演示。

文中代碼github地址

作者:蘇州韭菜明
鏈接:https://www.jianshu.com/p/6e6569ba2237
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章