前言:
学习笔记,随时更新。如有谬误,欢迎指正。
说明:
- 红色字体为较为重要部分。
- 绿色字体为个人理解部分。
11 自定义命令
通常,软件项目的构建过程不仅仅是简单地编译库和可执行程序。在许多情况下,在构建过程期间或之后可能需要额外的任务。常见的例子包括:使用文档包编译文档;通过运行另一个可执行文件生成源文件;使用 CMake 没有的工具(如 lex 和 yacc )生成文件;移动生成的可执行文件;后处理可执行文件等。 CMake 使用 add_custom_command 和 add_custom_target 命令来支持这些附加任务。本章将描述如何使用自定义命令和目标来执行 CMake 本身不支持的复杂任务。
11.1 可移植的自定义命令
在详细讨论如何使用自定义命令之前,我们将讨论如何处理它们的一些可移植性问题。自定义命令通常涉及以文件作为输入或输出来运行程序。即使是一个简单的命令,比如复制文件,在跨平台的情况下也会很棘手。例如,在 UNIX 上使用 cp 命令复制文件,而在 Windows 上则使用 copy 命令。更糟糕的是,在不同的平台上,文件名经常会发生变化。 Windows 上的可执行文件以 .exe 结尾,而 UNIX 上不是。即使在 UNIX 实现之间也存在差异,例如共享库使用哪些扩展: .so , .sl , .dylib 等。
CMake 提供了三个主要工具来处理这些差异。第一个是 cmake 的 -E 选项( execute 的缩写)。当向 cmake 可执行文件传递 -E 选项时,它充当通用的跨平台实用工具命令。选项 -E 后面的参数表示 cmake 应该做什么。这些选项提供了一个与平台无关的方式来执行一些常规任务,包括复制或删除文件,比较和有条件地复制(文件),计时,创建符号链接等。 cmake 可执行文件可以在你的 CMakeLists 文件中通过 CMAKE_COMMAND 变量来被使用,后面的例子将会展示。
当然, CMake 在所有自定义命令中都不限制你仅使用 cmake -E 。你可以使用任何喜欢的命令,但是在使用时考虑可移植性问题很重要。一种常见的做法是使用 find_program 查找可执行文件(例如 Perl ) ,然后在自定义命令中使用该可执行文件。
CMake 提供的第二个解决可移植性问题的工具是描述平台特征的一些变量。 cmake-variables 手册列出了许多变量,这些变量对于需要引用具有平台相关名称的文件的自定义命令很有用。其中包括 CMAKE_EXECUTABLE_SUFFIX , CMAKE_SHARED_LIBRARY_PREFIX 等,它们描述了文件命名约定。
最后, CMake 支持自定义命令中的生成器表达式。这些表达式使用特殊语法 $<…> ,在处理 CMake 输入文件时(这些表达式)并不被计算,而是延迟到最终构建系统的生成(时刻才计算其值)。因此,替换它们的值知道它们的计算上下文的所有细节,包括当前构建配置和与目标相关的所有构建属性。
11.2 添加自定义命令
现在我们将考虑 add_custom_command 签名。在 Makefile 术语中, add_custom_command 向 Makefile 中添加一条规则。对于那些更熟悉 Visual Studio 的人来说,它向文件添加了一个自定义构建步骤。 add_custom_command 有两个主要签名:一个用于向目标添加自定义命令,另一个用于添加用于构建文件的自定义命令。
目标是要向其中添加定制命令的 CMake 目标(可执行文件、库或自定义)的名称。可以选择何时执行自定义命令(通过设置 PRE_BUILD 、 PRE_LINK 、 POST_BUILD 等标识)。你可以为自定义命令指定任意多的命令。它们将按照指定的顺序执行。
现在让我们考虑一个简单的自定义命令,用于在构建完可执行文件后复制它。
# 首先常规地定义可执行目标
add_executable(Foo bar.c)
# 然后添加自定义命令进行复制
add_custom_command(
TARGET Foo
POST_BUILD
COMMAND ${CMAKE_COMMAND}
ARGS -E copy $<TARGET_FILE:Foo> /testing_department/files
)
本例中的第一个命令是用于从源文件列表创建可执行文件的标准命令。在这种情况下,从源文件 bar.c 创建一个名为 Foo 的可执行文件。接下来是 add_custom_command 调用。这里的目标是简单的 Foo ,我们添加了一个后构建命令。要执行的命令是 cmake ,它的完整路径在 CMAKE_COMMAND 变量中指定。它的参数是 -E copy 以及源和目标位置。在本例中,它将从构建 Foo 可执行文件的地方将其复制到 /testing_department/files 目录中。注意, TARGET 参数接受 CMake 目标(本例中为 Foo ),但是指定给 COMMAND 形参的实参通常需要可执行文件的全路径。在本例中,我们通过 $<TARGET_FILE:…> 生成器表达式将的可执行文件的完整路径传递给 cmake -E copy 。
11.3 生成一个文件
add_custom_command 的第二个用途是添加如何构建输出文件的规则。这里提供的规则将取代用于构建该文件的任何当前规则。请记住, add_custom_command 输出必须由同一作用域内的目标使用。正如后面所讨论的, add_custom_target 命令可以用于此目的。
11.3.1 使用一个可执行文件来构建一个源文件
有时,软件项目构建一个可执行文件,然后使用该可执行文件生成源文件,源文件又用于构建其他可执行文件或库。这听起来可能很奇怪,但却经常发生。一个例子是 TIFF 库的构建过程,它创建一个可执行文件,然后运行该可执行文件生成一个源文件,其中包含特定于系统的信息。然后将此文件用作构建主 TIFF 库的源文件。另一个例子是可视化工具包( VTK ),它构建了一个名为 vtkWrapTcl 的可执行文件,将 C++ 类封装为 Tcl 。可执行文件被构建,然后用于为构建过程创建更多的源文件。
###################################################
# 测试使用编译后的程序来创建文件
####################################################
# 添加将创建文件的可执行文件
# 从 creator.cxx 构建 creator 可执行文件
add_executable(creator creator.cxx)
# 添加自定义命令以生成 created.c
add_custom_command(
OUTPUT ${PROJECT_BINARY_DIR}/created.c
DEPENDS creator
COMMAND creator ${PROJECT_BINARY_DIR}/created.c
)
# 添加一个使用 created.c 的可执行文件
add_executable(Foo ${PROJECT_BINARY_DIR}/created.c)
本示例的第一部分从源文件 creator.cxx 生成 creator 可执行文件。然后,自定义命令通过运行 creator 可执行文件应用规则来生成源文件 create.c 。自定义命令依赖于 creator 目标,并将其结果写入输出树( PROJECT_BINARY_DIR )。最后,添加一个名为 Foo 的可执行文件目标,它是使用 created.c 源文件构建的。 CMake 将在 Makefile (或 Visual Studio 工作区)中创建所有所需的规则,以便在构建项目时首先构建 created.c 可执行文件,并运行它来创建 create.c ,然后使用 create .c 构建 Foo 可执行文件。
11.4 添加自定义目标
在使用自定义命令和自定义目标时,通常需要指定依赖项。在指定依赖项或自定义命令的输出时,应该始终指定完整路径。例如,如果该命令在二进制树中生成 foo.h ,那么它的输出应该类似于 ${PROJECT_BINARY_DIR}/foo.h 。如果未指定, CMake 将尝试确定文件的正确路径。复杂的项目通常会在源码树和构建树中同时使用文件,如果没有指定完整的路径,这最终会导致错误。
当将目标指定为依赖项时,你可以省略完整的路径和可执行扩展,只通过其名称引用它。在本章前面的示例中,将生成器目标的规范视为 add_custom_command 的依赖项。 CMake 将 creator 识别为匹配现有目标并正确处理依赖项。
11.5 当规则与输出不是一对一
在使用自定义命令时,可能会出现一些不寻常的情况,需要进一步解释。第一种情况是一个命令(或可执行文件)可以创建多个输出,第二种情况是可以使用多个命令创建单个输出。
11.5.1 一个命令产生多个输出
在 CMake 中,自定义命令可以通过在 OUTPUT 关键字后面列出多个输出来生成多个输出。 CMake 将为构建系统创建正确的规则,以便无论目标需要哪个输出,都将运行正确的规则。如果可执行文件碰巧产生了一些输出,但构建过程只使用其中的一个输出,那么在创建自定义命令时,可以简单地忽略其他输出。假设可执行文件生成一个在构建过程中使用的源文件,以及一个未使用的执行日志。自定义命令应该指定源文件作为输出,并忽略还生成了日志文件这一事实。
一个命令有多个输出的另一种情况是,命令是相同的,但它的参数改变了。这实际上等同于使用不同的命令,并且每种情况都应该有自己的自定义命令。这方面的一个例子是文档示例,其中为每个 .tex 文件添加了一个自定义命令。命令是相同的,但传递给它的参数每次都不同。
11.5.2 同一个输出可以由不同的命令生成
在极少数情况下,你可能会发现可以使用多个命令来生成一个输出。大多数构建系统,如 make 和 Visual Studio ,不支持这一点, CMake 也不支持。有两种常用的方法可以用来解决这个问题。如果你确实有两个不同的命令,它们产生相同的输出,而没有其他重要的输出,那么你可以简单地选择其中一个命令并为其创建一个自定义命令。
在更复杂的情况下,有多个命令和多个输出。例如:
Command1 produces foo.h and bar.h
Command2 produces widget.h and bar.h
在这种情况下,有几种方法可以使用。你可以考虑将这两个命令和所有三个输出组合到一个定制命令中,以便在需要一个输出时,同时构建所有三个输出。你还可以创建三个自定义命令,每个命令对应一个惟一的输出。 foo.h 的自定义命令将调用 Command1 ,而 widget.h 的自定义命令将调用 Command2 。在为 bar.h 指定自定义命令时,可以选择 Command1 或 Command2 。