编写 CMakeLists 文件 — 掌握 CMak

本章将介绍编写有效 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_SCOPEfoo

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.foobarfoobar

命令

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

CMake 不再区分大小写,因此在你看到的地方,你可以使用 or 代替。它被认为是 使用小写命令的最佳做法。所有空格(空格、换行符、 制表符)被忽略,但分隔参数除外。因此,命令可能跨越 多行,只要命令名称和左括号在 同一行。commandCOMMANDCommand

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}TestButterworthLowPass

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

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++ 中的函数。您可以 将参数传递到其中,它们成为 功能。同样,一些标准变量,如、、、和、等。是 定义。函数调用具有动态作用域。在一个函数中,你 在新的变量范围内;这就像你如何掉进一个 使用该命令的子目录,并且位于新的 变量范围。函数时定义的所有变量 被称为保持定义,但对变量的任何更改或新的 变量仅存在于函数中。当函数返回时, 这些变量将消失。更简单地说:当你调用 函数,推送一个新的变量范围;当它返回时, 弹出变量范围。ARGCARGVARGNARGV0ARGV1

该命令定义一个新函数。第一个参数 是要定义的函数的名称;所有其他参数均为 函数的形式参数。

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_timecurrent_timePARENT_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")

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

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

# 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")

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

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

正则表达式

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

高级命令

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

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

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

你可能感兴趣的:(开源软件,开源协议,开放原子,运维开发,github,开源,gitee)