likes
comments
collection
share

编写 CMakeLists 文件 — 掌握 CMake

作者站长头像
站长
· 阅读数 12

本章将介绍编写有效 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 "")      # 1 quoted arg -> value is ""set(Foo a)       # 1 unquoted arg -> value is "a"set(Foo "a b c") # 1 quoted arg -> value is "a b c"set(Foo a b c)   # 3 unquoted args -> value is "a;b;c"

可以使用语法在命令参数中引用变量,其中 是变量名称。如果命名变量 未定义,引用将替换为空字符串; 否则,它将替换为变量的值。更换是 在扩展未带引号的参数之前执行,因此可变 包含分号的值被拆分为零个或多个参数 原始未引用论点的位置。例如: ${VAR} VAR

set(Foo a b c)    # 3 unquoted args -> value is "a;b;c"command(${Foo})   # unquoted arg replaced by a;b;c                  # and expands to three argumentscommand("${Foo}") # quoted arg value is "a;b;c"set(Foo "")       # 1 quoted arg -> value is empty stringcommand(${Foo})   # unquoted arg replaced by empty string                  # and expands to zero argumentscommand("${Foo}") # quoted arg value is empty string

系统环境变量和 Windows 注册表值可以是 直接在CMake中访问。要访问系统环境变量, 使用语法 。CMake 还可以引用注册表 许多命令中的条目使用形式的语法,其中路径 从注册表树和注册表项生成。 $ENV{VAR} [HKEY_CURRENT_USER\Software\path1\path2;key]

可变范围

CMake 中的变量的作用域与大多数变量略有不同 语言。设置变量时,该变量对当前可见 CMakeLists文件或函数以及任何子目录的CMakeLists文件, 调用的任何函数或宏,以及 包含使用命令。 当新的子目录 被处理(或调用函数),创建一个新的变量范围,并且 使用调用中所有变量的当前值初始化 范围。在子作用域中创建的任何新变量或所做的更改 对现有变量,不会影响父范围。考虑 以下示例:

function(foo)  message(${test}) # test is 1 here  set(test 2)  message(${test}) # test is 2 here, but only in this scopeendfunction()set(test 1)foo()message(${test}) # test will still be 1 here

在某些情况下,您可能希望函数或子目录设置 变量在其父级的作用域中。有一种方法可以让CMake返回一个 值,可以通过使用带有命令的选项来完成。我们可以修改 前面的示例,以便函数更改测试的值 在其父级的范围内,如下所示:PARENT_SCOPE foo

function(foo)  message(${test}) # test is 1 here  set(test 2 PARENT_SCOPE)  message(${test}) # test still 1 in this scopeendfunction()set(test 1)foo()message(${test}) # test will now be 2 here

CMake 中的变量按命令执行的顺序定义。

请考虑以下示例:

# FOO is undefinedset(FOO 1)# FOO is now set to 1set(FOO 0)# FOO is now set to 0

要了解变量的范围,请考虑以下示例:

set(foo 1)# process the dir1 subdirectoryadd_subdirectory(dir1)# include and process the commands in file1.cmakeinclude(file1.cmake)set(bar 2)# process the dir2 subdirectoryadd_subdirectory(dir2)# include and process the commands in file2.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("")          # 1 quoted argumentcommand("a b c")     # 1 quoted argumentcommand("a;b;c")     # 1 quoted argumentcommand("a" "b" "c") # 3 quoted argumentscommand(a b c)       # 3 unquoted argumentscommand(a;b;c)       # 1 unquoted argument expands to 3

基本命令

正如我们之前看到的,和命令 显式设置或取消设置变量。、 和 命令提供字符串和列表的基本操作。

和命令是主要的 用于定义要构建的可执行文件和库的命令,以及 哪些源文件组成它们。对于 Visual Studio 项目, 源文件将照常显示在 IDE 中,但任何头文件都显示在 项目使用不会。要显示头文件,只需 将它们添加到可执行文件或库的源文件列表中; 这可以为所有发电机完成。任何不使用的生成器 头文件直接(例如基于Makefile的生成器)将 干脆忽略它们。

流控制

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

  • 条件语句(例如 )
  • 循环构造(例如和 )
  • 程序定义(例如 )

条件语句

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

if(FOO)  # do something hereelse()  # do something elseendif()

CMake 还支持帮助顺序测试多个 条件。例如:

if(MSVC80)  # do something hereelseif(MSVC90)  # do something elseelseif(APPLE)  # do something elseendif()

该命令记录了它可以测试的许多条件。

循环构造

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

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

foreach(tfile        TestAnisotropicDiffusion2D        TestButterworthLowPass        TestButterworthHighPass        TestCityBlockDistance        TestConvolve        )  add_test(${tfile}-image ${VTK_EXECUTABLE}    ${VTK_SOURCE_DIR}/Tests/rtImageTest.tcl    ${VTK_SOURCE_DIR}/Tests/${tfile}.tcl    -D ${VTK_DATA_ROOT}    -V Baseline/Imaging/${tfile}.png    -A ${VTK_SOURCE_DIR}/Wrapping/Tcl    )endforeach()

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

值得一提的是,循环可以嵌套,并且 循环变量在任何其他变量之前被替换 扩张。这意味着在循环的主体中,您可以 使用循环变量构造变量名称。在下面的代码中, 循环变量展开,然后与 连接。然后扩展并测试新变量名称 看看它是否匹配.tfile _TEST_RESULT FAILED

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

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

###################################################### run paraview and ctest test dashboards for 6 hours#while(${CTEST_ELAPSED_TIME} LESS 36000)  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)  # pass the result up to whatever invoked this  set(${_time} "1:23:45" PARENT_SCOPE)endfunction()# now use the function we just definedDetermineTime(current_time)if(DEFINED current_time)  message(STATUS "The time is now: ${current_time}")endif()

请注意,在此示例中,用于传递 返回变量。调用该命令时,其值为 ,该值为 。最后,该命令使用该选项在 调用方的作用域,而不是本地作用域。 _time _time current_time PARENT_SCOPE

宏的定义和调用方式与函数相同。这 主要区别在于宏不会推送和弹出新变量 范围,并且宏的参数不被视为变量 而是在执行之前替换字符串。这很像 宏与 C 或 C++ 中的函数之间的差异。第一个 参数是要创建的宏的名称;所有其他参数 是宏的形式参数。

# define a simple macromacro(assert TEST COMMENT)  if(NOT ${TEST})    message("Assertion failed: ${COMMENT}")  endif()endmacro()# use the macrofind_library(FOO_LIB foo /usr/local/lib)assert(${FOO_LIB} "Unable to find library foo")

上面的简单示例创建了一个名为 的宏。宏 定义为两个参数;第一个是要测试的值和 第二个是如果测试失败,要打印出的注释。身体的 宏是带有命令的简单命令 里面。当命令为 发现。只需使用宏的名称即可调用宏,就好像它是 命令。在上面的例子中,如果未找到,则 将显示消息,指示错误条件。assert FOO_LIB

该命令还支持定义采用变量的宏 参数列表。如果要定义一个宏,这会很有用 具有可选参数或多个签名。变量参数可以 改为使用 and 、、等进行引用 的形式参数。 表示第一个参数 宏; 表示下一个,依此类推。你也可以 混合使用正式参数和变量参数,如 下面的示例。ARGC ARGV0 ARGV1 ARGV0 ARGV1

# define a macro that takes at least two arguments# (the formal arguments) plus an optional third argumentmacro(assert TEST COMMENT)  if(NOT ${TEST})    message("Assertion failed: ${COMMENT}")    # if called with three arguments then also write the    # message to a file specified as the third argument    if(${ARGC} MATCHES 3)      file(APPEND ${ARGV2} "Assertion failed: ${COMMENT}")    endif()  endif()endmacro()# use the macrofind_library(FOO_LIB foo /usr/local/lib)assert(${FOO_LIB} "Unable to find library foo")

在此示例中,两个必需的参数是 和 。这些必需的参数可以按名称引用,如 它们在本例中,或通过引用和 .如果要将参数作为列表进行处理,请使用 和 变量。 (与 , 等相反)是宏的所有参数的列表,而 是正式之后所有参数的列表 参数。在宏中,您可以使用以下命令 迭代或根据需要迭代。TEST COMMENT ARGV0 ARGV1 ARGV ARGN ARGV ARGV0 ARGV1 ARGN ARGV ARGN

该命令从函数、目录或文件返回。注意 与函数不同,宏是就地扩展的,因此不能 手柄 .

正则表达式

一些 CMake 命令(如 和 )使用 正则表达式,也可以将正则表达式作为 论点。在最简单的形式中,正则表达式是 用于搜索完全匹配字符的字符。然而,许多 乘以要找到的确切序列未知,或者仅匹配 字符串的开头或结尾是必需的。由于有几个 指定正则表达式的不同约定,CMake 的 命令文档中描述了标准。这 描述基于来自德克萨斯州的开源正则表达式类 CMake 用于解析正则表达式的仪器。

高级命令

有一些命令可能非常有用,但不是 通常用于编写 CMakeLists 文件。本节将讨论 其中一些命令以及它们何时有用。

首先,考虑创建 两个目标之间的依赖关系。CMake 自动创建依赖项 在目标之间,当它可以确定它们时。例如,CMake 将 自动为依赖于 库目标。该命令通常是 用于指定目标之间的目标间依赖关系,其中至少有一个 是自定义目标(请参阅添加自定义命令部分)。

该命令还涉及 依赖。此命令控制正则表达式 用于跟踪源代码依赖项。默认情况下,CMake 将 跟踪源文件(包括系统文件)的所有依赖项 如。如果使用命令指定正则表达式,则该正则表达式将 用于限制处理哪些包含文件。例如;如果 软件项目的包含文件都以前缀 foo 开头 (例如,等),您可以指定常规 表达式,将依赖项检查限制为仅 项目的文件。stdio.h fooMain.c fooStruct.h ^foo.*$