在 CMake 中使用源代码生成器的正确方法是什么

What is the proper way of using a source generator in CMake

在我的 C++ 项目中,我使用源代码生成器将一些资源嵌入到二进制文件中。

我使用 CMake 来构建我的项目,我的代码可以工作,但有一些问题。 我很确定我想完成的事情是可能的,但我没有在网上找到任何答案。

我目前遇到的问题是:

在这两种情况下(查看输出)生成器在编译器之前运行。

这种情况并非不可持续,但我想知道是否有更好的解决方案。 这是我的 CMakeLists.txt

中的代码片段
add_subdirectory(Generator)
file(GLOB RESOURCES Resources/*)
add_custom_command(
    OUTPUT src/Resources/Generated.hpp src/Resources/Generated.cpp
    COMMAND Generator ${RESOURCES}
    DEPENDS ${RESOURCES}
    DEPENDS Generator
)

add_custom_target(Generated DEPENDS src/Resources/Generated.hpp src/Resources/Generated.cpp)
add_dependencies(${PROJECT_NAME} Generated)

Here's a minimal reproducible example,抱歉之前没有。


编辑:我在同一个仓库的 fix 分支中根据正确答案实现了功能解决方案。它可能对将来的参考有用:)

这个很有趣,因为有 多个 错误和文体问题,它们部分重叠。

首发:

file(GLOB_RECURSE SRC src/*.cpp src/*.hpp)
add_executable(${PROJECT_NAME} ${SRC})

虽然一开始很方便,但遍历您的资源并不是一个好主意。在某些时候,您将在那里有一个 testme.cpp 不应该与其余部分一起构建,或者一个 conditionally_compiled.cpp 应该只在设置了某个选项的情况下编译。您最终编译了您真正不打算编译的资源。

在这种情况下,文件 src/Generated.hpp 来自您的 git 存储库。该文件应该是 生成的 ,而不是从 repo 中检出的。它是怎么进去的?

add_custom_command(
    OUTPUT src/Generated.hpp
    COMMAND ${PROJECT_SOURCE_DIR}/generator.sh
            ${PROJECT_SOURCE_DIR}/Resources/data.txt
            > ${PROJECT_SOURCE_DIR}/src/Generated.hpp
    DEPENDS Resources/data.txt
    DEPENDS ${PROJECT_SOURCE_DIR}/generator.sh
)

你看到那里的输出重定向了吗?您写信给 ${PROJECT_SOURCE_DIR}。那不是一个好主意。你的源代码树永远不应该有任何编译或生成的东西。因为这些事情最终会与其他事情一起发生......就像发生在你身上一样。

下一期:

add_custom_target(Generated DEPENDS src/Generated.hpp)

这将创建一个 make 目标 Generated。试试看:make Generated。您不断得到以下输出:

[100%] Generating src/Generated.hpp
[100%] Built target Generated

显然它没有意识到 Generated.hpp 已经是 up-to-date。为什么不呢?

让我们再看看您的自定义命令:

add_custom_command(
    OUTPUT src/Generated.hpp
    COMMAND ${PROJECT_SOURCE_DIR}/generator.sh
            ${PROJECT_SOURCE_DIR}/Resources/data.txt
            > ${PROJECT_SOURCE_DIR}/src/Generated.hpp
    DEPENDS Resources/data.txt
    DEPENDS ${PROJECT_SOURCE_DIR}/generator.sh
)

如果我告诉你你的 OUTPUT 从未真正生成过呢?

引用自CMake docs on add_custom_command,强调我的:

OUTPUT

Specify the output files the command is expected to produce. If an output name is a relative path it will be interpreted relative to the build tree directory corresponding to the current source directory.

所以你的输出声称是 binary 树,但你的命令重定向到 source 树......难怪生成器不断得到 re-run.

在错误的位置生成 header 不会给您带来 编译器 错误,因为 header 来自您的 树被你的 GLOB_RECURSE 捡起。随着那个不断得到 re-generated,您的可执行文件也不断得到重新编译。

从你的构建目录试试这个:

mkdir src && touch src/Generated.hpp && make Generated

输出:

[100%] Built target Generated

您看到 makeGenerated 目标无关,因为它现在看到您的自定义命令的 OUTPUT 比其依赖项更新。当然,touched 文件不是生成的文件;我们需要将它们整合在一起。


解决方案

不要写入您的源代码树。

由于 ${PROJECT_BINARY_DIR}/src 不存在,您需要创建它,或者将创建的文件放在顶层目录中。为了简单起见,我在这里做了后者。我还删除了不必要的 ${PROJECT_SOURCE_DIR} 用途。

add_custom_command(
    OUTPUT Generated.hpp
    COMMAND ${PROJECT_SOURCE_DIR}/generator.sh \
            ${PROJECT_SOURCE_DIR}/Resources/data.txt \
            > Generated.hpp
    DEPENDS Resources/data.txt
    DEPENDS generator.sh
)

不要 glob,控制实际编译的内容:

add_executable( ${PROJECT_NAME} src/main.cpp )

将二叉树添加到目标的包含路径中。 add_executable之后:

target_include_directories( ${PROJECT_NAME} PRIVATE ${PROJECT_BINARY_DIR} )

就是这样,现在一切正常。