How to build an external library downloaded with CMake FetchContent? - cmake

I have a program that depends on an external library (SDL for example). I want CMake to take care of that dependency for me, so I was looking into FetchContent. As far as I understand, this module simply downloads the source code so that information on the external library is available at configure time. For example:
include(FetchContent)
FetchContent_Declare(sdl
GIT_REPOSITORY <...>
)
FetchContent_GetProperties(sdl)
# sdl_POPULATED, sdl_SOURCE_DIR and sdl_BINARY_DIR are ready now
if(NOT sdl_POPULATED)
FetchContent_Populate(sdl)
endif()
At some point, however, I want to build that source code and link it to my main executable. How to do it the "modern CMake way"?

The recommended way to build external libraries from source as part of your build depends on what the external lib provides build-wise.
External lib builds with cmake
If the external lib builds with cmake then you could add the lib to your build via a add_subdirectory(${libname_SOURCE_DIR}) call. That way cmake will build the external lib as a subfolder ("subproject"). The CMakeLists.txt file of the external lib will have some add_library(ext_lib_name ...) statements in it. In order to then use the external lib in your targets (an application or library that depends on the external lib) you can simply call target_link_libraries(your_application <PRIVATE|PUBLIC|INTERFACE> ext_lib_name) https://cmake.org/cmake/help/latest/command/target_link_libraries.html
I had a quick look at this github repo https://github.com/rantoniello/sdl - (let me know if you are referring to another library) and it actually looks like it is building with cmake and that it allows clients to statically or dynamically link against it: https://github.com/rantoniello/sdl/blob/master/CMakeLists.txt#L1688-L1740
So, ideally your applicaiton should be able to do
add_executable(myapp ...)
target_link_libraries(myapp PRIVATE SDL2-static) // Statically link againt SDL2
Due to their CMakeLists.txt file the SDL2-static comes with properties (include directories, linker flags/commands) that will automatically propergate to myapp.
External lib does not build with cmake
If a external lib doesn't build with cmake then one can try to use add_custom_target https://cmake.org/cmake/help/latest/command/add_custom_target.html to build the library. Something along the lines of:
add_custom_target(myExternalTarget COMMAND <invoke the repo's build system>)
You'd then need to set the target properties that are important for clients yourself via the the proper cmake functions set_target_properties, target_include_directories ... A great writeup how to get started with these kinds of things: https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/

Related

Adding External Library to Zephyr

Context:
I am trying to add an external library which uses CMake to my Zephyr project. I have explored the modules documentation, but this does not seem a good fit as I am unable to modify the upstream library and would like to avoid forking.
To add the library, I am using FetchContent in my Cmake file. This is working successfully and I am able to download and build the files.
Problem:
When linking, I encounter a "Conflicting CPU architectures" error. After inspecting into compile_commands.json, I can see the libraries source code is not receiving the same CMAKE_ARGS as the other files, leading to the architecture mismatch.
I am looking for the suggested way of adding external libraries to a zephyr project, without using the module system.
FetchContent_Declare(
my-lib
GIT_REPOSITORY git#github.com:<HostRepo>/<repoName>.git
GIT_TAG v0.1.7
)
FetchContent_MakeAvailable(my-lib)
target_link_libraries(app PRIVATE my-lib)
I recommend doing the repo fetching with a toplevel west.yml manifest and including your lib with add_library() or the appropriate cmake function.

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

SET(CPACK_COMPONENTS_ALL ...) with ExternalProject Installing Additional Components

I use the ExtrenalProject cmake module to add 3rd party or internal dependencies to my build. I then use the CPack module with components to install only components from the current code base in the following manner.
set(CPACK_COMPONENTS_ALL
common-lib
common-include
common-depends
)
An example of one of these components declared in CMake is:
install(TARGETS common
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
COMPONENT common-lib
)
However, other projects added using add_subdirectory such as google test or other internal libraries also declare install targets. When I run
make package
and then list the contents of the .deb or .tar generated, I see the contents of other components not set in the CPACK_COMPONENTS_ALL variable.
What is the proper way to get CMake and CPack to only install the components requested?
You can just add the argument EXCLUDE_FROM_ALL to the end of the add_subdirectory() call. This will essentially disable all of the include() calls made in the added subdirectories.

Cmake executable can not link with the new interface library depending on another library

I have the following dependencies:
add_library(lib)
add_library(ilib INTERFACE)
add_dependencies(ilib lib)
target_link_libraries(ilib INTERFACE
"-Wl,--whole-archive $<TARGET_FILE:lib> Wl,--no-whole-archive")
add_executable(exe ilib)
When I changed some source codes of lib, the lib as expected was compiled and built again. However, exe did not link the new lib. If I use add_executable(exe lib), then exe will always link the new lib. (The reason why I use the ilib is that I need to process lib before using it.)
You expect lib to be propagated when one links with ilib.
But command add_dependencies doesn't add properties for propagation. You need
# Linking with `ilib` will transitively link with a `lib`
target_link_libraries(ilib INTERFACE lib)
When need to use --whole-archive option for linker, it could be done in the following way:
target_link_libraries(ilib INTERFACE "-Wl,--whole-archive" lib "Wl,--no-whole-archive")
When parse arguments for given function, CMake will finds argument lib to be a target name, and will add proper file-level dependency. With that dependency the executable will be relinked whenever the library file has been changed.

CMake package configuration files for upstream projects using Qt5 problems

I am working on a larger C++ library that is using CMake and depends on Qt.
We moved from Qt4 to Qt5 and now I encounter a problem when using our lib
in an upstream project. As a minimal working example demonstrating the problem please have a look at this repo:
https://github.com/philthiel/cmake_qt5_upstream
It contains two separate CMake projects:
MyLIB: a tiny library that uses QString from Qt5::Core.
It generates and installs package configuration files
MyLIBConfig.cmake, MyLIBConfigVersion.cmake, and MyLIBTargets.cmake
in order to be searchable by CMake find_package()
MyAPP: a tiny executable depending on MyLIB
The project uses find_package(MyLIB) and creates an executable that uses MyLIB
The problem is that CMake gives me the following error message when configuring the MyAPP project:
CMake Error at CMakeLists.txt:11 (add_executable):
Target "MyAPP" links to target "Qt5::Core" but the target was not found.
Perhaps a find_package() call is missing for an IMPORTED target, or an
ALIAS target is missing?
The reason for this behaviour is that in the automatically generated MyLIBTargets.cmake file the INTERFACE_LINK_LIBRARIES entry for Qt5 Core is the Qt5::Core symbol. Using Qt4, the absolute path to the Qt core lib was specified here.
Now, I simply can resolve this by using
find_package(Qt5Core 5.X REQUIRED)
in the MyAPP project.
However, I would like to know if this is the intended/generic way to go, i.e. requesting upstream projects of our lib to search for the required transitive Qt5 dependencies themselves, or if I probably misuse CMake here and need to change my configuration procedure?
The CMake docu on package file generation
https://cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html
mentions that macros can be provided by the package configuration files to upstream. Maybe this would be the correct place to search for imported targets like Qt5 and break upstream configuration runs when these dependencies are not found?
Best,
Philipp
[edit of the edit] Full Source Example
You need to deliver a CMake config file for your project, and probably the ConfigFile should be generated via CMake itself (because you cannot know for shure where the user will install your software).
Tip, use the ECM cmake modules to ease the creation of that:
find_package(ECM REQUIRED NO_MODULE)
include(CMakePackageConfigHelpers)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX ATCORE
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/atcore_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5AtCoreConfigVersion.cmake"
SOVERSION 1
)
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5AtCoreConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KF5AtCoreConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
and the KF5AtCoreConfig.cmake.in:
#PACKAGE_INIT#
find_dependency(Qt5Widgets "#REQUIRED_QT_VERSION#")
find_dependency(Qt5SerialPort "#REQUIRED_QT_VERSION#")
find_dependency(KF5Solid "#KF5_DEP_VERSION#")
include("${CMAKE_CURRENT_LIST_DIR}/KF5AtCoreTargets.cmake")
This will generate the correct FindYourSortware.cmake with all your dependencies.
[edit] Better explanation on what's going on.
If you are providing a library that will use Qt, and that would also need to find the Qt5 library before compilling the user's source, you need to provide yourself a FindYourLibrary.cmake code, that would call
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Whatever)
Now, if it's your executable that needs to be linked, use the Components instead of the way you are doing it now.
find_package(Qt5 REQUIRED COMPONENTS Core)
then you link your library with
target_link_libraries(YourTarget Qt5::Core)