当 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 数组中删除对象