boostorg / boost_install

8 stars 32 forks source link

CMake - Cannot link both debug AND release libraries #38

Closed jjELT closed 2 years ago

jjELT commented 4 years ago

As far as I know, it is currently not possible to add both the debug and the release libraries to a target using the cmake code generated by Boost.Build (b2) during the built process. This is a problem for multi-configuration generators like MS VS2019 or Xcode, as well as e.g. the CMAKE_BUILD_TYPE RelWithDebInfo.

For single-configuration generators and a CMAKE_BUILD_TYPE Debug or Release it is easy to add boost. I first define both variables Boost_USE_DEBUG_LIBS and Boost_USE_RELEASE_LIBS, then search for the boost components I want to add, e.g. Boost::log, and link the found targets.

# Define Boost_USE_DEBUG_LIBS and Boost_USE_RELEASE_LIBS 
# Check if a multi-config generator is used.
if(GENERATOR_IS_MULTI_CONFIG)
    # A multi-configuration generator is used. Use debug and release libs.
    set(Boost_USE_DEBUG_LIBS ON)  
    set(Boost_USE_RELEASE_LIBS ON)
else()
    # If not, check which kind of configuration is beeing built.
    if(${CMAKE_BUILD_TYPE} STREQUAL "Debug" )
        # Only debug libraries are needed.
        set(Boost_USE_DEBUG_LIBS ON)  
        set(Boost_USE_RELEASE_LIBS OFF)  
    elseif(${CMAKE_BUILD_TYPE} STREQUAL  "RelWithDebInfo")
        # Both debug and release libraries are needed.
        set(Boost_USE_DEBUG_LIBS ON)  
        set(Boost_USE_RELEASE_LIBS ON)
    else()
        # Only release libraries are needed.
        set(Boost_USE_DEBUG_LIBS OFF)  
        set(Boost_USE_RELEASE_LIBS ON)
    endif()
endif()

# Find Boost
find_package(Boost 1.72
                REQUIRED 
                COMPONENTS log
                PATHS ${BOOST_HOME}/cmake)

# Automagically add the component to the target, adding the 'include' folder and static or shared and import library.
target_link_libraries(${TARGET_NAME} PRIVATE Boost::log)

If both Boost_USE_DEBUG_LIBS and Boost_USE_RELEASE_LIBS is set to ON only the debug libraries are found! Because of that it is not possible to add both debug and release libraries to the target!

In my eyes the files created by Boost.Build (b2) and copied into the install folder (e.g. ${BOOST_HOME}/cmake) should make the method find_package(Boost ...) create variables like for example so: Boost::Release::log, Boost::Debug::log. Maybe even including the compiler, target architecture and bitness for cross-compilation, like so: Boost::<CONFIG>::<COMPILER_TAG>::<TARGET_ARCH>::<COMPONENT>, e.g. Boost::Release::mgw93::x86::log?!

That way one could link the appropiate libraries like so:

target_link_libraries(${TARGET_NAME} PRIVATE 
                    debug Boost::Debug::mgw93::x86::log 
                    optimized Boost::Release::mgw93::x86::log)
pdimov commented 4 years ago

There's no such problem with the Visual Studio generator. Both debug and release libraries are found, and the appropriate one is used. You can see an Appveyor log testing this here: https://ci.appveyor.com/project/pdimov/boost-install/build/job/9ybx1pin08noj0u7

What problem were you trying to solve with setting Boost_USE_DEBUG_LIBS and Boost_USE_RELEASE_LIBS?

pdimov commented 4 years ago

I also tried it with MinGW; without setting any of Boost_USE_DEBUG_LIBS or Boost_USE_RELEASE_LIBS, find_package finds both variants and building with CMAKE_BUILD_TYPE set to Debug or RelWithDebInfo correctly links to the debug libraries in the first case and the release libraries in the second.

pdimov commented 4 years ago

Appveyor log for MinGW with CMAKE_BUILD_TYPE=Debug: https://ci.appveyor.com/project/pdimov/boost-install/builds/32203718/job/gs9c3d587lxmy2v5 For CMAKE_BUILD_TYPE=RelWithDebInfo: https://ci.appveyor.com/project/pdimov/boost-install/builds/32203718/job/v3s4vpuel0bpqm99

Note that with 1.72.0, you'd have another problem that is unrelated to debug/release: the libraries are built with a tag of -mgw73 (f.ex.) but BoostConfig looks for -mgw7. This is fixed in 1.73; the libraries are now correctly built with -mgw7. With 1.72, you need to either set Boost_COMPILER in order for the libraries to be found, or rename them.

jjELT commented 4 years ago

Sorry for the long post :)

Background Information

Reading the FindBoost documentation I understood setting Boost_USE_DEBUG_LIBS / Boost_USE_RELEASE_LIBS was necessary for find_package(Boost ...) to work properly.

Boost_USE_DEBUG_LIBS - Set to ON or OFF to specify whether to search and use the debug libraries. Default is ON.

Boost_USE_RELEASE_LIBS - Set to ON or OFF to specify whether to search and use the release libraries. Default is ON.

For multi-configuration generators I tried to set both variables to true, since during the CMake configuration it is obviously not yet clear which configuration is built, which lies in the nature of multi-configuration generators. I just now see that both are set to ON by default anyways, so I was just disabling the not needed variant in case CMAKE_BUILD_TYPE was Debug or Release.

In the meanwhile I understood that for the RelWithDebInfo configuration you do not need to somehow add external libraries in both Debug and Release configuration, but only the Release variant. I had therefore already removed the case:

elseif(${CMAKE_BUILD_TYPE} STREQUAL  "RelWithDebInfo")
    # Both debug and release libraries are needed.
    set(Boost_USE_DEBUG_LIBS ON)  
    set(Boost_USE_RELEASE_LIBS ON)

But the former realization renders this moot. I'll give it a try not setting the variables at all.

Problem Description

I guess I don't fully understand how target_link_libraries( ... ) works, especially for multi-configuration generators (VS2019).

How does e.g. target_link_libraries(${TARGET_NAME} PRIVATE Boost::log) "know" which variant of the Boost::log library to link, if the configuration can be changed dynamically in e.g. Visual Studio?

Also, what happens if I wanted to link a special configuration of the Boost library for the configurations RelWithDebInfo and MinSizeRel, which I have built with the appropriate cflags/cxxflags and a custom ID? For example:

# Excerpt of the command to build boost in the RelWithDebInfo variant 
b2 ... --buildid=RelWithDebInfo cflags=/DWIN32 /D_WINDOWS /Zi /O2 /Ob1 /DNDEBUG cxxflags=/DWIN32 /D_WINDOWS /GR /EHsc /Zi /O2 /Ob1 /DNDEBUG ...

# Excerpt of the command to build boost in the MinSizeRel variant 
b2 ... --buildid=MinSizeRel cflags=/DWIN32 /D_WINDOWS /O1 /Ob1 /DNDEBUG cxxflags=/DWIN32 /D_WINDOWS /GR /EHsc /O1 /Ob1 /DNDEBUG ...

Does this not make sense?

I guess in that case I could not use the line target_link_libraries(${TARGET_NAME} PRIVATE Boost::log) but I would have to manually add the concrete library file and all libraries Boost::log depends on, i.e. create a list and link that list like so:

set(boost_LIBS_TO_LINK  ${BOOST_ROOT}/lib/libboost_log-mgw93-mt-x64-1_72-RelWithDebInfo.dll
            ${BOOST_ROOT}/lib/libboost_atomic-mgw93-mt-x64-1_72-RelWithDebInfo.dll
            ${BOOST_ROOT}/lib/libboost_chrono-mgw93-mt-x64-1_72-RelWithDebInfo.dll
            ...)

target_include_directories(${TARGET_NAME} PRIVATE ${BOOST_ROOT}/include)
target_link_libraries(${TARGET_NAME} PRIVATE ${boost_LIBS_TO_LINK})

Right?

Single-Configuration Generators Work

What I experience is, that for single-configuration generators everything works as expected. I am creating different projects for Eclipse CDT (-G"Eclipse CDT4 - MinGW Makefiles") in several configurations (Debug, Release, RelWithDebInfo) and both for the mingw32 (32bit) and mingw64 (64bit) toolchains. Compiling and running the created executable works.

Problem with VS2019

Using VS2019 (-G"Visual Studio 16 2019") on the other hand does not work as expected. I create two solutions, one 32bit (-A Win32) and one 64bit (-A x64). Since VS2019 is a multi-configuration generator I do not need to specify the configuration.

Only Debug Works

Opening one of the solutions, the configuration Debug is selected by default. I choose the application project an right select "Set as Startup Project", press the play button ("Local Windows Debugger") to run the application and it works fine.

All other Configurations create Errors

Selecting any other configuration, e.g. Release results in an error when trying to run the application:

Symbol file not loaded Unhandled exception at 0x00007FFCA8215390 (.dll) in .exe: Stack cookie instrumentation code detected a stack-based buffer overrun.

Debug instead of Release Libraries linked

At this point it may be worth mentioning that I am using shared libraries (BUILD_SHARED_LIBS=ON, b2 ... link=shared runtime-link=shared ...), since I am using Boost::log in several of my libraries and it is more convenient to link the shared libs.

For testing purposes, I removed/renamed the boost libraries (located in e.g. ${BOOST_ROOT}/lib) I would expect to be linked for the release configuration, i.e. all files with the pattern boost_<COMPONENT>-vc142-mt-x64-1_72. To my surprise the application is not complaining about a missing .DLL file, but starts as before, crashing with the same error. I then removed/renamed all boost libraries I would expect to be linked to a Debug configuration, i.e. all files with the pattern boost_<COMPONENT>-vc142-mt-gd-x64-1_72. Now I get the expected "DLL not found error":

The code execution cannot proceed because boost_log-vc142-mt-gd-x64-1_72.dll was not found. Reinstalling the program may fix this problem.

Since I do not really now how to solve this problem I created this issue.

Next Steps

So I guess you do not experience the same problem!?

My next test would be to create a minimal example. If the problem persists I could attach that example to a comment made here in this issue for you to have a look at it.

jjELT commented 4 years ago

Note that with 1.72.0, you'd have another problem that is unrelated to debug/release: the libraries are built with a tag of -mgw73 (f.ex.) but BoostConfig looks for -mgw7. This is fixed in 1.73; the libraries are now correctly built with -mgw7. With 1.72, you need to either set Boost_COMPILER in order for the libraries to be found, or rename them.

BTW: I heard about the problem and I am setting Boost_COMPILER explicitly to override the wrong value.

pdimov commented 4 years ago

How does e.g. target_link_libraries(${TARGET_NAME} PRIVATE Boost::log) "know" which variant of the Boost::log library to link, if the configuration can be changed dynamically in e.g. Visual Studio?

In CMake, many properties have configuration-specific versions. In this case, BoostConfig sets the configuration-specific IMPORTED_LOCATION properties: https://cmake.org/cmake/help/latest/prop_tgt/IMPORTED_LOCATION_CONFIG.html

That is, it sets IMPORTED_LOCATION_DEBUG and IMPORTED_LOCATION_RELEASE. CMake then knows what to use depending on the current configuration (for multiconfig generators) or CMAKE_BUILD_TYPE (for single config generators.)

For configurations that aren't Debug/Release, https://cmake.org/cmake/help/latest/prop_tgt/MAP_IMPORTED_CONFIG_CONFIG.html is used, pretty much as in the given example:

set_target_properties(foo PROPERTIES
  MAP_IMPORTED_CONFIG_MINSIZEREL Release
  MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
  )

So I guess you do not experience the same problem!?

No, I don't; I gave you the Appveyor logs.

Linking Debug and Release under MSVC generally gives an error, because the linker has a consistency check, but I suppose this doesn't apply when using DLLs as in your case.

The only reason that comes to mind for the behavior you're seeing is for the "Release" configuration to not be named "Release" but something else. Unknown configuration names map to Debug by default.

Reading the FindBoost documentation I understood setting Boost_USE_DEBUG_LIBS / Boost_USE_RELEASE_LIBS was necessary for find_package(Boost ...) to work properly.

I don't think this is necessary.

pdimov commented 4 years ago

Also, what happens if I wanted to link a special configuration of the Boost library for the configurations RelWithDebInfo and MinSizeRel, which I have built with the appropriate cflags/cxxflags and a custom ID?

This is currently not supported. For it to work, IMPORTED_LOCATION_RELWITHDEBINFO would have to be set appropriately to the location of the library built as "RelWithDebInfo", and perhaps MAP_IMPORTED_CONFIG_RELWITHDEBINFO would need to be removed (or not, I'm not sure whether it applies when IMPORTED_LOCATION_RELWITHDEBINFO is set.)

jjELT commented 4 years ago

No, I don't; I gave you the Appveyor logs.

I just created a very simple project with one application only and I do not have the same error. I will have to investigate what I put into my "real" project, preventing find_package(Boost ...) to work properly and will post my solution here when I found the cause.

Thanks for your support.

jjELT commented 4 years ago

I found the culprit. I had the following code in one of my CMake files:

if(MSVC AND NOT DEFINED Boost_USE_DEBUG_RUNTIME)
    set(Boost_USE_DEBUG_RUNTIME ON)  
endif()

I now commented that part, even though the code should work, right? See FindBoost

Boost_USE_DEBUG_RUNTIME - Set to ON or OFF to specify whether to use libraries linked to the MS debug C++ runtime ('g' tag). Default is ON.

It even states that the default is ON!?

Should this variable only be set to ON in case Boost_NO_BOOST_CMAKE=ON?

pdimov commented 4 years ago

I see no reason for you to define this variable in either case.

The debug Boost libraries use the debug runtime, and the release ones use the release runtime. If you force the use of the debug runtime, you effectively force the use of the debug Boost libraries (because they are the ones that use the debug runtime.)

The only possible use I see is if you have a nonstandard Boost build in which both debug and release libraries are linked to the release runtime, in which case you'd set Boost_USE_DEBUG_RUNTIME to OFF. But I don't think anyone does that.

(Apparently FindBoost interprets this variable differently and ignores it for the release libraries, which is why it doesn't have a problem with your setting it to ON. But that's unnecessary.)

jjELT commented 4 years ago

All right, I guess this is not an issue then.

Maybe the documentation of the Boost_USE_DEBUG_RUNTIME variable could be improved though, to make sure other users can avoid this pitfall.

But I guess I would have to create an issue at the Kitware bug tracker, since that seems to be their domain, right?

pdimov commented 4 years ago

I probably should just ignore this variable, this will create fewer user problems. :-)

The behavior of FindBoost is Kitware's domain, yes. The behavior of BoostConfig is mine.