下面是一个分步教程,介绍CMake有助于解决的常见构建系统问题。将这些主题中的许多主题引入到掌握CMake中,作为单独的问题,但是在一个示例项目中看到它们如何协同工作都是非常有帮助的。本教程可以在CMake源代码树的Tests / Tutorial目录中找到。每个步骤都有自己的子目录,其中包含该步骤的教程的完整副本
基本起点(Step1)
最基本的项目是从源代码文件构建的可执行文件。对于简单的项目,需要两行CMakeLists.txt文件。这将是我们教程的出发点。CMakeLists.txt文件如下所示:
cmake_minimum_required(VERSION 2.6) 项目(教程) add_executable(Tutorial tutorial.cxx)
请注意,此示例在CMakeLists.txt文件中使用小写命令。CMake支持上,下,混合命令。tutorial.cxx的源代码将计算一个数字的平方根,其第一个版本非常简单,如下所示:
//一个计算数字平方根的简单程序 #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]); 返回1; } double inputValue = atof(argv [1]); double outputValue = sqrt(inputValue); fprintf(stdout,“%g的平方根是%g \ n”, inputValue,outputValue); 返回0; }
添加版本号和配置的头文件
我们将添加的第一个功能是为我们的可执行文件和项目提供版本号。尽管您可以在源代码中完成此操作,但是在CMakeLists.txt文件中提供更多的灵活性。要添加版本号,请修改CMakeLists.txt文件,如下所示:
cmake_minimum_required(VERSION 2.6) 项目(教程) #版本号。 设置(Tutorial_VERSION_MAJOR 1) 设置(Tutorial_VERSION_MINOR 0) #配置头文件以传递一些CMake设置 #到源代码 configure_file( “$ {} PROJECT_SOURCE_DIR /TutorialConfig.h.in” “$ {} PROJECT_BINARY_DIR /TutorialConfig.h” ) #将二叉树添加到包含文件的搜索路径 #,以便我们找到TutorialConfig.h include_directories( “$ {} PROJECT_BINARY_DIR”) #添加可执行文件 add_executable(Tutorial tutorial.cxx)
由于配置的文件将被写入二进制树,我们必须将该目录添加到搜索包含文件的路径列表中。然后,我们在源代码树中创建一个TutorialConfig.h.in文件,其内容如下:
//配置的教程选项和设置 #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以包含配置的头文件并使用版本号。得到的源代码如下所示。
//一个计算数字平方根的简单程序 #include <stdio.h> #include <stdlib.h> #include <math.h> #include“TutorialConfig.h” int main(int argc,char * argv []) { if(argc <2) { fprintf(stdout,“%s版本%d。%d \ n”, 的argv [0], Tutorial_VERSION_MAJOR, Tutorial_VERSION_MINOR); fprintf(stdout,“Usage:%s number \ n”,argv [0]); 返回1; } double inputValue = atof(argv [1]); double outputValue = sqrt(inputValue); fprintf(stdout,“%g的平方根是%g \ n”, inputValue,outputValue); 返回0; }
主要的变化是包括TutorialConfig.h头文件,并打印一个版本号作为使用消息的一部分。
添加库(步骤2)
现在我们将添加一个图书馆到我们的项目。这个库将包含我们自己的计算一个数字的平方根的实现。然后,可执行文件可以使用这个库,而不是编译器提供的标准平方根函数。对于本教程,我们将把库放入名为MathFunctions的子目录中。它将具有以下一行CMakeLists.txt文件:
add_library(MathFunctions mysqrt.cxx)
源文件mysqrt.cxx有一个名为mysqrt的函数,它为编译器的sqrt函数提供了类似的功能。为了使用新的库,我们在顶级的CMakeLists.txt文件中添加一个add_subdirectory调用,以便构建库。我们还添加了另一个include目录,以便可以为函数原型找到MathFunctions / MathFunctions.h头文件。最后一个更改是将新库添加到可执行文件。顶级CMakeLists.txt文件的最后几行现在看起来像:
include_directories(“$ {PROJECT_SOURCE_DIR} / MathFunctions”) add_subdirectory(MathFunction) #添加可执行文件 add_executable(Tutorial tutorial.cxx) target_link_libraries(Tutorial MathFunctions)
现在让我们考虑使MathFunctions库可选。在本教程中,实际上没有任何理由这样做,但是使用更多的依赖第三方代码的库或库可能需要。第一步是向顶级的CMakeLists.txt文件添加一个选项。
#我们应该用自己的数学函数吗? 选项(USE_MYMATH “使用教程提供数学实现”ON)
这将显示在CMake GUI中,默认值为ON,用户可以根据需要进行更改。此设置将存储在缓存中,以便用户在每次在此项目上运行CMake时不需要继续设置它。下一个变化是使MathFunctions库的构建和链接成为条件。为此,我们将顶级CMakeLists.txt文件的结尾改为如下所示:
#添加MathFunctions库? # if(USE_MYMATH) include_directories(“$ {PROJECT_SOURCE_DIR} / MathFunctions”) add_subdirectory(MathFunction) set(EXTRA_LIBS $ {EXTRA_LIBS} MathFunctions) endif(USE_MYMATH) #添加可执行文件 add_executable(Tutorial tutorial.cxx) target_link_libraries(Tutorial $ {EXTRA_LIBS})
这使用USE_MYMATH的设置来确定是否应该编译和使用MathFunctions。注意使用变量(在这种情况下为EXTRA_LIBS)来收集任何可选的库,以便以后链接到可执行文件中。这是一种常用的方法,用于保持大型项目与许多可选组件清洁。对源代码的相应更改是相当简单的,并留给我们:
//一个计算数字平方根的简单程序 #include <stdio.h> #include <stdlib.h> #include <math.h> #include“TutorialConfig.h” #ifdef USE_MYMATH #include“MathFunctions.h” #万一 int main(int argc,char * argv []) { if(argc <2) { fprintf(stdout,“%s版本%d。%d \ n”,argv [0] Tutorial_VERSION_MAJOR, Tutorial_VERSION_MINOR); fprintf(stdout,“Usage:%s number \ n”,argv [0]); 返回1; } double inputValue = atof(argv [1]); #ifdef USE_MYMATH double outputValue = mysqrt(inputValue); #其他 double outputValue = sqrt(inputValue); #万一 fprintf(stdout,“%g的平方根是%g \ n”, inputValue,outputValue); 返回0; }
在源代码中,我们也使用USE_MYMATH。通过添加以下行,通过TutorialConfig.h.in配置的文件从CMake提供给源代码:
#cmakedefine USE_MYMATH
安装和测试(步骤3)
下一步我们将为我们的项目添加安装规则和测试支持。安装规则相当简单。对于MathFunctions库,我们通过将以下两行添加到MathFunctions的CMakeLists.txt文件中来设置要安装的库和头文件:
安装(TARGETS MathFunctions DESTINATION bin) 安装(FILES MathFunctions.h DESTINATION包括)
对于应用程序,以下行将添加到顶级CMakeLists.txt文件以安装可执行文件和已配置的头文件:
#添加安装目标 安装(TARGETS教程DESTINATION bin) 安装(FILES“$ {PROJECT_BINARY_DIR} /TutorialConfig.h” 目的地包括)
这就是它的一切。此时,您应该能够构建教程,然后键入make install(或从IDE构建INSTALL目标),并安装适当的头文件,库和可执行文件。CMake变量CMAKE_INSTALL_PREFIX用于确定文件将在哪里安装的根。添加测试也是一个相当简单的过程。在顶级CMakeLists.txt文件的最后,我们可以添加一些基本测试来验证应用程序是否正常工作。
包括(CTEST) #运行应用程序 add_test(TutorialRuns Tutorial 25)
#这是sqrt的25 add_test(TutorialComp25 Tutorial 25)
set_tests_properties(TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION“25 is 5”)
#它处理负数 add_test(TutorialNegative Tutorial -25) set_tests_properties(TutorialNegative PROPERTIES PASS_REGULAR_EXPRESSION“-25 is 0”)
#它处理小数字 add_test(TutorialSmall Tutorial 0.0001) set_tests_properties(TutorialSmall PROPERTIES PASS_REGULAR_EXPRESSION“0.0001 is 0.01”)
#使用消息是否正常工作?
add_test(TutorialUsage Tutorial) set_tests_properties(TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION“Usage:。* number”)
建立后可以运行“ctest”命令行工具来运行测试。第一个测试只是验证应用程序是否运行,不会出现故障或其他方式崩溃,并具有零返回值。这是CTest测试的基本形式。接下来的几个测试都使用PASS_REGULAR_EXPRESSION测试属性来验证测试的输出是否包含某些字符串。在这种情况下,验证计算的平方根应该是什么,并且当提供不正确数量的参数时打印使用消息。如果您想添加大量测试来测试不同的输入值,您可能会考虑创建如下所示的宏:
#定义一个宏以简化添加测试,然后使用它 宏(do_test arg结果) add_test(TutorialComp $ {arg} Tutorial $ {arg}) set_tests_properties(TutorialComp $ {arg} 属性PASS_REGULAR_EXPRESSION $ {result}) endmacro(do_test) #做一堆基于结果的测试 do_test(25“25 is 5”) do_test(-25“-25为0”)
对于do_test的每个调用,根据传递的参数,将另一个测试添加到具有名称,输入和结果的项目中。
添加系统内省(步骤4)
接下来,我们考虑将一些代码添加到我们的项目中,这些代码取决于目标平台可能没有的功能。对于这个例子,我们将添加一些取决于目标平台是否具有日志和exp函数的代码。当然,几乎每一个平台都有这些功能,但是对于这个教程来说,假设它们不常见。如果平台有日志,那么我们将使用它来计算mysqrt函数中的平方根。我们首先使用顶级CMakeLists.txt文件中的CheckFunctionExists.cmake宏来测试这些功能的可用性,如下所示:
#这个系统是否提供log和exp函数? 包括(CheckFunctionExists) check_function_exists(log HAVE_LOG) check_function_exists(exp HAVE_EXP)
接下来我们修改TutorialConfig.h.in来定义这些值,如果CMake在平台上找到它们,如下所示:
//平台是否提供exp和log函数? #cmakedefine HAVE_LOG #cmakedefine HAVE_EXP
对于TutorialConfig.h的configure_file命令,对log和exp的测试是很重要的。configure_file命令立即使用CMake中的当前设置配置文件。最后在mysqrt函数中,我们可以使用以下代码在系统上提供基于log和exp的替代实现:
//如果我们同时使用log和exp然后使用它们 #if定义(HAVE_LOG)&&定义(HAVE_EXP) result = exp(log(x)* 0.5); #else //否则使用迭代方法 。。。
添加生成的文件和生成器(步骤5)
在本节中,我们将介绍如何将生成的源文件添加到应用程序的构建过程中。对于这个例子,我们将在构建过程的一部分中创建一个预计算平方根的表,然后将该表编译到我们的应用程序中。要完成这个,我们首先需要一个生成表的程序。在MathFunctions子目录中,一个名为MakeTable.cxx的新的源文件将这样做。
//构建sqrt表的简单程序 #include <stdio.h> #include <stdlib.h> #include <math.h> int main(int argc,char * argv []) { 我的 双重结果; //确保我们有足够的参数 if(argc <2) { 返回1; } //打开输出文件 FILE * fout = fopen(argv [1],“w”); 如果(!fout) { 返回1; } //创建一个具有平方根表的源文件 fprintf(fout,“double sqrtTable [] = {\ n”); for(i = 0; i <10; ++ i) { result = sqrt(static_cast <double>(i)); fprintf中(FOUT, “%克,\ n” 个,结果); } //用零关闭表 fprintf中(FOUT, “0}; \ n”); FCLOSE(FOUT); 返回0; }
请注意,该表生成为有效的C ++代码,并且将输出的文件的名称作为参数传入。下一步是将适当的命令添加到MathFunctions的CMakeLists.txt文件中以构建MakeTable可执行文件,然后作为构建过程的一部分运行它。需要几个命令来完成此操作,如下所示。
#首先我们添加生成表的可执行文件 add_executable(MakeTable MakeTable.cxx) #添加生成源代码的命令 add_custom_command( OUTPUT $ {CMAKE_CURRENT_BINARY_DIR} /Table.h COMMAND MakeTable $ {CMAKE_CURRENT_BINARY_DIR} /Table.h DEPENDS MakeTable ) #将二叉树目录添加到搜索路径 #包含文件 include_directories($ {CMAKE_CURRENT_BINARY_DIR}) #添加主库 add_library(MathFunctions mysqrt.cxx $ {CMAKE_CURRENT_BINARY_DIR} /Table.h)
首先添加MakeTable的可执行文件,因为可以添加任何其他可执行文件。然后我们添加一个自定义命令,指定如何通过运行MakeTable来生成Table.h。接下来我们要让CMake知道mysqrt.cxx取决于生成的文件Table.h。这通过将生成的Table.h添加到库MathFunctions的源的列表来完成。我们还必须将当前的二进制目录添加到include目录列表中,以便可以找到并包含mysqrt.cxx的Table.h。当该项目构建时,它将首先构建MakeTable可执行文件。然后它将运行MakeTable来生成Table.h。最后,它将编译mysqrt.cxx,其中包括Table.h来生成MathFunctions库。此时,我们添加的所有功能的顶级CMakeLists.txt文件如下所示:
cmake_minimum_required(VERSION 2.6) 项目(教程) 包括(CTEST) #版本号。 设置(Tutorial_VERSION_MAJOR 1) 设置(Tutorial_VERSION_MINOR 0) #这个系统是否提供log和exp函数? include($ {CMAKE_ROOT} /Modules/CheckFunctionExists.cmake) check_function_exists(log HAVE_LOG) check_function_exists(exp HAVE_EXP) #我们应该用自己的数学函数 选项(USE_MYMATH “使用教程提供数学实现”ON) #配置头文件以传递一些CMake设置 #到源代码 configure_file( “$ {} PROJECT_SOURCE_DIR /TutorialConfig.h.in” “$ {} PROJECT_BINARY_DIR /TutorialConfig.h” ) #将二叉树添加到包含文件的搜索路径 #,以便我们找到TutorialConfig.h include_directories(“$ {PROJECT_BINARY_DIR}”) #添加MathFunctions库? if(USE_MYMATH) include_directories(“$ {PROJECT_SOURCE_DIR} / MathFunctions”) add_subdirectory(MathFunction) set(EXTRA_LIBS $ {EXTRA_LIBS} MathFunctions) endif(USE_MYMATH) #添加可执行文件 add_executable(Tutorial tutorial.cxx) target_link_libraries(Tutorial $ {EXTRA_LIBS}) #添加安装目标 安装(TARGETS教程DESTINATION bin) 安装(FILES“$ {PROJECT_BINARY_DIR} /TutorialConfig.h” 目的地包括) #运行应用程序 add_test(TutorialRuns Tutorial 25) #使用消息是否正常工作? add_test(TutorialUsage Tutorial) set_tests_properties(TutorialUsage 性能 PASS_REGULAR_EXPRESSION“用法:。* number” ) #定义一个宏以简化添加测试 宏(do_test arg结果) add_test(TutorialComp $ {arg} Tutorial $ {arg}) set_tests_properties(TutorialComp $ {arg} 属性PASS_REGULAR_EXPRESSION $ {result} ) endmacro(do_test) #做一堆基于结果的测试 do_test(4“4是2”) do_test(9“9 is 3”) do_test(5“5是2.236”) do_test(7“7是2.645”) do_test(25“25 is 5”) do_test(-25“-25为0”) do_test(0.0001“0.0001为0.01”)
TutorialConfig.h.in看起来像:
//配置的教程选项和设置 #define Tutorial_VERSION_MAJOR @ Tutorial_VERSION_MAJOR @ #define Tutorial_VERSION_MINOR @ Tutorial_VERSION_MINOR @ #cmakedefine USE_MYMATH //平台是否提供exp和log函数? #cmakedefine HAVE_LOG #cmakedefine HAVE_EXP
而MathFunctions的CMakeLists.txt文件如下所示:
#首先我们添加生成表的可执行文件 add_executable(MakeTable MakeTable.cxx) #添加生成源代码的命令 add_custom_command( OUTPUT $ {CMAKE_CURRENT_BINARY_DIR} /Table.h DEPENDS MakeTable COMMAND MakeTable $ {CMAKE_CURRENT_BINARY_DIR} /Table.h ) #将二叉树目录添加到搜索路径 #包含文件 include_directories($ {CMAKE_CURRENT_BINARY_DIR}) #添加主库 add_library(MathFunctions mysqrt.cxx $ {CMAKE_CURRENT_BINARY_DIR} /Table.h) 安装(TARGETS MathFunctions DESTINATION bin) 安装(FILES MathFunctions.h DESTINATION包括)
构建安装程序(步骤6)
接下来假设我们想将我们的项目分发给其他人,以便他们可以使用它。我们希望在各种平台上提供二进制和源代码分发。这与以前在安装和测试(第3步)部分中安装的安装有所不同,我们正在安装从源代码构建的二进制文件。在这个例子中,我们将构建支持二进制安装和包管理功能的安装包,如在cygwin,debian,RPMs等中所述。为了实现这一点,我们将使用CPack创建平台特定的安装程序,如“包装与CPack”中所述。具体来说,我们需要在我们的顶级CMakeLists.txt文件的底部添加几行。
构建一个CPack驱动的安装程序包 include(InstallRequiredSystemLibraries) 设置(CPACK_RESOURCE_FILE_LICENSE “$ {} CMAKE_CURRENT_SOURCE_DIR /License.txt”) 设置(CPACK_PACKAGE_VERSION_MAJOR“$ {Tutorial_VERSION_MAJOR}”) 设置(CPACK_PACKAGE_VERSION_MINOR“$ {Tutorial_VERSION_MINOR}”) 包括(CPack)
这就是它的一切。我们从包括InstallRequiredSystemLibraries开始。该模块将包括项目为当前平台所需的任何运行时库。接下来,我们将一些CPack变量设置到我们存储此项目的许可证和版本信息的位置。版本信息使用本教程前面设置的变量。最后,我们包括将使用这些变量的CPack模块和您所在系统的一些其他属性来设置安装程序。
下一步是以通常的方式构建项目,然后运行CPack。要构建一个二进制发行版,你可以运行:
cpack --config CPackConfig.cmake
要创建一个您将键入的源分发
cpack --config CPackSourceConfig.cmake
添加对仪表板的支持(步骤7)
添加支持将测试结果提交到仪表板非常容易。我们已经在本教程的早期步骤中为我们的项目定义了一些测试。我们只需要运行这些测试并将它们提交到仪表板。要包括对仪表板的支持,我们将CTest模块包含在我们的高级CMakeLists.txt文件中。
#启用仪表板脚本 包括(CTest)
我们还创建了一个CTestConfig.cmake文件,我们可以在该文件中指定仪表板的此项目的名称。
设置(CTEST_PROJECT_NAME“教程”)
CTest将在运行时读入此文件。要创建一个简单的仪表板,您可以在项目中运行CMake,将目录更改为二叉树,然后运行ctest -D实验。仪表板的结果将被上传到Kitware的公共仪表板在这里。
浙公网安备 33010602011771号