Preface : 本文是CMake官方文檔CMake Tutorial (http://www.cmake.org/cmake/help/cmake_tutorial.html) 的翻譯。通過一個樣例工程從簡單到復雜的完善過程,文檔介紹了CMake主要模塊(cmake, ctest, cpack)的功能和使用環境;從中可以一窺cmake的大體形貌。正文如下:
本文下述內容是一個手把手的使用指南;它涵蓋了CMake需要解決的公共構建系統的一些問題。這些主題中的許多主題已經在Mastering CMake一書中以單獨的章節被介紹過,但是通過一個樣例工程看一看它們如何工作也是非常有幫助的。本指南可以在CMake源碼樹的Tests/Tutorial路徑下找到。每一步都有它自己的子路徑,其中包含該步驟的一個完整的指南。
作為基礎的起始點(步驟1)
最基本的工程是一個從源代碼文件中構建可執行文件的例子。對於簡單工程,只要一個兩行的CMakeLists文件就足夠了。這將會作為我們指南的起點。這份CMakeLists文件看起來像是這樣:
cmake_minimum_required (VERSION 2.6) project (Tutorial) add_executable(Tutorial tutorial.cxx)
注意到這個例子在CMakeLists文件中使用了小寫。CMake支持大寫、小寫、混合大小寫的命令。tutorial.cxx中的源代碼用來計算一個數的平方根,並且它的第一版非常簡單,如下所示:
// A simple program that computes the square root of a number // 計算一個數的平方根的簡單程序 #include <stdio.h> #include <stdlib.h> #include <math.h> int main (int argc, char *argv[]) { if (argc < 2) { fprintf(stdout,"Usage: %s number\n",argv[0]); return 1; } double inputValue = atof(argv[1]); double outputValue = sqrt(inputValue); fprintf(stdout,"The square root of %g is %g\n", inputValue, outputValue); return 0; }
我們添加的第一個特性用來為工程和可執行文件指定一個版本號。雖然你可以在源代碼中唯一指定它,但是你在CMakeLists文件中指定它可以提供更好的靈活性。如下所示,我么可以通過添加一個版本號來修改CMakeLists文件:
cmake_minimum_required (VERSION 2.6) project (Tutorial) # 版本號 set (Tutorial_VERSION_MAJOR 1) set (Tutorial_VERSION_MINOR 0) # 配置一個頭文件,通過它向源代碼中傳遞一些CMake設置。 configure_file ( "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in" "${PROJECT_BINARY_DIR}/TutorialConfig.h" ) # 將二進制文件樹添加到包含文件的搜索路徑中,這樣我們可以找到TutorialConfig.h include_directories("${PROJECT_BINARY_DIR}") # 添加可執行文件 add_executable(Tutorial tutorial.cxx)
由於配置過的文件將會被寫到二進制文件目錄下,我們必須把該目錄添加到包含文件的搜索路徑清單中。然后,以下的代碼就可以在源目錄下創建一份TotorialConfig.h.in文件:
// 與tutorial相關的配置好的選項與設置; #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
當CMake配置這份頭文件時,@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@的值將會被從CMakeLists文件中傳遞過來的值替代。下一步,我們要修改tutorial.cxx來包含configured頭文件然后使用其中的版本號。修改過的源代碼展列於下:
// 計算平方根的簡單程序。 #include <stdio.h> #include <stdlib.h> #include <math.h> #include "TutorialConfig.h" int main (int argc, char *argv[]) { if (argc < 2) { fprintf(stdout,"%s Version %d.%d\n", argv[0], Tutorial_VERSION_MAJOR, Tutorial_VERSION_MINOR); fprintf(stdout,"Usage: %s number\n",argv[0]); return 1; } double inputValue = atof(argv[1]); double outputValue = sqrt(inputValue); fprintf(stdout,"The square root of %g is %g\n", inputValue, outputValue); return 0; }
引入庫(步驟2)
現在我們將會在我們的工程中引入一個庫。這個庫會包含我們自己實現的計算一個數的平方根的函數。可執行文件隨后可以使用這個庫文件而不是編譯器提供的標准開平方函數。在本指南中,我們將會把庫文件放到一個子目錄MathFunctions中。它包含下述的單行CMakeLists文件:
add_library(MathFunctions mysqrt.cxx)
源文件mysqrt.cxx有一個叫做mysqrt的函數,它提供了與編譯器的sqrt函數類似的功能。為了使用新的庫,我們在頂層的CMakeLists中增加一個add_subrirectory調用,這樣這個庫也會被構建。我們也要向可執行文件中增加另一個頭文件路徑,這樣就可以從MathFunctions/mysqrt.h頭文件中找到函數的原型。最后的一點更改是在向可執行文件中引入新的庫。頂層CMakeLists文件的最后幾行現在看起來像是這樣:
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") add_subdirectory (MathFunctions) # 引入可執行文件 add_executable (Tutorial tutorial.cxx) target_link_libraries (Tutorial MathFunctions)
現在,讓我們考慮下讓MathFunctions庫變為可選的。在本指南中,確實沒有必要這樣畫蛇添足;但是對於更大型的庫或者依賴於第三方代碼的庫,你可能需要這種可選擇性。第一步是為頂層的CMakeLists文件添加一個選項:
# 我們應該使用我們自己的數學函數嗎? option (USE_MYMATH "Use tutorial provided math implementation" ON)
這將會在CMake的GUI中顯示一個默認的ON值,並且用戶可以隨需改變這個設置。這個設置會被存儲在cache中,那么用戶將不需要在cmake該工程時,每次都設置這個選項。第二處改變是,讓鏈接MathFunctions庫變為可選的。要實現這一點,我們修改頂層CMakeLists文件的結尾部分:
# 添加MathFunctions庫嗎? if (USE_MYMATH) include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") add_subdirectory (MathFunctions) set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH) # 添加可執行文件 add_executable (Tutorial tutorial.cxx) target_link_libraries (Tutorial ${EXTRA_LIBS})
這里用USE_MYMATH設置來決定是否MathFunctions應該被編譯和執行。注意到,要用一個變量(在這里是EXTRA_LIBS)來收集所有以后會被連接到可執行文件中的可選的庫。這是保持帶有許多可選部件的較大型工程干凈清爽的一種通用的方法。源代碼對應的改變相當直白,如下所示:
// 計算一個數平方根的簡單程序 #include <stdio.h> #include <stdlib.h> #include <math.h> #include "TutorialConfig.h" #ifdef USE_MYMATH #include "MathFunctions.h" #endif int main (int argc, char *argv[]) { if (argc < 2) { fprintf(stdout,"%s Version %d.%d\n", argv[0], Tutorial_VERSION_MAJOR, Tutorial_VERSION_MINOR); fprintf(stdout,"Usage: %s number\n",argv[0]); return 1; } double inputValue = atof(argv[1]); #ifdef USE_MYMATH double outputValue = mysqrt(inputValue); #else double outputValue = sqrt(inputValue); #endif fprintf(stdout,"The square root of %g is %g\n", inputValue, outputValue); return 0; }
在源代碼中,我們也使用了USE_MYMATH。這個宏是由CMake通過TutorialConfig.h.in配置文件中的下述語句行提供給源代碼的:
#cmakedefine USE_MYMATH
安裝與測試(步驟3)
下一步我們會為我們的工程引入安裝規則以及測試支持。安裝規則相當直白,對於MathFunctions庫,我們通過向MathFunctions的CMakeLists文件添加如下兩條語句來設置要安裝的庫以及頭文件:
install (TARGETS MathFunctions DESTINATION bin) install (FILES MathFunctions.h DESTINATION include)
對於應用程序,在頂層CMakeLists文件中添加下面幾行,它們用來安裝可執行文件以及配置頭文件:
# 添加安裝目標 install (TARGETS Tutorial DESTINATION bin) install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)
這就是要做的全部;現在你應該可以構建tutorial工程了。然后,敲入命令make install(或者從IDE中構建INSTALL目標)然后它就會安裝需要的頭文件,庫以及可執行文件CMake的變量CMAKE_INSTALL_PREFIX用來確定這些文件被安裝的根目錄。添加測試同樣也只需要相當淺顯的過程。在頂層CMakeLists文件的的尾部補充許多基本的測試代碼來確認應用程序可以正確工作。
# 應用程序是否運行? add_test (TutorialRuns Tutorial 25) # 它是否對25做了開平方運算 add_test (TutorialComp25 Tutorial 25) set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5") # 它是否能處理是負數作為輸入的情況 add_test (TutorialNegative Tutorial -25) set_tests_properties (TutorialNegative PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0") # 它是否可以處理較小的數字。 add_test (TutorialSmall Tutorial 0.0001) set_tests_properties (TutorialSmall PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01") # 用法信息是否可用? add_test (TutorialUsage Tutorial) set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
第一個測試用例僅僅用來驗證程序可以運行,沒有出現段錯誤或其他的崩潰,並且返回值必須是0。這是CTest所做測試的基本格式。余下的幾個測試都是用PASS_REGULAR_EXPRESSION 測試屬性來驗證測試代碼的輸出是否包含有特定的字符串。在本例中,測試樣例用來驗證計算得出的平方根與預定值一樣;當指定錯誤的輸入數據時,要打印用法信息。如果你想要添加許多測試不同輸入值的樣例,你應該考慮創建如下所示的宏:
#定義一個宏來簡化添加測試的過程,然后使用它 macro (do_test arg result) add_test (TutorialComp${arg} Tutorial ${arg}) set_tests_properties (TutorialComp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result}) endmacro (do_test)
# 做一系列基於結果的測試 do_test (25 "25 is 5") do_test (-25 "-25 is 0")
對於每個do_test宏調用,都會向工程中添加一個新的測試用例;宏參數是測試名、函數的輸入以及期望結果。
增加系統內省(步驟4)
下一步,讓我們考慮向我們的工程中引入一些依賴於目標平台上可能不具備的特性的代碼。在本例中,我們會增加一些依賴於目標平台是否有log或exp函數的代碼。當然,幾乎每個平台都有這些函數;但是對於tutorial工程,我們假設它們並非如此普遍。如果該平台有log函數,那么我們會在mysqrt函數中使用它去計算平方根。我們首先在頂層CMakeLists文件中使用宏CheckFunctionExists.cmake測試這些函數的可用性:
# 該系統提供log和exp函數嗎?
include (CheckFunctionExists.cmake) check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
下一步,如果CMake在對應平台上找到了它們,我們修改TutorialConfig.h.in來定義這些值;如下:
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
這些log和exp函數的測試要在TutorialConfig.h的configure_file命令之前被處理,這一點很重要。最后,在mysqrt函數中,如果log和exp在當前系統上可用的話,我們可以提供一個基於它們的可選的實現:
// 如果我們有log和exp兩個函數,那么使用它們
#if defined (HAVE_LOG) && defined (HAVE_EXP) result = exp(log(x)*0.5); #else // 否則使用替代方法
添加一個生成文件以及生成器(步驟5)
在本節,我們會展示你應該怎樣向一個應用程序的構建過程中添加一個生成的源文件。在本范例中,我們會創建一個預先計算出的平方根表作為構建過程的一部分。MathFunctions子路徑下,一個新的MakeTable.cxx源文件來做這件事。
// 一個簡單的用於構建平方根表的程序 #include <stdio.h> #include <stdlib.h>
#include <math.h> int main (int argc, char *argv[]) { int i; double result; // 確保有足夠多的參數 if (argc < 2) { return 1; } // 打開輸出文件 FILE *fout = fopen(argv[1],"w"); if (!fout) { return 1; } // 創建一個帶有平方根表的源文件
fprintf(fout,"double sqrtTable[] = {\n"); for (i = 0; i < 10; ++i) { result = sqrt(static_cast<double>(i)); fprintf(fout,"%g,\n",result); } // 該表以0結尾 fprintf(fout,"0};\n"); fclose(fout); return 0; }
注意到這個表是由合法的C++代碼生成的,並且被寫入的輸出文件的名字是作為一個參數輸入的。下一步是將合適的命令添加到MathFunction的CMakeLists文件中,來構建MakeTable可執行文件,然后運行它,作為構建過程的一部分。完成這幾步,需要少數的幾個命令,如下所示:
# 首先,我們添加生成該表的可執行文件
add_executable(MakeTable MakeTable.cxx) # 然后添加該命令來生成源文件 add_custom_command ( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h DEPENDS MakeTable ) # 為包含文件,向搜索路徑中添加二進制樹路徑 include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
# 添加main庫 add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h )
首先,MakeTable的可執行文件也和其他被加入的文件一樣被加入。然后,我們添加一個自定義命令來指定如何通過運行MakeTable來生成Table.h。這是通過將生成Table.h增加到MathFunctions庫的源文件列表中來實現的。我們還必須增加當前的二進制路徑到包含路徑的清單中,這樣Table.h可以被找到並且可以被mysqrt.cxx所包含。當該工程被構建后,它首先會構建MakeTable可執行文件。然后它會運行MakeTable來生成Table.h文件。最后,它會編譯mysqrt.cxx(其中包含Table.h)來生成MathFunctions庫。到目前為止,擁有我們添加的完整特性的頂層CMakeLists文件看起來像是這樣:
cmake_minimum_required (VERSION 2.6) project (Tutorial) # 版本號 set (Tutorial_VERSION_MAJOR 1) set (Tutorial_VERSION_MINOR 0) # 本系統是否提供log和exp函數? include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) check_function_exists (log HAVE_LOG) check_function_exists (exp HAVE_EXP) # 我們應該使用自己的math函數嗎? option(USE_MYMATH "Use tutorial provided math implementation" ON) # 配置一個頭文件來向源代碼傳遞一些CMake設置。 configure_file ( "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in" "${PROJECT_BINARY_DIR}/TutorialConfig.h" ) # 為包含文件的搜索路徑添加二進制樹,這樣才能發現TutorialConfig.h頭文件。 include_directories ("${PROJECT_BINARY_DIR}") # 添加MathFunctions庫嗎? if (USE_MYMATH) include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") add_subdirectory (MathFunctions) set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH) # 添加可執行文件 add_executable (Tutorial tutorial.cxx) target_link_libraries (Tutorial ${EXTRA_LIBS}) # 添加安裝的目標 install (TARGETS Tutorial DESTINATION bin) install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include) # 測試1 :應用程序可以運行嗎? add_test (TutorialRuns Tutorial 25) # 測試2 : 使用信息可用嗎? add_test (TutorialUsage Tutorial) set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number" ) # 定義一個可以簡化引入測試過程的宏 macro (do_test arg result) add_test (TutorialComp${arg} Tutorial ${arg}) set_tests_properties (TutorialComp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result} ) endmacro (do_test) # do a bunch of result based tests # 執行一系列基於結果的測試 do_test (4 "4 is 2") do_test (9 "9 is 3") do_test (5 "5 is 2.236")
do_test (7 "7 is 2.645") do_test (25 "25 is 5") do_test (-25 "-25 is 0") do_test (0.0001 "0.0001 is 0.01")
TutorialConfig.h文件看起來像是這樣:
// Tutorial的配置選項與設置如下 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@ #cmakedefine USE_MYMATH // 該平台提供exp和log函數嗎? #cmakedefine HAVE_LOG #cmakedefine HAVE_EXP
然后,MathFunctions工程的CMakeLists文件看起來像是這樣:
# 首先,我們添加生成這個表的可執行文件 add_executable(MakeTable MakeTable.cxx) # 添加生成源代碼的命令 add_custom_command ( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h DEPENDS MakeTable COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h ) # 為包含文件向搜索路徑中添加二進制樹目錄 include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) # 添加main庫 add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h) install (TARGETS MathFunctions DESTINATION bin) install (FILES MathFunctions.h DESTINATION include)
構建一個安裝器(步驟6)
下一步假設我們想要向其他人分發我們的工程,這樣他們就可以使用它。我們想同時提供在許多不同平台上的源代碼和二進制文檔發行版。這與之前我們在“安裝與測試(步驟3)”做過的安裝有一點不同,那里我們僅僅安裝我們從源碼中構建出來的二進制文件。在本例子中,我們會構建支持二進制安裝以及類似於cygwin,debian,RPM等具有包管理特性的安裝包。為了完成這個目標,我們會使用CPack來創建Packaging with CPack一章中描述的特定平台的安裝器。
# 構建一個CPack驅動的安裝包 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變量設置為保存本工程的許可證和版本信息的位置。版本信息使用了我們在本指南中先前設置的變量。最后,我們要包含CPack模塊,它會使用這些變量以及你所處的系統的一些別的屬性,然后來設置一個安裝器。下一步是以通常的方式構建該工程然后隨后運行CPack。如果要構建一個二進制發行包,你應該運行:
cpack -C CPackConfig.cmake
為了創建一個源代碼發行版,你應該鍵入:
cpack -C CPackSourceConfig.cmake
增加對Dashboard的支持(步驟7)
增加對向一個dashboard提交我們的測試結果的功能的支持非常簡單。我們在本指南的先前步驟中已經定義了我們工程中的許多測試樣例。我們僅僅需要運行這些測試樣例然后將它們提交到dashboard即可。為了包含對dashboards的支持,我們需要在頂層CMakeLists文件中包含CTest模塊。
# 支持dashboard腳本 include (CTest)
我們也可以創建一個CTestConfig.cmake文件,在其中來指定該dashboard的工程名。
set (CTEST_PROJECT_NAME "Tutorial")
CTest 將會在運行期間讀取這個文件。為了創建一個簡單的dashboard,你可以在你的工程下運行CMake,然后切換到二進制樹,然后運行ctest -DExperimental. 你的dashboard將會被更新到Kitware的公共dashboard.