本章将介绍编写有效 CMakeList 的基础知识 文件。它将包括根本指令和问题 您将需求处理大多数项目。虽然CMake能够处理极其杂乱的问题 项目,关于大多数项目,你会发现本章的内容会告诉 你需求知道的全部。CMake 由 CMakeList 驱动.txt写入的文件 关于软件项目。CMakeLists文件确定从中获取的一切内容 向用户显现的选项,要编译到哪些源文件。在 除了评论怎么编写 CMakeLists 文件之外,本章还评论了 还将介绍怎么使它们健壮且可保护。

修正 CMakeList 文件

CMakeLists文件几乎能够在任何文本修正器中进行修正。一些 修正器,如记事本++,带有CMake语法突出显现和 内置缩进支撑。关于像Emacs或Vim这样的修正器,CMake 包括缩进和语法突出显现方法。这些能够找到 在源发行版的目录中,或从 CMake下载页面。Auxiliary

在任何受支撑的生成器(Makefiles、Visual Studio 等)中,假如 您修正 CMakeLists 文件并重建,有些规则会主动 调用 CMake 以更新生成的文件(例如生成文件或项目 文件),根据需求。这有助于确保您生成的文件是 一直与您的 CMakeList 文件同步。

清宗言语

CMake 言语由注释、指令和变量组成。

评论

注释从行尾开始并一直运行到行尾。有关更多详细信息,请参阅手册。 #

变量

CMakeLists文件运用变量与任何编程言语十分相似。清明 变量称号差异大小写,只能包括字母数字 字符和下划线。

许多有用的变量由 CMake 主动界说,它们是手册中评论过。 这些变量以 最初。防止此命名约好(而且, 理想情况下,建立自己的)用于特定于项目的变量。CMAKE_

一切 CMake 变量在内部存储为字符串,虽然它们或许 有时被解释为其他类型的。

运用该指令设置变量值。在最简略的方法中, 第一个参数是变量的称号和 其他参数是值。打包了多个值参数 到以分号分隔的列表中并存储在 变量作为字符串。例如:

set(Foo"")#1quotedarg->valueis""set(Fooa)#1unquotedarg->valueis"a"set(Foo"abc")#1quotedarg->valueis"abc"set(Fooabc)#3unquotedargs->valueis"a;b;c"

能够运用语法在指令参数中引证变量,其间 是变量称号。假如命名变量 未界说,引证将替换为空字符串; 否则,它将替换为变量的值。替换是 在扩展未带引号的参数之前履行,因而可变 包括分号的值被拆分为零个或多个参数 原始未引证观点的方位。例如: ${VAR} VAR

set(Fooabc)#3unquotedargs->valueis"a;b;c"command(${Foo})#unquotedargreplacedbya;b;c#andexpandstothreeargumentscommand("${Foo}")#quotedargvalueis"a;b;c"set(Foo"")#1quotedarg->valueisemptystringcommand(${Foo})#unquotedargreplacedbyemptystring#andexpandstozeroargumentscommand("${Foo}")#quotedargvalueisemptystring

系统环境变量和 Windows 注册表值能够是 直接在CMake中拜访。要拜访系统环境变量, 运用语法 。CMake 还能够引证注册表 许多指令中的条目运用方法的语法,其间路径 从注册表树和注册表项生成。 $ENV{VAR} [HKEY_CURRENT_USER\Software\path1\path2;key]

可变规模

CMake 中的变量的效果域与大多数变量略有不同 言语。设置变量时,该变量对当时可见 CMakeLists文件或函数以及任何子目录的CMakeLists文件, 调用的任何函数或宏,以及 包括运用指令。当新的子目录 被处理(或调用函数),创立一个新的变量规模,而且 运用调用中一切变量的当时值初始化 规模。在子效果域中创立的任何新变量或所做的更改 对现有变量,不会影响父规模。考虑 以下示例:

function(foo)message(${test})#testis1hereset(test2)message(${test})#testis2here,butonlyinthisscopeendfunction()set(test1)foo()message(${test})#testwillstillbe1here

在某些情况下,您或许期望函数或子目录设置 变量在其父级的效果域中。有一种办法能够让CMake回来一个 值,能够经过运用带有指令的选项来完成。咱们能够修正 前面的示例,以便函数更改测验的值 在其父级的规模内,如下所示:PARENT_SCOPE foo

function(foo)message(${test})#testis1hereset(test2PARENT_SCOPE)message(${test})#teststill1inthisscopeendfunction()set(test1)foo()message(${test})#testwillnowbe2here

CMake 中的变量按指令履行的次序界说。

请考虑以下示例:

#FOOisundefinedset(FOO1)#FOOisnowsetto1set(FOO0)#FOOisnowsetto0

要了解变量的规模,请考虑以下示例:

set(foo1)#processthedir1subdirectoryadd_subdirectory(dir1)#includeandprocessthecommandsinfile1.cmakeinclude(file1.cmake)set(bar2)#processthedir2subdirectoryadd_subdirectory(dir2)#includeandprocessthecommandsinfile2.cmakeinclude(file2.cmake)

在此示例中,因为变量是在 首要,它将在处理 DIR1 和 DIR2 时界说。在 相反,仅在处理 DIR2 时界说。同样,将在处理 file1.cmake 和 file2.cmake,而只会在处理时界说 文件2.cmake.foo bar foo bar

指令

指令由指令称号、左括号、空格组成 分隔的参数和右括号。每个指令在 它在 CMakeLists 文件中的显现次序。有关完整列表,请参阅手册CMake 指令。

CMake 不再差异大小写,因而在你看到的当地,你能够运用 or 代替。它被认为是 运用小写指令的最佳做法。一切空格(空格、换行符、 制表符)被疏忽,但分隔参数在外。因而,指令或许跨越 多行,只要指令称号和左括号在 同一行。command COMMAND Command

CMake 指令参数以空格分隔且差异大小写。指令 参数能够是引证的,也能够是未引证的。引证的参数开始和结束 在双引号 (“) 中,而且一直只表明一个参数。恣意双倍 值中包括的引号必须运用反斜杠进行转义。考虑 对需求转义的参数运用括号参数,请参阅手册。一个没有引证的观点 以双引号以外的任何字符最初(后边的双引号是 文字),并经过以下方法主动扩展为零个或多个参数 在值内的分号上分隔。例如:

command("")#1quotedargumentcommand("abc")#1quotedargumentcommand("a;b;c")#1quotedargumentcommand("a""b""c")#3quotedargumentscommand(abc)#3unquotedargumentscommand(a;b;c)#1unquotedargumentexpandsto3

根本指令

正如咱们之前看到的,和指令 显式设置或撤销设置变量。、和 指令供给字符串和列表的根本操作。

和指令是首要的 用于界说要构建的可履行文件和库的指令,以及 哪些源文件组成它们。关于 Visual Studio 项目, 源文件将照常显现在 IDE 中,但任何头文件都显现在 项目运用不会。要显现头文件,只需 将它们增加到可履行文件或库的源文件列表中; 这能够为一切发电机完成。任何不运用的生成器 头文件直接(例如根据Makefile的生成器)将 爽性疏忽它们。

流控制

CMake 言语供给了三种流控制结构来协助组织 您的 CMakeList 文件并保持它们可保护。

  • 条件句子(例如)
  • 循环结构(例如和)
  • 程序界说(例如)

条件句子

首要,咱们将考虑该指令。在许多方面,CMake 中的指令就像任何其他言语。它核算其表达式并运用它来履行代码 在其正文中或子句中的代码(可选)。为 例:

if(FOO)#dosomethinghereelse()#dosomethingelseendif()

CMake 还支撑协助次序测验多个 条件。例如:

if(MSVC80)#dosomethinghereelseif(MSVC90)#dosomethingelseelseif(APPLE)#dosomethingelseendif()

该指令记录了它能够测验的许多条件。

循环结构

和指令允许您处理 按次序发生的重复性使命。指令中断在正常情况下脱离 OR循环 结束。

该指令使您能够履行组 的 CMake 指令在列表成员上重复履行。考虑 以下示例改编自 VTK

foreach(tfileTestAnisotropicDiffusion2DTestButterworthLowPassTestButterworthHighPassTestCityBlockDistanceTestConvolve)add_test(${tfile}-image${VTK_EXECUTABLE}${VTK_SOURCE_DIR}/Tests/rtImageTest.tcl${VTK_SOURCE_DIR}/Tests/${tfile}.tcl-D${VTK_DATA_ROOT}-VBaseline/Imaging/${tfile}.png-A${VTK_SOURCE_DIR}/Wrapping/Tcl)endforeach()

指令的第一个参数是 变量,每次迭代时将采用不同的值 循环;其他参数是要在其上履行的值列表 圈。在此示例中,循环的主体只是一个 CMake 指令,.在的正文中,每个 引证循环变量(在本例中)的时间将 替换为列表中的当时值。在第一个 迭代,出现的将替换为 。鄙人一次迭代中,将替换为 。循环将持续循环,直到处理完一切参数。tfile tfile∗∗∗∗TestAnisotropicDiffusion2D∗∗∗∗{tfile}** **TestAnisotropicDiffusion2D** **{tfile} TestButterworthLowPass

值得一提的是,循环能够嵌套,而且 循环变量在任何其他变量之前被替换 扩张。这意味着在循环的主体中,您能够运用循环变量结构变量称号。鄙人面的代码中, 循环变量展开,然后与 连接。然后扩展并测验新变量称号 看看它是否匹配.tfile _TEST_RESULT FAILED

if(${${tfile}_TEST_RESULT}MATCHESFAILED)message("Test${tfile}failed.")endif()

该指令根据测验条件供给循环。这 指令中测验表达式的格式与它适用于指令,如前所述。考虑 以下示例,由 CTest 运用。请留意,CTest 在内部更新的值。CTEST_ELAPSED_TIME

######################################################runparaviewandctesttestdashboardsfor6hours#while(${CTEST_ELAPSED_TIME}LESS36000)set(START_TIME${CTEST_ELAPSED_TIME})ctest_run_script("dash1_ParaView_vs71continuous.cmake")ctest_run_script("dash1_cmake_vs71continuous.cmake")endwhile()

进程界说

和指令支撑重复性使命 或许涣散在您的 CMakeLists 文件中。一旦宏或 函数被界说,它能够被任何CMakeList文件运用后处理 它的界说。

CMake 中的函数十分类似于 C 或 C++ 中的函数。您能够 将参数传递到其间,它们成为 功能。同样,一些规范变量,如、、、和、等。是 界说。函数调用具有动态效果域。在一个函数中,你 在新的变量规模内;这就像你怎么掉进一个 运用该指令的子目录,而且坐落新的变量规模。函数时界说的一切变量 被称为保持界说,但对变量的任何更改或新的 变量仅存在于函数中。当函数回来时, 这些变量将消失。更简略地说:当你调用 函数,推送一个新的变量规模;当它回来时, 弹出变量规模。ARGC ARGV ARGN ARGV0 ARGV1

该指令界说一个新函数。第一个参数 是要界说的函数的称号;一切其他参数均为 函数的方法参数。

function(DetermineTime_time)#passtheresultuptowhateverinvokedthisset(${_time}"1:23:45"PARENT_SCOPE)endfunction()#nowusethefunctionwejustdefinedDetermineTime(current_time)if(DEFINEDcurrent_time)message(STATUS"Thetimeisnow:${current_time}")endif()

请留意,在此示例中,用于传递 回来变量。调用该指令时,其值为 ,该值为 。最终,该指令运用该选项在 调用方的效果域,而不是本地效果域。 _time _time current_time PARENT_SCOPE

宏的界说和调用方法与函数相同。这 首要差异在于宏不会推送和弹出新变量 规模,而且宏的参数不被视为变量 而是在履行之前替换字符串。这很像 宏与 C 或 C++ 中的函数之间的差异。第一个 参数是要创立的宏的称号;一切其他参数 是宏的方法参数。

#defineasimplemacromacro(assertTESTCOMMENT)if(NOT${TEST})message("Assertionfailed:${COMMENT}")endif()endmacro()#usethemacrofind_library(FOO_LIBfoo/usr/local/lib)assert(${FOO_LIB}"Unabletofindlibraryfoo")

上面的简略示例创立了一个名为 的宏。宏 界说为两个参数;第一个是要测验的值和 第二个是假如测验失败,要打印出的注释。身体的 宏是带有指令的简略指令 里面。当指令为 发现。只需运用宏的称号即可调用宏,就好像它是 指令。在上面的例子中,假如未找到,则 将显现音讯,指示过错条件。assert FOO_LIB

该指令还支撑界说采用变量的宏 参数列表。假如要界说一个宏,这会很有用 具有可选参数或多个签名。变量参数能够 改为运用 and 、、等进行引证 的方法参数。 表明第一个参数 宏; 表明下一个,依此类推。你也能够 混合运用正式参数和变量参数,如 下面的示例。ARGC ARGV0 ARGV1 ARGV0 ARGV1

#defineamacrothattakesatleasttwoarguments#(theformalarguments)plusanoptionalthirdargumentmacro(assertTESTCOMMENT)if(NOT${TEST})message("Assertionfailed:${COMMENT}")#ifcalledwiththreeargumentsthenalsowritethe#messagetoafilespecifiedasthethirdargumentif(${ARGC}MATCHES3)file(APPEND${ARGV2}"Assertionfailed:${COMMENT}")endif()endif()endmacro()#usethemacrofind_library(FOO_LIBfoo/usr/local/lib)assert(${FOO_LIB}"Unabletofindlibraryfoo")

在此示例中,两个必需的参数是 和 。这些必需的参数能够按称号引证,如 它们在本例中,或经过引证和 .假如要将参数作为列表进行处理,请运用 和 变量。 (与 , 等相反)是宏的一切参数的列表,而 是正式之后一切参数的列表 参数。在宏中,您能够运用以下指令迭代或根据需求迭代。TEST COMMENT ARGV0 ARGV1 ARGV ARGN ARGV ARGV0 ARGV1 ARGN ARGV ARGN

该指令从函数、目录或文件回来。留意 与函数不同,宏是就地扩展的,因而不能 手柄.

正则表达式

一些 CMake 指令(如和)运用 正则表达式,也能够将正则表达式作为 观点。在最简略的方法中,正则表达式是 用于搜索完全匹配字符的字符。然而,许多 乘以要找到的确切序列未知,或许仅匹配 字符串的最初或结尾是必需的。因为有几个 指定正则表达式的不同约好,CMake 的指令文档中描绘了规范。这 描绘根据来自德克萨斯州的开源正则表达式类 CMake 用于解析正则表达式的仪器。

高档指令

有一些指令或许十分有用,但不是 通常用于编写 CMakeLists 文件。本节将评论 其间一些指令以及它们何时有用。

首要,考虑创立 两个目标之间的依靠关系。CMake 主动创立依靠项 在目标之间,当它能够确定它们时。例如,CMake 将 主动为依靠于 库目标。该指令通常是 用于指定目标之间的目标间依靠关系,其间至少有一个 是自界说目标(请参阅增加自界说指令部分)。

该指令还涉及 依靠。此指令控制正则表达式 用于跟踪源代码依靠项。默认情况下,CMake 将 跟踪源文件(包括系统文件)的一切依靠项 如。假如运用指令指定正则表达式,则该正则表达式将 用于约束处理哪些包括文件。例如;假如 软件项目的包括文件都以前缀 foo 最初 (例如,等),您能够指定常规 表达式,将依靠项检查约束为仅 项目的文件。stdio.h fooMain.cfooStruct.h ^foo.*$