在CMake中将资源目录从源目录复制到构建目录

20

我正在尝试用 C++ 编写一个练习的游戏。我正在使用 CMake 进行此操作,并且我的项目会由我的 IDE 自动构建到一个单独的构建目录中。然而,我的 assets 文件夹并没有被复制到新的目录中。

目前,我正尝试通过以下方式来解决这个问题:

add_custom_command(TARGET ${PROJECT_NAME}
    POST_BUILD
    COMMAND cp -r ${PROJECT_SOURCE_DIR}/assets ${CMAKE_BINARY_DIR}
    )
然而,这似乎对结果没有任何影响,资产目录仍然不存在于我的构建目录中。
我应该如何使其实际上将此资产目录复制到我的构建位置?是否有更聪明的方法来指向我的程序资产位置(也许是可以与/usr/share中的资产一起使用的方法)?
4个回答

21

在进行更多搜索后,我终于找到了file(COPY {files} DESTINATION {directory})命令:

file(COPY assets DESTINATION ${CMAKE_BINARY_DIR})

1
谢谢!我遇到了同样的问题,这正是我正在寻找的。 - emankcin
9
请注意,如果您修改文件,这将不会更新目标。 您应该使用add_custom_target创建资源目标,并使用add_dependencies使程序依赖于它。请注意保持原文意思,尽可能通俗易懂。 - spectras

18

我找到了两种方法,都能确保在目标构建时进行复制,并且即使目标不需要重新构建也能保持最新状态。


第一种方法使用CMake命令行工具copy_directory。从文档中不清楚这个复制操作是否总是发生,无论 assets 目录中的文件是否已更新,或者仅在不存在资产文件或资产文件已更新时才进行复制:

add_custom_target(copy_assets
    COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/assets ${CMAKE_CURRENT_BINARY_DIR}/assets
)
add_dependencies(mytarget copy_assets)

第二种方法需要一个小的额外的CMake脚本文件,但它允许我们使用file(COPY ...) CMake命令,它在文档中说明“复制保留输入文件时间戳,并优化掉具有相同时间戳的目标位置上已存在的文件”。

首先,在与您的CMakeLists.txt相同的目录中创建名为copy-assets.cmake的CMake脚本文件,其内容类似于Ethan McTague的答案。

file(COPY ${CMAKE_CURRENT_LIST_DIR}/assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

接下来在你的CMakeLists.txt中添加以下内容:

add_custom_target(copy_assets
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/copy-assets.cmake
)
add_dependencies(mytarget copy_assets)

1
这可能是最好的解决方案,感谢您包含第二种方法,我可能很快会使用它! - SeedyROM
如果您不在意资产在mytarget之前或之后被复制,可以删除行:add_dependencies(mytarget copy_assets)并将ALL添加到custom_target中,如下所示:add_custom_target(copy_assets ALL) - Riyyi

1

以下是我在Vulkan着色器中所做的。只需将编译步骤替换为复制命令即可:

macro(add_shaders)
    cmake_parse_arguments("MY" "TARGET" "SOURCES" ${ARGN})

    foreach(src ${MY_SOURCES})
        set(OUTF "${CMAKE_CURRENT_BINARY_DIR}/shaders/${src}.spv")
        get_filename_component(PARENT_DIR "${OUTF}" DIRECTORY)
        add_custom_command(
            OUTPUT "${OUTF}"
            COMMAND ${CMAKE_COMMAND} -E make_directory "${PARENT_DIR}"
            COMMAND "${Vulkan_GLSLC_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/${src}" -o "${OUTF}"
            DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${src}"
            VERBATIM
        )
        # src in order to get listed in the IDE, OUTF to declare the build relationship
        target_sources("${MY_TARGET}" PRIVATE "${src}" "${OUTF}")
    endforeach()
endmacro()

add_shaders(TARGET my_target SOURCES asset1 asset2 asset3 ...)

您需要对每个资产单独指定(或循环处理列表)。将通配符包含在构建中是一个不好的主意 [tm]。 这也只会在资产发生更改时复制/处理它们。


1

上面使用add_custom_target的解决方案适用于大多数情况,但存在一些问题。

  • 不支持依赖生成表达式(例如$<TARGET_FILE_DIR:tgt>)
  • 每次构建都重新复制(对于许多/大型文件速度较慢)
  • 不太可读,特别是从多个位置复制

相反,我一直在使用file GENERATE

set(ASSETS
    assetfile
    ...)
foreach(ASSET ${ASSETS})
    file(GENERATE OUTPUT ${ASSET} INPUT ${ASSET})
endforeach()

注意,我目前还没有需要生成器表达式的情况,但是最新版本的CMake(3.19)似乎在这个函数中添加了生成器表达式支持。

不幸的是,这个命令会将你所有的文件都视为文本文件,并可能进行EOL转换和其他你不想对二进制资源(如图像、模型等)进行的操作。 - Alexander Revo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接