參考 CMake 入門實戰
在 linux 平台下使用 CMake 生成 Makefile 並編譯的流程如下:
- 編寫 CMake 配置文件 CMakeLists.txt 。
- 執行命令
cmake PATH
或者ccmake PATH
生成 Makefile(ccmake
和cmake
的區別在於前者提供了一個交互式的界面)。其中,PATH
是 CMakeLists.txt 所在的目錄。 - 使用
make
命令進行編譯。
本文將從實例入手,一步步講解 CMake 的常見用法,文中所有的實例代碼可以在這里找到。如果你讀完仍覺得意猶未盡,可以繼續學習文章末尾提供的其他資源。
基本語法
添加頭文件目錄INCLUDE_DIRECTORIES
語法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
它相當於g++選項中的-I
參數的作用,也相當於環境變量中增加路徑到CPLUS_INCLUDE_PATH變量的作用。
include_directories(../../../thirdparty/comm/include)
添加需要鏈接的庫文件目錄LINK_DIRECTORIES
語法:link_directories(directory1 directory2 ...)
它相當於g++命令的-L
選項的作用,也相當於環境變量中增加LD_LIBRARY_PATH的路徑的作用。
link_directories("/home/server/third/lib")
查找庫所在目錄FIND_LIBRARY
語法:
A short-hand signature is:
find_library (<VAR> name1 [path1 path2 ...])
The general signature is:
find_library (
<VAR>
name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
[HINTS path1 [path2 ... ENV var]]
[PATHS path1 [path2 ... ENV var]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[DOC "cache documentation string"]
[NO_DEFAULT_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_CMAKE_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
)
例子如下:
FIND_LIBRARY(RUNTIME_LIB rt /usr/lib /usr/local/lib NO_DEFAULT_PATH)
cmake會在目錄中查找,如果所有目錄中都沒有,值RUNTIME_LIB就會被賦為NO_DEFAULT_PATH
添加需要鏈接的庫文件路徑LINK_LIBRARIES
語法:
link_libraries(library1 <debug | optimized> library2 ...)
# 直接是全路徑
link_libraries(“/home/server/third/lib/libcommon.a”)
# 下面的例子,只有庫名,cmake會自動去所包含的目錄搜索
link_libraries(iconv)
# 傳入變量
link_libraries(${RUNTIME_LIB})
# 也可以鏈接多個
link_libraries("/opt/MATLAB/R2012a/bin/glnxa64/libeng.so" "/opt/MATLAB/R2012a/bin/glnxa64/libmx.so")
可以鏈接一個,也可以多個,中間使用空格分隔.
設置要鏈接的庫文件的名稱TARGET_LINK_LIBRARIES
語法:
target_link_libraries(<target> [item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)
# 以下寫法都可以:
target_link_libraries(myProject comm) # 連接libhello.so庫,默認優先鏈接動態庫
target_link_libraries(myProject libcomm.a) # 顯示指定鏈接靜態庫
target_link_libraries(myProject libcomm.so) # 顯示指定鏈接動態庫
# 再如:
target_link_libraries(myProject libcomm.so) #這些庫名寫法都可以。
target_link_libraries(myProject comm)
target_link_libraries(myProject -lcomm)
為工程生成目標文件
語法:
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
簡單的例子如下:
add_executable(demo main.cpp )
設置文件變量
file(GLOB_RECURSE variable [RELATIVE path]** **[FOLLOW_SYMLINKS] [globbingexpressions]...)
GLOB_RECURSE
與GLOB
類似,區別在於它會遍歷匹配目錄的所有文件以及子目錄下面的文件。對於屬於符號鏈接的子目錄,只有FOLLOW_SYMLINKS
指定一或者cmake策略CMP0009沒有設置為NEW時,才會遍歷這些目錄。
Examples of recursive globbing include:
/dir/*.py- match all python files in /dir and subdirectories
構建庫
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
<name>
:庫的名字,直接寫名字即可,不要寫lib,會自動加上前綴的哈。
[STATIC | SHARED | MODULE]
:類型有三種。
-
SHARED,動態庫
-
STATIC,靜態庫
-
MODULE,在使用 dyld 的系統有效,如果不支持 dyld,則被當作 SHARED 對待。
EXCLUDE_FROM_ALL
:這個庫不會被默認構建,除非有其他的組件依賴或者手工構建。
例子:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
注意,一般我們使用的靜態庫/動態庫只是后綴名不同而已,上面構建的libhello.so與libhello_static.a,顯然名字不同。這時你會有一個想法,那我把hello_static改成hello,結果是不可行的,靜態庫無法構建。重名會忽略第二條指令。
解決方法:改libhello_static.a的屬性的輸出名字
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
關於動態庫的版本號
#VERSION 指代動態庫版本,SOVERSION 指代 API 版本。
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
動態庫版本是什么,對外調用總是libxxx.so。
構建使用動態庫例子
目錄結構:

添加zmq支持
include_directories("F:/ZMQ/include") # 對應tasks的 "-I"參數 .h等
link_directories("F:/ZMQ/lib") # 對應tasks的 "-L"參數 .dll等
link_libraries("F:/ZMQ/lib/libzmq-v141-mt-4_3_2.lib") # 對應tasks的 "-l"參數 .lib
#將所有cpp添加成動態庫
file(GLOB_RECURSE CPP_SRC "${PROJECT_SOURCE_DIR}/src/*.cpp")
#add_library(cpps_lib ${CPP_SRC})——這是靜態庫
add_library(cpps_lib_shared SHARED ${CPP_SRC})
# 添加可執行程序
add_executable( Server ${PROJECT_SOURCE_DIR}/server.cpp )
add_executable( Client ${PROJECT_SOURCE_DIR}/client.cpp )
完整模板
cmake_minimum_required (VERSION 2.6)
INCLUDE_DIRECTORIES(../../thirdparty/comm)
FIND_LIBRARY(COMM_LIB comm ../../thirdparty/comm/lib NO_DEFAULT_PATH)
FIND_LIBRARY(RUNTIME_LIB rt /usr/lib /usr/local/lib NO_DEFAULT_PATH)
link_libraries(${COMM_LIB} ${RUNTIME_LIB})
ADD_DEFINITIONS(
-O3 -g -W -Wall
-Wunused-variable -Wunused-parameter -Wunused-function -Wunused
-Wno-deprecated -Woverloaded-virtual -Wwrite-strings
-D__WUR= -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DTIXML_USE_STL
)
add_library(lib_demo
cmd.cpp
global.cpp
md5.cpp
)
link_libraries(lib_demo)
add_executable(demo
main.cpp
)
# link library in static mode
target_link_libraries(demo libuuid.a)
指定 C++ 版本
# Enable C++11
set(CMAKE_CXX_STANDARD 11)
入門案例
單個源文件
本節對應的源代碼所在目錄:Demo1。
對於簡單的項目,只需要寫幾行代碼就可以了。例如,假設現在我們的項目中只有一個源文件 main.cc ,該程序的用途是計算一個數的指數冪。
#include <stdio.h>
#include <stdlib.h>
/**
* power - Calculate the power of number.
* @param base: Base value.
* @param exponent: Exponent value.
*
* @return base raised to the power exponent.
*/
double power(double base, int exponent)
{
int result = base;
int i;
if (exponent == 0) {
return 1;
}
for(i = 1; i < exponent; ++i){
result = result * base;
}
return result;
}
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
編寫 CMakeLists.txt
首先編寫 CMakeLists.txt 文件,並保存在與 main.cc 源文件同個目錄下:
# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 項目信息
project (Demo1)
# 指定生成目標
add_executable(Demo main.cc)
CMakeLists.txt 的語法比較簡單,由命令、注釋和空格組成,其中命令是不區分大小寫的。符號 #
后面的內容被認為是注釋。命令由命令名稱、小括號和參數組成,參數之間使用空格進行間隔。
對於上面的 CMakeLists.txt 文件,依次出現了幾個命令:
cmake_minimum_required
:指定運行此配置文件所需的 CMake 的最低版本;project
:參數值是Demo1
,該命令表示項目的名稱是Demo1
。add_executable
: 將名為 main.cc 的源文件編譯成一個名稱為 Demo 的可執行文件。
編譯項目
之后,在當前目錄執行 cmake .
,得到 Makefile 后再使用 make
命令編譯得到 Demo1 可執行文件。
[ehome@xman Demo1]$ cmake .
-- The C compiler identification is GNU 4.8.2
-- The CXX compiler identification is GNU 4.8.2
-- Check for working C compiler: /usr/sbin/cc
-- Check for working C compiler: /usr/sbin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/sbin/c++
-- Check for working CXX compiler: /usr/sbin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ehome/Documents/programming/C/power/Demo1
[ehome@xman Demo1]$ make
Scanning dependencies of target Demo
[100%] Building C object CMakeFiles/Demo.dir/main.cc.o
Linking C executable Demo
[100%] Built target Demo
[ehome@xman Demo1]$ ./Demo 5 4
5 ^ 4 is 625
[ehome@xman Demo1]$ ./Demo 7 3
7 ^ 3 is 343
[ehome@xman Demo1]$ ./Demo 2 10
2 ^ 10 is 1024
多個源文件
同一目錄,多個源文件
本小節對應的源代碼所在目錄:Demo2。
上面的例子只有單個源文件。現在假如把 power
函數單獨寫進一個名為 MathFunctions.c
的源文件里,使得這個工程變成如下的形式:
./Demo2
|
+--- main.cc
|
+--- MathFunctions.cc
|
+--- MathFunctions.h
這個時候,CMakeLists.txt 可以改成如下的形式:
# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 項目信息
project (Demo2)
# 指定生成目標
add_executable(Demo main.cc MathFunctions.cc)
唯一的改動只是在 add_executable
命令中增加了一個 MathFunctions.cc
源文件。這樣寫當然沒什么問題,但是如果源文件很多,把所有源文件的名字都加進去將是一件煩人的工作。更省事的方法是使用 aux_source_directory
命令,該命令會查找指定目錄下的所有源文件,然后將結果存進指定變量名。其語法如下:
aux_source_directory(<dir> <variable>)
因此,可以修改 CMakeLists.txt 如下:
# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 項目信息
project (Demo2)
# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)
# 指定生成目標
add_executable(Demo ${DIR_SRCS})
這樣,CMake 會將當前目錄所有源文件的文件名賦值給變量 DIR_SRCS
,再指示變量 DIR_SRCS
中的源文件需要編譯成一個名稱為 Demo 的可執行文件。
多個目錄,多個源文件
本小節對應的源代碼所在目錄:Demo3。
現在進一步將 MathFunctions.h 和 MathFunctions.cc 文件移動到 math 目錄下。
./Demo3
|
+--- main.cc
|
+--- math/
|
+--- MathFunctions.cc
|
+--- MathFunctions.h
對於這種情況,需要分別在項目根目錄 Demo3 和 math 目錄里各編寫一個 CMakeLists.txt 文件。
使用靜態鏈接庫
為了方便,我們可以先將 math 目錄里的文件編譯成靜態庫再由 main 函數調用。
根目錄中的 CMakeLists.txt :
# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 項目信息
project (Demo3)
# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目錄
add_subdirectory(math)
# 指定生成目標
add_executable(Demo main.cc)
# 添加鏈接庫
target_link_libraries(Demo MathFunctions)
該文件添加了下面的內容: 第3行,使用命令 add_subdirectory
指明本項目包含一個子目錄 math,這樣 math 目錄下的 CMakeLists.txt 文件和源代碼也會被處理 。第6行,使用命令 target_link_libraries
指明可執行文件 main 需要連接一個名為 MathFunctions 的鏈接庫 。
子目錄中的 CMakeLists.txt:
# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_LIB_SRCS 變量
aux_source_directory(. DIR_LIB_SRCS)
# 生成鏈接庫
add_library (MathFunctions ${DIR_LIB_SRCS})
在該文件中使用命令 add_library
將 src 目錄中的源文件編譯為靜態鏈接庫。
使用變量的方式
如果想學習不使用靜態庫的處理,可以看看 @zhc2019github 貢獻的一個示例。
根目錄中的 CMakeLists.txt :
# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 項目信息
project (Demo3)
set(ALL_SOURCES CACHE INTERNAL "All sources to be compiled in this project" )
# 添加 src 子目錄
add_subdirectory(src)
# 指定生成目標
include_directories(src)
add_executable(Demo main.cpp ${ALL_SOURCES})
子目錄中的 CMakeLists.txt:
aux_source_directory(. DIR_LIB_SRCS)
message(STATUS "CMake step for googletest failed: ${PROJECT_SOURCE_DIR}")
# 生成鏈接庫
set(SDS_STATES_ALL_SOURCES ${PROJECT_SOURCE_DIR}/src/MathFunctions.cc)
message(STATUS "CMake zhang huaichao test: ${SDS_STATES_ALL_SOURCES}")
#add_library (MathFunctions ${DIR_LIB_SRCS})
set(ALL_SOURCES
${SDS_STATES_ALL_SOURCES}
PARENT_SCOPE
)
注:
- 每一個新的目錄或者函數都會創建新的作用域,當在新的作用域創建一般變量且后面加上
PARENT_SCOPE
,該變量可以在父目錄或者調用新函數的函數上起作用。 - 當改變cache中的變量時,同名的一般變量會被刪除。如果在父工程和子工程中都對同一緩存變量賦值,cmake時父工程率先將變量存入cache中,子工程直接在cache中調用該值,保證了父子工程的一致性。當父工程需要改變該變量,而子程序需要利用原值時,可以直接在父工程中設置同名稱的一般變量即可。
vscode使用cmake
- 安裝好平台上的C++編譯器,以及調試器gdb 這里不多說。
- 安裝vscode中的C++,cmake,cmake tools插件。
- 選擇command Pallete>cmake:Quick Start就可以創建一個cmake工程。
- 編譯,調試
接下來點擊左側欄的CMake工具按鈕。
右鍵可執行文件,選擇Debug。
進入調試界面。
或者使用VSCode下方Cmake工作條:
自定義編譯選項
本節對應的源代碼所在目錄:Demo4。
CMake 允許為項目增加編譯選項,從而可以根據用戶的環境和需求選擇最合適的編譯方案。
例如,可以將 MathFunctions 庫設為一個可選的庫,如果該選項為 ON
,就使用該庫定義的數學函數來進行運算。否則就調用標准庫中的數學函數庫。
修改 CMakeLists 文件
我們要做的第一步是在頂層的 CMakeLists.txt 文件中添加該選項:
# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 項目信息
project (Demo4)
# 加入一個配置頭文件,用於處理 CMake 對源碼的設置
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的 MathFunctions 庫
option (USE_MYMATH
"Use provided math implementation" ON)
# 是否加入 MathFunctions 庫
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)
# 指定生成目標
add_executable(Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})
其中:
- 第7行的
configure_file
命令用於加入一個配置頭文件 config.h ,這個文件由 CMake 從 config.h.in 生成,通過這樣的機制,將可以通過預定義一些參數和變量來控制代碼的生成。 - 第13行的
option
命令添加了一個USE_MYMATH
選項,並且默認值為ON
。 - 第17行根據
USE_MYMATH
變量的值來決定是否使用我們自己編寫的 MathFunctions 庫。
修改 main.cc 文件
之后修改 main.cc 文件,讓其根據 USE_MYMATH
的預定義值來決定是否調用標准庫還是 MathFunctions 庫:
#include <stdio.h>
#include <stdlib.h>
#include "config.h"
#ifdef USE_MYMATH
#include "math/MathFunctions.h"
#else
#include <math.h>
#endif
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#ifdef USE_MYMATH
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#else
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
編寫 config.h.in 文件
上面的程序值得注意的是第2行,這里引用了一個 config.h 文件,這個文件預定義了 USE_MYMATH
的值。但我們並不直接編寫這個文件,為了方便從 CMakeLists.txt 中導入配置,我們編寫一個 config.h.in 文件,內容如下:
#cmakedefine USE_MYMATH
這樣 CMake 會自動根據 CMakeLists 配置文件中的設置自動生成 config.h 文件。
編譯項目
現在編譯一下這個項目,為了便於交互式的選擇該變量的值,可以使用 ccmake
命令(也可以使用 cmake -i
命令,該命令會提供一個會話式的交互式配置界面):
CMake的交互式配置界面
從中可以找到剛剛定義的 USE_MYMATH
選項,按鍵盤的方向鍵可以在不同的選項窗口間跳轉,按下 enter
鍵可以修改該選項。修改完成后可以按下 c
選項完成配置,之后再按 g
鍵確認生成 Makefile 。ccmake 的其他操作可以參考窗口下方給出的指令提示。
我們可以試試分別將 USE_MYMATH
設為 ON
和 OFF
得到的結果:
USE_MYMATH 為 ON
運行結果:
[ehome@xman Demo4]$ ./Demo
Now we use our own MathFunctions library.
7 ^ 3 = 343.000000
10 ^ 5 = 100000.000000
2 ^ 10 = 1024.000000
此時 config.h 的內容為:
#define USE_MYMATH
USE_MYMATH 為 OFF
運行結果:
[ehome@xman Demo4]$ ./Demo
Now we use the standard library.
7 ^ 3 = 343.000000
10 ^ 5 = 100000.000000
2 ^ 10 = 1024.000000
此時 config.h 的內容為:
/* #undef USE_MYMATH */
安裝和測試
本節對應的源代碼所在目錄:Demo5。
CMake 也可以指定安裝規則,以及添加測試。這兩個功能分別可以通過在產生 Makefile 后使用 make install
和 make test
來執行。在以前的 GNU Makefile 里,你可能需要為此編寫 install
和 test
兩個偽目標和相應的規則,但在 CMake 里,這樣的工作同樣只需要簡單的調用幾條命令。
定制安裝規則
首先先在 math/CMakeLists.txt 文件里添加下面兩行:
# 指定 MathFunctions 庫的安裝路徑
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
指明 MathFunctions 庫的安裝路徑。之后同樣修改根目錄的 CMakeLists 文件,在末尾添加下面幾行:
# 指定安裝路徑
install (TARGETS Demo DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
DESTINATION include)
通過上面的定制,生成的 Demo 文件和 MathFunctions 函數庫 libMathFunctions.o 文件將會被復制到 /usr/local/bin
中,而 MathFunctions.h 和生成的 config.h 文件則會被復制到 /usr/local/include
中。我們可以驗證一下(順帶一提的是,這里的 /usr/local/
是默認安裝到的根目錄,可以通過修改 CMAKE_INSTALL_PREFIX
變量的值來指定這些文件應該拷貝到哪個根目錄):
[ehome@xman Demo5]$ sudo make install
[ 50%] Built target MathFunctions
[100%] Built target Demo
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/Demo
-- Installing: /usr/local/include/config.h
-- Installing: /usr/local/bin/libMathFunctions.a
-- Up-to-date: /usr/local/include/MathFunctions.h
[ehome@xman Demo5]$ ls /usr/local/bin
Demo libMathFunctions.a
[ehome@xman Demo5]$ ls /usr/local/include
config.h MathFunctions.h
為工程添加測試
添加測試同樣很簡單。CMake 提供了一個稱為 CTest 的測試工具。我們要做的只是在項目根目錄的 CMakeLists 文件中調用一系列的 add_test
命令。
# 啟用測試
enable_testing()
# 測試程序是否成功運行
add_test (test_run Demo 5 2)
# 測試幫助信息是否可以正常提示
add_test (test_usage Demo)
set_tests_properties (test_usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")
# 測試 5 的平方
add_test (test_5_2 Demo 5 2)
set_tests_properties (test_5_2
PROPERTIES PASS_REGULAR_EXPRESSION "is 25")
# 測試 10 的 5 次方
add_test (test_10_5 Demo 10 5)
set_tests_properties (test_10_5
PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")
# 測試 2 的 10 次方
add_test (test_2_10 Demo 2 10)
set_tests_properties (test_2_10
PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")
上面的代碼包含了四個測試。第一個測試 test_run
用來測試程序是否成功運行並返回 0 值。剩下的三個測試分別用來測試 5 的 平方、10 的 5 次方、2 的 10 次方是否都能得到正確的結果。其中 PASS_REGULAR_EXPRESSION
用來測試輸出是否包含后面跟着的字符串。
讓我們看看測試的結果:
[ehome@xman Demo5]$ make test
Running tests...
Test project /home/ehome/Documents/programming/C/power/Demo5
Start 1: test_run
1/4 Test #1: test_run ......................... Passed 0.00 sec
Start 2: test_5_2
2/4 Test #2: test_5_2 ......................... Passed 0.00 sec
Start 3: test_10_5
3/4 Test #3: test_10_5 ........................ Passed 0.00 sec
Start 4: test_2_10
4/4 Test #4: test_2_10 ........................ Passed 0.00 sec
100% tests passed, 0 tests failed out of 4
Total Test time (real) = 0.01 sec
如果要測試更多的輸入數據,像上面那樣一個個寫測試用例未免太繁瑣。這時可以通過編寫宏來實現:
# 定義一個宏,用來簡化測試工作
macro (do_test arg1 arg2 result)
add_test (test_${arg1}_${arg2} Demo ${arg1} ${arg2})
set_tests_properties (test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# 使用該宏進行一系列的數據測試
do_test (5 2 "is 25")
do_test (10 5 "is 100000")
do_test (2 10 "is 1024")
關於 CTest 的更詳細的用法可以通過 man 1 ctest
參考 CTest 的文檔。
支持 gdb
讓 CMake 支持 gdb 的設置也很容易,只需要指定 Debug
模式下開啟 -g
選項:
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
之后可以直接對生成的程序使用 gdb 來調試。
添加環境檢查
本節對應的源代碼所在目錄:Demo6。
有時候可能要對系統環境做點檢查,例如要使用一個平台相關的特性的時候。在這個例子中,我們檢查系統是否自帶 pow 函數。如果帶有 pow 函數,就使用它;否則使用我們定義的 power 函數。
添加 CheckFunctionExists 宏
首先在頂層 CMakeLists 文件中添加 CheckFunctionExists.cmake 宏,並調用 check_function_exists
命令測試鏈接器是否能夠在鏈接階段找到 pow
函數。
# 檢查系統是否支持 pow 函數
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (pow HAVE_POW)
將上面這段代碼放在 configure_file
命令前。
預定義相關宏變量
接下來修改 config.h.in 文件,預定義相關的宏變量。
// does the platform provide pow function?
#cmakedefine HAVE_POW
在代碼中使用宏和函數
最后一步是修改 main.cc ,在代碼中使用宏和函數:
#ifdef HAVE_POW
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
添加版本號
本節對應的源代碼所在目錄:Demo7。
給項目添加和維護版本號是一個好習慣,這樣有利於用戶了解每個版本的維護情況,並及時了解當前所用的版本是否過時,或是否可能出現不兼容的情況。
首先修改頂層 CMakeLists 文件,在 project
命令之后加入如下兩行:
set (Demo_VERSION_MAJOR 1)
set (Demo_VERSION_MINOR 0)
分別指定當前的項目的主版本號和副版本號。
之后,為了在代碼中獲取版本信息,我們可以修改 config.h.in 文件,添加兩個預定義變量:
// the configured options and settings for Tutorial
#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@
這樣就可以直接在代碼中打印版本信息了:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#include "math/MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
// print version info
printf("%s Version %d.%d\n",
argv[0],
Demo_VERSION_MAJOR,
Demo_VERSION_MINOR);
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#if defined (HAVE_POW)
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
生成安裝包
本節對應的源代碼所在目錄:Demo8。
本節將學習如何配置生成各種平台上的安裝包,包括二進制安裝包和源碼安裝包。為了完成這個任務,我們需要用到 CPack ,它同樣也是由 CMake 提供的一個工具,專門用於打包。
首先在頂層的 CMakeLists.txt 文件尾部添加下面幾行:
# 構建一個 CPack 安裝包
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
"${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)
上面的代碼做了以下幾個工作:
- 導入 InstallRequiredSystemLibraries 模塊,以便之后導入 CPack 模塊;
- 設置一些 CPack 相關變量,包括版權信息和版本信息,其中版本信息用了上一節定義的版本號;
- 導入 CPack 模塊。
接下來的工作是像往常一樣構建工程,並執行 cpack
命令。
- 生成二進制安裝包:
cpack -C CPackConfig.cmake
- 生成源碼安裝包
cpack -C CPackSourceConfig.cmake
我們可以試一下。在生成項目后,執行 cpack -C CPackConfig.cmake
命令:
[ehome@xman Demo8]$ cpack -C CPackSourceConfig.cmake
CPack: Create package using STGZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /home/ehome/Documents/programming/C/power/Demo8/Demo8-1.0.1-Linux.sh generated.
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /home/ehome/Documents/programming/C/power/Demo8/Demo8-1.0.1-Linux.tar.gz generated.
CPack: Create package using TZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /home/ehome/Documents/programming/C/power/Demo8/Demo8-1.0.1-Linux.tar.Z generated.
此時會在該目錄下創建 3 個不同格式的二進制包文件:
[ehome@xman Demo8]$ ls Demo8-*
Demo8-1.0.1-Linux.sh Demo8-1.0.1-Linux.tar.gz Demo8-1.0.1-Linux.tar.Z
這 3 個二進制包文件所包含的內容是完全相同的。我們可以執行其中一個。此時會出現一個由 CPack 自動生成的交互式安裝界面:
[ehome@xman Demo8]$ sh Demo8-1.0.1-Linux.sh
Demo8 Installer Version: 1.0.1, Copyright (c) Humanity
This is a self-extracting archive.
The archive will be extracted to: /home/ehome/Documents/programming/C/power/Demo8
If you want to stop extracting, please press <ctrl-C>.
The MIT License (MIT)
Copyright (c) 2013 Joseph Pan(http://hahack.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Do you accept the license? [yN]:
y
By default the Demo8 will be installed in:
"/home/ehome/Documents/programming/C/power/Demo8/Demo8-1.0.1-Linux"
Do you want to include the subdirectory Demo8-1.0.1-Linux?
Saying no will install in: "/home/ehome/Documents/programming/C/power/Demo8" [Yn]:
y
Using target directory: /home/ehome/Documents/programming/C/power/Demo8/Demo8-1.0.1-Linux
Extracting, please wait...
Unpacking finished successfully
完成后提示安裝到了 Demo8-1.0.1-Linux 子目錄中,我們可以進去執行該程序:
[ehome@xman Demo8]$ ./Demo8-1.0.1-Linux/bin/Demo 5 2
Now we use our own Math library.
5 ^ 2 is 25
關於 CPack 的更詳細的用法可以通過 man 1 cpack
參考 CPack 的文檔。
將其他平台的項目遷移到 CMake
CMake 可以很輕松地構建出在適合各個平台執行的工程環境。而如果當前的工程環境不是 CMake ,而是基於某個特定的平台,是否可以遷移到 CMake 呢?答案是可能的。下面針對幾個常用的平台,列出了它們對應的遷移方案。
autotools
- am2cmake 可以將 autotools 系的項目轉換到 CMake,這個工具的一個成功案例是 KDE 。
- Alternative Automake2CMake 可以轉換使用 automake 的 KDevelop 工程項目。
- Converting autoconf tests
qmake
- qmake converter 可以轉換使用 QT 的 qmake 的工程。
Visual Studio
- vcproj2cmake.rb 可以根據 Visual Studio 的工程文件(后綴名是
.vcproj
或.vcxproj
)生成 CMakeLists.txt 文件。 - vcproj2cmake.ps1 vcproj2cmake 的 PowerShell 版本。
- folders4cmake 根據 Visual Studio 項目文件生成相應的 “source_group” 信息,這些信息可以很方便的在 CMake 腳本中使用。支持 Visual Studio 9/10 工程文件。
CMakeLists.txt 自動推導
- gencmake 根據現有文件推導 CMakeLists.txt 文件。
- CMakeListGenerator 應用一套文件和目錄分析創建出完整的 CMakeLists.txt 文件。僅支持 Win32 平台。
相關鏈接
類似工具
- SCons:Eric S. Raymond、Timothee Besset、Zed A. Shaw 等大神力薦的項目架構工具。和 CMake 的最大區別是使用 Python 作為執行腳本。