Keeping file hierarchy across subdirectories in CM

2019-01-15 10:46发布

问题:

Till date I still do not really understand what the 'best practice' is for doing this for a CMake project with many subdirectories.

Say I have a project hierarchy as such and each subdirectory has source files in it...

--CMake Project Source dir
 |-- SubD1
   |-- SubSubD1
 |-- SubD2

What I would usually do is to do add_subdirectory(SubD1) and respectively for D2 in the CMakeLists.txt of the root directory and recursively for the subdirectory in the CMakeLists.txt of the SubD1 directory, while declaring variables in each subdirectory and making them visible in the root directory with PARENT_SCOPE.

That means if a file Source2.cpp exists in `SubSubD1', I'd simply do

set(SUBSUBD1_SOURCES Source2.cpp PARENT_SCOPE)

and expect to be able to use SUBSUBD1_SOURCE in my SubD1 directory. Subsequently, say Source.cpp exists in SubD1, I would do

set(SUBD1_SOURCES ${SUBSUBD1_SOURCES} Source.cpp PARENT_SCOPE)

so that all sources would be visible in root dir.

The problem is of course that the file paths aren't kept when the variables arrive at the root directory. What I'm currently doing is for all source files that I set, I include a ${CMAKE_CURRENT_LIST_DIR}, making it

set(SUBSUBD1_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Source2.cpp PARENT_SCOPE)

and

set(SUBD1_SOURCES ${SUBSUBD1_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/Source.cpp PARENT_SCOPE)

In this case, I could then say, do add_executable(myProg SUBSUBD1_SOURCES) in the root directory of my CMake project.

Are there any better ways of doing this then having to always include a CMake variable in front of all source files?

回答1:

There are 3 ways I have used before. I normally prefer the 1st way, but have already used all 3 depending on the use case:

1. You directly name the sources in your root CMakeLists.txt file

set(
    SUBD1_SOURCES
    "SubD1/SubSubD1/Source2.cpp"
    "SubD1/Source.cpp"
)

set(
    SUBD2_SOURCES
    "SubD2/Source3.cpp"
)

add_executable(myProg ${SUBD1_SOURCES} ${SUBD2_SOURCES})

2. You use OBJECT intermediate libraries to collect/group your sources

SubD1/SubSubD1/CMakeLists.txt:

add_library(SubSubD1Objs OBJECT Source2.cpp)

SubD1/CMakeLists.txt:

add_subdirectory(SubSubD1)
add_library(SubD1Objs OBJECT Source.cpp)

CMakeLists.txt:

add_executable(myProg $<TARGET_OBJECTS:SubSubD1Objs> $<TARGET_OBJECTS:SubD1Objs>)

3. You write your own function() to collect the data (and do the prefixing)

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})

SubD1/CMakeLists.txt:

add_subdirectory(SubSubD1)
my_collect_sources(Source.cpp)

SubD1/SubSubD1/CMakeLists.txt:

my_collect_sources(Source2.cpp)


回答2:

There is a fourth way if you're using newer versions of CMake.

Take a look at target_sources() command of CMake.

It seems like you are declaring your target in your CMakeLists.txt

add_executable(my_target "subd1/CMakeLists.txt" "subd2/CMakeLists.txt")
add_subdirectory(subd1)
add_subdirectory(subd2)

Instead of propagating your Source files up to the root you can depend on the target you have defined in the root CMakeLists.txt. That means subd1/CMakeLists.txt may look like:

target_sources(my_target PRIVATE "subd1/Source.cpp" "subd1/Source2.cpp")

[EDIT]

As stated in the comments you must give the relative path of the source-files to target_sources(). I use target_sources() because I do not want the explicit source file listing to pollute the targets CMakeLists.txt. Another use case is that target_sources() can be invoked with the PUBLIC or INTERFACE keyword to propagate source files to depending targets. Well I never used target_sources() that way.

[/EDIT]

If you're using IDEs like Visual Studio that support folders you make want to also declare a source_group() in the CMakeLists.txt that contains your target. So the root CMakeLists.txt may look like:

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/*")

I'm using this approach because it leads to much cleaner CMakeLists.txt files, its lesser work and I think the introduction of not needed variables only raises the complexity of your CMakeLists.txt files.

CMakeLists.txt as target sources

I currently use the CMakeLists.txt of the sub folders as source files of the target because otherwise CMake will complain that the add_executable command has no source files given.



回答3:

In your case there's no need to use add_subdirectory since you have only one target which is created in the root CMakeLists.txt. You can simply write this:

add_executable(myProg
    SubD1/Source.cpp
    SubD1/SubSubD1/Source2.cpp)

Use add_subdirectory for subdirectories creating their own targets so there's no information to pass upwards.



标签: c++ cmake