【问题标题】:Keeping file hierarchy across subdirectories in CMake在 CMake 中跨子目录保持文件层次结构
【发布时间】:2015-07-21 12:00:17
【问题描述】:

到目前为止,我仍然不太了解对于具有许多子目录的 CMake 项目执行此操作的“最佳实践”是什么。

假设我有一个项目层次结构,每个子目录中都有源文件......

--CMake Project Source dir
 |-- SubD1
   |-- SubSubD1
 |-- SubD2

我通常会做的是add_subdirectory(SubD1)和分别对根目录的CMakeLists.txt中的D2和SubD1目录的CMakeLists.txt中的子目录进行递归,同时在每个子目录中声明变量并制作它们在根目录中可见,PARENT_SCOPE

这意味着如果 `SubSubD1' 中存在文件 Source2.cpp,我会这样做

set(SUBSUBD1_SOURCES Source2.cpp PARENT_SCOPE)

并希望能够在我的SubD1 目录中使用 SUBSUBD1_SOURCE。 随后,说 Source.cpp 存在于 SubD1 中,我会这样做

set(SUBD1_SOURCES ${SUBSUBD1_SOURCES} Source.cpp PARENT_SCOPE)

以便所有源在根目录中可见。

问题是当然是当变量到达根目录时文件路径没有保留。我目前正在做的是我set 的所有源文件,我包括${CMAKE_CURRENT_LIST_DIR},使其成为

set(SUBSUBD1_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Source2.cpp PARENT_SCOPE)

set(SUBD1_SOURCES ${SUBSUBD1_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/Source.cpp PARENT_SCOPE)

在这种情况下,我可以说,在我的 CMake 项目的根目录中执行 add_executable(myProg SUBSUBD1_SOURCES)

有没有更好的方法来做到这一点,然后必须总是在所有源文件前面包含一个 CMake 变量?

【问题讨论】:

    标签: c++ cmake


    【解决方案1】:

    如果您使用的是较新版本的 CMake,还有第四种方法。

    看看target_sources() CMake的命令。

    您似乎在 CMakeLists.txt 中声明了您的目标

    add_executable(my_target "subd1/CMakeLists.txt" "subd2/CMakeLists.txt")
    add_subdirectory(subd1)
    add_subdirectory(subd2)
    

    您可以依赖在根 CMakeLists.txt 中定义的目标,而不是将源文件传播到根。这意味着 subd1/CMakeLists.txt 可能看起来像:

    target_sources(my_target PRIVATE "subd1/Source.cpp" "subd1/Source2.cpp")
    

    [编辑]

    如 cmets 中所述,您必须将源文件的相对路径指定为 target_sources()。我使用target_sources(),因为我不希望显式源文件列表污染目标 CMakeLists.txt。另一个用例是 target_sources() 可以使用 PUBLIC 或 INTERFACE 关键字调用,以将源文件传播到依赖的目标。好吧,我从来没有那样用过target_sources()

    [/编辑]

    如果您使用像 Visual Studio 这样支持文件夹的 IDE,您还想在包含您的目标的 CMakeLists.txt 中声明 source_group()。所以根 CMakeLists.txt 可能看起来像:

    add_executable(my_target "subd1/CMakeLists.txt" "subd2/CMakeLists.txt")
    add_subdirectory(subd1)
    add_subdirectory(subd2)
    ...
    source_group(subd1 REGULAR_EXPRESSION "subd1/*")
    source_group(subd2 REGULAR_EXPRESSION "subd2/*")
    

    我使用这种方法是因为它会导致 CMakeLists.txt 文件更清晰,工作量更少,而且我认为引入不需要的变量只会增加 CMakeLists.txt 文件的复杂性。

    CMakeLists.txt 作为目标源

    我目前使用子文件夹的 CMakeLists.txt 作为目标的源文件,否则 CMake 会抱怨 add_executable 命令没有给出源文件。

    【讨论】:

    • 一个有效的解决方案。我认为不同之处在于,在target_source() 案例中,您通过CMakeLists.txt 文件的所有级别传播主要目标的名称,而在OBJECT 库用例中,主要目标需要知道子级别目标名称。我个人更喜欢第二种情况,因为我的子级 CMake 文件在其他项目中的可重用性。一点点玩后的一个提示:我认为您仍然需要在子级 CMake 文件中添加 ${CMAKE_CURRENT_SOURCE_DIR} 前缀。 target_sources() 好像不是自己做的。
    • 你说得对,我必须添加路径。我已经编辑了我的答案并添加了一些激励我使用 target_sources() 的论点。我确实使用 add_library() 和 target_link_library() 来处理我的多模块项目中的依赖关系。也许我将来会尝试对象库的方法。
    • 我只是想知道它是否有助于“增强”target_sources() 前缀路径。这将消除您必须手动为源添加前缀的缺点。这是代码:function(target_sources _target) | foreach(_arg IN ITEMS ${ARGN}) | if ("${_arg}" MATCHES "(INTERFACE|PUBLIC|PRIVATE)" OR IS_ABSOLUTE "${_arg}") | list(APPEND _new_args "${_arg}") | else() | list(APPEND _new_args "${CMAKE_CURRENT_LIST_DIR}/${_arg}") | endif() | endforeach() | _target_sources(${_target} ${_new_args}) | endfunction(target_sources)
    • 至少在某些平台上,您可以简单地将顶级 CMakeLists.txt 文件调用中的源列表的空字符串传递给 add_executable()。假设所有平台的行为都是这样,您可以避免像这个答案当前那样添加任何任意文件,例如子目录的 CMakeLists.txt 。目前我只在 OS X 上使用 CMake 3.4.1 对此进行了测试。
    【解决方案2】:

    我以前用过3种方法。我通常更喜欢第一种方式,但根据用例已经使用了所有三种方式:

    1.您直接在根CMakeLists.txt 文件中命名源

    set(
        SUBD1_SOURCES
        "SubD1/SubSubD1/Source2.cpp"
        "SubD1/Source.cpp"
    )
    
    set(
        SUBD2_SOURCES
        "SubD2/Source3.cpp"
    )
    
    add_executable(myProg ${SUBD1_SOURCES} ${SUBD2_SOURCES})
    

    2.您使用OBJECT 中间libraries 来收集/分组您​​的来源

    SubD1/SubSubD1/CMakeLists.txt:

    add_library(SubSubD1Objs OBJECT Source2.cpp)
    

    SubD1/CMakeLists.txt:

    add_subdirectory(SubSubD1)
    add_library(SubD1Objs OBJECT Source.cpp)
    

    CMakeLists.txt:

    add_executable(myProg $<TARGET_OBJECTS:SubSubD1Objs> $<TARGET_OBJECTS:SubD1Objs>)
    

    3.你自己写function()来收集数据(并做前缀)

    CMakeLists.txt:

    function(my_collect_sources)
        foreach(_source IN ITEMS ${ARGN})
            if (IS_ABSOLUTE "${_source}")
                set(source_abs "${_source}")
            else()
                get_filename_component(_source_abs "${_source}" ABSOLUTE)
            endif()
            set_property(GLOBAL APPEND PROPERTY GlobalSourceList "${_source_abs}")
        endforeach()
    endfunction(my_collect_sources)
    
    add_subdirectory(SubD1)
    #add_subdirectory(SubD2)
    
    get_property(MY_SOURCES GLOBAL PROPERTY GlobalSourceList)
    add_executable(myProg ${MY_SOURCES})
    

    SubD1/CMakeLists.txt:

    add_subdirectory(SubSubD1)
    my_collect_sources(Source.cpp)
    

    SubD1/SubSubD1/CMakeLists.txt:

    my_collect_sources(Source2.cpp)
    

    【讨论】:

    • 如果您提供一些示例,这将是一个很好的答案。特别是对于我通常推荐的#1 和#3。
    • 我认为你的第二种方法是最好的。它在概念上就像我目前在我的项目中使用的那样工作。当我开始使用 CMake 时,我使用了您的第一种方法,但对它并不满意。您似乎对 CMake 有很多经验。您知道我在回答中描述的 target_sources 方法有什么缺点吗?我认为它是在 2.8.xx 左右引入的,因此许多旧项目可能会坚持使用旧方法。
    • @epicbrew 你是对的。我添加了示例并合并了我之前的方法 #2 和 #4(看到它们属于一起)。所以现在是你推荐的#1 和#2(抱歉重新编号)。有时 #2 有它的优势,因为您可以为子目标/目录赋予它们自己的属性(无需在源文件级别执行此操作)。
    • @DarthB 我仍然主要使用 CMake 2.8.10,所以我忘了提及 CMake 3.1 引入的新命令 target_sources()。您的方法是另一种有效的可能性(我认为需要考虑一些细微的使用差异;我会在您的答案中添加评论)。我认为在您决定采取何种方法之前要回答的主要问题是何时打开一个新的范围是有意义的。例如。如果其中一个子目录带有自己的单元测试,则暗示它有自己的一组独特功能,并且有资格拥有自己的库目标。
    【解决方案3】:

    在您的情况下,无需使用add_subdirectory,因为您只有一个在根CMakeLists.txt 中创建的目标。你可以这么写:

    add_executable(myProg
        SubD1/Source.cpp
        SubD1/SubSubD1/Source2.cpp)
    

    add_subdirectory 用于创建自己的目标的子目录,这样就没有向上传递的信息。

    【讨论】:

    • 他的例子就是这样。他正在寻找解决大局的方法。
    • 我以为他需要根目录下的子目录文件,因为他想在根目录下创建目标。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-02
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 2012-11-04
    • 2016-07-08
    相关资源
    最近更新 更多