CMake是我非常喜歡且一直使用的工具臼予。它不但能幫助我跨平臺、跨編譯器啃沪,而且最酷的是粘拾,它幫我節(jié)約了太多的存儲空間。特別是與水銀結(jié)合起來使用创千,其友好的體驗缰雇,足以給我們這些苦逼碼農(nóng)一絲慰藉。
以下內(nèi)容翻譯自官網(wǎng)教程
CMake Tutorial
A Basic Starting Point (Step 1)
最基本的就是將一個源代碼文件編譯成一個exe可執(zhí)行程序追驴。對于一個簡單的工程來說械哟,兩行的CMakeLists.txt文件就足夠了。這將是我們教程的開始殿雪。CMakeLists.txt文件看起來會像這樣:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
注意暇咆,在這個例子中,CMakeLists.txt都是使用的小寫字母丙曙。事實上爸业,CMake命令是大小寫不敏感的,你可以用大寫亏镰,也可以用小寫扯旷,也可以混寫。tutorial.cxx源碼會計算出一個數(shù)的平方根索抓。它的第一個版本看起來非常簡單钧忽,如下:
// 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;
}
Adding a Version Number and Configured Header File
我們第一個要加入的特性是,在工程和可執(zhí)行程序上加一個版本號逼肯。雖然你可以直接在源代碼里面這么做耸黑,然而如果用CMakeLists文件來做的話會提供更多的靈活性。為了增加版本號汉矿,我們可以如此更改CMakeLists文件:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
# add the executable
add_executable(Tutorial tutorial.cxx)
由于配置文件必須寫到binary tree中崎坊,因此我們必須將這個目錄添加到頭文件搜索目錄中备禀。我們接下來在源碼目錄中創(chuàng)建了TutorialConfig.h.in文件洲拇,其內(nèi)容如下:
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
當(dāng)CMake配置了這個頭文件奈揍, @Tutorial_VERSION_MAJOR@ 和 @Tutorial_VERSION_MINOR@ 的值將會被改變。接下來赋续,我們修改了tutorial.cxx來包含配置的頭文件并且使用版本號男翰。最終的源代碼如下所示:
// A simple program that computes the square root of a number
#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;
}
最主要的變更是包含了TutorialConfig.h頭文件,并輸出了版本號纽乱。
Adding a Library (Step 2)
現(xiàn)在我們給工程添加一個庫蛾绎。這個庫會包含我們自己的平方根實現(xiàn)柴我。如此宰睡,應(yīng)用程序就可以使用這個庫而非編譯器提供的庫了。在這個教程中遏片,我們將庫放入一個叫MathFunctions的子文件夾中薯嗤。它會使用如下的一行CMakeLists文件:
add_library(MathFunctions mysqrt.cxx)
原文件mysqrt.cxx有一個叫做mysqrt的函數(shù)可以提供與編譯器的sqrt相似的功能顽爹。為了使用新的庫,我們需要在頂層的CMakeLists 文件中添加add_subdirectory的調(diào)用骆姐。我們也要添加一個另外的頭文件搜索目錄镜粤,使得MathFunctions/mysqrt.h可以被搜索到。最后的改變就是將新的庫加到可執(zhí)行程序中玻褪。頂層的CMakeLists 文件現(xiàn)在看起來是這樣:
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)
現(xiàn)在我們來考慮如何使得MathFunctions庫成為可選的肉渴。雖然在這個教程當(dāng)中沒有什么理由這么做,然而如果使用更大的庫或者當(dāng)依賴于第三方的庫時带射,你或許希望這么做同规。第一步是要在頂層的CMakeLists文件中加上一個選擇項。
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
這個選項會顯示在CMake的GUI窟社,并且其默認值為ON捻浦。當(dāng)用戶選擇了之后,這個值會被保存在CACHE中桥爽,這樣就不需要每次CMAKE都進行更改了朱灿。下面一步條件構(gòu)建和鏈接MathFunctions庫。為了達到這個目的钠四,我們可以改變頂層的CMakeLists文件盗扒,使得其看起來像這樣:
# add the MathFunctions library?
#
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial ${EXTRA_LIBS})
這里使用了USE_MYMATH來決定MathFunctions是否會被編譯和使用。注意這里變量EXTRA_LIBS的使用方法缀去。這是保持一個大的項目看起來比較簡潔的一個方法侣灶。源代碼中相應(yīng)的變化就比較簡單了:
// A simple program that computes the square root of a number
#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
Installing and Testing (Step 3)
接下來我們會為我們的工程增加安裝規(guī)則和測試支持褥影。安裝規(guī)則是非常非常簡單的。對于MathFunctions庫我們安裝庫和頭文件只需要添加如下的語句:
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
對于應(yīng)用程序咏雌,我們只需要在頂層CMakeLists 文件中如此配置即可以安裝可執(zhí)行程序和配置了的頭文件:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
這就是所有需要做的》苍酰現(xiàn)在你就可以編譯這個教程了校焦,然后輸入make install(或者編譯IDE中的INSTALL目標),則頭文件统倒、庫和可執(zhí)行程序等就會被正確地安裝寨典。CMake變量CMAKE_INSTALL_PREFIX被用來決定那些文件會被安裝在哪個根目錄下。添加測試也是一個相當(dāng)簡單的過程房匆。在最頂層的CMakeLists文件的最后我們可以添加一系列的基礎(chǔ)測試來確認這個程序是否在正確工作耸成。
# does the application run
add_test (TutorialRuns Tutorial 25)
# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)
set_tests_properties (TutorialComp25
PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative
PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")
# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall
PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
PROPERTIES
PASS_REGULAR_EXPRESSION "Usage:.*number")
第一個測試簡單地確認應(yīng)用是否運行,沒有段錯誤或者其它的崩潰問題浴鸿,并且返回0井氢。這是CTest的最基本的形式。下面的測試都使用了PASS_REGULAR_EXPRESSION測試屬性來確認輸出的結(jié)果中是否含有某個字符串岳链。如果你需要添加大量的測試來判斷不同的輸入值毙沾,則你需要考慮創(chuàng)建一個類似于下面的宏:
#define a macro to simplify adding tests, then use it
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 (25 "25 is 5")
do_test (-25 "-25 is 0")
對do_test的任意一次調(diào)用,就有另一個測試被添加到工程中宠页。
Adding System Introspection (Step 4)
接下來左胞,我們來考慮添加一些有些目標平臺可能不支持的代碼。在這個樣例中举户,我們將根據(jù)目標平臺是否有l(wèi)og和exp函數(shù)來添加我們的代碼烤宙。當(dāng)然大多數(shù)平臺都是有這些函數(shù)的,只是本教程假設(shè)這兩個函數(shù)沒有被那么普遍地支持俭嘁。如果平臺有l(wèi)og躺枕,那么在mysqrt中,就用它來計算平方根供填。我們首先使用CheckFunctionExists.cmake來測試這些函數(shù)的是否存在拐云,在頂層的CMakeLists文件中:
# does this system provide the log and exp functions?
include (CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
接下來我們修改TutorialConfig.h.in來定義CMake是否找到這些函數(shù)的宏
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
重要的一點是,對tests和log的測試必須要在配置文件命令前完成近她。配置文件命令會使用CMake中的配置立馬配置文件叉瘩。最后在mysqrt函數(shù)中我們提供了兩種實現(xiàn)方式:
// if we have both log and exp then use them
#if defined (HAVE_LOG) && defined (HAVE_EXP)
result = exp(log(x)*0.5);
#else // otherwise use an iterative approach
. . .
Adding a Generated File and Generator (Step 5)
在這一節(jié)當(dāng)中,我們會告訴你如何將一個生成的源文件加入到應(yīng)用程序的構(gòu)建過程中粘捎。在此例中薇缅,我們會創(chuàng)建一個預(yù)先計算好的平方根的表,并將這個表編譯到應(yīng)用程序中去攒磨。為了達到這個目的泳桦,我們首先需要一個程序來生成這樣的表。在MathFunctions這個子目錄下一個新的叫做MakeTable.cxx的源文件就是用來干這個的娩缰。
// A simple program that builds a sqrt table
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
int i;
double result;
// make sure we have enough arguments
if (argc < 2)
{
return 1;
}
// open the output file
FILE *fout = fopen(argv[1],"w");
if (!fout)
{
return 1;
}
// create a source file with a table of square roots
fprintf(fout,"double sqrtTable[] = {\n");
for (i = 0; i < 10; ++i)
{
result = sqrt(static_cast<double>(i));
fprintf(fout,"%g,\n",result);
}
// close the table with a zero
fprintf(fout,"0};\n");
fclose(fout);
return 0;
}
注意到這張表是由一個有效的C++代碼產(chǎn)生的灸撰,并且輸出文件的名字是由參數(shù)代入的。下一步就是添加合適的命令到MathFunctions的CMakeLists文件中來構(gòu)建MakeTable這個可執(zhí)行程序,并且作為構(gòu)建過程中的一部分浮毯。完成它需要一些命令完疫,如下:
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
# add the binary tree directory to the search path for
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h )
首先,就像其它可執(zhí)行程序一樣亲轨,MakeTable被添加為可執(zhí)行程序趋惨。然后我們添加了一個自定義命令來詳細描述如何通過運行MakeTable來產(chǎn)生Table.h鸟顺。接下來惦蚊,我們需要讓CMake知道m(xù)ysqrt.cxx依賴于生成的文件Table.h。這是通過往MathFunctions這個庫里面添加生成的Table.h來實現(xiàn)的讯嫂。我們也需要添加當(dāng)前的生成目錄到搜索路徑中蹦锋,從而Table.h可以被mysqrt.cxx找到。
當(dāng)這個工程被構(gòu)建時欧芽,它首先會構(gòu)建MakeTable這個可執(zhí)行程序莉掂。然后運行MakeTable從而生成Table.h。最后千扔,它會編譯mysqrt.cxx來生成MathFunctions library憎妙。
在這一刻,我們添加了所有的特征到最頂層的CMakeLists文件曲楚,它現(xiàn)在看起來是這樣的:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
# should we use our own math functions
option(USE_MYMATH
"Use tutorial provided math implementation" ON)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories ("${PROJECT_BINARY_DIR}")
# add the MathFunctions library?
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial ${EXTRA_LIBS})
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
# does the application run
add_test (TutorialRuns Tutorial 25)
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
PROPERTIES
PASS_REGULAR_EXPRESSION "Usage:.*number"
)
#define a macro to simplify adding tests
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是這樣的:
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
最后MathFunctions的CMakeLists文件看起來是這樣的:
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# add the binary tree directory to the search path
# for include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
Building an Installer (Step 6)
最后假設(shè)我們想要把我們的工程發(fā)布給別人從而讓他們?nèi)ナ褂美逋佟N覀兿胍瑫r給他們不同平臺的二進制文件和源代碼。這與步驟3中的install略有不同龙誊,install是安裝我們從源代碼中構(gòu)建的二進制文件抚垃。而在此例中,我們將要構(gòu)建安裝包來支持二進制安裝以及cygwin趟大,debian鹤树,RPMs等的包管理特性。為了達到這個目的逊朽,我們會使用CPack來創(chuàng)建平臺相關(guān)的安裝包罕伯。具體地說,我們需要在頂層CMakeLists.txt文件中的底部添加數(shù)行叽讳。
# build a CPack driven installer package
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}")
set (CPACK_PACKAGE_VERSION_PATCH "${Tutorial_VERSION_PATCH}")
include (CPack)
這就是所有要做的捣炬。我們首先包含了InstallRequiredSystemLibraries。這個模塊將會包含當(dāng)前平臺所需要的所有運行時庫绽榛。接下來湿酸,我們設(shè)置了一些CPack的變量來保存license以及工程的版本信息。版本信息利用了我們在早前的教程中使用到的變量灭美。最后我們包含了CPack這個模塊來使用這些變量和你所使用的系統(tǒng)的其它特性來設(shè)置安裝包推溃。
接下來一步是用通常的方式構(gòu)建工程,然后在CPack上運行它届腐。如果要構(gòu)建一個二進制包你需要運行:
cpack --config CPackConfig.cmake
如果要創(chuàng)建一個關(guān)代碼包你需要輸入
cpack --config CPackSourceConfig.cmake
Adding Support for a Dashboard (Step 7)
將測試結(jié)果上傳到dashboard上是非常簡單的铁坎。我們在早前的步驟中已經(jīng)定義了一些測試蜂奸。我們僅需要運行這些例程然后提交到dashboard上。為了包含對dashboards的支持硬萍,我們需要在頂層的CMakeLists文件中包含CTest模塊扩所。
# enable dashboard scripting
include (CTest)
我們也創(chuàng)建了一個CTestConfig.cmake文件來指定這個工程在dashobard上的的名字。
set (CTEST_PROJECT_NAME "Tutorial")
CTest會讀這個文件并且運行它朴乖。如果要創(chuàng)建一個簡單的dashboard祖屏,你可以在你的工程上運行CMake,改變生成路徑的目錄买羞,然后運行ctest -D Experimental袁勺。你的dashboard的結(jié)果會被上傳到Kitware的公共dashboard中。
最后畜普,可以在這里下載到整個教程的源代碼期丰。