CMake是我非常喜歡且一直使用的工具。它不但能幫助我跨平臺(tái)、跨編譯器,而且最酷的是,它幫我節(jié)約了太多的存儲(chǔ)空間。特別是與水銀結(jié)合起來使用,其友好的體驗(yàn),足以給我們這些苦逼碼農(nóng)一絲慰藉。
以下內(nèi)容翻譯自官網(wǎng)教程
CMake Tutorial

A Basic Starting Point (Step 1)
最基本的就是將一個(gè)源代碼文件編譯成一個(gè)exe可執(zhí)行程序。對(duì)于一個(gè)簡(jiǎn)單的工程來說,兩行的CMakeLists.txt文件就足夠了。這將是我們教程的開始。CMakeLists.txt文件看起來會(huì)像這樣:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
注意,在這個(gè)例子中,CMakeLists.txt都是使用的小寫字母。事實(shí)上,CMake命令是大小寫不敏感的,你可以用大寫,也可以用小寫,也可以混寫。tutorial.cxx源碼會(huì)計(jì)算出一個(gè)數(shù)的平方根。它的第一個(gè)版本看起來非常簡(jiǎn)單,如下:
// 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
我們第一個(gè)要加入的特性是,在工程和可執(zhí)行程序上加一個(gè)版本號(hào)。雖然你可以直接在源代碼里面這么做,然而如果用CMakeLists文件來做的話會(huì)提供更多的靈活性。為了增加版本號(hào),我們可以如此更改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中,因此我們必須將這個(gè)目錄添加到頭文件搜索目錄中。我們接下來在源碼目錄中創(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配置了這個(gè)頭文件, @Tutorial_VERSION_MAJOR@ 和 @Tutorial_VERSION_MINOR@ 的值將會(huì)被改變。接下來,我們修改了tutorial.cxx來包含配置的頭文件并且使用版本號(hào)。最終的源代碼如下所示:
// 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頭文件,并輸出了版本號(hào)。
Adding a Library (Step 2)
現(xiàn)在我們給工程添加一個(gè)庫。這個(gè)庫會(huì)包含我們自己的平方根實(shí)現(xiàn)。如此,應(yīng)用程序就可以使用這個(gè)庫而非編譯器提供的庫了。在這個(gè)教程中,我們將庫放入一個(gè)叫MathFunctions的子文件夾中。它會(huì)使用如下的一行CMakeLists文件:
add_library(MathFunctions mysqrt.cxx)
原文件mysqrt.cxx有一個(gè)叫做mysqrt的函數(shù)可以提供與編譯器的sqrt相似的功能。為了使用新的庫,我們需要在頂層的CMakeLists 文件中添加add_subdirectory的調(diào)用。我們也要添加一個(gè)另外的頭文件搜索目錄,使得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庫成為可選的。雖然在這個(gè)教程當(dāng)中沒有什么理由這么做,然而如果使用更大的庫或者當(dāng)依賴于第三方的庫時(shí),你或許希望這么做。第一步是要在頂層的CMakeLists文件中加上一個(gè)選擇項(xiàng)。
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
這個(gè)選項(xiàng)會(huì)顯示在CMake的GUI,并且其默認(rèn)值為ON。當(dāng)用戶選擇了之后,這個(gè)值會(huì)被保存在CACHE中,這樣就不需要每次CMAKE都進(jìn)行更改了。下面一步條件構(gòu)建和鏈接MathFunctions庫。為了達(dá)到這個(gè)目的,我們可以改變頂層的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是否會(huì)被編譯和使用。注意這里變量EXTRA_LIBS的使用方法。這是保持一個(gè)大的項(xiàng)目看起來比較簡(jiǎn)潔的一個(gè)方法。源代碼中相應(yīng)的變化就比較簡(jiǎn)單了:
// 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這個(gè)宏。它由CMAKE通過配置文件TutorialConfig.h.in來提供給源代碼。
#cmakedefine USE_MYMATH
Installing and Testing (Step 3)
接下來我們會(huì)為我們的工程增加安裝規(guī)則和測(cè)試支持。安裝規(guī)則是非常非常簡(jiǎn)單的。對(duì)于MathFunctions庫我們安裝庫和頭文件只需要添加如下的語句:
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
對(duì)于應(yīng)用程序,我們只需要在頂層CMakeLists 文件中如此配置即可以安裝可執(zhí)行程序和配置了的頭文件:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
這就是所有需要做的。現(xiàn)在你就可以編譯這個(gè)教程了,然后輸入make install(或者編譯IDE中的INSTALL目標(biāo)),則頭文件、庫和可執(zhí)行程序等就會(huì)被正確地安裝。CMake變量CMAKE_INSTALL_PREFIX被用來決定那些文件會(huì)被安裝在哪個(gè)根目錄下。添加測(cè)試也是一個(gè)相當(dāng)簡(jiǎn)單的過程。在最頂層的CMakeLists文件的最后我們可以添加一系列的基礎(chǔ)測(cè)試來確認(rèn)這個(gè)程序是否在正確工作。
# 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")
第一個(gè)測(cè)試簡(jiǎn)單地確認(rèn)應(yīng)用是否運(yùn)行,沒有段錯(cuò)誤或者其它的崩潰問題,并且返回0。這是CTest的最基本的形式。下面的測(cè)試都使用了PASS_REGULAR_EXPRESSION測(cè)試屬性來確認(rèn)輸出的結(jié)果中是否含有某個(gè)字符串。如果你需要添加大量的測(cè)試來判斷不同的輸入值,則你需要考慮創(chuàng)建一個(gè)類似于下面的宏:
#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")
對(duì)do_test的任意一次調(diào)用,就有另一個(gè)測(cè)試被添加到工程中。
Adding System Introspection (Step 4)
接下來,我們來考慮添加一些有些目標(biāo)平臺(tái)可能不支持的代碼。在這個(gè)樣例中,我們將根據(jù)目標(biāo)平臺(tái)是否有l(wèi)og和exp函數(shù)來添加我們的代碼。當(dāng)然大多數(shù)平臺(tái)都是有這些函數(shù)的,只是本教程假設(shè)這兩個(gè)函數(shù)沒有被那么普遍地支持。如果平臺(tái)有l(wèi)og,那么在mysqrt中,就用它來計(jì)算平方根。我們首先使用CheckFunctionExists.cmake來測(cè)試這些函數(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
重要的一點(diǎn)是,對(duì)tests和log的測(cè)試必須要在配置文件命令前完成。配置文件命令會(huì)使用CMake中的配置立馬配置文件。最后在mysqrt函數(shù)中我們提供了兩種實(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)中,我們會(huì)告訴你如何將一個(gè)生成的源文件加入到應(yīng)用程序的構(gòu)建過程中。在此例中,我們會(huì)創(chuàng)建一個(gè)預(yù)先計(jì)算好的平方根的表,并將這個(gè)表編譯到應(yīng)用程序中去。為了達(dá)到這個(gè)目的,我們首先需要一個(gè)程序來生成這樣的表。在MathFunctions這個(gè)子目錄下一個(gè)新的叫做MakeTable.cxx的源文件就是用來干這個(gè)的。
// 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;
}
注意到這張表是由一個(gè)有效的C++代碼產(chǎn)生的,并且輸出文件的名字是由參數(shù)代入的。下一步就是添加合適的命令到MathFunctions的CMakeLists文件中來構(gòu)建MakeTable這個(gè)可執(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í)行程序。然后我們添加了一個(gè)自定義命令來詳細(xì)描述如何通過運(yùn)行MakeTable來產(chǎn)生Table.h。接下來,我們需要讓CMake知道m(xù)ysqrt.cxx依賴于生成的文件Table.h。這是通過往MathFunctions這個(gè)庫里面添加生成的Table.h來實(shí)現(xiàn)的。我們也需要添加當(dāng)前的生成目錄到搜索路徑中,從而Table.h可以被mysqrt.cxx找到。
當(dāng)這個(gè)工程被構(gòu)建時(shí),它首先會(huì)構(gòu)建MakeTable這個(gè)可執(zhí)行程序。然后運(yùn)行MakeTable從而生成Table.h。最后,它會(huì)編譯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)ナ褂?。我們想要同時(shí)給他們不同平臺(tái)的二進(jìn)制文件和源代碼。這與步驟3中的install略有不同,install是安裝我們從源代碼中構(gòu)建的二進(jìn)制文件。而在此例中,我們將要構(gòu)建安裝包來支持二進(jìn)制安裝以及cygwin,debian,RPMs等的包管理特性。為了達(dá)到這個(gè)目的,我們會(huì)使用CPack來創(chuàng)建平臺(tái)相關(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。這個(gè)模塊將會(huì)包含當(dāng)前平臺(tái)所需要的所有運(yùn)行時(shí)庫。接下來,我們?cè)O(shè)置了一些CPack的變量來保存license以及工程的版本信息。版本信息利用了我們?cè)谠缜暗慕坛讨惺褂玫降淖兞?。最后我們包含了CPack這個(gè)模塊來使用這些變量和你所使用的系統(tǒng)的其它特性來設(shè)置安裝包。
接下來一步是用通常的方式構(gòu)建工程,然后在CPack上運(yùn)行它。如果要構(gòu)建一個(gè)二進(jìn)制包你需要運(yùn)行:
cpack --config CPackConfig.cmake
如果要?jiǎng)?chuàng)建一個(gè)關(guān)代碼包你需要輸入
cpack --config CPackSourceConfig.cmake
Adding Support for a Dashboard (Step 7)
將測(cè)試結(jié)果上傳到dashboard上是非常簡(jiǎn)單的。我們?cè)谠缜暗牟襟E中已經(jīng)定義了一些測(cè)試。我們僅需要運(yùn)行這些例程然后提交到dashboard上。為了包含對(duì)dashboards的支持,我們需要在頂層的CMakeLists文件中包含CTest模塊。
# enable dashboard scripting
include (CTest)
我們也創(chuàng)建了一個(gè)CTestConfig.cmake文件來指定這個(gè)工程在dashobard上的的名字。
set (CTEST_PROJECT_NAME "Tutorial")
CTest會(huì)讀這個(gè)文件并且運(yùn)行它。如果要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的dashboard,你可以在你的工程上運(yùn)行CMake,改變生成路徑的目錄,然后運(yùn)行ctest -D Experimental。你的dashboard的結(jié)果會(huì)被上傳到Kitware的公共dashboard中。
最后,可以在這里下載到整個(gè)教程的源代碼。