当 CMake 中的生成器或输入发生更改时,如何仅构建自动生成的代码?

Posted

技术标签:

【中文标题】当 CMake 中的生成器或输入发生更改时,如何仅构建自动生成的代码?【英文标题】:How to only build auto generated code when the generator or input changes in CMake? 【发布时间】:2014-11-29 08:17:35 【问题描述】:

我正在开发一个源代码存储库,该存储库通过运行 python 脚本输出标头和实现来生成一些 C++ 代码。此代码随后被编译并链接到我的库和可执行文件。我知道只有当两个条件之一为真时,生成的代码才会改变:

    生成器代码本身发生变化 生成器的输入(XML 文件)发生变化

我想使用 cmake 来管理构建过程。目前,我正在使用execute_process 来关闭发电机。但是,每次我运行 cmake 时都会运行它并且它会触及文件,导致我生成的代码被重新编译并增加了我的总编译时间。

我还想确保生成的代码始终在我的库之前运行。换句话说,我希望库依赖于生成器来运行。

在 cmake 中处理这种情况的正确方法是什么?我已经看到了这个先前的答案:“Get CMake to execute a target in project before building a library”。但这依赖于预先知道的代码生成器的输出。我的代码生成器将生成可变数量的文件。

【问题讨论】:

causing my generated code to be recompiled 那么您可能需要改进您的生成器吗? The generator code itself changes configure_file 可以解决问题:***.com/a/26076132/2288008 @ruslo:只是为了检查我是否理解你的意思......我会让代码生成器以 CMAKE 可以读取的格式吐出它生成的文件列表? (一个 SET (foo1 foo2) 列表)... @ruslo:另一件事。我知道生成的文件最终在哪里,并且将它们包含在最终的可执行文件中目前是使用文件(GLOB)完成的。我可以继续这样做吗? 【参考方案1】:

使用ADD_CUSTOM_COMMAND 触发您的生成器。它允许您定义输入和输出依赖关系,并且仅当输出比输入更早时才会运行。

ADD_CUSTOM_COMMAND( OUTPUT generatedfile1 generatedfile2
                    COMMAND python generateSources.py xmlfile1 xmlfile2
                    DEPENDS xmlfile1 xmlfile2 generateSources.py 
                    COMMENT "Generating source code from XML" )

确保生成的文件不会在多个可能并行编译的独立目标中使用,否则您可能(将)在构建过程中遇到冲突。为确保这一点,以下应该可以解决问题:

ADD_CUSTOM_TARGET( RunGenerator DEPENDS generatedfile1 generatedfile2 
                   COMMENT "Checking if re-generation is required" )

然后让你的其他目标依赖于这个:

ADD_DEPENDENCIES( MyTarget RunGenerator )

注意:RunGenerator 目标将始终被视为过期,因此始终运行。但是,由于在这种情况下它什么都不做(除了打印注释和检查依赖项),这并不重要。如果需要,自定义命令将负责重新生成。

cmets 后更新:

如果不知道文件名,可以使用

ADD_CUSTOM_COMMAND( OUTPUT generated.timestamp
                    COMMAND python generateSources.py xmlfile1 xmlfile2
                    COMMAND $CMAKE_COMMAND -E touch generated.timestamp
                    DEPENDS xmlfile1 xmlfile2 generateSources.py 
                    COMMENT "Generating source code from XML" )

但是:使用 GLOB 需要您显式运行 CMake 来更新您的文件列表。 将此集成到自定义命令中可能会弄乱您的构建过程(如果多个项目正在并行构建并且一个项目重新启动 CMake 配置)。 IIRC,当您知道 python 脚本或 XML 文件已更改时,您可以手动运行 CMake,但您的问题是当其他任何事情需要重新运行 CMake 时,这些文件会被触及。

如果 python 脚本运行时间不长,你可以让它在每次 CMake 运行时运行(就像你现在做的那样),但要确保未触及未更改的文件,你可以尝试以下方法(未经测试) :

# generated sources files into a temporary directory (adjust your current execute_process)
EXECUTE_PROCESS( COMMAND python ../generateSources.py ../xmlfile1 ../xmlfile2 
                 WORKING_DIRECTORY tmp )

# get the filenames
FILE( GLOB GENERATED_TEMP_FILES tmp/* )

# copy to the "expected" directory, but only if content CHANGED
FOREACH( F $GENERATED_TEMP_FILES )
    GET_FILENAME_COMPONENT( "$F" FN NAME)
    CONFIGURE_FILE( "$F" "./generated/$FN" COPY_ONLY )
ENDFOREACH()

# use your current globbing command
FILE( GLOB GENERATED_SOURCES ./generated/* )

【讨论】:

只是偶然发现了这个***.com/questions/24416133/…(没有尝试重现此),您在尝试ADD_CUSTOM_COMMAND 时可能想要/需要查看它。 我尝试了自定义命令。不幸的是,我事先不知道我会生成哪些文件(所以 OUTPUT 是未知的)。有没有办法解决这个问题? 实际上,我可以修改我的代码生成器以始终输出一个“keystone”文件,该文件可用于跟踪上次运行的时间。之后,我可以为实际的 OUTPUT 归档(GLOB)...或者 GLOB 每次都会运行并导致完全重建? 关于 GLOB:不,FILE(GLOB...) 仅在 cmake 时执行。因此,如果文件列表发生更改,您必须手动调用 cmake。这就是 CMake 建议明确列出相关文件的原因。有什么方法可以让您拥有固定/可确定的文件名? 就像一个魅力。优秀。我认为您的意思是说“add_dependencies”,否则我会在“ADD_DEPENDENCY”上收到 cmake 语法错误。【参考方案2】:

低级解决方案在另一个目录中生成您的文件,将文件与当前文件进行比较,如果它们不同则仅复制,不复制不重新编译。

这当然只有在生成器不填充一些随机噪声(如版本和日期)时才起作用。在这种情况下,您可以尝试将其过滤掉。

【讨论】:

【参考方案3】:

我今天或多或少遇到了这个问题。我将二进制资源嵌入到 c++ 可执行文件中(它是一个嵌入式 Web 服务器)。

这样解决了:

    #get the file timestamp of the 'source' resource file
    FILE(TIMESTAMP "$CMAKE_CURRENT_SOURCE_DIR/$_resource" RESOURCE_TIME)

    #get the file time of the 'template' file
    FILE(TIMESTAMP $TEMPLATE_FILE TEMPLATE_TIME)

    #get the timestamp (if any) of the generated file
    FILE(TIMESTAMP "$CMAKE_CURRENT_BINARY_DIR/$_filename" TARGET_TIME)
....
    #only configure the file if the target is older than either the
    #source or the template
    IF((RESOURCE_TIME > TARGET_TIME) OR (TEMPLATE_TIME > TARGET_TIME) OR (NOT TARGET_TIME))
        MESSAGE(STATUS "configuring $...")
        FILE(READ $_resource RESOURCE_CONTENT HEX)
        string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," RESOURCE_CONTENT $RESOURCE_CONTENT)
        configure_file("$TEMPLATE_FILE"
            "$CMAKE_CURRENT_BINARY_DIR/$_filename"
            @ONLY)
    ELSE()
        MESSAGE(STATUS "already configured $...")
    ENDIF()

【讨论】:

有趣的方法!【参考方案4】:

我们在我工作的地方也遇到过类似的问题。只是我们不使用 cmake,而是使用另一个构建系统。

我不知道 cmake,但这可能会给你几个想法:

我们的解决方案涉及将 xml 和生成器视为需要编译的代码,将生成器视为依赖项,将 xml 文件视为源。如果生成器或 xml 文件发生更改,则构建系统会运行生成器(生成所有内容 - 即使只有一个文件更改,这意味着它会触及所有生成的文件)。

构建系统当然没有直接运行生成器,而是我们编写了一个小的 python 脚本来决定如何正确运行生成器 - 例如,我们可以添加的改进是将所有文件生成到 /tmp 并且只移动更改的文件(使用 diff 比较),因此只会触及更改的文件。 (我们目前不需要它,因为我们的文件不会经常更改)

我们最终还使用两张不同的图表运行了构建系统两次,一张用于生成器,另一张用于其他文件。我们将其设计为允许生成器生成多个级别的依赖项,以便一个生成器可以依赖另一个生成器生成的产品。

另外两个要考虑的技巧,如果您的构建系统允许您使用正则表达式来构建文件,您可能想要使用它。此外,您可以在生成过程中为您的构建系统生成配置文件。

【讨论】:

感谢您的意见。我想知道 cmake 是否有更简单的方法。

以上是关于当 CMake 中的生成器或输入发生更改时,如何仅构建自动生成的代码?的主要内容,如果未能解决你的问题,请参考以下文章

当对象中的字段发生更改时如何从 v-model 数组中删除对象

由于 cmake 项目中的源更改,查找所有受影响的目标

CMake:如何添加仅针对一种配置执行的自定义命令?

CMake 在生成 Makefile 时生成的路径名太长

当对象 Hashcode 更改时,Hashmap 或 Hashset 中的查找会发生啥

当Android中Firebase数据库中的数据发生更改时将数据发送到应用程序[关闭]