CMake:编写 CMakeLists 文件


本章将介绍编写有效 CMakeList 的基础知识 文件。它将涵盖基本命令和问题 您将需要处理大多数项目。虽然 CMake 可以处理极其复杂的问题 项目,对于大多数项目,您会发现本章的内容会告诉您 你所有你需要知道的。CMake 由写入的 CMakeLists.txt 文件驱动 对于软件项目。CMakeLists 文件确定所有内容 要向用户显示的选项,以及要编译到哪些源文件的选项。在 除了讨论如何编写 CMakeLists 文件外,本章 还将介绍如何使它们健壮和可维护。

编辑 CMakeLists 文件

CMakeLists 文件几乎可以在任何文本编辑器中编辑。一些 编辑器(如 Notepad++)带有 CMake 语法突出显示和 内置缩进支持。对于 Emacs 或 Vim 等编辑器,CMake 包括缩进和语法突出显示模式。这些可以找到 在源代码分发的目录中,或从 CMake 下载页面。Auxiliary

在任何受支持的生成器(Makefile、Visual Studio 等)中,如果 您编辑 CMakeLists 文件并重新生成,有一些规则会自动 调用 CMake 来更新生成的文件(例如 Makefile 或项目 files)。这有助于确保您生成的文件是 始终与您的 CMakeLists 文件同步。

CMake 语言

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

注释

注释从行的开头并一直延伸到行的末尾。有关详细信息,请参阅手册。#

变量

CMakeLists 文件使用变量的方式与任何编程语言非常相似。CMake的 变量名称区分大小写,只能包含字母数字 字符和下划线。

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 arguments
command("${Foo}") # quoted arg value is "a;b;c"
set(Foo "")       # 1 quoted arg -> value is empty string
command(${Foo})   # unquoted arg replaced by empty string
                  # and expands to zero arguments
command("${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 scope
endfunction()

set(test 1)
foo()
message(${test}) # test will still be 1 here

在某些情况下,您可能希望函数或子目录将 变量。CMake 有一种方法可以返回一个 值,并且可以通过将该选项与命令一起使用来完成。我们可以修改 前面的示例,以便函数更改 test 的值 在其父级的作用域中,如下所示:PARENT_SCOPEfoo

function(foo)
  message(${test}) # test is 1 here
  set(test 2 PARENT_SCOPE)
  message(${test}) # test still 1 in this scope
endfunction()

set(test 1)
foo()
message(${test}) # test will now be 2 here

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

请看以下示例:

# FOO is undefined

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

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

要了解变量的作用域,请考虑以下示例:

set(foo 1)

# process the dir1 subdirectory
add_subdirectory(dir1)

# include and process the commands in file1.cmake
include(file1.cmake)

set(bar 2)
# process the dir2 subdirectory
add_subdirectory(dir2)

# include and process the commands in file2.cmake
include(file2.cmake)

在此示例中,由于变量在 开始时,它将在处理 dir1 和 dir2 时定义。在 对比度,仅在处理 dir2 时定义。同样,在处理 file1.cmake 和 file2.cmake,而只会在处理时定义 file2.cmake。foobarfoobar

命令

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

CMake 不再对命令名称区分大小写,因此在看到 的地方,可以改用 或。它被认为是 使用小写命令的最佳做法。所有空格(空格、换行符、 tabs) 被忽略,除非分隔参数。因此,命令可能会跨越 多行,只要命令名称和左括号打开即可 同一行。commandCOMMANDCommand

CMake 命令参数是空格分隔的,并且区分大小写。命令 参数可以带引号,也可以不带引号。带引号的参数开始和结束 在双引号 (“) 中,并且始终只表示一个参数。任何双人间 值中包含的引号必须使用反斜杠进行转义。考虑 使用括号参数 对于需要转义的参数,请参阅手册。未加引号的论点 以双引号以外的任何字符开头(后面的双引号是 literal),并自动扩展为零个或多个参数 在值内用分号分隔。例如:

command("")          # 1 quoted argument
command("a b c")     # 1 quoted argument
command("a;b;c")     # 1 quoted argument
command("a" "b" "c") # 3 quoted arguments
command(a b c)       # 3 unquoted arguments
command(a;b;c)       # 1 unquoted argument expands to 3

基本命令

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

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

流控制

CMake 语言提供了三个流控制构造来帮助组织 您的 CMakeLists 文件,并使其保持可维护性。

条件语句(例如 )

循环结构(例如和 )

程序定义(例如和 )

条件语句

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

if(FOO)
  # do something here
else()
  # do something else
endif()

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

if(MSVC80)
  # do something here
elseif(MSVC90)
  # do something else
elseif(APPLE)
  # do something else
endif()

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

循环构造

和 命令允许您处理 按顺序发生的重复性任务。命令中断 在正常情况下,它之前会退出 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 t f i l e T e s t A n i s o t r o p i c D i f f u s i o n 2 D {tfile}TestAnisotropicDiffusion2D tfileTestAnisotropicDiffusion2D{tfile}TestButterworthLowPass

值得一提的是,循环可以嵌套,并且 循环变量在任何其他变量之前被替换 扩张。这意味着在循环的主体中,您可以 使用 loop 变量构造变量名称。在下面的代码中, 循环变量被展开,然后与 连接。然后,将新的变量名称展开并测试为 看看它是否匹配。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 文件中。一次宏或 函数,它可以通过处理后处理的任何 CMakeLists 文件 它的定义。

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 defined
DetermineTime(current_time)

if(DEFINED current_time)
  message(STATUS "The time is now: ${current_time}")
endif()

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

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

# define a simple macro
macro(assert TEST COMMENT)
  if(NOT ${TEST})
    message("Assertion failed: ${COMMENT}")
  endif()
endmacro()

# use the macro
find_library(FOO_LIB foo /usr/local/lib)
assert(${FOO_LIB} "Unable to find library foo")

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

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

# define a macro that takes at least two arguments
# (the formal arguments) plus an optional third argument
macro(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 macro
find_library(FOO_LIB foo /usr/local/lib)
assert(${FOO_LIB} "Unable to find library foo")

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

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

正则表达式

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

高级命令

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

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

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

相关推荐

  1. CMake编写 CMakeLists 文件

    2024-03-11 13:24:01       40 阅读
  2. cmake构建动态库实例(cmakelist

    2024-03-11 13:24:01       54 阅读
  3. CMake】顶层 CMakeList.txt 常用命令总结

    2024-03-11 13:24:01       43 阅读
  4. CmakeLists

    2024-03-11 13:24:01       31 阅读
  5. linux下建立cpp文件,然后通过cmake编译

    2024-03-11 13:24:01       32 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-11 13:24:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-11 13:24:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-11 13:24:01       87 阅读
  4. Python语言-面向对象

    2024-03-11 13:24:01       96 阅读

热门阅读

  1. Django中的Cookie和Session

    2024-03-11 13:24:01       39 阅读
  2. 创建Django项目,实现视图,路由

    2024-03-11 13:24:01       36 阅读
  3. 安装rocketmq-dashboard1.0.1

    2024-03-11 13:24:01       47 阅读
  4. Superset二次开发之Superset Organizations

    2024-03-11 13:24:01       43 阅读
  5. Node.js概述与安装运行浅记

    2024-03-11 13:24:01       29 阅读
  6. gitlab的CI/CD的Job作业

    2024-03-11 13:24:01       46 阅读
  7. Redis高可用方案

    2024-03-11 13:24:01       35 阅读
  8. 力扣496. 下一个更大元素 I

    2024-03-11 13:24:01       37 阅读
  9. 5.52 BCC工具之dbslower.py解读

    2024-03-11 13:24:01       47 阅读