Using protobuf with CMake's different build configurations (RelWithDebugInfo and Debug) - cmake

In my project I'm using protobuf 3.5. I need at least the debug and the RelWithDebugInfo configurations. To be able to build the project with protobuf debug libraries led to some problems alone:
I needed to build the protobuf libraries from source using both the release and the debug target since the _ITERATOR_DEBUG_LEVEL of my libraries (= 2) didn't match the level of the protobuf libraries (= 0). After building the debug libraries as well as the release libraries, compiling in debug configuration was possible.
Now, after changing back to RelWithDebugInfo I get the same error again, but now just the opposite: The _ITERATOR_DEBUG_LEVEL of my libraries is 0 and the level of the used protobuf libraries is 2.
When checking the linker configuration, I can see that my libraries are linked against the libprotobufd.lib. This makes sense since I've read somewhere that everything which is not Release will use the debug libraries, if available. And this leads to my problem:
I won't build my libraries in Release during development. It's most of the time RelWithDebugInfo. But the _ITERATOR_DEBUG_LEVEL for this configuration is obviously set to 0 (because it is a release configuration with additional information). But CMake then links against protobuf's debug libraries which are not compatible with the rest.
I'm now looking for a possibility to tell CMake to not use the debug version of the libraries but the release version instead without changing the CMake scripts of protobuf itself.
Normally my way to go would be to link different libraries depending on the actual build configuration. But unfortunately, the protobuf CMake configuration tries to handle this by itself.
# Load information for each installed configuration.
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
file(GLOB CONFIG_FILES "${_DIR}/protobuf-targets-*.cmake")
foreach(f ${CONFIG_FILES})
include(${f})
endforeach()
whilst the imported target is overwritten selected depending on the actual configuration:
protobuf-targets-release.cmake:
# Import target "protobuf::libprotobuf-lite" for configuration "Release"
set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotobuf-lite.lib"
)
protobuf-targets-debug.cmake:
# Import target "protobuf::libprotobuf-lite" for configuration "Debug"
set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/libprotobuf-lited.lib"
)
whereas the linking in my CMakeLists.txt looks like this:
target_link_libraries(${PROJECT_NAME} PRIVATE
protobuf::libprotobuf
protobuf::libprotoc
)
I don't see any possibility here to specify the desired library. Normally I'd say I would specify it somehow like this:
target_link_libraries(MyEXE
debug protobuf::libprotobufd optimized protobuf::libprotobuf
debug protobuf::libprotocd optimized protobuf::libprotoc)
or wrap some fancy if-condition for the different build configurations around it. But due to protobuf effectively overwriting the target, I don't know how to extract the correct library for each build.
Any ideas?

I'm now looking for a possibility to tell CMake to not use the debug version of the libraries but the release version instead
Variables CMAKE_MAP_IMPORTED_CONFIG_ are intended for resolve exactly that problem:
# When build your project in RelWithDebugInfo configuration,
# try to use Release configuration of the *IMPORTED* libraries.
# If some IMPORTED library has no Release configuration, fallback to Debug one.
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBUGINFO Release Debug)
# Imported targets set with given call will be aware of the variable's set above
find_package(Protobuf REQUIRED)
# Simply link with an IMPORTED library.
target_link_libraries(MyExe protobuf::libprotobuf)
If you want to use Release configuration only from the specific IMPORTED libraries, but not all, you may set corresponded properties for these specific libraries:
# Firstly, create needed IMPORTED target.
find_package(Protobuf REQUIRED)
# Then adjust its properties.
# While the target is created by others, given property is specifically
# designed to be set in *your project*.
set_property(TARGET protobuf::libprotobuf PROPERTY MAP_IMPORTED_CONFIG_RELWITHDEBUGINFO Release Debug)
# Simply link with an IMPORTED library.
target_link_libraries(MyExe protobuf::libprotobuf)

Related

CMake: Modifying library output directory of a target from a sub-project

I have a top-level CMakeLists.txt which includes another third party project from a subdirectory, like
add_subdirectory(ext/third_party/cmake)
third_party contains a library target which I want to build, but I want to modify some properties and want to avoid to modify the original CMake file. I do not want to link some of my own targets to that library, I'd rather want that third party library to be build with some modified properties and then put it into a custom output directory. So I do
set_target_properties(libthird_party PROPERTIES
# some properties that successfully get applied here
LIBRARY_OUTPUT_DIRECTORY "/my/output/dir")
I can see that other properties are successfully applied and the build is modified to my needs correctly, but the generated output library is not put into the directory I set. What could be the reason for that?
If this is a totally wrong or bad approach please also feel free to propose a better approach for my goal.
Figured it out myself with help from the comments. I was trying to modify a static library target, which is not affected by LIBRARY_OUTPUT_DIRECTORY (which only applies to dynamic libraries) but which needs the setting ARCHIVE_OUTPUT_DIRECTORY. So the corrected call is
set_target_properties(libthird_party PROPERTIES
# some properties that successfully get applied here
ARCHIVE_OUTPUT_DIRECTORY "/my/output/dir")
You have to deal with [LIBRARY, ARCHIVE, EXECUTABLE] x [Single, Multi]config generator x [Unix, Windows]way.
note: On Windows everything (.dll, .exe) is on the same directory while on Unix you generally have a bin and a lib directories.
include(GNUInstallDirs)
# Single config (e.g. makefile, ninja)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
if(UNIX)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
else()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
endif()
# For multi-config build system (e.g. xcode, msvc, ninja-multiconfig)
foreach(OUTPUTCONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
if(UNIX)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR})
else()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
endif()
endforeach()

How to rewrite a find_package based library into one which can be embedded into the parent project directly?

The library libwebrtc from https://github.com/cloudwebrtc/libwebrtc-build/blob/dev/CMakeLists.txt was built to be used with make; make install and the project which wants to use the library must later use the find_package from CMake.
I, however, want to change libwebrtc so it can be added as a git submodule into my current project as a custom library, as, for instance, https://github.com/itay-grudev/SingleApplication which is compiled when I type: cmake ..; make into a static/dynamic library and then linked in my main application. (The Qt library example I references earlier was confusing since this is build outside of my main project and only linked to afterwards - which is not what I want). Sorry for that confusion.
To be able to do that, I think that the ExternalProject_Add at https://github.com/cloudwebrtc/libwebrtc-build/blob/a24a5e5947658d43339d4bfd85d3f4c52fc71057/CMakeLists.txt#L100 must be changed into a add_library call.
The problem here is that the include_directories is used by the main project before the library has been completely built.
Question
How to rewrite libwebrtc to be used as a simple static library with proper build dependencies so that my main project is only compiled/linked after the libwebrtc build was finished and custom header files were generated in the CMAKE_CURRENT_BINARY_DIR of libwebrtc.
Or in other words, how to rewrite libwebrtc to be used without having to call make install for the library and then use find_package to use that library.
The hack (which is working already)
With this hack I am already able to:
Build the library from my parent project
Depend on the generated header files which exist only after the libwebrtc has been built completely (thus, delay main project building until dependencies are meet)
Depend on the generated webrtc.a static library for the linker step
I imaging that make install will work since libwebrtc is statically linked.
add_dependencies(${PROJECT_NAME} libwebrtcx)
add_subdirectory(third-party/libwebrtcx)
include_directories(
${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/include/webrtc
${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/include/webrtc/third_party/libyuv/include/
${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/webrtc/src/third_party/abseil-cpp
)
add_library(libwebrtc STATIC IMPORTED)
set_property(TARGET libwebrtc PROPERTY IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/webrtc/src/out/Release/obj/libwebrtc.a")
target_link_libraries(${PROJECT_NAME} libwebrtc)
Note: It requires to rename the libwebrtc project to libwebrtcx and also the ExternalProject_Add at https://github.com/cloudwebrtc/libwebrtc-build/blob/a24a5e5947658d43339d4bfd85d3f4c52fc71057/CMakeLists.txt#L100 must be renamed to libwebrtcx.
Note: It also requires to rename all CMAKE_BINARY_DIR into CMAKE_CURRENT_BINARY_DIR and CMAKE_SOURCE_DIR to CMAKE_CURRENT_SOURCE_DIR. Details can be found here: CMake: Using add_subproject with a library using Include ends up in wrong relative path

CMake link to only release config of target in both debug and release

Is there another a way to link only the release lib of a target when including the target with target_link_libraries for both release and debug configs.
I know target_link_libraries has the options optimize and debug and that it can be done like this
target_link_libraries(current_target
optimized $<TARGET_PROPERTY:lib_target,IMPORTED_IMPLIB_RELEASE>
debug $<TARGET_PROPERTY:lib_target,IMPORTED_IMPLIB_RELEASE>
)
However I generally keep the targets in a list
set(target_list
lib_target1
lib_target2
...
)
and I perform other things on the same list, like getting the binary directory of the target to include in the search path for debugging. Using the optimized and debug options also do not allow the lib_target... properties to be passed along through the current_target. I can work around it just curious if there is another way?
If you link with IMPORTED target, then its configuration-dependent properties refer to "imported configuration". You may always adjust mapping between configurations of your project and imported ones:
Global configuration mapping is adjusted by CMAKE_MAP_IMPORTED_CONFIG_<CONFIG> variables.
The setting below will use Release configuration of every IMPORTED target for any of Release, Debug or RelWithDebugInfo configurations of your project:
set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release)
set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG Release)
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBUGINFO Release)
Note, that these settings should be issued before creating of IMPORTED targets. That is, if such targets are created with find_package() calls, the settings should precede these calls.
Per-target configuration mapping is adjusted by MAP_IMPORTED_CONFIG_<CONFIG> properties.
Settings below do the same as global settings above but only for lib_target1 IMPORTED target:
set_target_properties(lib_target1 PROPERTIES
MAP_IMPORTED_CONFIG_RELEASE Release
MAP_IMPORTED_CONFIG_DEBUG Release
MAP_IMPORTED_CONFIG_RELWITHDEBUGINFO Release)
These settings can be applied only after given IMPORTED target is created, e.g. after find_package() call.
It is worth to mention, that you may also specify fallback imported configurations:
set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG Release Debug)
With such setting, if your project is built in Debug configuration, and some IMPORTED target doesn't have Release configuration, then its Debug configuration will be used. (But if that target has neither Release nor Debug configuration, CMake will emit an error).

Why use add_library({tgt} IMPORTED) versus target_link_libraries( -l {.so | .a})?

What is the purpose of using the statement:
add_library(<tgt> [SHARED|STATIC] IMPORTED)
From what I have found even if you create an imported library target above you still would need to specify the specific location of the actual .so or .a. This would take at least 3 cmake commands to link to an executable and the compiler still would not automatically search through the common include directories on your OS.
Example:
cmake code to link IMPORTED lib
From the CMake documentation I understand there are really 3 ways to link a library that is not built as a target in a subproject of your overall application/library.
CMake target_link_libraries() documentation
Using a CMake package for one of the shipped package scripts.
Using a linker flag:
target_link_libraries(<tgt> [SHARED|STATIC|...] -lncursesw)
Or using the IMPORTED library method (showcased in code at top).
A major difference when using the second method is that it only takes a single line of code and will search through all of your compiler's predefined include directories on you OS. Could anyone help me understand why the add_library() method is used?
Additional Realated SO Posts:
Include directories for IMPORTED libs
CMake imported library behavior
You should use add_library(<tgt> [SHARED|STATIC] IMPORTED) whenever you need to set properties such as dependencies, compile definitions, compile flags etc for <tgt>, and/or by extension, any targets that are linking against <tgt>.
Let's say you have two static libraries; libfoobar.a and libraboof.a, where libfoobar.a requires libraboof.a. Let's also say that these libraries contain some features that are enabled by -DSOME_FEATURE.
add_library(raboof STATIC IMPORTED)
set_target_properties(raboof PROPERTIES
IMPORTED_LOCATION <path-to-libraboof.a>
INTERFACE_COMPILE_DEFINITIONS "SOME_FEATURE"
)
add_library(foobar STATIC IMPORTED)
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION <path-to-libfoobar.a>
INTERFACE_LINK_LIBRARIES raboof
)
So when you link against libfoobar.a:
add_executable(my_app main.cpp)
target_link_libraries(my_app foobar)
CMake will make sure to link all dependencies in the correct order and will in this case also append -DSOME_FEATURE to the compile flags when you build my_app. Note that since we added libraboof.a as a dependency to libfoobar.a, -DSOME_FEATURE is added to any target that link against libfoobar.a through the transitive property.
If you don't use add_library(<tgt> <SHARED|STATIC> IMPORTED) in a scenario like this, you would have to manage any dependencies and required build options yourself for each target, which is quite error-prone.
This method is also often used in Config-modules for multi-component libraries to manage dependencies between the components.

CMake dependencies: force recompile on external library change

I'm trying to correctly manage a dependency of a target on a externally built library, and somehow I'm not succeeding. I have read tutorials, posts and examples aplenty and yet, since I'm new to CMake, I guess I'm missing some obvious thing.
Setup is as follows. An external library built in another (CMake unsupported) language produces a libadatest.a. I've used ExternalProject_Add for this. Then, there is another regular C target that uses this lib. Everything works fine, but if I change the original lib, even if I recompile it, the C target is not recompiled. Here is a complete sample. I'm using CMake 2.8.12:
cmake_minimum_required(VERSION 2.8)
include(ExternalProject)
ExternalProject_Add(
AdaTestExternal # Not important
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
BUILD_COMMAND gprbuild -P${CMAKE_CURRENT_SOURCE_DIR}/adalibtest -XOBJ_DIR=${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY} -XLIB_DIR=${CMAKE_CURRENT_BINARY_DIR}
ALWAYS 1 # Force build, gprbuild will take care of dependencies
# BUILD_ALWAYS 1 # For 3.0 higher versions?
INSTALL_COMMAND ""
)
add_custom_target(AdaTest DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/libadatest.a)
link_directories(${CMAKE_CURRENT_BINARY_DIR}) # Needed or won't find it
add_executable(main_ada main.c)
add_dependencies(main_ada AdaTest) # We must depend on the final output lib
target_link_libraries(main_ada adatest)
What I've attempted is to create an intermediate custom target which depends on the actual library, and in turn make the main C target depend on this target.
When I remove the externally built library (libadatest.a), that's properly externally recompiled but the main executable is not re-linked. Plainly seen in that the timestamp of the library is fresher than the executable which uses it.
I've also tried this instead of the custom target, with same negative result:
add_library(AdaTest
UNKNOWN IMPORTED
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/libadatest.a)
Found the proper solution (which was, as expected, simple) in this old post: http://www.cmake.org/pipermail/cmake/2010-November/041072.html
The gist is to use the actual file in target_link_libraries, so its timestamp is checked. So no need for intermediate or custom dependencies:
set(AdaTestLib ${CMAKE_CURRENT_BINARY_DIR}/libadatest.a)
add_executable(main_ada main.c)
add_dependencies(main_ada AdaTestExternal)
target_link_libraries(main_ada ${AdaTestLib})