Cmake: target_link_libraries propagation of include directories - cmake

I have a small static library that requires boost headers and that requires the "include" directory in the include directories.
...
add_library(alib STATIC ...)
target_include_directories(alib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(alib PRIVATE ${Boost_INCLUDE_DIRS})
...
I have another installation on the system of alib, exactly where ${Boost_INCLUDE_DIRS} point to, but this is an older version, required by other packages of my system.
The point is that I want to build a custom version of alib as a target of my project. So this is very important that nothing from the system alib is being included or linked against.
Now I have another library mylib that depends on alib, so I do the following:
...
add_library(mylib STATIC ...)
target_link_libraries(mylib PUBLIC alib)
target_include_directories(mylib PRIVATE ${EXPAT_INCLUDE_DIRS})
...
The target_link_libraries(mylib PUBLIC alib) call properly add includes from alib but it includes them AFTER the includes specified by the second line target_include_directories(mylib PRIVATE ${EXPAT_INCLUDE_DIRS}), even though they are specified afterwards.
Problem is that ${EXPAT_INCLUDE_DIRS} points to the include path where the system alib is located.
It shouldn't matter if Cmake would correctly append include directories in the order they are provided, that is, the ones of target_link_libraries(mylib PUBLIC alib)then the ones of target_include_directories(mylib PRIVATE ${EXPAT_INCLUDE_DIRS}).
However cmake does not respect this order and appends the include directories from the call of target_link_libraries in the end of the command line, resulting in the system headers being picked up instead of my alib version headers.
I cannot change the name of directories of alib in any way.
My current solution is to hack a custom call to target_include_directories by referencing the alib target manually:
target_include_directories(mylib PUBLIC $<TARGET_PROPERTY:alib,INCLUDE_DIRECTORIES>)
Is there a better way of doing so? How can I force Cmake to include directories of alib right-away in the target_link_directories call, and not after ?

No, your "hack" is more or less the recommended way in the documentation:
For example, if the linked libraries for a target must be specified in the order lib1 lib2 lib3, but the include directories must be specified in the order lib3 lib1 lib2:
target_link_libraries(myExe lib1 lib2 lib3)
target_include_directories(myExe
PRIVATE $<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>)

Related

CMake problem finding an include file in another subproject

I have a project that we will call proj.
It has a subproject call config which does not link to anything because we want to manage it separately from the primary image.
But config has some configuration files, lets call them sys_config.h and user_config.h, that define the structure that others need to access and to be clean about it, I want those stored with the config subproject.
Finally, there is a library, we will call lib that is included in proj. It needs to be able to reference those config include files.
So, the file structure is approximately like this:
proj
main
src
include
(this references and includes lib below)
config
src
sys_config.c
user_config.c
include
config
sys_config.h
user_config.h
mylib
src
mylib.c
#include <config/sys_config.h>
or
#include <sys_config.h>
include
mylib.h
Now, what do I put in lib's CMakeLists.txt to get it to be able to see sys_config.h?
For example, this does not work:
target_include_directories(mylib
PUBLIC include
PRIVATE "{$CMAKE_SOURCE_DIR}/config/include"
)
nor does this work:
target_include_directories(mylib
PUBLIC include
PRIVATE "{$CMAKE_SOURCE_DIR}/config/include/config"
)
nor does this:
target_include_directories(mylib
PUBLIC include
PRIVATE "config/include"
)
My hope was maybe if I go back to the top and include the full path to config/include it would find it but it doesn't.
I feel that I must have a complete misunderstanding of how target_include_directories() is supposed to work and what it is supposed to do for me.
Roger
With modern CMake, notion of a subdir / subproject is to be an independent library / executable. Each subproject (lib / exe) should define set of PUBLIC / PRIVATE headers. See - https://cmake.org/cmake/help/latest/command/target_include_directories.html. Rest dependent projects should just link with subproject libs. Inclusion of libs is internally managed by CMake.
Sample cmake files based on your directory layout (I have not tested them so treat them as reference purpose only).
Top level cmake file # proj/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(main)
subdirs(config)
subdirs(mylib)
add_executable(hello main/src/main.c)
target_link_libraries(hello config mylib)
install(TARGETS hello DESTINATION bin)
config cmake file # proj/config/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
add_library(config src/sys_config.c src/user_config.c)
target_include_directories(config PUBLIC include)
# uncomment if you have any internal headers within config/src
# target_include_directories(config PRIVATE src)
As best practice keep all your module config exposed headers at config/include/config/. This will make them to be imported as #include "config/sys_config.h" in mylib source files.
mylib cmake file # proj/mylib/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
add_library(mylib src/mylib.c)
target_include_directories(mylib PUBLIC include)
# uncomment if you have any internal headers in proj/mylib/src
# target_include_directories(mylib PRIVATE src)
target_link_libraries(mylib config)

target_include_directories - INTERFACE doesn't export an include path

I have created a very simple cmake project for testing cmake features. The project directory contains two libraries. I would like to export MyLibA include path.
The main CMakeLists.txt:
cmake_minimum_required(VERSION 3.11)
project(TestProject)
add_subdirectory(MyLibA)
add_subdirectory(MyLibB)
MyLibA CMakeLists.txt:
add_library(MyLibA SHARED)
target_sources(MyLibA PRIVATE fileA.h fileA.cpp)
target_include_directories(MyLibA INTERFACE "${CMAKE_SOURCE_DIR}/MyLibA")
MyLibB CMakeLists.txt:
add_library(MyLibB SHARED)
target_sources(MyLibB PRIVATE fileB.h fileB.cpp)
target_link_libraries(MyLibB PRIVATE /home/user/MyProjects/CmakeTestProject/build/MyLibA/libMyLibA.so)
I have exported an include path using INTERFACE keyword but the following include in fileB.h:
#include "fileA.h"
is not found. What am I doing wrong ?
What am I doing wrong?
Several things:
Never put absolute paths in your CMakeLists.txt and always link to targets rather than library files.
# Linking to a target propagates usage requirements, like include paths.
target_link_libraries(MyLibB PRIVATE MyLibA)
CMAKE_SOURCE_DIR is not what you think. It refers always to the top-level build directory, which is a bad assumption if your project might be an add_subdirectory or FetchContent target. Your usage can be replaced by:
# Not optimal, see below.
target_include_directories(MyLibA INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
Missing $<BUILD_INTERFACE:...> on include path, if you intend to export your targets. When targets are exported, their properties are copied verbatim to the output. Not guarding the local include path with $<BUILD_INTERFACE:...> will break users of the exported target.
target_include_directories(
MyLibA
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
)
Instead of
target_link_libraries(MyLibB PRIVATE <path/to/MyLibA/file>)
use
target_link_libraries(MyLibB PRIVATE MyLibA)
This is how CMake is intended to be used: when link with the library target, CMake automatically transforms that into the path and actually propagates all interface properties of the target.

Providing include directory outside source folder for static library users

I am developing a simple static C library for learning purposes using cmake.
Some projects like GLFW provide an include folder on the root, so library users can copy it and use it as an include directory.
In my library, I want to have an include folder on the root, so when I use the library on other projects, I can just copy this folder and set it as an include directory.
Here is a simplified folder structure of my library:
include
+--mylib.h
src
+--myheader.h
+--mysource.c
+--CMakeLists.txt
CmakeLists.txt
The src folder has my headers and implementation files, and a CMakeLists.txt for building a static library out of mysource.c.
The CMakeLists on the root folder just sets the project and adds src as a subdirectory.
I want the mylib.h file to have a #include <myheader.h>.
Here's a detour to talk about how I want to use it when it's done.
The idea is that when using the lib on another project, I can have something like this:
deps
+--include
+--mylib.h
src
+--main.c
And in the main.c file, include mylib.h and use what's defined on myheader.h
Here the detour ends, and I'm talking about my actual lib project again.
How can I achieve this using cmake? As far as I know, the mylib.h file needs to know it's including files from the src diretory, but I see no way of setting that, as for exemple in GLFW this directory does not have a CMakeLists.txt.
I am gonna quess that this is a design issue since it would make sense to you if you would have installed the library to a system before you tried to use it. That is, not using add_subdirectory() but find_library() at usage.
First, if you are using a external library, but not installing it, you would include all files in you deps-folder. All files then include source-files and so on and will be compiled besides you main.c. This is done with add_subdirectory(deps/MyLib) and later also included in you main-project.
Example:
add_subdirectory(deps/MyLib EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE MyLib)
target_include_directories(${PROJECT_NAME} PRIVATE MyLib)
If you do not want to compile it all the time, then you must instruct cmake where it can find headers and library-files. Preferred way is to use find_library() which does some magic for you. Since you do not mention any installation i will assume that it does not exist and your only option is then to use add_subdirectory().
"I can just copy this folder and set it as an include directory."
CMake wants to handle these things for you so you should never copy headers around. You should either use add_subdirectory() to include a project/headers or make use of the find_library() which make sure you find where the headers are in the system.
I suggest that you push yourself to learn howto install a library into a system and how to utilize it later, but only by using find_library(). Then the library will be global for all projects and also not duplicated.
Adding some kind of psudo-code in hope it all makes more sense. Although it is around add_subdirectory() since the code for installing is quite large unfortunately.
CMakeLists.txt for main.c
cmake_minimum_required(VERSION 3.8)
project(MyLibTest)
add_executable(${PROJECT_NAME}
src/main.c
)
add_subdirectory(external/MyLib EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE MyLib)
target_include_directories(${PROJECT_NAME} PRIVATE MyLib)
CMakeLists.txt for library
cmake_minimum_required(VERSION 3.8)
project(MyLib)
add_library(${PROJECT_NAME} STATIC
src/MyLib.c
)
target_include_directories(${PROJECT_NAME}
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
The structure for the project would then be:
/
external/MyLib
external/MyLib/src
MyLib.c
external/MyLib/include
MyLib.h
src
main.c
CMakeLists.txt

Cmake add_library ALIAS

I am trying to figure out exactly what this line is for in the cmake file of this github json project,
add_library(${NLOHMANN_JSON_TARGET_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${NLOHMANN_JSON_TARGET_NAME} ALIAS ${NLOHMANN_JSON_TARGET_NAME})
Specifically with this example, what does this allow in this cmake file that otherwise would not be possible?
I see no other references to ${PROJECT_NAME}::${NLOHMANN_JSON_TARGET_NAME} in this CMakeLists.cmake, so I am confused as to what exactly this achieves.
Edit:
The key thing that this achieves, that the comment did not make obvious to me, is that it makes the targets work with the namespaces when the project is used through add_subdirectory()
Without the alias, you can still add the library via add_subdirectory however in the target_link_libraries command you would need to omit the namespace:
project(mySuperApp)
set(mySuperApp_SRC src/main.c)
add_subdirectory(thirdparty/json)
add_executable(${PROJECT_NAME} ${mySuperApp_SRC})
target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json)
If you did that but then decided to use find_package to include the library (as opposed to add_subdirectory), you would need to change target_link_libraries to use the namespaced targets i.e.
project(mySuperApp)
set(mySuperApp_SRC src/main.c)
find_package(nlohmann_json REQUIRED)
add_executable(${PROJECT_NAME} ${mySuperApp_SRC})
target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json)
by adding the alias, the target_link_libraries using the namespaced version (i.e. nlohmann_json::nlohmann_json) will work in either case and not require a change if you later decide to switch from find_package to add_subdirectory).
It allows you to add the library with find_package OR add_subdirectory using the same target name for both:
# creates nlohmann_json::nlohmann_json
find_package(nlohmann_json REQUIRED)
if (nlohmann_json_NOT_FOUND)
# creates nlohmann_json AND nlohmann_json::nlohmann_json
add_subdirectory(thirdparty/json)
endif()
add_executable(your_target_name ${your_target_sources})
target_link_libraries(your_target_name PRIVATE nlohmann_json::nlohmann_json)
Without the alias, you would need:
# creates nlohmann_json::nlohmann_json
find_package(nlohmann_json REQUIRED)
if (NOT nlohmann_json_FOUND)
# creates only nlohmann_json
add_subdirectory(thirdparty/json)
endif()
add_executable(your_target_name ${your_target_sources})
if (nlohmann_json_FOUND)
target_link_libraries(your_target_name PRIVATE nlohmann_json::nlohmann_json)
else()
target_link_libraries(your_target_name PRIVATE nlohmann_json)
endif()
This will allow using nlohmann/json project by adding it into your super project with add_subdirectory(...)
For example simple project structure:
<root project>\
\thirdparty\json <<-- git submodule to https://github.com/nlohmann/json
\include\
\src\
CMakeLists.txt
In your project CMakeLists.txt
...
project(mySuperApp)
set(mySuperApp_SRC src/main.c)
# can under some conditions...
add_subdirectory(thirdparty/json)
add_executable(${PROJECT_NAME} ${mySuperApp_SRC})
target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json)
Using git's blame function shows that line was added in this commit: 33a2154, which has the following comment attached:
CMake convention is to use a project namespace, i.e. Foo::, for imported
targets. When multiple targets are imported from a project, this looks
like Foo::Bar1 Foo::Bar2, etc. This adds the nlohmann_json:: namespace to
the exported target names.
This also allows the generated project config files to be used from the
build directory instead of just the install directory.

Library depend on a header file

Let's assume I have a project with a series of libraries. I also need to generate a header file that will be used by all of these projects. So I created a CMake file, like this:
project(main)
add_subdirectory(sub_1)
add_subdirectory(sub_2)
# ...
add_subdirectory(sub_n)
add_custom_command(
OUTPUT CustomHeader.h
COMMENT "Generating custom header for all the libraries"
COMMAND ...)
add_library(${PROJECT_NAME} STATIC ${OBJECT_LIST})
The problem is, that I don't know how to tell CMake to run my custom command (that generates this CustomHeader.h) before it would try to build the libraries in the subfolders.
I tried add_custom_target(TARGET MyPrebuild PRE_BUILD ...) but I'm running on Linux, and this option only works on Windows platform according to the documentation.
add_dependencies only work between targets, and not a target and a single file.
I could, in theory, add the header to be among the source files of the individual libraries (in the sub_1, .., sub_n folders) but it feels wrong, as the header is not required to be part of those libraries.
So I just have no idea how I can make a library depend on an include file, that is not part of it.
Any tips how I can overcome this problem?
For make header file (re)built before a library in subdirectory is compiled, you may create target, which builds the file, and make the library dependent from the target:
# *CMakeLists.txt*
# ...
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h ...)
add_custom_target(generate_custom_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h)
# *sub/CMakeLists.txt*
# ...
add_library(libA ...)
add_dependencies(libA generate_custom_header)
Instead of using add_dependencies, you may create header-only library which "implements" you header and link with it:
# *CMakeLists.txt*
# ...
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h ...)
add_custom_target(generate_custom_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h)
add_library(libCustom INTERFACE) # Header only library
add_dependencies(libCustom generate_custom_header) # which depends on generated header
# You may even assign include directories for the header-only library
target_include_directories(libCustom INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
# *sub/CMakeLists.txt*
# ...
add_library(libA ...)
target_link_libraries(libA libCustom) # Common linking with a header-only library.
Note, that INTERFACE library is a "fake" - it is never created by itself. Instead, all "features" of INTERFACE library are just propagated to its users.
I would suggest to add another library target that will both keep track of the generated headers and will help to properly configure other libraries to know where to find them (i.e. target_include_directories).
cmake_minimum_required(VERSION 3.0)
project(testable)
set(CustomHeaderInPath ${CMAKE_CURRENT_SOURCE_DIR}/CustomHeader.example)
set(CustomHeaderPath ${CMAKE_CURRENT_BINARY_DIR}/CustomHeader.h)
add_custom_command(
OUTPUT ${CustomHeaderPath}
COMMAND cp ${CustomHeaderInPath} ${CustomHeaderPath}
COMMENT "Generated file"
DEPENDS ${CustomHeaderInPath})
add_library(CustomHeaderLibrary ${CustomHeaderPath})
target_include_directories(CustomHeaderLibrary PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(CustomHeaderLibrary PROPERTIES LINKER_LANGUAGE C)
add_library(LibA a.c)
target_link_libraries(LibA CustomHeaderLibrary)
add_library(LibB b.c)
target_link_libraries(LibB CustomHeaderLibrary)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} PUBLIC LibA LibB)
Note that I had to explicitly set the LINKER_LANGUAGE of the new target as cmake won't be able to deduce it properly if no c or cpp files are added to the library.