【问题标题】:CMake: configure deep sub-folder treeCMake:配置深层子文件夹树
【发布时间】:2016-04-15 00:56:06
【问题描述】:

我仍然不完全理解使用源代码配置具有深层嵌套文件夹层次结构的 CMake 项目的正确方法是什么。这里,为 MSVC 2013 进行配置

以下是此类项目的示例:

+RootFolder(no code)
    +NestedFolder1(no code)
        +NestedNestedFolder1(has code .h and .cpp)
        +NestedNestedFolder2(has code and nested folder with code)
             +NestedNestedNestedFolder1(has code .h and .cpp)

这就是我在 CMake 中的配置方式:

RootFolder CMakeLists:

project(MyDemoCmakeProject)
add_subdirectory(NestedFolder1)

NestedFolder1 CMakeLists:

 add_subdirectory(NestedNestedFolder1)
 add_subdirectory(NestedNestedFolder2)     
 add_definitions(.....)
 include_directories(.....)

NestedNestedFolder1 CMakeLists:

 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })

NestedNestedFolder2 CMakeLists:

 subdirs(NestedNestedNestedFolder1)
 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })

NestedNestedNestedFolder1 CMakeLists:

 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })

运行 CMake 时使用此配置,我得到:

add_library 无法创建目标“MyLib”,因为另一个目标 同名已存在。现有目标是静态库 在源目录 NestedNestedFolder1 中创建

现在,如果我删除 add_subdirectory(NestedNestedFolder1) add_subdirectory(NestedNestedFolder2)

从 NestedFolder1 CMakeLists.txt 它工作正常。我不完全理解这一刻。例如在 NestedNestedFolder2 内,我也为嵌套的子目录做 add_subdirectory 但它没有抱怨。另外我不明白 CMake如果我不 add_subdirectory() ,则能够“显示” NestedFolder1 的子目录。从不同的示例中,我了解到每个具有源子目录的目录都必须使用subdirs()add_subdirectory() 公开它。做什么我想念这里?

【问题讨论】:

    标签: visual-c++ cmake


    【解决方案1】:

    问题是您有多个具有相同名称的目标:“MyLib”。只需给每个库一个唯一的名称,它就可以工作。

    【讨论】:

    • 那些不是不同的库。我正在尝试从所有这些子文件夹构建一个库
    • 我认为这是将源代码从嵌套文件夹添加到库的方法:/
    • 如果嵌套文件夹仅包含源代码,则无需向其中添加 CMakeLists.txt。只需在 NestedFolder 目录中添加源文件: set(sources NestedNestedFolder1/file1.cpp ... NestedNestedNestedFolder1/filen.cpp)
    【解决方案2】:

    subdirs 是添加子目录的旧方法,已被弃用。 CMake docs 说要改用 add_subdirectorysubdirs 的行为与add_subdirectory 有点不同,因为前者也在子目录中搜索CMakeLists.txt 文件并自动添加它们。

    有几种方法可以像您描述的场景那样管理深层层次结构。按照我个人的喜好顺序:

    方法 1:

    这需要 CMake 3.1 或更高版本,因为它使用 target_sources 命令。您可以在顶层定义可执行目标,然后在每个子目录中调用 target_sources 以向其中添加源。您还可以根据需要在每个子目录中使用target_include_directoriestarget_compile_definitions,而不是在顶层调用add_definitionsinclude_directories 以使这些依赖项附加到目标而不是应用到所有内容(只有在您稍后将更多内容添加到您的顶级 CMakeLists.txt 文件中)。对于您的场景,它看起来类似于以下内容(为了说明目的,我插入了虚拟源文件名并编译了定义):

    RootFolder CMakeLists:

    project(MyDemoCmakeProject)
    
    # add_library() requires a source file argument to be
    # present, but it can be an empty string if there are
    # no source files, headers, etc. that should be added
    # from this directory.
    add_library(MyLib STATIC "")
    add_subdirectory(NestedFolder1)
    

    NestedFolder1 CMakeLists:

    add_subdirectory(NestedNestedFolder1)
    add_subdirectory(NestedNestedFolder2)
    

    NestedNestedFolder1 CMakeLists:

    target_sources(MyLib PRIVATE
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1a.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1b.cpp"
    )
    target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
    

    NestedNestedFolder2 CMakeLists:

    add_subdirectory(NestedNestedNestedFolder1)
    target_sources(MyLib PRIVATE
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2a.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2b.cpp"
    )
    target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
    target_compile_definitions(MyLib PUBLIC -DSOME_VAL=42)
    

    NestedNestedNestedFolder1 CMakeLists:

    target_sources(MyLib PRIVATE
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1a.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1b.cpp"
    )
    target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
    

    这种方法的优点是每个子目录都保持独立。上层不必知道下层在做什么。此外,在信息最相关/已知的级别将信息添加到目标。一个缺点是您必须添加每个源及其完整路径,但这是一个小小的不便。

    方法 2:

    不使用target_sources,而是使用一个变量来累积源文件名,并在包含所有子目录后在顶层添加该变量的内容。我们也使用include 而不是add_subdirectory,这样我们的变量总是在同一个范围内被操作。由于直到拉入子目录之后才定义目标,因此您也不能使用target_compile_definitionstarget_include_directories,因此这些也必须通过变量来处理。

    RootFolder CMakeLists:

    project(MyDemoCmakeProject)
    
    set(MyLib_SOURCES)
    set(MyLib_INCLUDE_DIRS)
    set(MyLib_DEFINITIONS)
    include(NestedFolder1/CMakeLists.txt)
    add_library(MyLib STATIC ${MyLib_SOURCES})
    target_include_directories(MyLib PUBLIC ${MyLib_INCLUDE_DIRS})
    target_compile_definitions(MyLib PUBLIC ${MyLib_DEFINITIONS})
    

    NestedFolder1 CMakeLists:

    include(NestedNestedFolder1/CMakeLists.txt)
    include(NestedNestedFolder2/CMakeLists.txt)
    

    NestedNestedFolder1 CMakeLists:

    list(APPEND MyLib_SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1a.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN1b.cpp"
    )
    list(APPEND MyLib_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")
    

    NestedNestedFolder2 CMakeLists:

    include(NestedNestedNestedFolder1/CMakeLists.txt)
    list(APPEND MyLib_SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2a.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNN2b.cpp"
    )
    list(APPEND MyLib_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")
    list(APPEND MyLib_DEFINITIONS -DSOME_VAL=42)
    

    NestedNestedNestedFolder1 CMakeLists:

    list(APPEND MyLib_SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1a.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/fileNNN1b.cpp"
    )
    list(APPEND MyLib_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")
    

    这不如方法 1 的主要原因是通过 in 变量携带所有源、头文件路径和编译定义使其更加脆弱。所有子目录都必须很好地发挥作用,并且不能对其他任何东西使用相同的变量名(小问题),并且处理带有嵌入空格的路径需要更多小心(更棘手的问题)。另一个缺点是必须更仔细地处理变量范围。任何add_subdirectory 调用都会改变范围。与方法 1 相比,此方法的一个优点是它适用于更旧版本的 CMake。

    方法 3:

    另一种选择是在每个子目录级别定义一个库,并针对它们建立顶级库链接。这些子目录级别的库可以是静态库或共享库,也可以是对象库(这需要更新的 CMake 版本)。这种方法的优点是它甚至适用于旧版本的 CMake。缺点取决于你的观点。这将导致定义的目标数量更多,并且可能会降低并行构建的有效性。对于少数子目录,我建议这种方法实际上可能是最好的,但是一旦超出了几个子目录,事情很快就会失控。

    【讨论】:

    • 好东西。但是方法 4 怎么样,就像上面提出的 @David Marquant 一样?这在嵌套的 CMakeLists.txt 中是没有必要的。嵌套源的所有路径都在根 CMakeLists.txt 文件中定义。有可能吗?这是一个好习惯吗?
    • 是的,我在发布后意识到我忽略了第四种方法。在顶层包含所有源很简单,但是如果您要这样做,那么它首先会降低拥有子目录的价值。您最终必须在顶层定义所有源,并且它会将子目录结构重复到最深层次。当然在某些情况下这样做是可以的,但是您拥有的文件和子目录越多,这种方法就会变得越混乱。
    • 方法 1 对我来说似乎最干净。
    • @CraigScott 您能否在 target_sources 中的 PRIVATE vs PUBLIC 参数上扩展一点?
    • 查看docs for target_sources 了解详情。简而言之,PRIVATE 使源仅由命名目标编译,而 PUBLIC 也使其由链接到它的任何东西编译。这可能看起来很奇怪,但添加并非真正的源(例如,标题、图像、脚本等)的源可能有一些用途,因为它们应该显示在 Visual Studio、Qt Creator 等 IDE 中的这些目标下. (但我还没有检查/确认这一点)。
    猜你喜欢
    • 1970-01-01
    • 2016-01-30
    • 2017-10-20
    • 1970-01-01
    • 2015-05-18
    • 2017-07-15
    • 1970-01-01
    相关资源
    最近更新 更多