CMake: Set up Multiple Projects and Dependencies Between Them
Asked Answered
H

4

16

I need help with writing good CMakeLists.txt files for C++ projects.

This is the structure of my projects:

MainProj
|  ProjLib/
|  |  include/
|  |  |  proj_lib.h
|  |  src/
|  |  |  proj_lib.cc
|  |  CMakeLists.txt
|  ProjExec/
|  |  include/
|  |  |  proj_exec.h
|  |  src/
|  |  |  proj_exec.cc
|  |  CMakeLists.txt
|  CMakeLists.txt

MainProj CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)
project(MainProj CXX)

# enable C and C++ language
enable_language(C CXX)

# Add sub-directories
add_subdirectory(ProjLib)
add_subdirectory(ProjExec)

ProjLib CMakeLists.txt:

set (PROJLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
set (PROJLIB_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
 
set(PROJLIB_SRCS 
    ${CMAKE_CURRENT_SOURCE_DIR}/src/proj_lib.cc
)
 
include_directories("${PROJLIB_SOURCE_DIR}")
include_directories("${PROJLIB_INCLUDE_DIR}")
 
add_library(ProjLib SHARED ${PROJLIB_SRCS} ${PROJLIB_INCLUDE_DIR})

target_include_directories (ProjLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

ProjExec CMakeLists.txt:

set (PROJEXEC_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
set (PROJEXEC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)

set(PROJEXEC_SRCS 
    ${PROJEXEC_SOURCE_DIR}/proj_exec.cc
)
 
include_directories("${PROJEXEC_SOURCE_DIR}")
include_directories("${PROJEXEC_INCLUDE_DIR}")

add_executable(ProjExec ${PROJEXEC_SRCS})

target_link_libraries (ProjExec LINK_PUBLIC ProjLib)

proj_exec.cc:

#include "proj_lib.h"
...

And it doesn't found proj_lib.h in proj_exec.cc file. Do I need some additional entries in some cmake?

Hornstein answered 24/10, 2016 at 20:50 Comment(1)
In ProjExec/CMakeLists.txt you include directories ProjExec/src and ProjExec/include. Your library target carries include directory (added with target_include_directories) ProjLib, which is propagated to executable. But none of these directories is equal to ProjLib/include where header proj_lib.h is located. So, why do you expect that the header should be found?Stagner
V
31

You need to make use of the CMake targets and their properties:

MainProj/CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)
project(MainProj)

# Add sub-directories
add_subdirectory(ProjLib)
add_subdirectory(ProjExec)

ProjLib/CMakeLists.txt

add_library(ProjLib SHARED src/proj_lib.cc)
target_include_directories(ProjLib PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)

ProjExec/CMakeLists.txt

add_executable(ProjExec src/proj_exec.cc)
target_include_directories(ProjExec PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(ProjExec ProjLib)

target_link_libraries makes sure that when building a target, its dependencies' PUBLIC and INTERFACE include directories will be used appropriately.

Vevine answered 25/10, 2016 at 14:8 Comment(8)
Interesting: so OP just needs to change include_directories("${PROJLIB_INCLUDE_DIR}") into target_include_directories (ProjLib PUBLIC ${PROJLIB_INCLUDE_DIR}) in ProjLib's CMakeLists.txt? Where is this behaviour documented?Insnare
@Antonio: the page on target_include_directories talks about PUBLIC populating the INTERFACE_INCLUDE_DIRECTORIES property, which is used when you call target_link_libraries: "When target dependencies are specified using target_link_libraries(), CMake will read this property from all target dependencies to determine the build properties of the consumer."Vevine
In short, if you're not using these features, you're doing it wrong. There are still some IMHO stupid limitations in very useful corner cases, but I still think CMake does the best job even though their syntax is abysmal.Vevine
Thanks, it was really well hidden! Indeed, it is the best way of doing this!Insnare
I have added an answer here.Insnare
Thanks! I really had a problem with understanding all of this cmake stuff... :) Now, it's quite clear.Hornstein
@Vevine : However I have another question : If there in ProjExec/CMakeLists.txt shouldn't be target_include_directories(ProjExec PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include)Hornstein
@Hornstein Might be. I didn't test the above code. I changed it in the answer because your suggestion has less chance of being wrong ;).Vevine
A
1

Your cmake project template looks fine and self-contained. First, I'm going to assume GAITPARAMS_SRCS was supposed to be PROJEXEC_SRCS which is currently pointing at proj_exec.cc contains a main() method. (If you're going to manage a SRCS list, be careful not to add source files at the top of the list, add_executable expects the main function to be in the first entry)

Secondly, the problem is in your ProjLib/CMakeLists.txt. Try replacing your target_include_directories call with this:

target_include_directories (ProjLib PUBLIC ${PROJLIB_INCLUDE_DIR})

This will propagate the include directory information to the ProjExec when the target_link_libraries command is applied. If you don't wan't to do that, I guess you can access via #include <include/ProjLib.h> but that's just confusing. My recommendation is to add another folder in the include folder (named exactly the same with the cmake folder) and add your headers in it. Like this:

MainProj
|  ProjLib/
|  |  include/
|  |  |  ProjLib/
|  |  |  |  proj_lib.h
|  |  src/
|  |  |  proj_lib.cc
|  |  CMakeLists.txt

This lets you include your headers with a foldername (Not to mention preventing name collision.). Like this:

#include <ProjLib/proj_lib.h>

And to configure your CMakeLists.txt files to match the pattern.

Avruch answered 25/10, 2016 at 6:2 Comment(7)
It is failing because it can not find the header files of ProjLib. It can not find ProjLib headers because ProjLib didn't configure it's include directory path correctly. I have created the folder structure and tested it. You can also try it yourself, it's pretty straightforward.Avruch
I suggest you read through my answer. I have given the solution and THEN my recommendations. Like I said, try it yourself.Avruch
This is getting ridiculous. I suggest you look into target_include_directories documentation here. cmake.org/cmake/help/v3.0/command/… . It acts here, as a tip for other targets who are linking ProjLib into them, and make the include statements, without ProjExec knowing what to include. So, It is ProjLib's problem. It's not compilation of ProjLib thats the issue. It's not properly configured to aid future targets to include it's headers. That's the purpose of that function. I ,once again, advise you to try it up.Avruch
I sort of get now what you meant. This answer could be much clearer if you rephrased You're trying to include the directory itself (where exactly?), cmake has no way of resolving where to link to (are we speaking about linking or including?).Insnare
Yes, good point with PROJEXEC_SRCS :) It was mistake. Edited :) Thanks for your answer.Hornstein
@Hornstein Could you accept this as an answer? Downvoters are welcome to comment.Avruch
Sorry I hadn't possibility to do that before. You're answer is good, but rubenvb explained everything better. However +1 ☺Hornstein
I
0

All of the following are valid, but the best option is explained in rubenvb's answer.


You have at least 3 options:

1) Add the following line in ProjExec/CMakeLists.txt:

 target_include_directories (ProjExec PUBLIC ${CMAKE_SOURCE_DIR}/ProjLib/include)

2) You can extend the visibility of the variable PROJLIB_INCLUDE_DIR, by adding the CACHE INTERNAL keywords

 set(PROJLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE INTERNAL "")

and then use it in ProjExec/CMakeLists.txt:

 target_include_directories (ProjExec PUBLIC ${PROJLIB_INCLUDE_DIR})

See How to set the global variable in a function for cmake?
Of course ProjLib must come first in the add_subdirectory sequence in the main CMakeLists.txt file.

3) I couldn't test this one. If you in ProjLib's CMakeLists.txt use this line:

 target_include_directories (ProjLib PUBLIC ${PROJLIB_INCLUDE_DIR})

Then in ProjExec's CMakeLists.txt you can extract the property INTERFACE_INCLUDE_DIRECTORIES, and use it:

 get_target_property(ProjLib PROJLIB_INCLUDE_DIR INTERFACE_INCLUDE_DIRECTORIES) #Do not use anymore CACHE INTERNAL (Point 2)
 target_include_directories (ProjExec PUBLIC ${PROJLIB_INCLUDE_DIR}) #or PRIVATE

These 2 lines can be compacted in one, using cmake-generator-expressions (untested):

target_include_directories (ProjExec PUBLIC $<TARGET_PROPERTY:ProjLib,INTERFACE_INCLUDE_DIRECTORIES>) #or PRIVATE

See also target_include_directories and get_target_property.


Other notes:
  • Either use include_directories or target_include_directories, not both.
  • You normally wouldn't need to have the source directory as include directory, i.e. you can remove include_directories("${PROJLIB_SOURCE_DIR}") and include_directories("${PROJEXEC_SOURCE_DIR}")
Insnare answered 25/10, 2016 at 12:46 Comment(1)
Thanks for all notes :) It is very helpfulHornstein
W
0

When you are setting the properties of a target, a key point is to comprehend the command of target_include_directories and its keywords of PRIVATE, INTERFACE, and PUBLIC. PRIVATE indicates that the include directory is only needed by the target. INTERFACE indicates that the include directory is needed when other targets would like to link this target, and cmake will deal with it automatically. PUBLIC means both this target and other targets which depend on it have to compass this directory into its search path. All of this is determined by your code.

Waterrepellent answered 26/7, 2023 at 6:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.