第十三章:调试与诊断

Posted by lili on

当构建表现良好时,用户往往不会太关注CMake生成的输出。然而,对于正在开发项目的开发人员来说,诊断输出和调试功能是至关重要的。CMake一直提供基本的打印功能,但在3.15到3.18版本中添加的增强功能显著扩展了可用的功能。

13.1. 日志消息

CMake一直支持使用message()命令记录任意文本,该命令在第5.7节“打印变量值”中简要介绍过。该命令的更通用形式是:

message([mode] msg1 [msg2]...)

如果指定了多个msg,则它们将连接成一个没有分隔符的字符串。为了保留空格、分号或换行符,请用引号括起消息(详细说明请参见第8.8节“参数处理的问题”)。

消息输出可以受到可选的mode参数、cmake命令行选项以及调用时一些变量的值的影响。接下来的几个子节详细介绍了这些内容。

13.1.1. 日志级别

message()命令接受一个可选的mode关键字,它提供有关所提供消息类型的信息。它影响消息的输出方式和位置,以及是否输出消息,有时还可以停止该CMake运行的进一步处理。按照重要性的顺序,被识别的mode值有:

  • FATAL_ERROR:表示严重错误。在打印消息后,处理将立即停止,日志通常还会记录致命message()命令的位置。
  • SEND_ERROR:类似于FATAL_ERROR,但处理将继续,直到配置阶段完成,但不会执行生成。这可能会让用户感到困惑,因此项目应避免使用此模式,最好使用FATAL_ERROR。
  • WARNING:表示警告。日志通常还会记录引发警告的message()命令的位置。处理将继续。
  • AUTHOR_WARNING:类似于WARNING,但仅在启用开发人员警告时显示(在cmake命令行上使用-Wno-dev选项禁用它们)。项目通常不使用此特定类型的消息,它们通常由CMake自身生成。
  • DEPRECATION:用于记录弃用消息的特殊类别。如果CMAKE_ERROR_DEPRECATED变量设置为true,则该消息将被视为错误。如果CMAKE_WARN_DEPRECATED变量设置为true,则该消息将被视为警告。如果未设置任何变量,则该消息将在CMake 3.5或更高版本中显示,并在更早版本中隐藏。
  • NOTICE:此关键字仅在CMake 3.15或更高版本中才能识别,但对于所有版本,这是在未提供mode关键字时的默认日志级别。添加此关键字是为了保持一致性,并允许项目更清晰地表达此类消息的含义。如果消息不需要用户采取任何操作,请避免使用此日志级别(请参见下文)。
  • STATUS:简洁的状态信息,通常期望是单行。纯粹信息性消息的情况下,最好使用此消息模式而不是NOTICE。
  • VERBOSE:(仅适用于CMake 3.15或更高版本)更详细的信息,通常不会引起兴趣,但对于寻求对正在发生的情况有更深入理解的项目用户可能会有帮助。
  • DEBUG:(仅适用于CMake 3.15或更高版本)不适用于项目用户,而是适用于正在开发项目本身的开发人员。这些可能记录不会引起简单想要构建项目的人的兴趣的内部实现细节。
  • TRACE:(仅适用于CMake 3.15或更高版本)非常低级的详细信息,几乎仅在项目开发过程中用于临时消息。

STATUS到TRACE级别的消息将被打印到stdout,而NOTICE及以上级别的消息将被打印到stderr。这可能导致不同日志级别的消息有时在输出中以不同的顺序出现。此外,stderr上的消息通常暗示着问题或用户应该调查的事情,因此NOTICE通常不是纯粹信息性消息的好选择,这些消息不需要后续处理。对于这些消息,请使用STATUS或以下级别。

从STATUS到TRACE的消息可能还会自动添加两个连字符和一个空格前缀。CMake GUI应用程序和ccmake工具不会添加此前缀,而cmake工具的当前版本将添加。未来的CMake版本可能会完全删除此前缀,因此不要依赖它存在。NOTICE级别及以上的消息不会添加此前缀。

CMake 3.15还添加了使用--loglevel=…命令行选项设置最小日志级别的功能。出于一致性原因,该选项在CMake 3.16中更名为--log-level,但--loglevel仍然为向后兼容而接受。该选项指定所需的日志级别,只会显示该级别或更高级别的消息。当未提供--log-level选项时,仅会记录STATUS级别或更高级别的消息。

cmake --log-level=VERBOSE ...

CMake 3.17增加了使用CMAKE_MESSAGE_LOG_LEVEL变量指定默认日志级别的功能。如果两者都存在,则--log-level命令行选项将覆盖它。缓存变量仅供开发人员使用,项目不应尝试读取或修改它。

如果项目希望仅在特定日志级别执行某些操作,在CMake 3.24或更早的版本中无法可靠实现。CMAKE_MESSAGE_LOG_LEVEL变量不是当前日志级别的可靠指标。–log-level选项可能已在cmake命令行上传递,并且当两者都给出时它将覆盖变量(变量值不会更新以反映命令行设置)。从CMake 3.25开始,可以使用cmake_language(GET_MESSAGE_LOG_LEVEL)可靠地获取当前日志级别。这考虑了变量和命令行选项,返回调用时的活动日志级别。然后可以实现类似以下的模式:

# 仅适用于CMake 3.25或更高版本
cmake_language(GET_MESSAGE_LOG_LEVEL logLevel)
# 仅在VERBOSE或更低级别执行耗时操作
if(logLevel MATCHES "VERBOSE|DEBUG|TRACE")
  doTimeConsumingDiagnostics()
endif()

13.1.2. 消息缩进

当项目记录大量输出时,添加一些结构可以帮助用户更好地理解每条消息与项目的哪些部分相关。一种方法是利用 CMAKE_MESSAGE_INDENT 变量。当使用 CMake 3.16 或更高版本时,该变量在调用 message() 时的内容将与通知级别及以下的消息连接在一起,并作为前缀添加到日志中。如果消息包含嵌套的换行符,则缩进内容将添加到输出的每一行之前。

set(CMAKE_MESSAGE_INDENT aa bb) # 不要这样做,见下文
message("First line\nSecond line")

输出结果将为:

aabbFirst line
aabbSecond line

尽管上面的示例演示了该功能的工作原理,但存在问题。一般期望是,CMAKE_MESSAGE_INDENT 中的列表元素只包含空白字符,通常是两个空格。虽然这不是必需的,但偏离这一规范可能会让用户感到困扰。项目也不应该使用 set() 来设置该变量,而应该只能通过调用 list(APPEND) 来追加元素。这样可以避免对变量内容的任何假设,并始终保留现有的缩进。对于分层项目的输出(在第30章“FetchContent”中详细讨论)或在函数调用中使用缩进,这一点尤为重要。

以下示例演示了上述准则以及它们在实践中的应用方式。请注意,输出的缩进如何根据调用堆栈而异,包括来自 funcA() 和 funcB() 的输出。

function(funcA)
 list(APPEND CMAKE_MESSAGE_INDENT " ")
 message("${CMAKE_CURRENT_FUNCTION}")
endfunction()

function(funcB)
 list(APPEND CMAKE_MESSAGE_INDENT " ")
 message("${CMAKE_CURRENT_FUNCTION}")
 funcA()
endfunction()

function(funcC)
 list(APPEND CMAKE_MESSAGE_INDENT " ")
 message("${CMAKE_CURRENT_FUNCTION}")
 funcB()
endfunction()

message("Top level")
funcA()
funcB()
funcC()
Top level
funcA
funcB
  funcA
funcC
  funcB
    funcA

此示例的另一个特性是,由于函数引入了它们自己的变量范围,因此在返回之前无需从列表的末尾弹出缩进。调用者有其自己独立的 CMAKE_MESSAGE_INDENT 变量副本,因此从其角度来看,该变量的值在函数调用的结果中并不发生变化。

即使项目的最低 CMake 版本低于3.16,项目仍然可以添加对缩进的支持。较旧的 CMake 版本将简单地忽略缩进,输出将保持不变。

13.1.3. 消息上下文

CMake 3.17进一步扩展了对消息元数据的支持。就像 CMAKE_MESSAGE_INDENT 可用于提供缩进一样,CMAKE_MESSAGE_CONTEXT 变量可用于提供有关生成每条消息的上下文的信息。这可以用于记录项目名称或项目中的某个逻辑部分等信息。然后,用户可以通过在 cmake 命令行中包含 --log-context 选项来指示 CMake 在每条消息中打印上下文信息。

当给定 --log-context 选项且 CMAKE_MESSAGE_CONTEXT 不为空时,对于调用 message() 生成的每一行输出,将生成一个前缀。此前缀将是 CMAKE_MESSAGE_CONTEXT 中各项的连接,每个项之间用点号分隔。其结果将被括在方括号内,并在前缀末尾添加一个空格。对于以 STATUS 级别或更低级别记录的消息,上下文将位于 cmake 可能添加的任何前导连字符之后。

CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.17)
list(APPEND CMAKE_MESSAGE_CONTEXT Coolio)
project(Coolio)
message("Adding features\nHere we go:")
add_subdirectory(networking)
add_subdirectory(graphics)
message("All done")

networking/CMakeLists.txt:

list(APPEND CMAKE_MESSAGE_CONTEXT net)
message("Doing something")

graphics/CMakeLists.txt:

list(APPEND CMAKE_MESSAGE_CONTEXT graphics)
message("Doing something else")

运行上述命令 cmake –log-context 将产生以下输出:

-- [Coolio] The C compiler identification is GNU 9.3.0
-- [Coolio] The CXX compiler identification is GNU 9.3.0
-- [Coolio] Detecting C compiler ABI info
-- [Coolio] Detecting C compiler ABI info - done
-- [Coolio] Check for working C compiler: /.../cc - skipped
-- [Coolio] Detecting C compile features
-- [Coolio] Detecting C compile features - done
-- [Coolio] Detecting CXX compiler ABI info
-- [Coolio] Detecting CXX compiler ABI info - done
-- [Coolio] Check for working CXX compiler: /.../c++ - skipped
-- [Coolio] Detecting CXX compile features
-- [Coolio] Detecting CXX compile features - done
[Coolio] Adding features
[Coolio] Here we go:
[Coolio.net] Doing something
[Coolio.graphics] Doing something else
[Coolio] All done
-- Configuring done
-- Generating done
-- Build files have been written to: /...

输出的最后几行记录了运行各阶段的完成情况,它们始终没有上下文。

一般期望是,当启用上下文信息的输出时,该输出将经过进一步的某种后处理,而不是直接显示给用户。例如,IDE 工具可以使用此功能来了解输出的结构并为用户提供过滤功能。IDE 可能支持用户根据兴趣展开和折叠,或者显示和隐藏 cmake 输出的部分。

另一个用例可能是脚本化构建。这些构建可能使用类似于 tee 和 awk 的 Unix 命令将带有注释的日志保存到文件,但仍然以不包含上下文细节的方式显示输出,以便实时监控。保存的文件然后可以在以后搜索以找到感兴趣的特定行。以下是在类Unix系统的bash shell中完成此操作的一种方式(它还会去除任何前导连字符前缀以及上下文信息):

cmake . --log-context |& \
 tee out.log | \
 awk 'sub("^((-- )?(\\[[^\\]]*\\] ))?", "")'

然后,可以使用类似于 grep 的工具从保存的日志文件中提取与网络相关的消息:

grep -E '^(-- )?\[.*Coolio\.net\] ' out.log

CMake对项目可以用作上下文名称的内容名称施加了一些限制。有效的上下文名称(即CMAKE_MESSAGE_CONTEXT列表中的每个项)是那些可以用作CMake变量名称的内容。在大多数情况下,这基本上意味着字母、数字和下划线。此外,以 “cmake_” 或前导下划线开头的上下文名称被认为是保留供CMake自身使用的。

当message()调用指定适当的日志级别时,消息上下文特别有效。例如,项目可以使用VERBOSE日志级别提供更详细的信息,但在STATUS级别或更高级别仅提供相对较少的输出。这将使默认输出变得清晰,但使用–log-level VERBOSE将在需要时提供额外的详细信息。用户然后可以通过搜索感兴趣的消息上下文来专注于他们想要的详细信息。

13.1.4. 检查消息

CMake 3.17 或更高版本提供的另一个有用功能是支持记录某种检查状态的消息。语法与 message() 命令的主要形式基本相同,但第一个参数的含义不同:

message(checkState msg1 [msg2]...)

checkState 参数预期是以下值之一:

CHECK_START:表示检查的开始。该消息应该很短,最好不超过几个词。它将作为检查结束时的通过或失败消息的一部分而重复显示。 CHECK_PASS:检查成功完成。 CHECK_FAIL:检查以失败完成。

开始、通过和失败的消息始终以 STATUS 日志级别输出。成功或失败的含义取决于项目,并且失败不一定意味着错误。例如,项目可能想要检查多个相关事物,并在找到第一个成功的事物时停止。

在检查完成(通过或失败)后,message() 命令将重复最近的 CHECK_START 消息,然后“忘记”该消息。嵌套检查的输出则直观地与适当使用 CMAKE_MESSAGE_INDENT 进行缩进相结合,输出的可读性和一致性尤为出色。

# Functions just to demonstrate pass/fail behavior
function(checkSomething resultVar)
 set(${resultVar} YES PARENT_SCOPE)
endfunction()

function(checkSomethingElse resultVar)
 set(${resultVar} NO PARENT_SCOPE)
endfunction()

# Outer check starts here
message(CHECK_START "Checking things")
list(APPEND CMAKE_MESSAGE_INDENT "  ")

# Inner check 1
message(CHECK_START "Checking support for something")
checkSomething(successVar1)
if(successVar1)
 message(CHECK_PASS "supported")
else()
 message(CHECK_FAIL "not supported")
endif()

# Inner check 2
message(CHECK_START "Checking support for something else")
checkSomethingElse(successVar2)
if(successVar2)
 message(CHECK_PASS "supported")
else()
 message(CHECK_FAIL "not supported")
endif()

# Outer check finishes here
list(POP_BACK CMAKE_MESSAGE_INDENT)
if(successVar1 OR successVar2) 
 message(CHECK_PASS "ok")
else()
 message(CHECK_FAIL "failed")
endif()

输出如下:

--  Checking things
--    Checking support for something
--    Checking support for something - supported
--    Checking support for something else
--    Checking support for something else - not supported
--  Checking things - ok

13.2. 彩色诊断

一些编译器支持使用彩色文本显示其警告和错误消息。当构建输出很长或自定义任务生成大量不那么重要的输出时,将警告和错误显示为不同颜色有助于引起对更重要信息的关注。这对开发人员在日常工作中非常有帮助。

彩色输出通常通过在输出中插入 ANSI 格式代码来完成。然后,这些代码由消费者解释,应用它们所代表的颜色化指令,而不是显示原始字符。ANSI 代码的约定非常古老且已经确立,但并非在所有场景中都受支持。提供此功能的编译器通常会尝试自动检测调用环境是否支持它们,仅在安全的情况下添加 ANSI 代码。

不幸的是,这种自动检测通常会被编译器调用的方式所破坏。编译器输出可能在进程之间进行管道传递,可能会被构建工具缓冲,或者 IDE 可能会捕获输出并在 UI 组件中显示它。在这些情况下,编译器无法查询其功能的终端,因此通常会禁用彩色输出。

编译器通常提供覆盖自动检测的功能,但命令行标志是特定于编译器的。在 CMake 3.24 或更高版本中,可以设置 CMAKE_COLOR_DIAGNOSTICS 变量以编译器无关的方式指定行为。将此变量设置为 true 将启用支持它的编译器的彩色输出,而将其设置为 false 将禁用彩色输出(如果编译器的自动检测错误地认为支持彩色输出)。如果未设置此变量,则将使用编译器的自动检测,与 CMake 3.23 及更早版本的行为相匹配。

如果在构建目录中首次运行 CMake 时未定义 CMAKE_COLOR_DIAGNOSTICS 变量,它将从同名的环境变量中初始化,如果设置的话。这主要是为了 IDE,以便它们可以默认为它们启动的 CMake 调用打开彩色诊断。

CMAKE_COLOR_DIAGNOSTICS 变量还可以启用一些构建工具的彩色输出。它替代了旧的 CMAKE_COLOR_MAKEFILE 变量,这个变量应该不再需要。CMAKE_COLOR_DIAGNOSTICS 更通用,控制更广泛范围工具的颜色诊断,因此应优先使用。

13.3. 打印辅助工具

CMakePrintHelpers 模块提供了两个宏,使在开发过程中更方便地打印属性和变量的值。它们并非用于永久使用,而是更倾向于帮助开发人员临时快速地记录信息,以便调查项目中的问题。

cmake_print_properties(
 [TARGETS target1 [target2...]]
 [SOURCES source1 [source2...]]
 [DIRECTORIES dir1 [dir2...]]
 [TESTS test1 [test2...]]
 [CACHE_ENTRIES var1 [var2...]]
 PROPERTIES property1 [property2...]
)

该命令基本上将 get_property() 与 message() 结合成一个单独的调用。必须明确指定属性类型中的一个,每个列出的实体都将为每个命名属性打印。对于记录多个实体或属性的值,这尤其方便。

add_executable(MyApp main.c)
add_executable(MyAlias ALIAS MyApp)
add_library(MyLib STATIC src.cpp)
include(CMakePrintHelpers)
cmake_print_properties(TARGETS MyApp MyLib MyAlias
 PROPERTIES TYPE ALIASED_TARGET
)
Properties for TARGET MyApp:
  MyApp.TYPE = "EXECUTABLE"
  MyApp.ALIASED_TARGET = <NOTFOUND>
Properties for TARGET MyLib:
  MyLib.TYPE = "STATIC_LIBRARY"
  MyLib.ALIASED_TARGET = <NOTFOUND>
Properties for TARGET MyAlias:
  MyAlias.TYPE = "EXECUTABLE"
  MyAlias.ALIASED_TARGET = "MyApp"

该模块还提供了一个类似的功能,用于记录一个或多个变量的值:

cmake_print_variables(var1 [var2...])

这适用于所有变量,无论它们是否已被项目显式设置,由 CMake 自动设置,还是根本未设置。

set(foo "My variable")
unset(bar)

include(CMakePrintHelpers)
cmake_print_variables(foo bar CMAKE_VERSION)
foo="My variable" ; bar="" ; CMAKE_VERSION="3.8.2"

13.4. 变量访问追踪

用于调试变量使用的另一机制是 variable_watch() 命令。这适用于更复杂的项目,其中可能不清楚变量如何最终具有特定的值。当监视变量时,将记录所有试图读取或修改它的尝试。

variable_watch(myVar [command])

对于绝大多数情况,仅列出要监视的变量而不使用可选的命令就足够了,因为它会记录对指定变量的所有访问。为了更自定义的控制程度,可以提供一个命令,该命令将在每次读取或修改变量时执行。该命令预计是一个 CMake 函数或宏的名称,它将接收以下参数:

  • 变量的名称。
  • 访问类型。
  • 变量的值。
  • 当前列表文件的名称。
  • 列表文件堆栈。

实际上,在 variable_watch() 中指定一个命令是非常罕见的。通常情况下,默认消息就足以帮助诊断通常使用 variable_watch() 的情况。默认消息还包含比传递给自定义监视器命令的最后一个参数中可用的调用堆栈更详细的信息。

13.5. 调试生成表达式

生成表达式可能会很快变得复杂,确认其正确性可能会很困难。因为它们仅在生成时评估,所以它们的结果在配置阶段不可用,因此无法使用 message() 命令打印。

调试生成表达式值的一种方法是使用 file(GENERATE) 命令,该命令在第20.3节“直接读写文件”中有介绍。生成表达式可以写入临时文件,并在CMake执行完成后进行检查。例如:

add_executable(someTarget ...)
target_include_directories(someTarget ...)

set(incDirs $<TARGET_PROPERTY:someTarget,INCLUDE_DIRECTORIES>)
set(genex "-I$<JOIN:${incDirs}, -I>")
file(GENERATE OUTPUT genex.txt CONTENT "${genex}\n")

另一种方法是创建一个临时的自定义构建目标,其命令打印生成表达式的值(参见第19.1节“自定义目标”)。构建该目标然后会打印表达式的结果。

add_custom_target(printGenex
 COMMENT "Result of generator expression:"
 COMMAND ${CMAKE_COMMAND} -E echo "${genex}"
 VERBATIM
)

构建该目标和一些代表性的输出可能如下所示:

cmake --build . --target printGenex
[1/1] Result of generator expression:
-I/some/path -I/some/other/path
)

这种技术在配置特定的生成表达式和使用多配置生成器(如Xcode、Visual Studio和Ninja Multi-Config)时特别有用:

set(genex "$<IF:$<CONFIG:Debug>,is debug,not debug>")
add_custom_target(printGenex
 COMMENT "Result of generator expression:"
 COMMAND ${CMAKE_COMMAND} -E echo "${genex}"
 VERBATIM
)

对于多配置生成器,可以使用构建命令指定配置:

cmake --build . --target printGenex --config Release
[1/1] Result of generator expression:
not debug
cmake --build . --target printGenex --config Debug
[1/1] Result of generator expression:
is debug

13.6. 对CMake调用进行性能分析

CMake 3.18 增加了对项目中的CMake自身处理进行性能分析的功能。对于大型、复杂的项目,在配置阶段花费很长时间时,这可以为您提供有关时间花费在何处的宝贵见解。启用性能分析时,将记录每个CMake命令调用的分析输出。

为了启用性能分析,需要提供以下两个命令行选项:

--profiling-output=fileName 性能分析数据将写入指定的 fileName。

--profiling-format=fmt 这指定了性能分析数据的格式。fmt 的唯一受支持的值是 google-trace,但未来的CMake版本可能会扩展到包括其他格式。

对于 google-trace 格式,输出文件可以直接加载到 Chrome web 浏览器(导航到 about:tracing 的 URL)或某些IDE(例如 Qt Creator)中。在输出文件名中使用 .json 扩展名可能会更容易找到,并加载到理解 google-trace 格式的工具中。

性能分析结果通常显示像 try_compile() 和 execute_process() 这样的调用占据了大部分执行时间。与专注于这两个调用本身不同,应该检查导致这些命令的调用堆栈。可能有机会通过避免在调用堆栈的较高位置进行不必要或过于悲观的逻辑,从而减少这两个命令的调用频率。

13.7. 丢弃先前的结果

在尝试追踪项目的 CMake 逻辑中出现的意外行为时,建议的调试步骤是丢弃现有构建目录中的任何缓存结果,然后确认问题是否仍然存在。对于小型项目,删除整个构建目录通常是实现这一目标的最简单方式。对于非常大的项目,丢失所有已编译的对象文件和其他构建工件可能是不可接受的。可能需要更精确的方法来删除一小部分文件和目录。

构建目录顶部的 CMakeCache.txt 文件是信息被缓存的主要位置。在某些情况下,开发人员可能需要删除此文件,以便重新计算缓存信息,或者丢弃手动更改。应删除该文件的示例情况包括:

  • 依赖项可能已更新或删除。
  • 开发人员添加或暂时修改的缓存变量可能不再需要,应改为使用默认值。
  • 与工具链关联的编译器或其他工具可能已更新或更改。

如果工具链的任何部分发生更改,还应删除 CMakeFiles 目录。这是 CMake 在第一次运行时执行各种检查后缓存工具链信息的地方。 在 CMake 3.24 或更高版本中,可以在 cmake 命令行上传递 –fresh 选项。此选项告诉 CMake 删除 CMakeCache.txt 文件和 CMakeFiles 目录。这主要是为了方便开发人员,其额外优势是不需要记住 CMake 缓存事物的详细位置。相同的功能在较早版本的 CMake GUI 应用程序中也是可用的。它可以在文件菜单中找到,作为“删除缓存…”操作。

13.8. 推荐做法

许多项目的常见问题是它们在配置步骤期间记录了过多的输出。这往往会让用户忽略输出,从而导致重要的消息和警告被轻易忽略。当输出相当少而发生警告时,用户往往会注意并调查原因。因此,目标是最小化 STATUS 日志级别的输出量,将更详细的输出保留给 VERBOSE 或更低的日志级别。如果支持的 CMake 版本早于 3.15,其中 STATUS 以下的日志级别不可用,请考虑将详细日志记录放在项目特定的缓存选项后面,默认情况下该选项应该关闭。

对于旨在保留为构建的日志消息,始终将日志级别指定为 message() 命令的第一个参数。如果消息是一般信息性质的,请使用 STATUS 而不是没有关键字,以便消息输出不会在构建日志中出现无序。为了方便,临时调试消息通常会忽略指定日志级别,但如果它们可能在项目中保留任何时间,最好也指定日志级别。

对于非平凡项目,考虑添加消息上下文信息,以允许用户过滤日志输出并仅关注他们感兴趣的消息。永远不要丢弃 CMAKE_MESSAGE_CONTEXT 变量的现有内容,始终在开始新消息上下文时使用 list(APPEND)。如果消息上下文应在当前变量范围结束之前结束,请使用 list(POP_BACK)。不要对变量的内容作出其他假设,除了可以使用追加/弹出模式。考虑在顶层 CMakeLists.txt 文件的第一个 project() 调用之前立即附加项目名称作为消息上下文,以便编译器功能检查也具有消息上下文。

以类似的方式,还可以考虑使用 CMAKE_MESSAGE_INDENT 变量为消息输出提供一些逻辑结构。更喜欢附加两个空格作为缩进。虽然允许其他缩进,但遵循这个约定将使输出更一致,尤其是在使用外部依赖关系的分层项目中。使用 list(APPEND) 来添加到现有的缩进,永远不要替换或丢弃 CMAKE_MESSAGE_INDENT 变量的现有内容。如果需要,可以使用 list(POP_BACK) 在当前变量范围结束之前再次减少缩进。

CMAKE_MESSAGE_CONTEXT 和 CMAKE_MESSAGE_INDENT 变量都可以由项目填充,而不考虑最低支持的 CMake 版本。当使用不了解这些变量的早期 CMake 版本时,它们将简单地忽略它们,输出不受影响。因此,即使项目需要支持较早的 CMake 版本,也考虑使用这些功能。请注意,list(POP_BACK) 命令需要 CMake 3.15 或更高版本,因此如果项目需要支持早于此版本的版本,必须使用替代命令来实现相同的效果。然而,在大多数情况下,新的消息上下文或缩进级别将一直应用到当前变量范围的末尾,在这种情况下,弹出列表变量末尾的最后一个值将是不必要的。

考虑使用 message() 命令的 CHECK_START、CHECK_PASS 和 CHECK_FAIL 形式来记录检查的详细信息。这减少了消息的重复性,并提供了更好的可读性。当与 CMAKE_MESSAGE_INDENT 变量提供的缩进支持一起使用时,效果尤为明显。

如果项目的配置阶段花费很长时间,请考虑使用 –profiling-output 和 –profiling-format 选项运行 cmake,以调查时间花费在哪里。这些选项在 CMake 3.18 或更高版本中可用,它们启用命令级别的分析信息生成,可以使用 Chrome 网页浏览器或 Qt Creator 和 CLion 等 IDE 工具查看。

第 25.7 节“调试 find_…() 调用”还讨论了在 CMake 3.17 及更高版本中添加的进一步调试功能。这些功能涉及查找文件、软件包和其他内容,详细介绍在第 25 章“查找事物”中。