CMake簡介
CMake是一個跨平台的、開源的構建工具。cmake是makefile的上層工具,它們的目的正是為了產生可移植的makefile,並簡化自己動手寫makefile時的巨大工作量.目前很多開源的項目都可以通過CMake工具來輕松構建工程,例如博客之前分享的openHMD、hidapi、OSVR-Core等等,代碼的分享者提供源代碼和相應的Cmake配置文件,使用者就可以非常方便的在自己的電腦上構建相應的工程,進行開發和調試。
CMake教程
第一步:一個簡單的起點
最簡單的工程就是將一些源文件編譯為可執行程序。對於簡單工程來說,只需要在CMakeList.txt添加2行內容即可。這就是我們這個教程的起點,CMakeLists.txt文件內容如下:
1 cmake_minimum_required (VERSION 2.6) 2 project (Tutorial) 3 add_executable(Tutorial tutorial.cxx)
注意:CMake文件是不區分大小寫的,這個例子中CMakeLists.txt文件中都使用小寫字母。源文件”tutorial.cxx”用來計算一個數的平方根,它的第一個版本非常簡單,如下所示:
1 // A simple program that computes the square root of a number
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <math.h>
5 int main (int argc, char *argv[]) 6 { 7 if (argc < 2) 8 { 9 fprintf(stdout,"Usage: %s number\n",argv[0]); 10 return 1; 11 } 12 double inputValue = atof(argv[1]); 13 double outputValue = sqrt(inputValue); 14 fprintf(stdout,"The square root of %g is %g\n", 15 inputValue, outputValue); 16 return 0; 17 }
添加版本號和配置頭文件
我們添加的第一個特性就是為我們的可執行程序和工程提供一個版本號.你可以在源代碼中寫入版本號,然而在CMakeLists.txt中提供(版本號)更靈活.修改CMakeLists.txt增加一個版本號,內容如下:
1 cmake_minimum_required (VERSION 2.6) 2 project (Tutorial) 3 # The version number. 4 set (Tutorial_VERSION_MAJOR 1) 5 set (Tutorial_VERSION_MINOR 0) 6
7 # configure a header file to pass some of the CMake settings 8 # to the source code 9 configure_file ( 10 "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
11 "${PROJECT_BINARY_DIR}/TutorialConfig.h"
12 ) 13
14 # add the binary tree to the search path for include files 15 # so that we will find TutorialConfig.h 16 include_directories("${PROJECT_BINARY_DIR}") 17
18 # add the executable 19 add_executable(Tutorial tutorial.cxx)
因為配置文件將會被寫入到二進制樹當中,我們需要把這個目錄添加到頭文件路徑當中。我們接着創建一個”TutorialConfig.h.in”文件,內容如下:
1 // the configured options and settings for Tutorial
2 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
3 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
當CMake配置這個頭文件是,”@Tutorial_VERSION_MAJOR@”和”@Tutorial_VERSION_MINOR@”的值將會被CMakeLists.txt文件中的值替換。下一步我們修改”tutorial.cxx”來包含配置頭文件來使用版本號。修改后的代碼如下:
1 // A simple program that computes the square root of a number
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <math.h>
5 #include "TutorialConfig.h"
6
7 int main (int argc, char *argv[]) 8 { 9 if (argc < 2) 10 { 11 fprintf(stdout,"%s Version %d.%d\n", 12 argv[0], 13 Tutorial_VERSION_MAJOR, 14 Tutorial_VERSION_MINOR); 15 fprintf(stdout,"Usage: %s number\n",argv[0]); 16 return 1; 17 } 18 double inputValue = atof(argv[1]); 19 double outputValue = sqrt(inputValue); 20 fprintf(stdout,"The square root of %g is %g\n", 21 inputValue, outputValue); 22 return 0; 23 }
上面代碼最主要的修改就是包含了TutorialConfig.h頭文件,並且打印出版本號。
第二步:添加一個庫
現在我們為我們的工程添加一個庫。這個庫包含了自己實現的一個用來計算數的平方根函數。應用程序可以使用這個庫來計算平方根,而不是使用編譯器提供的標准庫。教程里我們會把這個庫放到一個叫做”MathFunctions”的子文件夾中。它包含一個只有一行內容的CMakeLists.txt文件:
add_library(MathFunctions mysqrt.cxx)
源文件”mysqrt.cxx”有一個叫做mysqrt的函數,它跟編譯器提供的sqrt函數類似。為了使用這個新庫,我們要在頂層的CMakeLists.txt中添加”add_subdirectory”調用,以便使這個庫能夠被編譯到.我們還需要添加另外一個包含文件夾以便找到包含在頭文件”MathFunctions/MathFunctions.h”中的函數原型.最后把新庫添加到應用程序當中,在頂層的CMakeLists.txt文件中添加如下內容:
1 include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") 2 add_subdirectory (MathFunctions) 3
4 # add the executable 5 add_executable (Tutorial tutorial.cxx) 6 target_link_libraries (Tutorial MathFunctions)
注意:這里需要自己手動在MathFunctions文件夾里添加”mysqrt.cxx”和”MathFunctions.h”文件,這2個文件里的代碼可以參考文章最后提供的附件
現在讓我們把MathFunctions庫做成可選的.當然在這個教程的工程中這個操作有點多余.但是在你的其他工程中,多數情況會有大量的依賴第三方的庫文件.添加一個可選庫,需要在最高層的CMakeLists.txt文件中添加一些內容:
1 # should we use our own math functions?
2 option (USE_MYMATH 3 "Use tutorial provided math implementation" ON)
這個USE_MYMATH將會顯示在CMake GUI當中,並且默認值為ON,這個設置將會被保存到CACHE當中,所以用戶每次打開工程時不必重新配置.下一步需要做一些修改,以便使MathFunctions庫參與編譯和鏈接.這個修改是在頂層的CMakeLists.txt文件中,內容如下:
1 # add the MathFunctions library?
2 # 3 if (USE_MYMATH) 4 include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") 5 add_subdirectory (MathFunctions) 6 set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) 7 endif (USE_MYMATH) 8
9 # add the executable 10 add_executable (Tutorial tutorial.cxx) 11 target_link_libraries (Tutorial ${EXTRA_LIBS})
通過配置USB_MYMATH來決定是否編譯和使用MathFunctions.注意上面的”EXTRA_LIBS”變量是用來收集每一個可選庫的,以便他們鏈接到可執行程序中.這是一個保持大項目工程可選庫清潔的通用方法.相應的源代碼修改是很直觀的,如下:
1 // A simple program that computes the square root of a number
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <math.h>
5 #include "TutorialConfig.h"
6 #ifdef USE_MYMATH 7 #include "MathFunctions.h"
8 #endif
9
10 int main (int argc, char *argv[]) 11 { 12 if (argc < 2) 13 { 14 fprintf(stdout,"%s Version %d.%d\n", argv[0], 15 Tutorial_VERSION_MAJOR, 16 Tutorial_VERSION_MINOR); 17 fprintf(stdout,"Usage: %s number\n",argv[0]); 18 return 1; 19 } 20
21 double inputValue = atof(argv[1]); 22
23 #ifdef USE_MYMATH 24 double outputValue = mysqrt(inputValue); 25 #else
26 double outputValue = sqrt(inputValue); 27 #endif
28
29 fprintf(stdout,"The square root of %g is %g\n", 30 inputValue, outputValue); 31 return 0; 32 }
在源文件中,我們也使用了USE_MYMATH,CMake通過修改配置文件TutorialConfig.h.in配置文件來為源文件這個提供支持,添加內容如下:
#cmakedefine USE_MYMATH
第三步:安裝和測試
下一步我們將為我們的工程添加安裝規則和測試.安裝規則相當簡單,為了安裝MathFunctions庫和頭文件,我們需要在MathFunctions文件夾的CMakeLists.txt文件中,添加如下內容:
1 install (TARGETS MathFunctions DESTINATION bin) 2 install (FILES MathFunctions.h DESTINATION include)
對於應用程序,需要在最頂層的CMakeLists.txt文件中安裝可執行程序和配置頭文件.
1 # add the install targets 2 install (TARGETS Tutorial DESTINATION bin) 3 install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
4 DESTINATION include)
注意:上面這幾行內容,需要添加在如下代碼之后,否則編譯會報錯,提示找不到”Tutorial”
1 # add the executable 2 add_executable (Tutorial tutorial.cxx) 3 target_link_libraries (Tutorial ${EXTRA_LIBS})
這就是所有要做的.到這里你應該可以構建這個教程了,然后輸入”make install”(或則使用IDE編譯出INSTALL目標),它將安裝適當的頭文件,庫,和可執行程序.CMake變量”CMAKE_INSTALL_PREFIX”用來決定這些文件將被安裝的根路徑.添加測試也是很簡單的.在頂層的CMakeLists.txt文件中添加一些簡單的測試,來確認應用程序工作正常.
1 include(CTest) 2
3 # does the application run 4 add_test (TutorialRuns Tutorial 25) 5
6 # does it sqrt of 25
7 add_test (TutorialComp25 Tutorial 25) 8 set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5") 9
10 # does it handle negative numbers 11 add_test (TutorialNegative Tutorial -25) 12 set_tests_properties (TutorialNegative PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0") 13
14 # does it handle small numbers 15 add_test (TutorialSmall Tutorial 0.0001) 16 set_tests_properties (TutorialSmall PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01") 17
18 # does the usage message work?
19 add_test (TutorialUsage Tutorial) 20 set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
在構建完成之后需要運行”ctest”命令來運行上面的測試.第一個測試用例用來確保應用程序是否有段錯誤,crash,返回值非0.這個是一個簡單的CTest測試.接下來的幾個測試都是利用”PASS_REGULAR_EXPRESSION”測試屬性來驗證輸出是否包含特定字符串.在這個例子當中驗證平方根計算是否正確,以及當輸入錯誤信息時候輸出使用信息.如果你希望添加更多測試來測試不同的輸入值,你可以考慮創建一個宏像如下這樣:
1 #define a macro to simplify adding tests, then use it
2 macro (do_test arg result) 3 add_test (TutorialComp${arg} Tutorial ${arg}) 4 set_tests_properties (TutorialComp${arg} 5 PROPERTIES PASS_REGULAR_EXPRESSION ${result}) 6 endmacro (do_test) 7
8 # do a bunch of result based tests 9 do_test (25 "25 is 5") 10 do_test (-25 "-25 is 0")
do_test的每一次調用,就會有一次測試被添加到工程當中,測試的名字、輸入、結果,基於傳遞的參數.
第四步:添加系統自檢
下一步讓我們考慮添加一些代碼到我們的工程,以支持目標平台沒有的特性.在這個例子當中,我們將添加一些代碼來驗證目標平台是否具有log和exp函數.當然幾乎所有的平台都有這些函數,教程假設一下這種少數情況.如果平台有log函數,那么我們在mysqrt函數中用它來計算輸出.第一步我們利用CheckFunctionExists.cmake宏測試這些函數是否有效,在頂層CMakeLists.txt文件中添加如下內容:
1 # does this system provide the log and exp functions?
2 include (CheckFunctionExists) 3 check_function_exists (log HAVE_LOG) 4 check_function_exists (exp HAVE_EXP)
接着我們修改”TutorialConfig.h.in”來定義那些CMake在平台上查找到的值,修改如下:
1 // does the platform provide exp and log functions?
2 #cmakedefine HAVE_LOG 3 #cmakedefine HAVE_EXP
在使用log和exp之前,確定他們是否被定義,是非常重要的.在CMake中,配置文件的命令會立刻使用目前的配置.最后在mysqrt函數中,我們可以提供一個基於log和exp函數的實現,代碼如下:
1 // if we have both log and exp then use them
2 #if defined (HAVE_LOG) && defined (HAVE_EXP)
3 result = exp(log(x)*0.5); 4 #else // otherwise use an iterative approach
5 . . .
注意:這里需要在”mysqrt.cxx”文件里添加頭文件#include “TutorialConfig.h”,否則找不到定義的宏
第五步:添加/生成一個通用的文件
這一節我們將演示如何添加一個源文件到一個應用程序當中.在這個例子當中,我們將會創建一個已經計算好的平方根表,它作為構建過程的一部分,並且編譯進我們的應用程序.為了實現這個功能,我們需要一個程序來生成這個表.在MathFunctions子文件夾中我們添加一個叫做MakeTable.cxx的文件,它的內容如下:
1 // A simple program that builds a sqrt table
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <math.h>
5
6 int main (int argc, char *argv[]) 7 { 8 int i; 9 double result; 10
11 // make sure we have enough arguments
12 if (argc < 2) 13 { 14 return 1; 15 } 16
17 // open the output file
18 FILE *fout = fopen(argv[1],"w"); 19 if (!fout) 20 { 21 return 1; 22 } 23
24 // create a source file with a table of square roots
25 fprintf(fout,"double sqrtTable[] = {\n"); 26 for (i = 0; i < 10; ++i) 27 { 28 result = sqrt(static_cast<double>(i)); 29 fprintf(fout,"%g,\n",result); 30 } 31
32 // close the table with a zero
33 fprintf(fout,"0};\n"); 34 fclose(fout); 35 return 0; 36 }
注意,這個表格是用C++代生成的,並且文件的名字是通過該程序的運行參數傳入的.下一步就是添加一個適當的命令到MathFunctions的CMakeLists.txt文件來構建MakeTable應用程序,然后在構建過程中運行它.完成這些操作,我們需要添加如下命令:
1 # first we add the executable that generates the table 2 add_executable(MakeTable MakeTable.cxx) 3
4 # add the command to generate the source code 5 add_custom_command ( 6 OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h 7 COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h 8 DEPENDS MakeTable 9 ) 10
11 # add the binary tree directory to the search path for
12 # include files 13 include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) 14
15 # add the main library 16 add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h )
首先,可執行程序MakeTable像所有可執行程序一樣被添加.接着我們添加一些命令來讓MakeTable生成Table.h.然后我們讓CMake知道mysqrt.cxx依賴於生成的Table.h.這需要為MathFunctions庫把生成的Table.h文件添加到源文件列表當中,我們還需要添加當前的二進制文件夾添加到include文件夾列表當中,以便Table.h文件可以被mysqrt.cxx找到並包含.當工程被構建時,它將會先構建MakeTable可執行程序.它將運行MakeTable來生成Table.h文件.最后,它編譯包含了Table.h文件的mysqrt.cxx來生成MathFunctions庫.為了達到這個目的,頂層的CMakeLists.txt需要添加如下代碼:
1 cmake_minimum_required (VERSION 2.6) 2 project (Tutorial) 3 include(CTest) 4
5 # The version number. 6 set (Tutorial_VERSION_MAJOR 1) 7 set (Tutorial_VERSION_MINOR 0) 8
9 # does this system provide the log and exp functions?
10 include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) 11
12 check_function_exists (log HAVE_LOG) 13 check_function_exists (exp HAVE_EXP) 14
15 # should we use our own math functions 16 option(USE_MYMATH 17 "Use tutorial provided math implementation" ON) 18
19 # configure a header file to pass some of the CMake settings 20 # to the source code 21 configure_file ( 22 "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
23 "${PROJECT_BINARY_DIR}/TutorialConfig.h"
24 ) 25
26 # add the binary tree to the search path for include files 27 # so that we will find TutorialConfig.h 28 include_directories ("${PROJECT_BINARY_DIR}") 29
30 # add the MathFunctions library?
31 if (USE_MYMATH) 32 include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") 33 add_subdirectory (MathFunctions) 34 set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) 35 endif (USE_MYMATH) 36
37 # add the executable 38 add_executable (Tutorial tutorial.cxx) 39 target_link_libraries (Tutorial ${EXTRA_LIBS}) 40
41 # add the install targets 42 install (TARGETS Tutorial DESTINATION bin) 43 install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
44 DESTINATION include) 45
46 # does the application run 47 add_test (TutorialRuns Tutorial 25) 48
49 # does the usage message work?
50 add_test (TutorialUsage Tutorial) 51 set_tests_properties (TutorialUsage 52 PROPERTIES 53 PASS_REGULAR_EXPRESSION "Usage:.*number"
54 ) 55
56
57 #define a macro to simplify adding tests
58 macro (do_test arg result) 59 add_test (TutorialComp${arg} Tutorial ${arg}) 60 set_tests_properties (TutorialComp${arg} 61 PROPERTIES PASS_REGULAR_EXPRESSION ${result} 62 ) 63 endmacro (do_test) 64
65 # do a bunch of result based tests 66 do_test (4 "4 is 2") 67 do_test (9 "9 is 3") 68 do_test (5 "5 is 2.236") 69 do_test (7 "7 is 2.645") 70 do_test (25 "25 is 5") 71 do_test (-25 "-25 is 0") 72 do_test (0.0001 "0.0001 is 0.01")
TutorialConfig.h.in包含如下內容:
1 // the configured options and settings for Tutorial
2 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
3 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
4 #cmakedefine USE_MYMATH 5
6 // does the platform provide exp and log functions?
7 #cmakedefine HAVE_LOG 8 #cmakedefine HAVE_EXP
MathFunctions文件夾中的CMakeLists.txt文件,內容如下:
1 # first we add the executable that generates the table 2 add_executable(MakeTable MakeTable.cxx) 3 # add the command to generate the source code 4 add_custom_command ( 5 OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h 6 DEPENDS MakeTable 7 COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h 8 ) 9 # add the binary tree directory to the search path 10 # for include files 11 include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) 12
13 # add the main library 14 add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h) 15
16 install (TARGETS MathFunctions DESTINATION bin) 17 install (FILES MathFunctions.h DESTINATION include)
第六步:構建一個安裝器
接着假設我們想把工程發布給其他人使用.我們希望提供不同平台的二進制文件和源文件.這里與我們之前在第三步中的安裝有一些不同,那時我們安裝我們已經從源代碼構建出來的二進制文件.在這個例子中,我們將構建一個安裝包,它支持二進制安裝,以及cygwin、 debian、RPMs等工具的包管理特性.為了實現這個功能,我們會使用CPack來創建平台相關的安裝包.我們需要在頂層的CMakeLists.txt文件中的最后添加一些代碼,內容如下:
1 # build a CPack driven installer package 2 include (InstallRequiredSystemLibraries) 3 set (CPACK_RESOURCE_FILE_LICENSE 4 "${CMAKE_CURRENT_SOURCE_DIR}/License.txt") 5 set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}") 6 set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}") 7 include (CPack)
官方教程中沒有License.txt文件,所有要手動在頂層目錄下添加一個”License.txt”文件
以上就是我們所有要做的.以包含InstallRequiredSystemLibraries為開始.這個模塊將包含工程在目前平台上所需要的所有運行時庫.接着我們設置一些CPack變量,包括我們保存license的地方,工程的版本信息.版本信息就是用了我們教程中之前設置的變量值.最后我們包含CPake模塊,它將使用這些變量和一些系統的其他屬性來設置安裝包.
下一步是以通常的方法構建工程,然后在CPack上運行它.為了構建二進制發行包,你需要運行如下命令:
cpack --config CPackConfig.cmake
為了創建一個源文件發行包,你需要運行:
cpack --config CPackSourceConfig.cmake
注意:這里的2條命令需要在構建工程的目錄下運行!
第七步:添加對Dashboard支持
將我們的測試結果添加到Dashboard上是非常容易的.在教程的前面步驟當中我們已經為我們的工程定義了一些測試.我們只需要運行這些測試,並且提交他們到一個dashboard上.為了支持dashboard我們需要在頂層的CMakeLists.txt文件中包含CTest模塊.
1 # enable dashboard scripting 2 include (CTest)
我們還需要創建一個CTestConfig.cmake文件,用來為dashboard指定工程名字.
set (CTEST_PROJECT_NAME "Tutorial")
CTest運行時將讀取這個文件.你可以在你的工程中運行CMake來生成一個簡單的dashboard,進入構建目錄下,然后執行”ctest -D Experimental”,將會把你的dashboard結果上傳到Kitware’s工程dashboard上.