CMake基础教程
Contact me
- Blog -> https://cugtyt.github.io/blog/index
- Email -> cugtyt@qq.com
- GitHub -> Cugtyt@GitHub
起点 (第一步)
最简单的一个CMakeLists.txt差不多长这样:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)
cmake命令不区分大小写,这里的tutorial.cxx
可以很简单:
// 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;
}
添加版本号和配置头文件
当然版本号可以在源代码中添加,但是在CMakeLists.txt中做更加灵活,修改CMakeLists.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 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
文件添加如下内容:
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
CMake配置这个头文件的时候,@Tutorial_VERSION_MAJOR@
和 @Tutorial_VERSION_MINOR@
会替换成CMakeLists.txt中的值,我们再修改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;
}
添加库 (第二步)
我们把计算平方根的函数作为库,然后用cmake编译。这里我们把库放入一个子文件夹MathFunctions中,然后在CMakeLists.txt添加如下:
add_library(MathFunctions mysqrt.cxx)
源文件 mysqrt.cxx中有一个mysqrt 的函数。为了使用新的库,我们使用add_subdirectory 添加在 CMakeLists.txt的顶端。同时添加文件路径,这样可以 MathFunctions/MathFunctions.h
就可以找到了,现在 CMakeLists.txt最后的几行是:
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)
现在考虑把MathFunctions 作为可选。虽然这不是必要的,但是对于大的库或者依赖于第三方库来说,可能就需要这个功能。第一步是在顶部添加一个可选项:
# should we use our own math functions?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
下一步是根据条件构建和链接MathFunctions 库,修改代码如下:
# 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})
注意到使用了一个变量EXTRA_LIBS 来手机可选的库,用于后续的链接。这是在大型工程里处理可选模块的常用方法,源代码的修改也很直接:
// 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 ,他来自于 TutorialConfig.h.in
配置文件:
#cmakedefine USE_MYMATH
安装和测试 (第三步)
在MathFunctions下的CMakeLists.txt中添加两行:
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
在顶层的CMakeLists.txt中添加这两行:
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
现在就可以编译了,输入make install
,就会安装正确的头文件,库和可执行文件。cmake的变量CMAKE_INSTALL_PREFIX 用于确定文件所在的根目录是否被安装。添加测试需要在顶层的CMakeLists.txt结尾添加基本的测试,用于确保应用工作正常:
include(CTest)
# 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")
在构建完以后,运行ctest命令运行测试。后面的PASS_REGULAR_EXPRESSION 测试属性用于确保输出的测试包括确定的字符串。如果需要大量测试不同的输入,可能需要考虑如下的宏:
#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")
添加系统Introspection (步骤4)
考虑我们的项目可以依赖于目标系统没有的特性。我们需要添加一些代码来看目标系统有没有log或exp函数。如果系统有log函数,我们就使用他来计算平方根,首先使用CheckFunctionExists宏来测试这些函数的可用性:
# 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
然后使用这些代码来使用:
// 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
. . .
添加生成的文件和生成器 (第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;
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;
}
接下来在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
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 )
第一行就是普通的要添加执行文件,然后添加自定义命令,确定如何生成Table.h,然后需要让cmake知道mysqrt.cxx依赖于生成的文件Table.h。通过添加生成的Table.h到MathFunctions的库中完成。同时也要吧当前的二进制目录添加到包含目录中,让Table.h可以被mysqrt.cxx发现。这个工程构建时,它会先构建MakeTable可执行文件,然后运行MakeTable生成Table.h,最后,编译mysqrt.cxx,包含Table.h头文件生成MathFunction库。现在顶层的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 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)
构建安装器 (第六步)
我们希望用户使用,因此和第三步做的就有点不一样了,第三步我们从源码编译来安装。这里我们构建安装包来支持二进制安装和cygwin,debian,RPM等的包管理特性。我们使用CPake来创建特定的安装器,在顶层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,这个模块会包含工程所需要的任何运行时库,接下来设置一些CPack变量。最后引入CPack模块来构建安装器。下边的步骤是构建二进制安装:
cpack --config CPackConfig.cmake
需要创建源可以:
cpack --config CPackSourceConfig.cmake
添加Dashboard 支持 (第七步)
导入CTest 模块,在CMakeLists.txt中,
# enable dashboard scripting
include (CTest)
我们也创建了 CTestConfig.cmake文件,可以指定dashboard的名字:
set (CTEST_PROJECT_NAME "Tutorial")
CTest运行时,会先读这个文件。