CMake:配置深层子文件夹树

Mic*_* IV 3 cmake visual-c++

我仍然不完全理解使用源代码配置具有深层嵌套文件夹层次结构的 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)
Run Code Online (Sandbox Code Playgroud)

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

RootFolder CMakeLists:

project(MyDemoCmakeProject)
add_subdirectory(NestedFolder1)
Run Code Online (Sandbox Code Playgroud)

NestedFolder1 CMakeLists:

 add_subdirectory(NestedNestedFolder1)
 add_subdirectory(NestedNestedFolder2)     
 add_definitions(.....)
 include_directories(.....)
Run Code Online (Sandbox Code Playgroud)

NestedNestedFolder1 CMakeLists:

 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })
Run Code Online (Sandbox Code Playgroud)

NestedNestedFolder2 CMakeLists:

 subdirs(NestedNestedNestedFolder1)
 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })
Run Code Online (Sandbox Code Playgroud)

NestedNestedNestedFolder1 CMakeLists:

 set(mysources ${sources})
 add_library(MyLib STATIC ${mysources })
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Cra*_*ott 8

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

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

方法 1:

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

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)
Run Code Online (Sandbox Code Playgroud)

NestedFolder1 CMakeLists:

add_subdirectory(NestedNestedFolder1)
add_subdirectory(NestedNestedFolder2)
Run Code Online (Sandbox Code Playgroud)

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}")
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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}")
Run Code Online (Sandbox Code Playgroud)

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

方法 2:

不要使用target_sources,而是使用变量来累积源文件名,并在包含所有子目录后在顶层添加该变量的内容。我们使用include而不是add_subdirectoryas well 以便我们的变量总是在相同的范围内被操作。由于拉入子目录之后才定义目标,因此您也不能使用target_compile_definitionsor target_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})
Run Code Online (Sandbox Code Playgroud)

NestedFolder1 CMakeLists:

include(NestedNestedFolder1/CMakeLists.txt)
include(NestedNestedFolder2/CMakeLists.txt)
Run Code Online (Sandbox Code Playgroud)

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}")
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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}")
Run Code Online (Sandbox Code Playgroud)

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

方法 3:

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