subdirs 是添加子目录的旧方法,已被弃用。 CMake docs 说要改用 add_subdirectory。 subdirs 的行为与add_subdirectory 有点不同,因为前者也在子目录中搜索CMakeLists.txt 文件并自动添加它们。
有几种方法可以像您描述的场景那样管理深层层次结构。按照我个人的喜好顺序:
方法 1:
这需要 CMake 3.1 或更高版本,因为它使用 target_sources 命令。您可以在顶层定义可执行目标,然后在每个子目录中调用 target_sources 以向其中添加源。您还可以根据需要在每个子目录中使用target_include_directories 和target_compile_definitions,而不是在顶层调用add_definitions 和include_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_definitions或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})
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。缺点取决于你的观点。这将导致定义的目标数量更多,并且可能会降低并行构建的有效性。对于少数子目录,我建议这种方法实际上可能是最好的,但是一旦超出了几个子目录,事情很快就会失控。