CMake 用法导览
Preface : 本文是CMake官方文檔CMake Tutorial (http://www.cmake.org/cmake/help/cmake_tutorial.html) 的翻譯。通過一個(gè)樣例工程從簡單到復(fù)雜的完善過程,文檔介紹了CMake主要模塊(cmake, ctest, cpack)的功能和使用環(huán)境;從中可以一窺cmake的大體形貌。正文如下:
???? 本文下述內(nèi)容是一個(gè)手把手的使用指南;它涵蓋了CMake需要解決的公共構(gòu)建系統(tǒng)的一些問題。這些主題中的許多主題已經(jīng)在Mastering CMake一書中以單獨(dú)的章節(jié)被介紹過,但是通過一個(gè)樣例工程看一看它們?nèi)绾喂ぷ饕彩欠浅S袔椭摹1局改峡梢栽贑Make源碼樹的Tests/Tutorial路徑下找到。每一步都有它自己的子路徑,其中包含該步驟的一個(gè)完整的指南。
作為基礎(chǔ)的起始點(diǎn)(步驟1)
最基本的工程是一個(gè)從源代碼文件中構(gòu)建可執(zhí)行文件的例子。對(duì)于簡單工程,只要一個(gè)兩行的CMakeLists文件就足夠了。這將會(huì)作為我們指南的起點(diǎn)。這份CMakeLists文件看起來像是這樣:
| 1 2 3 | cmake_minimum_required (VERSION 2.6) project (Tutorial) add_executable(Tutorial tutorial.cxx) |
注意到這個(gè)例子在CMakeLists文件中使用了小寫。CMake支持大寫、小寫、混合大小寫的命令。tutorial.cxx中的源代碼用來計(jì)算一個(gè)數(shù)的平方根,并且它的第一版非常簡單,如下所示:
| // A simple program that computes the square root of a number // 計(jì)算一個(gè)數(shù)的平方根的簡單程序 #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; } |
????? 我們添加的第一個(gè)特性用來為工程和可執(zhí)行文件指定一個(gè)版本號(hào)。雖然你可以在源代碼中唯一指定它,但是你在CMakeLists文件中指定它可以提供更好的靈活性。如下所示,我么可以通過添加一個(gè)版本號(hào)來修改CMakeLists文件:
| cmake_minimum_required (VERSION 2.6) project (Tutorial) # 版本號(hào) set (Tutorial_VERSION_MAJOR 1) set (Tutorial_VERSION_MINOR 0) # 配置一個(gè)頭文件,通過它向源代碼中傳遞一些CMake設(shè)置。 configure_file ( ??"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in" ??"${PROJECT_BINARY_DIR}/TutorialConfig.h" ??) # 將二進(jìn)制文件樹添加到包含文件的搜索路徑中,這樣我們可以找到TutorialConfig.h include_directories("${PROJECT_BINARY_DIR}") # 添加可執(zhí)行文件 add_executable(Tutorial tutorial.cxx) |
????? 由于配置過的文件將會(huì)被寫到二進(jìn)制文件目錄下,我們必須把該目錄添加到包含文件的搜索路徑清單中。然后,以下的代碼就可以在源目錄下創(chuàng)建一份TotorialConfig.h.in文件:
| 1 2 3 | // 與tutorial相關(guān)的配置好的選項(xiàng)與設(shè)置; #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@ |
????? 當(dāng)CMake配置這份頭文件時(shí),@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@的值將會(huì)被從CMakeLists文件中傳遞過來的值替代。下一步,我們要修改tutorial.cxx來包含configured頭文件然后使用其中的版本號(hào)。修改過的源代碼展列于下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 計(jì)算平方根的簡單程序。 #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)
???? 現(xiàn)在我們將會(huì)在我們的工程中引入一個(gè)庫。這個(gè)庫會(huì)包含我們自己實(shí)現(xiàn)的計(jì)算一個(gè)數(shù)的平方根的函數(shù)。可執(zhí)行文件隨后可以使用這個(gè)庫文件而不是編譯器提供的標(biāo)準(zhǔn)開平方函數(shù)。在本指南中,我們將會(huì)把庫文件放到一個(gè)子目錄MathFunctions中。它包含下述的單行CMakeLists文件:
| 1 | add_library(MathFunctions mysqrt.cxx) |
???? 源文件mysqrt.cxx有一個(gè)叫做mysqrt的函數(shù),它提供了與編譯器的sqrt函數(shù)類似的功能。為了使用新的庫,我們?cè)陧攲拥腃MakeLists中增加一個(gè)add_subrirectory調(diào)用,這樣這個(gè)庫也會(huì)被構(gòu)建。我們也要向可執(zhí)行文件中增加另一個(gè)頭文件路徑,這樣就可以從MathFunctions/mysqrt.h頭文件中找到函數(shù)的原型。最后的一點(diǎn)更改是在向可執(zhí)行文件中引入新的庫。頂層CMakeLists文件的最后幾行現(xiàn)在看起來像是這樣:
| 1 2 3 4 5 | include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") add_subdirectory (MathFunctions) # 引入可執(zhí)行文件 add_executable (Tutorial tutorial.cxx) target_link_libraries (Tutorial MathFunctions) |
現(xiàn)在,讓我們考慮下讓MathFunctions庫變?yōu)榭蛇x的。在本指南中,確實(shí)沒有必要這樣畫蛇添足;但是對(duì)于更大型的庫或者依賴于第三方代碼的庫,你可能需要這種可選擇性。第一步是為頂層的CMakeLists文件添加一個(gè)選項(xiàng):
| 1 2 3 | # 我們應(yīng)該使用我們自己的數(shù)學(xué)函數(shù)嗎? option (USE_MYMATH ????????"Use tutorial provided math implementation"?ON) |
這將會(huì)在CMake的GUI中顯示一個(gè)默認(rèn)的ON值,并且用戶可以隨需改變這個(gè)設(shè)置。這個(gè)設(shè)置會(huì)被存儲(chǔ)在cache中,那么用戶將不需要在cmake該工程時(shí),每次都設(shè)置這個(gè)選項(xiàng)。第二處改變是,讓鏈接MathFunctions庫變?yōu)榭蛇x的。要實(shí)現(xiàn)這一點(diǎn),我們修改頂層CMakeLists文件的結(jié)尾部分:
| 1 2 3 4 5 6 7 8 9 | # 添加MathFunctions庫嗎? if?(USE_MYMATH) ??include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") ??add_subdirectory (MathFunctions) ??set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH) # 添加可執(zhí)行文件 add_executable (Tutorial tutorial.cxx) target_link_libraries (Tutorial? ${EXTRA_LIBS}) |
這里用USE_MYMATH設(shè)置來決定是否MathFunctions應(yīng)該被編譯和執(zhí)行。注意到,要用一個(gè)變量(在這里是EXTRA_LIBS)來收集所有以后會(huì)被連接到可執(zhí)行文件中的可選的庫。這是保持帶有許多可選部件的較大型工程干凈清爽的一種通用的方法。源代碼對(duì)應(yīng)的改變相當(dāng)直白,如下所示:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 計(jì)算一個(gè)數(shù)平方根的簡單程序 #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。這個(gè)宏是由CMake通過TutorialConfig.h.in配置文件中的下述語句行提供給源代碼的:
+ View Code 安裝與測試(步驟3)
下一步我們會(huì)為我們的工程引入安裝規(guī)則以及測試支持。安裝規(guī)則相當(dāng)直白,對(duì)于MathFunctions庫,我們通過向MathFunctions的CMakeLists文件添加如下兩條語句來設(shè)置要安裝的庫以及頭文件:
| 1 2 | install (TARGETS MathFunctions DESTINATION bin) install (FILES MathFunctions.h DESTINATION include) |
對(duì)于應(yīng)用程序,在頂層CMakeLists文件中添加下面幾行,它們用來安裝可執(zhí)行文件以及配置頭文件:
| 1 2 3 4 | # 添加安裝目標(biāo) install (TARGETS Tutorial DESTINATION bin) install (FILES?"${PROJECT_BINARY_DIR}/TutorialConfig.h"??????? ?????????DESTINATION include) |
這就是要做的全部;現(xiàn)在你應(yīng)該可以構(gòu)建tutorial工程了。然后,敲入命令make install(或者從IDE中構(gòu)建INSTALL目標(biāo))然后它就會(huì)安裝需要的頭文件,庫以及可執(zhí)行文件CMake的變量CMAKE_INSTALL_PREFIX用來確定這些文件被安裝的根目錄。添加測試同樣也只需要相當(dāng)淺顯的過程。在頂層CMakeLists文件的的尾部補(bǔ)充許多基本的測試代碼來確認(rèn)應(yīng)用程序可以正確工作。
+ View Code第一個(gè)測試用例僅僅用來驗(yàn)證程序可以運(yùn)行,沒有出現(xiàn)段錯(cuò)誤或其他的崩潰,并且返回值必須是0。這是CTest所做測試的基本格式。余下的幾個(gè)測試都是用PASS_REGULAR_EXPRESSION 測試屬性來驗(yàn)證測試代碼的輸出是否包含有特定的字符串。在本例中,測試樣例用來驗(yàn)證計(jì)算得出的平方根與預(yù)定值一樣;當(dāng)指定錯(cuò)誤的輸入數(shù)據(jù)時(shí),要打印用法信息。如果你想要添加許多測試不同輸入值的樣例,你應(yīng)該考慮創(chuàng)建如下所示的宏:
+ View Code對(duì)于每個(gè)do_test宏調(diào)用,都會(huì)向工程中添加一個(gè)新的測試用例;宏參數(shù)是測試名、函數(shù)的輸入以及期望結(jié)果。
增加系統(tǒng)內(nèi)省(步驟4)
下一步,讓我們考慮向我們的工程中引入一些依賴于目標(biāo)平臺(tái)上可能不具備的特性的代碼。在本例中,我們會(huì)增加一些依賴于目標(biāo)平臺(tái)是否有l(wèi)og或exp函數(shù)的代碼。當(dāng)然,幾乎每個(gè)平臺(tái)都有這些函數(shù);但是對(duì)于tutorial工程,我們假設(shè)它們并非如此普遍。如果該平臺(tái)有l(wèi)og函數(shù),那么我們會(huì)在mysqrt函數(shù)中使用它去計(jì)算平方根。我們首先在頂層CMakeLists文件中使用宏CheckFunctionExists.cmake測試這些函數(shù)的可用性:
+ View Code下一步,如果CMake在對(duì)應(yīng)平臺(tái)上找到了它們,我們修改TutorialConfig.h.in來定義這些值;如下:
// 該平臺(tái)提供exp和log函數(shù)嗎?#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
這些log和exp函數(shù)的測試要在TutorialConfig.h的configure_file命令之前被處理,這一點(diǎn)很重要。最后,在mysqrt函數(shù)中,如果log和exp在當(dāng)前系統(tǒng)上可用的話,我們可以提供一個(gè)基于它們的可選的實(shí)現(xiàn):
| 1 2 3 | // 如果我們有l(wèi)og和exp兩個(gè)函數(shù),那么使用它們<br>#if defined (HAVE_LOG) && defined (HAVE_EXP) ??result =?exp(log(x)*0.5); #else // 否則使用替代方法 |
添加一個(gè)生成文件以及生成器(步驟5)
在本節(jié),我們會(huì)展示你應(yīng)該怎樣向一個(gè)應(yīng)用程序的構(gòu)建過程中添加一個(gè)生成的源文件。在本范例中,我們會(huì)創(chuàng)建一個(gè)預(yù)先計(jì)算出的平方根表作為構(gòu)建過程的一部分。MathFunctions子路徑下,一個(gè)新的MakeTable.cxx源文件來做這件事。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // 一個(gè)簡單的用于構(gòu)建平方根表的程序 #include <stdio.h> #include <stdlib.h><br>#include <math.h> ?? int?main (int?argc,?char?*argv[]) { ??int?i; ??double?result; ?? ??// 確保有足夠多的參數(shù) ??if?(argc < 2) ????{ ????return?1; ????} ??? ??// 打開輸出文件 ??FILE?*fout =?fopen(argv[1],"w"); ??if?(!fout) ????{ ????return?1; ????} ??? ??// 創(chuàng)建一個(gè)帶有平方根表的源文件<br>? fprintf(fout,"double sqrtTable[] = {\n"); ??for?(i = 0; i < 10; ++i) ????{ ????result =?sqrt(static_cast<double>(i)); ????fprintf(fout,"%g,\n",result); ????} ?? ??// 該表以0結(jié)尾 ??fprintf(fout,"0};\n"); ??fclose(fout); ??return?0; } |
注意到這個(gè)表是由合法的C++代碼生成的,并且被寫入的輸出文件的名字是作為一個(gè)參數(shù)輸入的。下一步是將合適的命令添加到MathFunction的CMakeLists文件中,來構(gòu)建MakeTable可執(zhí)行文件,然后運(yùn)行它,作為構(gòu)建過程的一部分。完成這幾步,需要少數(shù)的幾個(gè)命令,如下所示:
| 1 2 3 4 5 6 7 8 9 10 11 12 | # 首先,我們添加生成該表的可執(zhí)行文件<br>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 ??) ?? # 為包含文件,向搜索路徑中添加二進(jìn)制樹路徑 include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) <br># 添加main庫 add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h? ) |
首先,MakeTable的可執(zhí)行文件也和其他被加入的文件一樣被加入。然后,我們添加一個(gè)自定義命令來指定如何通過運(yùn)行MakeTable來生成Table.h。這是通過將生成Table.h增加到MathFunctions庫的源文件列表中來實(shí)現(xiàn)的。我們還必須增加當(dāng)前的二進(jìn)制路徑到包含路徑的清單中,這樣Table.h可以被找到并且可以被mysqrt.cxx所包含。當(dāng)該工程被構(gòu)建后,它首先會(huì)構(gòu)建MakeTable可執(zhí)行文件。然后它會(huì)運(yùn)行MakeTable來生成Table.h文件。最后,它會(huì)編譯mysqrt.cxx(其中包含Table.h)來生成MathFunctions庫。到目前為止,擁有我們添加的完整特性的頂層CMakeLists文件看起來像是這樣:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | cmake_minimum_required (VERSION 2.6) project (Tutorial) ?? # 版本號(hào) set (Tutorial_VERSION_MAJOR 1) set (Tutorial_VERSION_MINOR 0) # 本系統(tǒng)是否提供log和exp函數(shù)? include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) ?? check_function_exists (log?HAVE_LOG) check_function_exists (exp?HAVE_EXP) # 我們應(yīng)該使用自己的math函數(shù)嗎? option(USE_MYMATH ??"Use tutorial provided math implementation"?ON) # 配置一個(gè)頭文件來向源代碼傳遞一些CMake設(shè)置。 configure_file ( ??"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in" ??"${PROJECT_BINARY_DIR}/TutorialConfig.h" ??) # 為包含文件的搜索路徑添加二進(jìn)制樹,這樣才能發(fā)現(xiàn)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) ?? # 添加可執(zhí)行文件 add_executable (Tutorial tutorial.cxx) target_link_libraries (Tutorial? ${EXTRA_LIBS}) ?? # 添加安裝的目標(biāo) install (TARGETS Tutorial DESTINATION bin) install (FILES?"${PROJECT_BINARY_DIR}/TutorialConfig.h"??????? ?????????DESTINATION include) ?? # 測試1 :應(yīng)用程序可以運(yùn)行嗎? add_test (TutorialRuns Tutorial 25) ?? # 測試2 : 使用信息可用嗎? add_test (TutorialUsage Tutorial) set_tests_properties (TutorialUsage ??PROPERTIES ??PASS_REGULAR_EXPRESSION?"Usage:.*number" ??) # 定義一個(gè)可以簡化引入測試過程的宏 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 # 執(zhí)行一系列基于結(jié)果的測試 do_test (4?"4 is 2") do_test (9?"9 is 3") do_test (5?"5 is 2.236")<br>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的配置選項(xiàng)與設(shè)置如下 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@ #cmakedefine USE_MYMATH ?? // 該平臺(tái)提供exp和log函數(shù)嗎? #cmakedefine HAVE_LOG #cmakedefine HAVE_EXP |
然后,MathFunctions工程的CMakeLists文件看起來像是這樣:
+ View Code構(gòu)建一個(gè)安裝器(步驟6)
下一步假設(shè)我們想要向其他人分發(fā)我們的工程,這樣他們就可以使用它。我們想同時(shí)提供在許多不同平臺(tái)上的源代碼和二進(jìn)制文檔發(fā)行版。這與之前我們?cè)凇鞍惭b與測試(步驟3)”做過的安裝有一點(diǎn)不同,那里我們僅僅安裝我們從源碼中構(gòu)建出來的二進(jìn)制文件。在本例子中,我們會(huì)構(gòu)建支持二進(jìn)制安裝以及類似于cygwin,debian,RPM等具有包管理特性的安裝包。為了完成這個(gè)目標(biāo),我們會(huì)使用CPack來創(chuàng)建Packaging with CPack一章中描述的特定平臺(tái)的安裝器。
| 1 2 3 4 5 6 7 | # 構(gòu)建一個(gè)CPack驅(qū)動(dòng)的安裝包 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開始。這個(gè)模塊將會(huì)包含許多在當(dāng)前平臺(tái)上,當(dāng)前工程需要的運(yùn)行時(shí)庫。第一步我們將一些CPack變量設(shè)置為保存本工程的許可證和版本信息的位置。版本信息使用了我們?cè)诒局改现邢惹霸O(shè)置的變量。最后,我們要包含CPack模塊,它會(huì)使用這些變量以及你所處的系統(tǒng)的一些別的屬性,然后來設(shè)置一個(gè)安裝器。下一步是以通常的方式構(gòu)建該工程然后隨后運(yùn)行CPack。如果要構(gòu)建一個(gè)二進(jìn)制發(fā)行包,你應(yīng)該運(yùn)行:
| 1 | cpack -C CPackConfig.cmake |
為了創(chuàng)建一個(gè)源代碼發(fā)行版,你應(yīng)該鍵入:
| 1 | cpack -C CPackSourceConfig.cmake |
增加對(duì)Dashboard的支持(步驟7)
增加對(duì)向一個(gè)dashboard提交我們的測試結(jié)果的功能的支持非常簡單。我們?cè)诒局改系南惹安襟E中已經(jīng)定義了我們工程中的許多測試樣例。我們僅僅需要運(yùn)行這些測試樣例然后將它們提交到dashboard即可。為了包含對(duì)dashboards的支持,我們需要在頂層CMakeLists文件中包含CTest模塊。
| 1 2 | # 支持dashboard腳本 include (CTest) |
我們也可以創(chuàng)建一個(gè)CTestConfig.cmake文件,在其中來指定該dashboard的工程名。
| 1 | set (CTEST_PROJECT_NAME?"Tutorial") |
? CTest 將會(huì)在運(yùn)行期間讀取這個(gè)文件。為了創(chuàng)建一個(gè)簡單的dashboard,你可以在你的工程下運(yùn)行CMake,然后切換到二進(jìn)制樹,然后運(yùn)行ctest -DExperimental. 你的dashboard將會(huì)被更新到Kitware的公共dashboard.
總結(jié)
以上是生活随笔為你收集整理的CMake 用法导览的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CMake 手册详解(五)
- 下一篇: cmake使用示例与整理总结