CMake Tutorial
Step 1 起点
最基础的工程就是由源代码文件构筑的一个可执行程序。对于这种简单的工程仅需要一个只包含两行代码CMakeList.txt文件。这便是我们教程的起点。这个CMakeList.txt文件大概长这样:
cmake_minimum_required (VERSION 2.6) // minimum required cmake version
project (Tutorial) //project name
add_executable(Tutorial tutorial.cxx)
注意,虽然这个例子中使用的是小写命令行。但CMake支持大写、小写以及混用的[龚1] 。tutorial.cxx是用于计算平方根的程序,他的最初版本非常简单,如下:
// A simple program that computes the square rootof 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;
}
doubleinputValue = atof(argv[1]);
doubleoutputValue = sqrt(inputValue);
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
添加版本号以及配置头文件
我们对其做的第一个操作就是为它添加一个版本号。当然,我们可以在源代码中添加,但是将它加在CMakeList.txt中更具有灵活性。要添加版本号,我们需要对CMakeList.txt做如下修改:
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 CMakesettings
# 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 forinclude files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
# add the executable
add_executable(Tutorial tutorial.cxx)
由于配置文件最终会被写入到文件中,所以我们必须要将目录加入到包含文件的搜索范围中。接着我们要在资源树中创建一个TutorialConfig.h.in,写入如下代码:
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR@Tutorial_VERSION_MAJOR@ //major version
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@ //minorversion
当CMake配置头文件时@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@ 的值将会被CMakeLists.txt中的值替换。接下来我们修改tutorial.cxx文件令其包含配置头文件以及使用版本号。修改的结果如下:
// A simple program that computes the square rootof 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;
}
doubleinputValue = atof(argv[1]);
doubleoutputValue = sqrt(inputValue);
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
主要的修改其实就是加入了include TutorialConfig.h 头文件以及在最后输出版本号。
Step 2 添加库
现在我们要为我们的工程添加库文件。这个库里面是我们自己写的求平方根的实现。这样程序便可以使用库中的函数来替代编译器提供的计算平方根的函数。在这个教程中我们将会将其保存在叫MathFunctions的子目录中。它将会有一个只有一行如下语句的CMakeList.txt文件:
add_library(MathFunctions mysqrt.cxx)
资源文件mysqrt.cxx中包含一个价mysqrt的函数,它提供与编译器的sqrt函数类似的功能。想要使用它,我们要在顶层的CMakeList.txt中添加一行 add_subdirectory语句来将其导入。同时我们还要为include添加一个目录,这样我们调用函数时MathFunctions/MathFunctions.h 头文件才能被查找到。最后我们要做的就是将这个新库加入到可执行程序中。顶层的CMakeList.txt最后几行现在大概长这样:
include_directories[龚2] ("${PROJECT_SOURCE_DIR}/MathFunctions") //add include directory
add_subdirectory[龚3] (MathFunctions) //add subdirectory
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries[龚4] (Tutorial MathFunctions) //link A to B
现在,让我们思考如何将MathFunction设置为可选项。虽然在教学程序中我们完全没有必要这么做,但在使用大型库或是依赖第三方代码的库时我们便会用到。首先,我们要在顶层CMakeList.txt中添加一个选项:
# should we use our own math functions?
option[龚5] (USE_MYMATH
"Use tutorial provided math implementation" ON)
这样你就在CMake GUI中添加了一个用户可以根据自己的需求可以修改的选项,其默认值为ON。选项在用户做完选择后会被保存在缓存中,用户不必每次为工程运行CMake时都选一遍。下一步,我们要做的是为MathFunction库的构建和链接添加条件。我们需要将顶层CMakeList.txt的结尾修改成如下:
# add the MathFunctions library?
#
if (USE_MYMATH)[龚6]
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)来收集这些通过选项添加进来的库文件,这在最后链接的时候会用到。这种做法在有较多可选项的大型工程中常被用来保持整体结构的清爽。对源码要做的改动也十分明了,如下:
// A simple program that computes the square rootof a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include"MathFunctions.h"
#endif[龚7]
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[龚8]
fprintf(stdout,"The square root of %g is %g\n",
inputValue, outputValue);
return 0;
}
在源码中我们也用到了USE_MYMATH。这是CMake通过TutorialConfig.h.in 配置文件传入源码的,当然,我们要在其中添加下面这行代码:
#cmakedefine USE_MYMATH
Step 3 安装和检验
接下来我们将要为我们的工程文件添加安装规则以及测试功能。安装规则就很直白。以MathFunction的安装为例,我们通过在CMakeList.txt中添加以下两行代码实现库的建立以及头文件的生成:
install [龚9] (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
对于这个应用,我们要在顶层CMakeList.txt中添加如下代码来实现可执行程序的安装以及配置头文件:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES"${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
安装的部分就这些。学习到现在,你应该掌握了如何构建tutorial,并引导安装过程(或从IDE中构筑INSTALL目标)成功导入适当的头文件、库以及可执行程序。CMake中有一个叫CMAKE_INSTALL_PREEFIX变量用来决定安装时的根文件夹。添加检验的过程也十分明了。只需要在顶层的CMakeList.txt的结尾加上几个很简单的检查就能够知道应用是否工作正常。
include(CTest)[龚10]
# does the application run
add_test[龚11] (TutorialRuns Tutorial 25)
# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)
set_tests_properties[龚12] (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is5")
# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative PROPERTIESPASS_REGULAR_EXPRESSION "-25 is 0")
# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall PROPERTIESPASS_REGULAR_EXPRESSION "0.0001 is 0.01")
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage PROPERTIESPASS_REGULAR_EXPRESSION "Usage:.*number")
在构建之后用户可以运行“ctest”命令行工具来运行测试。第一个测试检查程序是否正常运行,没有段错误,若没有则崩溃,并返回一个0。这便是CTest最基础的一种形式。在接下来的测试语句中,我们使用了PASS_REGULAR_EXPRESSION测试属性来核实测试内容的输出是否与语句中的字符串相同。在这种情况下,验证计算的平方根是否正确,以及当输入错误时是否输出了有用的信息。如果您想要添加许多测试来测试不同的输入值,您可以考虑创建如下的宏:
#define a macro to simplify adding tests, then useit
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调用,将根据所传递的参数向项目添加另一个带有名称、输入和结果的测试。
Step 4 添加系统自检
接下来,我们要根据目标系统的特征来向源代码中添加一些内容。比如根据目标平台中是否具有计算对数和指数的功能。当然时下几乎所有的平台都有这些功能,但是为了教程我们就假设他们没有吧。如果平台中有对数函数,我们就在mysqrt函数中用它来计算平方根。首先我们在顶层CMakeList.txt中用CheckFunctionExists.cmake宏来看看有没有这个功能,代码如下:
# does this system provide the log and expfunctions?
include (CheckFunctionExists)[龚14]
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
接下来我们在TutorialConfig.h.in中定义这些用来确定CMake是否找到了他们的值,如下:
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
注意一定要在对TutorialConfig.h执行configure_file指令前检查log和exp。因为figure_file指令会立即使用现有的设置来配置未见。最后,我们可以为那些有这些功能的系统提供一份基于这些功能的代码,就像下面这样:
// 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
. . .
Step 5 添加生成的文件和生成器
在这部分,我们向大家展示如何为应用在构建过程中插入已经生成好的资源文件。在这个例子中我们将要在构筑的同时创建一个预先计算好的平方根表,并将其编译仅我们的应用中。我了实现这个目标,我们需要一个生成表的程序。在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;
doubleresult;
// makesure we have enough arguments
if (argc< 2)
{
return 1;
}
// open theoutput file
FILE *fout= fopen(argv[1],"w");
if (!fout)
{
return 1;
}
// create asource 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);
}
// closethe table with a zero
fprintf(fout,"0};\n");
fclose(fout);
return 0;
}
注意,表是作为有效的c++代码生成的,要写入输出的文件的名称作为参数传入的。下一步是向MathFunctions的CMakeLists.txt中添加适当的命令来构建MakeTable可执行文件,然后将其作为构建过程的一部分运行。为此需要一些命令,如下所示。
# first we add the executable that generates thetable
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command[龚15] (
OUTPUT${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMANDMakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDSMakeTable
)
# add the binary tree directory to the search pathfor
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
# add the main library
add_library(MathFunctions mysqrt.cxx${CMAKE_CURRENT_BINARY_DIR}/Table.h )
第一步,MakeTable的可执行程序添加方式与其他可执行程序一样。接着,我们加入了一句自定义语句来指定通过MakeTable 生成Table.h的方式。然后我们要让CMake知道mysqrt.cxx依赖于生成文件Table.h。这就需要将生成的Table.h添加到MathFunctions的资源库列表中。同时我们还必须要将现有的二进制文件目录添加到要包含的文件中,这样Table.h才能被找到并包含进mysqrt.cxx中。当工程构建时会先生成MakeTable可执行文件,并运行它生成Table.h。最后,再将包含Table.h的mysqrt.cxx编译为MathFunctions库。教程到这个步骤,包含我们添加的这些特征后,顶层的CMakeLists.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 expfunctions?
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
"Usetutorial provided math implementation" ON)
# configure a header file to pass some of the CMakesettings
# 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 forinclude 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.text文件则长这样:
# first we add the executable that generates thetable
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
OUTPUT${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDSMakeTable
COMMANDMakeTable ${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)
Step 6构建和安装
下一步,我们就是想让其他人来用我们的工程。我们希望同时提供二进制文件以及源文件给不同的平台使用。这就与我们上文(step 3)提到的安装有所不同,当时我们安装的是从源代码中已生成好的二进制文件。而在这一步中我们将构建支持二进制文件的安装以及包管理功能的安装包,就和cygwin、debian、RPMs等等中的安装包一样。为了实现这一点,我们要用到CPack。特别地,我们要在顶层的CMakeLists.txt文件的末尾加入以下的代码:
# build a CPack driven installer package
include (InstallRequiredSystemLibraries[龚16] )
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[龚17] )
就这些。首先我们要包含InstallRequiredSystemLibraries库,这个模块将会包含我们对应平台所需要的所有运行库。然后我们创建一些CPack变量来为工程指定许可文件和版本信息的存放位置。版本信息用的是我们之前设置好的变量。最后我们包含进CPack模块,它将会使用到刚刚的那些变量以及运行安装程序的系统的一些其他属性。
下一步和通常构建工程的方式一样构建工程,然后对它运行CPack即可。构建二进制文件目录你要输入:
cpack –config CPackConfig.cmake
创建资源目录则使用:
cpack –config CPackSourceConfig.cmake
Step 7支持控制面板
见我们的测试结果提交给控制面板非常简单。我们之前已经为我们的工程添加了一些测试。现在,我们只需要运行这些测试并将他们提交跟控制面板便可。要为控制面板提供支持我们需要在顶层CMakeLists.txt中包含CTest模块。
# enable dashboard scripting
include (CTest)
我们还要创建CTestConfig.cmake来指定我们希望工程在控制面板中所显示的名字。
set (CTEST_PROJECT_NAME "Tutorial")
在运行时CTest将会访问这个文件。添加到控制面板后你可以对你的工程运行CMake,将目录改为二进制树形网络,接着运行ctest -D 实验。你控制面板的运行结果最后会被上传到Kitwara的公开控制面板中。
[龚15]ADD_CUSTOM_COMMAND(OUTPUT output1 [output2...]
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS][args2...] ...]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[BYPRODUCTS [files...]]
[IMPLICIT_DEPENDS<lang1> depend1
[<lang2> depend2] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[DEPFILE depfile]
[VERBATIM] [APPEND][USES_TERMINAL]
[COMMAND_EXPAND_LISTS])