6

I am working on a really large project, which I'm in the process of moving from using custom Makefiles to using cmake instead, but I'm still missing a functionality that was implemented with the Makefiles.

The project has many sub-directories, each one of which is compiled into a static library, and then linked into the final executable.

Here is a small example

src/
  lib1/
  lib2/
  lib3/
  main.cpp
  CMakeLists.txt

and in CMakeLists.txt might be something like this:

add_subdirectory(lib1)
add_subdirectory(lib2)
add_subdirectory(lib3)
add_executable(test main.cpp)
target_link_libraries(test PUBLIC lib1 lib2 lib3)

I want to debug the final executable, but I don't want to build all static libraries with debug symbols and no optimizations, because then the debugging becomes too slow.

So I want to build lib2 with CMAKE_BUILD_TYPE=Release and lib1 and lib3 with CMAKE_BUILD_TYPE=Debug.

Please bear in mind that instead of three libraries, there are actually ~10, and I want to be able to do that for each one of them, and for a number of them at the same time.

Is there a way to do that from the main CMakeLists.txt?

What I would prefer would be something that would make this possible from the command line:

cmake -DDEBUG_LIBS={lib1,lib3} /path/to/src
cmake --build .
ChrisG
  • 221
  • 4
  • 12
  • CMake 3.17 will introduce multiple build types within one build. Not sure whether that will be enough to cover your need. – usr1234567 Mar 11 '20 at 12:59
  • References to that? – ChrisG Mar 11 '20 at 13:01
  • The new 3.17 mulit-config functionality is limited to the Ninja generator (see [here](https://cmake.org/cmake/help/latest/generator/Ninja%20Multi-Config.html)). – Kevin Mar 11 '20 at 13:08
  • 1
    That would build all the targets with the same configuration, it just allows you to build two different configurations. That's not what I'm asking. – ChrisG Mar 11 '20 at 13:11
  • 1
    Yes, you'd have to run the build twice to get the mixed-config you want. – Kevin Mar 11 '20 at 13:19
  • 2
    Don't set `CMAKE_BUILD_TYPE` keep it blank or make a custom one where you set exactly what you want the base flags to be. Then per the answer and comments below add the additional debug and optimizations for the libraries that you want. I would suggest creating a function that each library target calls that checks to see if it shows up in `DEBUG_LIBS` and then call `target_compile_options` with the correct values. But you should set is as `-DDEBUG_LIBS=lib1;lib3` so that list handling works. – fdk1342 Mar 11 '20 at 23:17
  • Thanks @fdk1342 that fits my needs. Post it as an answer if you want so I can mark it as accepted. – ChrisG Mar 12 '20 at 22:36

3 Answers3

1

You can use ExternalProject instead of add_subdirectory for your dependencies, tell it to install into a subdirectory of ${CMAKE_CURRENT_BINARY_DIR}, then use find_package to find it there. This lets you use any combination of build options that you desire.

This does require a two-stage process when you first build it: run cmake, then make to build the dependencies, then cmake again to detect the dependencies, then make again to build your own targets. There are ways around that if you want, too.

For example, here's how I'm building OpenCV as part of my project (simplified):

# Build OpenCV from source.
include(ExternalProject)
ExternalProject_Add(opencv
    SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/opencv
    CMAKE_ARGS
        -DBUILD_LIST=core,imgproc
        -DBUILD_SHARED_LIBS=OFF
        -DCMAKE_BUILD_TYPE=Release
        -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/deps/opencv
)

# Find it in the directory where we just built it.
find_package(OpenCV
    PATHS ${CMAKE_CURRENT_BINARY_DIR}/deps/opencv
    NO_DEFAULT_PATH
)

# Abort if we weren't able to find it.
if(NOT OpenCV_FOUND)
    message(STATUS "OpenCV was not found. This means it still needs to be built from source. To build it, run:")
    message(STATUS "")
    message(STATUS "    cmake --build ${CMAKE_CURRENT_BINARY_DIR} --target all")
    message(STATUS "")
    message(STATUS "Then re-run cmake to detect the built library.")
    return()
endif()

# Consume it in the usual way.
add_binary(my_binary ...)
target_link_libraries(my_binary ${OpenCV_LIBS})

No idea if this is "best practice" or not, but it works for me.

Thomas
  • 174,939
  • 50
  • 355
  • 478
1

Don't set CMAKE_BUILD_TYPE keep it blank or make a custom one where you set exactly what you want the base flags to be. Then add the additional debug and optimizations for the libraries that you want. I would suggest creating a function (or macro) that each library target calls that checks to see if it shows up in DEBUG_LIBS and then call target_compile_options with the correct values. But you should set is as -DDEBUG_LIBS=lib1;lib3 so that list handling works.

function(check_debug libname)
  if(${libname} IN_LIST DEBUG_LIBS)
    target_compile_options(${libname} PRIVATE -g -O0)
  endif()
end_function()
fdk1342
  • 3,274
  • 1
  • 16
  • 17
0

Debug symbols do not usually affect speed. You can probably keep it for all targets. If the symbols are a problem, they can be stripped from the executable/library afterwards. This approach has the benefit that you can still debug the library that you didn't expect the need to debug (although if it is optimised, it may not necessarily be ideal experience).

You could enable or disable optimisation for single target with target_compile_options so that it is always or never optimised.


Specifying a different CMAKE_BUILD_TYPE can be problematic because it can affect macros which can affect the ABI of the program if you do things like add members based on NDEBUG macro. If you mix build types, then you must make sure that debug macros are not used in headers.

Simplest way to achieve that is to build the libs in separate projects and import the built libs into the final program that depends on the libraries.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Debug symbols affect the amount of time needed to load the executable in gdb, and they make the size of the executable larger. As for the `target_compile_options`, is there a way to iterate over all libs passed by `-DDEBUG_LIBS`, and adding the compile options only for them? – ChrisG Mar 11 '20 at 12:51
  • 1
    @ChrisG You could use an `IN_LIST` [generator expression](https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#string-comparisons) to conditionally add compilation options if the current target is in the `DEBUG_LIBS` list: `target_compile_options(lib3 PRIVATE $<$:-O0>)` – Kevin Mar 11 '20 at 13:17
  • I'm in the same boat as @ChrisG and would love to have a good solution. In my case, the executable with debug symbols from all libraries weighs 170 MB; linking and even startup outside gdb takes a fair bit of time. The release build is only 8 MB. – Thomas Mar 12 '20 at 10:07