7

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?

Jon Gan
  • 867
  • 1
  • 11
  • 22

3 Answers3

7

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.

DarthB
  • 351
  • 2
  • 6
  • A valid solution. I think the difference is that in the `target_source()` case you are propagating the main target's name through all levels of `CMakeLists.txt` files and in the `OBJECT` library use case the main target needs to know the sub-level target names. I personally prefer the second case, because of the reusability of my sub-level CMake files in other projects. And one hint after playing with it at little: I think you still need to prefix `${CMAKE_CURRENT_SOURCE_DIR}` in the sub-level CMake files. `target_sources()` seems not do it by itself. – Florian Jul 21 '15 at 14:56
  • You're right I have to add the paths. I've edited my answer and add some arguments that motivating me to use target_sources(). I did use add_library() and target_link_library() to handle dependencies in my multi module projects. Maybe I will try out the object library approach in the future. – DarthB Jul 21 '15 at 16:08
  • I just had the idea if it would help to "enhance" `target_sources()` to prefix the path. This would negate the disadvantage that you would have to prefix your sources manually. Here is code: `function(target_sources _target)` | `foreach(_arg IN ITEMS ${ARGN})` | `if ("${_arg}" MATCHES "(INTERFACE|PUBLIC|PRIVATE)" OR IS_ABSOLUTE "${_arg}")` | `list(APPEND _new_args "${_arg}")` | `else()` | `list(APPEND _new_args "${CMAKE_CURRENT_LIST_DIR}/${_arg}")` | `endif()` | `endforeach()` | `_target_sources(${_target} ${_new_args})` | `endfunction(target_sources)` – Florian Jul 21 '15 at 19:10
  • On at least some platforms, you can simply pass an empty string for the sources list in your top level CMakeLists.txt file's call to `add_executable()`. Assuming all platforms behave like this, you can avoid having to add any arbitrary files such as the subdirectories' CMakeLists.txt as this answer currently does. I've only tested this with OS X using CMake 3.4.1 for the moment. – Craig Scott Jan 27 '16 at 10:06
6

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)
Florian
  • 39,996
  • 9
  • 133
  • 149
  • This would be a great answer if you provided some examples. Particularly for #1 and #3 which is what I'd normally recommand. – mshildt Jul 21 '15 at 13:01
  • I think your second approach is the best one. It works conceptually like the one I'm currently using in my project. When I started with CMake I used your first approach but was not happy with it. You seem to have a lot of experiences with CMake. Do you know any drawbacks for the target_sources approach I described in my answer? I think it was introduced around 2.8.xx so a lot of older project may stick to older approaches. – DarthB Jul 21 '15 at 13:56
  • @epicbrew You're right. I added examples and merged my earlier approaches #2 and #4 (seeing that they belong together). So it's now #1 and #2 you would recommend (sorry for the renumbering). Sometimes #2 has it's advantages because you can give the sub-targets/-directories their own properties (without the need to do it on source file level). – Florian Jul 21 '15 at 14:08
  • @DarthB I'm still mainly using CMake 2.8.10 so I just forgot to mention the new command `target_sources()` introduced with CMake 3.1. Your approach is another valid possibility (I think there are some slight usage differences to be considered; I will add a comment to your answer). I think the main question to be answered before you decide what approach to take is when it make sense to open a new scope. E.g. if one of the sub-directories would come with it's own unit test, it hints that it has its own set of unique features and would qualify to have its own library target. – Florian Jul 21 '15 at 14:27
1

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.

tamas.kenez
  • 7,301
  • 4
  • 24
  • 34