文章翻譯自:CMake Tutorial
第一步 | 第二步 | 第三步 | 第四步 | 第五步 | 第六步 | 第七步
下面會一步一步的來教你如何使用CMake完成軟件的構(gòu)建系統(tǒng)。大部分章節(jié)在Mastering CMake都有專門的介紹,但是把他們整理在一起還是非常有用的。本教程可以在CMake的源碼目錄中的Tests/Tutorial目錄找到。每一步都有它自己的子目錄,子目錄有完整的源代碼。
<a name="s1" id="s1"></a>
基本使用方法(第一步)
最常見的工程都是從源代碼文件構(gòu)建出可執(zhí)行程序。最簡單的CMakeLists.txt文件只需要兩行,我們就從這個文件開始。CMakeLists 文件是這樣的。
cmake_minimum_required (VERSION 2.6)
project(Tutorial)
add_executable(Tutorial tutorial.cxx)
需要說明一下本例程中CMakeLists文件都使用小寫的命令。對于大寫,小寫,大小寫混合的命令書寫方法,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) {
printf("Usage: %s number\n", argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
double outputValue = sqrt(inputValue);
printf("The square root of %g is %g\n", inputValue, outputValue);
return 0;
}
假如CMakeList.txt和tutorial.cxx文件都存放在~/cmake_tutorial目錄,使用如下命令就可以構(gòu)建出tutorial執(zhí)行文件:
# mkdir ~/cmake_tutorial/build
# cd ~/cmake_tutorial/build
# cmake -G "Unix Makefiles" ..
# make
# ./Tutorial 9
The square root of 9 is 3
添加版本號和配置頭文件
我們給工程添加的第一個功能就是版本號。當然了,你也可以專門修改源代碼,但是通過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)
因為我們把配置文件寫入到了二進制目錄中,所有,我們需要把二進制目錄加入到包含目錄中。在源代碼目錄中,我們新建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@
CMake 會使用 CMakeLists 文件中的Tutorial_VERSION_MAJOR 和 Tutorial_VERSION_MINOR 值替換配置文件的@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) {
printf("%s Version %d.%d\n",
argv[0],
Tutorial_VERSION_MAJOR,
Tutorial_VERSION_MINOR);
printf("Usage: %s number\n", argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
double outputValue = sqrt(inputValue);
printf("The square root of %g is %g\n", inputValue, outputValue);
return 0;
}
主要的修改就是包含了TutorialConfig.h頭文件,在使用消息中打印了版本號。
執(zhí)行的命令如下:
# cmake ..
# make
# ./Tutorial
Tutorial Version 1.0
Usage: Tutorial number
<a name="s2" id="s2"></a>
添加一個庫(第二步)
現(xiàn)在我們添加一個庫到工程中。這個庫是我們自己實現(xiàn)的求平方根??蓤?zhí)行文件使用我們自己的庫替換由編譯器提供的求平方根函數(shù)。在這個教程中,我們把庫源文件放到MathFunctions,這個單獨的目錄中。只需要一行的CMakeLists就足夠了:
add_library(MathFunctions mysqrt.cxx)
mysqrt.cxx文件只有一個函數(shù)叫mysqrt,mysqrt和編譯器提供的sqrt提供相同的功能。為了使用新庫,我們在頂層的CMakeLists文件中調(diào)用add_subdirectory,這樣庫才會被編譯。我們還需要把MathFunctions目錄添加到包含目錄中,這樣才能找到MathFunctions/mysqrt.h文件。最后,還需要把新庫添加到可執(zhí)行文件中。最終的CMakeLists的最后幾行是這樣的:
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作為可選項。在本教程中,沒有理由需要這么做,但是,對于更大的庫或依賴第三方代碼的庫,你可能就原因這么做了。第一步在頂層CMakeLists文件中添加一個選項:
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
這會在CMake GUI中顯示默認值為ON的選項,用戶可以根據(jù)需要修改。這個配置會存儲在緩存中,在用戶下次CMake工程時,不需要重新配置這個選項。下一個修改是,通過配置確定是否需要編譯、鏈接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)來收集可選庫,然后,鏈接進可執(zhí)行文件中。這是一個通用的方法來保持有很多可選庫的大工程的清晰性。對源代碼的修改已經(jī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)
{
printf("%s Version %d.%d\n", argv[0],
Tutorial_VERSION_MAJOR,
Tutorial_VERSION_MINOR);
printf(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
printf("The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
在源碼中,我們同樣使用了USE_MYMATH。這是通過在TutorialConfig.h.in配置文件中添加下面的內(nèi)容實現(xiàn)的:
#cmakedefine USE_MYMATH
<a name="s3" id="s3"></a>
安裝和測試(第三步)
在這一步中,我們給工程添加安裝規(guī)則和測試支持。安裝規(guī)則已經(jīng)很明顯了。對于MathFunctions庫,我們需要安裝庫文件和頭文件,在MathFunctions/CMakeLists中間添加下面兩行:
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
對于程序文件,我們在頂層CMakeLists文件中添加下面幾行來安裝可以執(zhí)行文件和配置文件:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
到此已經(jīng)全部做完了?,F(xiàn)在,你可以構(gòu)建本教程,執(zhí)行make install(或在IDE中構(gòu)建INSTALL目標),將會安裝合適的頭文件、庫文件、可執(zhí)行文件。CMake變量CMAKE_INSTALL_PREFIX用來確定文件安裝的根目錄。添加測試也已經(jīng)很明顯了。在頂層CMakeLists文件的最后,我們使用一些計算好的數(shù)字來驗證程序是否正確運行:
# 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")
第一個測試簡單的驗證程序是否可以執(zhí)行,有沒有段錯誤或其他原因?qū)е碌谋罎?,并且返回值為0。這個是最基本的CTest測試。接下來幾個測試都使用PASS_REGULAR_EXPRESSION測試屬性來驗證程序的輸出是否滿足特定的規(guī)則。這些測試案例都是用來驗證程序計算出的平方根是否是正確的和在錯誤參數(shù)時,是否打印出使用方法。如果,你需要驗證更多的數(shù)字,你可以考慮使用下面的宏:
#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")
每次調(diào)用do_test都會添加名稱、輸入、結(jié)果基于參數(shù)的測試案例。
<a name="s4" id="s4"></a>
添加系統(tǒng)內(nèi)省(第四步)
接下來,讓我們根據(jù)目標平臺是否支持某些特性,來增加一些代碼到我們的工程。比如這個例子,我們通過判斷目標平臺是否支持log和exp函數(shù),來確定是否啟用代碼。當然了,幾乎所有的平臺都支持這些函數(shù),但對于本教授假設(shè)它們是不太常見的。如果平臺有log函數(shù),我們在mysqrt中用于記錄平方根。我們首先在最上層的CMakeLists.txt里面,使用CheckFunctionExists.cmake宏來檢測是否有這些行數(shù)。
# does this system provide the log and exp functions?
include (CheckFunctionExists)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
接著我們在TutorialConfig.h.in定義以下變量
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
一定要在log和exp檢測完成后,才能使用configure_file命令。configure_file命令使用當前的設(shè)置生成配置文件。最后,我們根據(jù)平臺是否存在log和exp函數(shù),來完成不同的mysqrt函數(shù)
// 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
. . .
<a name="s5" id="s5"></a>
添加一個生成的文件和文件生成器(第五步)
在本章節(jié)中,我們將向你展示如何在軟件的構(gòu)建過程中添加一個生成的源文件。在這個例子中,我們將把預(yù)先計算好平方根的表格添加到構(gòu)建過程中,并且把表格編譯進軟件中。為了完成這個例子,我們首先需要一個程序來生成表格。在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++代碼,表格保存的文件名通過命令行參數(shù)傳遞。接著我們在MathFunctions的CMakeLists.txt文件中添加合適的命令來生成MakeTable可執(zhí)行文件,并且在構(gòu)建過程中執(zhí)行它。需要幾個命令來實現(xiàn)這個目的,如下所示:
# 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 )
首先添加命令生成MakeTable的可執(zhí)行文件。然后添加自定義命令指定如何使用MakeTable生成Table.h文件。接著,通過把生成的文件Table.h添加到MathFunctions庫的源文件列表,我們讓CMake知道m(xù)ysqrt.cxx依賴生成的文件Table.h。我們還需要把當前二進制目錄添加到包含目錄列表中,所以mysqrt.cxx才能包含Table.h,并且在包含目錄中發(fā)現(xiàn)Table.h。當工程被構(gòu)建時,首先構(gòu)建MakeTable可執(zhí)行文件,接著運行MakeTable生成Table.h。最后,編譯包含Table.h的mysqrt.cxx文件生成MathFunctions庫。這時,所有功能已完成的最上層的CMakeList.txt文件看起來是這樣的:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
include(CTest)
# 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.in是這樣的:
// 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.txt如下:
# 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)
<a name="s6" id="s6"></a>
構(gòu)建安裝包(第六步)
接下來,假設(shè)我們想要把我們的工程分發(fā)給其他人,以便他們可以使用它。我們想要在不同的平臺分發(fā)二進制和源碼。這里的安裝和我們在之前的章節(jié)安裝和測試(第三步)中的是有些區(qū)別的,在第三步我們編譯源代碼后,直接安裝二進制文件。在本例子中,我們將要構(gòu)建二進制安裝包,可以在包管理系統(tǒng)中使用,例如:cygwin,debian,RPMs等等。我們使用CPack來創(chuàng)建平臺相關(guān)的安裝包。我們需要在最上層的CMakeLists.txt的尾部添加一些代碼:
# 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}")
include (CPack)
做起來很簡單。我們從包含InstallRequiredSystemLibraries開始。這個模塊會包含當前平臺需要使用的運行時庫。接下來我們設(shè)置一些CPack變量,比如:版權(quán)許可文件目錄,軟件版本信息。在本教程的開始部分,我們已經(jīng)設(shè)置好了版本信息。最后我們包含CPack模塊,它會使用這些變量和其他的系統(tǒng)屬性去生成安裝包。
下一步,正常的構(gòu)建工程,然后運行CPack。建立一個二進制分發(fā),你可以運行:
cpack --config CPackConfig.cmake
建立一個源碼分發(fā),你可以運行:
cpack --config CPackSourceConfig.cmake
<a name="s7" id="s7"></a>
添加儀表盤(Dashboard)支持(第七步)
把我們的測試結(jié)果提交到儀表盤是非常簡單的。在本教程前面的步驟中,我們已經(jīng)定義了一些測試用例。我們只需要運行這些測試用例,并且提交結(jié)果到儀表盤。為了支持儀表盤,我們需要在最上層的CMakeLists.txt中包含CTest模塊。
# enable dashboard scripting
include (CTest)
我們需要新建CTestConfig.cmake,在文件中定義本項目在儀表盤中的名稱。
set (CTEST_PROJECT_NAME "Tutorial")
CTest在執(zhí)行時會讀取這個文件。你可以使用CMake為你的項目創(chuàng)建一個簡單的儀表盤,把目錄切換到二進制目錄,然后運行ctest -D Experimental。你的儀表盤的結(jié)果會提交到Kitware的公共儀表盤這里