在CMake中保持跨子目录的文件层次结构

mer*_*ial 6 c++ cmake

到目前为止,我仍然不明白为具有许多子目录的CMake项目执行此操作的"最佳实践"是什么.

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

--CMake Project Source dir
 |-- SubD1
   |-- SubSubD1
 |-- SubD2
Run Code Online (Sandbox Code Playgroud)

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

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

set(SUBSUBD1_SOURCES Source2.cpp PARENT_SCOPE)
Run Code Online (Sandbox Code Playgroud)

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

set(SUBD1_SOURCES ${SUBSUBD1_SOURCES} Source.cpp PARENT_SCOPE)
Run Code Online (Sandbox Code Playgroud)

这样所有来源都可以在根目录中看到.

问题当然是当变量到达根目录时不保留文件路径.什么我目前做的是所有的源文件,我set,我包括${CMAKE_CURRENT_LIST_DIR},使它

set(SUBSUBD1_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Source2.cpp PARENT_SCOPE)
Run Code Online (Sandbox Code Playgroud)

set(SUBD1_SOURCES ${SUBSUBD1_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/Source.cpp PARENT_SCOPE)
Run Code Online (Sandbox Code Playgroud)

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

有没有更好的方法来执行此操作,然后必须始终在所有源文件前包含CMake变量?

Flo*_*ian 6

我之前使用过3种方法.我通常更喜欢第一种方式,但根据用例已经使用了全部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})
Run Code Online (Sandbox Code Playgroud)

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

SubD1/SubSubD1 /的CMakeLists.txt:

add_library(SubSubD1Objs OBJECT Source2.cpp)
Run Code Online (Sandbox Code Playgroud)

SubD1 /的CMakeLists.txt:

add_subdirectory(SubSubD1)
add_library(SubD1Objs OBJECT Source.cpp)
Run Code Online (Sandbox Code Playgroud)

的CMakeLists.txt:

add_executable(myProg $<TARGET_OBJECTS:SubSubD1Objs> $<TARGET_OBJECTS:SubD1Objs>)
Run Code Online (Sandbox Code Playgroud)

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

SubD1 /的CMakeLists.txt:

add_subdirectory(SubSubD1)
my_collect_sources(Source.cpp)
Run Code Online (Sandbox Code Playgroud)

SubD1/SubSubD1 /的CMakeLists.txt:

my_collect_sources(Source2.cpp)
Run Code Online (Sandbox Code Playgroud)


Dar*_*thB 6

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

看看CMake的target_sources()命令.

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

add_executable(my_target "subd1/CMakeLists.txt" "subd2/CMakeLists.txt")
add_subdirectory(subd1)
add_subdirectory(subd2)
Run Code Online (Sandbox Code Playgroud)

您可以依赖于在根CMakeLists.txt中定义的目标,而不是将源文件传播到根目录.这意味着subd1/CMakeLists.txt可能如下所示:

target_sources(my_target PRIVATE "subd1/Source.cpp" "subd1/Source2.cpp")
Run Code Online (Sandbox Code Playgroud)

[编辑]

如注释中所述,您必须将源文件的相对路径赋予target_sources().我使用target_sources()因为我不希望显式源文件列表污染目标CMakeLists.txt.另一个用例是可以使用PUBLIC或INTERFACE关键字调用target_sources(),以将源文件传播到依赖目标.好吧,我从未使用过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/*")
Run Code Online (Sandbox Code Playgroud)

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

CMakeLists.txt作为目标来源

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