生成可能会或可能不会更新的源文件

Posted

技术标签:

【中文标题】生成可能会或可能不会更新的源文件【英文标题】:Generate a source file that may or may not be updated 【发布时间】:2021-08-26 16:20:36 【问题描述】:

我有一个 CMakeLists.txt,我想在其中生成几个源文件(即 versiondata.cppversion.rc.inc,包含在 res.rc 中),这取决于一般环境(当前 git HEAD,gcc -v 输出, CMakeCache.txt 本身,等等)。

如果它只依赖于某些文件,我将使用带有相关DEPENDSOUTPUT 子句的add_custom_command 指令生成它;然而,精确定位它的 file 依赖关系有点棘手;理想情况下,我希望每次调用make 时都运行我的脚本,仅在需要时更新文件;如果生成的文件实际上已经被触及,那么依赖于它们的目标应该被重建(如果它们的内容与以前相同,脚本会注意不要覆盖文件)。

我的第一次尝试是使用带有假主输出的add_custom_command,如下所示:

add_custom_command(OUTPUT versiondata.cpp.fake versiondata.cpp version.rc.inc
    COMMAND my_command my_options
    COMMENT "Generating versiondata.cpp"
)
# ...
# explicitly set the dependencies of res.rc, as they are not auto-deduced
set_source_files_properties(res.rc PROPERTIES OBJECT_DEPENDS "$PROJECT_BINARY_DIR/version.rc.inc;$PROJECT_SOURCE_DIR/other_stuff.ico")
# ...
add_executable(my_executable WIN32 ALL main.cpp versiondata.cpp res.rc)

versiondata.cpp.fake 从未真正生成,因此始终运行自定义命令。这工作正常,但总是重建 my_executable,因为 CMake 出于某些原因会自动触及输出文件(如果生成),即使我的脚本不理会它们。

然后我想我可以使用add_custom_target 让它工作,这会自动“从未满足过”:

add_custom_target(versiondata BYPRODUCTS versiondata.cpp version.rc.inc
    COMMAND my_command my_options
    COMMENT "Generating versiondata.cpp"
)
# ...
# explicitly set the dependencies of res.rc, as they are not auto-deduced
set_source_files_properties(res.rc PROPERTIES OBJECT_DEPENDS "$PROJECT_BINARY_DIR/version.rc.inc;$PROJECT_SOURCE_DIR/other_stuff.ico")
# ...
add_executable(my_executable WIN32 ALL main.cpp versiondata.cpp res.rc)

这里的想法是versiondata 目标应该从依赖其BYPRODUCTS 的目标中“拉入”,并且应该始终执行。这似乎适用于 CMake 3.20,BYPRODUCTS 似乎有一些效果,因为如果我从 my_executable 中删除依赖项,我的脚本不会被调用。

但是,在 CMake 3.5 上我得到了

make[2]: *** No rule to make target 'version.rc.inc', needed by 'CMakeFiles/my_executable.dir/res.rc.res'.  Stop.

如果我从 version.rc.inc 中删除显式依赖项,它根本不会生成

[ 45%] Building RC object CMakeFiles/my_executable.dir/res.rc.res
/co/my_executable/res.rc:386:26: fatal error: version.rc.inc: No such file or directory
 #include "version.rc.inc"
                          ^
compilation terminated.
/opt/mingw32-dw2/bin/i686-w64-mingw32-windres: preprocessing failed.
CMakeFiles/my_executable.dir/build.make:5080: recipe for target 'CMakeFiles/my_executable.dir/res.rc.res' failed
make[2]: *** [CMakeFiles/my_executable.dir/res.rc.res] Error 1

所以我怀疑这在 3.20 中有效的事实是偶然的。

长话短说:有什么方法可以按我的意愿完成这项工作吗?

【问题讨论】:

“我怀疑它在 3.20 中起作用的事实只是偶然” - 不,这种行为完全是 documented:因为 CMake 3.16 依赖于 BYPRODUCTS 生成 target-级别 依赖。在您的情况下,这是可执行目标 my_executable 和自定义目标 versiondata 之间的依赖关系。您可以通过add_dependencies(my_executable versiondata) 明确指定此依赖项,因此即使在 CMake 3.16 之前的版本中也可以构建。 兼容3.5版绝对没有任何优点 @Tsyvarev 如果为真,很高兴知道,尽管我不会说它是“完全记录在案”:我认为您引用的部分位于 DEPENDS 部分的 add_custom_target 下,其中,正如它所写的,将适用于依赖于副产品的自定义目标,而不是其他类型的目标。如果这适用于一般目标,它应该在BYPRODUCTSIMO 下。那么,明确的add_dependencies 是 3.16 之前 CMake 中的唯一方法吗?我很困惑,虽然我真的不应该使用 CMake。 @AlexReinking:而且,虽然我们这样做了,但使用 CMake 而不是更好的选择也绝对没有任何优点,但我们到了。有时存在复杂的限制,您必须选择较小的邪恶,如果我不遗余力地测试特定的旧 CMake 版本,它可能就是其中一种情况。 任何说你必须使用过时的 CMake 版本的约束都是虚假的,而且有点自虐。你越往后走,你就越会遇到像这样的荒谬问题,基本的事情不能按应有的方式工作,你会浪费时间解决你一开始就不应该遇到的“问题”。 【参考方案1】:

在 CMake 中有两种类型的依赖项:

    目标级别的依赖关系,在目标之间。

    只有在无条件构建了它所依赖的所有目标之后,才能构建目标。

    文件之间的文件级依赖关系。

    如果某个文件比其依赖项之一旧,则该文件将使用对应的COMMAND 重新生成。

关键因素是检查依赖文件时间戳是在构建依赖目标之后严格执行的。

为了正确重新生成versiondata.cpp 文件和基于它的可执行文件,需要两个依赖项:

    目标级别,这将确保versiondata 自定义目标 将在可执行文件之前构建。

    add_dependencies(my_executable versiondata)
    

    文件级,这将确保可执行文件将在任何时候被重建 文件versiondata.cpp 将被更新。

    这个依赖是通过列出versiondata.cpp自动创建的 在可执行文件的来源中。


现在关于副产品。

即使没有明确的add_dependencies,您的代码也可以在 CMake 3.20 上运行,因为 BYPRODUCTS 会自动生成所需的目标级别依赖项。

这可以从add_custom_target/add_custom_command中DEPENDS选项的描述推导出来:

在 3.16 版中更改:如果任何依赖项是目标的副产品或其任何构建事件在同一目录中,则会添加目标级别的依赖项,以确保副产品在此之前可用目标已构建。

请注意,add_executable 实际上依赖于它的每个源文件。

因为给定的 DEPENDS 注释仅适用于 CMake 3.16 及更高版本, 在较旧的 CMake 版本中,BYPRODUCTS 不会自动创建目标级别的依赖项,因此需要使用显式的add_dependencies

【讨论】:

以上是关于生成可能会或可能不会更新的源文件的主要内容,如果未能解决你的问题,请参考以下文章

每个循环可能会或可能不会运行 CDialog 的无限线程

避免 ORA-00904 - 在 java 中进行 sql 查询时出现无效标识符错误,因为该列可能会或可能不会在数据库中预设

Mac OS X 上的异步应用程序间通信

重命名IDEA14项目名

不输出到 STDOUT 的程序的管道输出

将2个表单字段并排放置在引导列中