我仍然不完全理解使用源代码配置具有深层嵌套文件夹层次结构的 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()公开它。我在这里想念什么?
subdirs
是添加子目录的旧方法,它已被弃用。该CMake的文档说使用add_subdirectory
来代替。的行为与subdirs
有点不同,add_subdirectory
因为前者也在CMakeLists.txt
子目录中搜索文件并自动添加它们。
有几种方法可以管理像您描述的场景这样的深层层次结构。按照我个人的喜好顺序:
方法 1:
这需要 CMake 3.1 或更高版本,因为它使用target_sources
命令。您可以在顶层定义可执行目标,然后target_sources
在每个子目录中调用以向其中添加源。您还可以根据需要在每个子目录中使用target_include_directories
andtarget_compile_definitions
而不是在顶层调用add_definitions
andinclude_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_subdirectory
as well 以便我们的变量总是在相同的范围内被操作。由于在拉入子目录之后才定义目标,因此您也不能使用target_compile_definitions
or 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。缺点取决于你的观点。这将导致定义更多的目标,并且可能会降低并行构建的有效性。对于少数子目录,我建议这种方法实际上可能是最好的,但是一旦超出几个子目录,事情很快就会开始失控。