什麼是 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。
本文采用 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 文件,依次出現了幾個命令:
-
cmake_minimum_required
:指定運行此配置文件所需的 CMake 的最低版本; -
project
:參數值是CMakeDemo
,該命令表示項目的名稱是 `CMakeDemo。 -
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})
其中:
configure_file
命令用於加入一個配置頭文件 config.h ,這個文件由 CMake 從 config.h.in生成,通過這樣的機制,將可以通過預定義一些參數和變量來控制代碼的生成。option
命令添加了一個USE_MATH_LIB
選項,並且默認值爲ON
。- 根據
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)
上面的代碼做了以下幾個工作:
- 導入 InstallRequiredSystemLibraries 模塊,以便之後導入 CPack 模塊;
- 設置一些 CPack 相關變量,包括版權信息和版本信息,其中版本信息用了上一節定義的版本號;
- 導入 CPack 模塊。
接下來的工作是像往常一樣構建工程,並執行 cpack
命令。
-
生成二進制安裝包:
cpack -C CPackConfig.cmake
-
生成源碼安裝包:
`cpack -C CPackSourceConfig.cmake`
此次並未成功生成安裝包,後續再進行調試,不多做演示。
作者:蘇州韭菜明
鏈接:https://www.jianshu.com/p/6e6569ba2237
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。