target_include_directories - INTERFACE doesn't export an include path - cmake

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.

Related

Cmake - add_definitions in library - gobally available

I have a cmake project with many libraries (standalone additional packages) which are build within my project and then my project is linked against them.
If i have the following ...
CMakeLists.txt (1)
main.cpp
main.hpp
library/CMakeLists.txt (2)
library/dummy.cpp
library/dummy.hpp
... add have "add_definitions(-DMYDEF=15)" inside the library cmakelists (2). Can i somehow make this available to main.cpp and main.hpp, so they "see" the macro definition which is made inside the lib at preprocessing?
So not only sources/headers within the lib shall work with my definition but also any other dependency, like the main project with main.cpp/main.hpp
Yes, there is a way; use target_compile_definitions(mylib PUBLIC MYDEF=15) for your library, instead of add_definifions(-DMYDEF=15). That way all other targets that are linked against mylib will inherit compile definitions from mylib
Please note that target_compile_definitions should be added after the target is created, otherwise, you will receive the error.
Correct usage would be as follows:
#add library first
add_library(mylib)
#compile definitions for the target mylib
target_compile_definitions(
mylib
PUBLIC
MYDEF=15
)
More about the subject might be found in cmake documentation for target_compile_definitions

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.

How to make imported target GLOBAL afterwards?

From the FindBoost.cmake module of CMake 3.8:
foreach(COMPONENT ${Boost_FIND_COMPONENTS})
if(_Boost_IMPORTED_TARGETS AND NOT TARGET Boost::${COMPONENT})
string(TOUPPER ${COMPONENT} UPPERCOMPONENT)
if(Boost_${UPPERCOMPONENT}_FOUND)
if(Boost_USE_STATIC_LIBS)
add_library(Boost::${COMPONENT} STATIC IMPORTED)
else()
# Even if Boost_USE_STATIC_LIBS is OFF, we might have static
# libraries as a result.
add_library(Boost::${COMPONENT} UNKNOWN IMPORTED)
endif()
and the corresponding comment from the docu of that module:
It is important to note that the imported targets behave differently than variables created by this module: multiple calls to find_package(Boost) in the same directory or sub-directories with different options (e.g. static or shared) will not override the values of the targets created by the first call.
I see the rational for having the targets not being GLOBAL.
However, what is the preferred way of making them global?
I'm used to defining the dependencies of my project in a sub-directory including any find_package(...) calls. Consequently, the Boost imported targets are not available in another directory, e.g. /tests/CMakeLists.txt:
<project_root>
/3rdparty
/git-submodule-of-a-small-lib
/CMakeLists.txt
/include
/...
/tests
/CMakeLists.txt
/CMakeLists.txt
There is a IMPORTED_GLOBAL target property for this in CMake >= 3.11:
set_target_properties(Boost::unit_test_framework PROPERTIES IMPORTED_GLOBAL TRUE)
For older versions: find_package() uses standard add_library() calls, so you can always change/extend its functionality to have IMPORTED targets always GLOBAL with something like:
3rdparty\CMakeLists.txt
function(add_library)
set(_args ${ARGN})
if ("${_args}" MATCHES ";IMPORTED")
list(APPEND _args GLOBAL)
endif()
_add_library(${_args})
endfunction()
find_package(Boost REQUIRED COMPONENTS unit_test_framework)
Disclaimer
As #CraigScott has commented overwriting CMake's build-in functions is dangerous:
[CMake] infinite loop when using function overriding
CMake Issue #14357: Defining an override macro/function of add_library more than once causes a segmentation fault
References
CMake Issue #1254: Add new target-property IMPORTED_GLOBAL
CMake Issue #1222: [Threads, Boost] Option to create IMPORTED targets with GLOBAL scope
CMake Issue #17256: Possibility to promote IMPORTED target to IMPORTED GLOBAL
I managed to workaround the problem of having the imported Boost targets not available in the global project scope by including 3rdparty/CMakeLists.txt not by add_subdirectory(3rdparty) but via include(3rdparty/CMakeLists.txt) as this evaluates 3rdparty/CMakeLists.txt in the caller's scope.

How to modify scope of imported library using CMake

CMake question,
Imported library has scope in the directory in which it is created and below.
If I want to use this library in parent scope, what should I do?
for example,
top CMakeLists.txt
add_subdirectory(sub)
add_executable(myapp main.cpp)
target_link_libraries(myapp imported_lib)
sub CMakeLists.txt
add_library(imported_lib STATIC IMPORTED)
Thanks for your helping~~
Unlike classic libraries, imported libraries are scoped to the directory.
This can be changed using the GLOBAL options.
Here is an extract from the documentation:
The target name has scope in the directory in which it is created and below, but the GLOBAL option extends visibility.
Example:
add_library(imported_lib STATIC IMPORTED GLOBAL)
As far as I can tell, this option has always been available.
You don't need to do anything, CMake will resolve this dependency automatically. See the documentation for add_subdirectory:
If a target built by the parent project depends on a target in the subdirectory, the dependee target will be included in the parent project build system to satisfy the dependency.
This is in contrast to the set() and list() commands, which require passing an explicit PARENT_SCOPE parameter.