Search code examples
c++dllcmakeconan

CMake Visual Studio Group together builded dll and dependend dlls from Conan


I have a project which i manage with CMake and build it with MSVC. For Dependency Management i use Conan with the cmake_find_package Generator.

The Build it self works without any problems. But i cannot run e.g. the Test Executable without copying the created dll and the dll from the dependencies to the Location of the Test Executable.

This is my conanfile.txt:

[requires]
gtest/1.10.0

[generators]
cmake_find_package

[options]
gtest:shared=True
gtest:build_gmock=True

This is my CMakeLists.txt File for the DLL:

cmake_minimum_required(VERSION 3.14)

if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
    message(FATAL_ERROR "Do not build in-source. Please remove CMakeCache.txt and the CMakeFiles/ directory. Then build out-of-source")
endif()

project("CommandDispatcher" VERSION 1.0.0 LANGUAGES CXX)

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

option(BUILD_SHARED_LIBS "Build shared (dynamic) libraries." ON)
option(ENABLE_TESTING "Build with Tests enabled." ON)

include(generate_product_version)
generate_product_version(
        VersionFilesOutputVariable
        ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/Product.ico
        NAME ${PROJECT_NAME}
        VERSION_MAJOR 1
        VERSION_MINOR 0
        VERSION_PATCH 0
        VERSION_REVISION 0
)

add_library(${PROJECT_NAME})
add_library("Product::CommandDispatcher" ALIAS ${PROJECT_NAME})

target_sources(${PROJECT_NAME}
                        PUBLIC
                        include/${PROJECT_NAME}/export.hpp
                        include/${PROJECT_NAME}/command_dispatcher.hpp
                        PRIVATE
                        src/command_dispatcher.cpp
                        src/test.hpp
                        src/test.cpp
                        ${VersionFilesOutputVariable}
)

target_include_directories(${PROJECT_NAME} PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
                            "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}>"
                            PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>")

set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 1 VERSION 1.0.0)

if (MSVC)
    set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX "d")
endif()

include(GenerateExportHeader)
string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER)
generate_export_header(${PROJECT_NAME}
    EXPORT_FILE_NAME ${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME}/export.hpp
    EXPORT_MACRO_NAME ${PROJECT_NAME_UPPER}_EXPORT
    STATIC_DEFINE ${PROJECT_NAME_UPPER}_STATIC
)

if(ENABLE_TESTING)
    message(STATUS "Build Library as Static Version for testing purpose")

    set(PROJECT_NAME_STATIC ${PROJECT_NAME}_static)
    add_library(${PROJECT_NAME_STATIC} STATIC)
    target_sources(${PROJECT_NAME_STATIC}
                        PUBLIC
                        include/${PROJECT_NAME}/export.hpp
                        include/${PROJECT_NAME}/command_dispatcher.hpp
                        PRIVATE
                        src/command_dispatcher.cpp
                        src/test.hpp
                        src/test.cpp
    )  
    
    target_include_directories(${PROJECT_NAME_STATIC}  PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
                                "$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}>"
                                PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>")

    string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER)
    set_target_properties(${PROJECT_NAME_STATIC} PROPERTIES COMPILE_FLAGS -D${PROJECT_NAME_UPPER}_STATIC)   

    if (MSVC)
       # set_target_properties(${PROJECT_NAME_STATIC} PROPERTIES DEBUG_POSTFIX "d")
    endif()

    message(STATUS "Running Tests for Library ${PROJECT_NAME}")

    include(CTest)
    enable_testing()
    add_subdirectory(tests)
endif()

I build the Library first as a DLL and also as a static library for testing purpose (to test also the private Parts of the Library.

This is the CMakeLists.txt inside the tests directory:

set(TEST_TARGET_NAME ${PROJECT_NAME}_TEST)

add_executable(${TEST_TARGET_NAME})

target_sources(${TEST_TARGET_NAME}
                    PRIVATE
                    ExampleTest.cpp
                    TestRunner.cpp
)                   

find_package(GTest)

if(TARGET GTest::GTest)
    message(STATUS "Target Found")
    target_link_libraries(${TEST_TARGET_NAME} PRIVATE GTest::GTest)
endif()

target_link_libraries(${TEST_TARGET_NAME} PRIVATE ${PROJECT_NAME_STATIC})

add_test(NAME ${TEST_TARGET_NAME}
         COMMAND ${TEST_TARGET_NAME}
         WORKING_DIRECTORY $<TARGET_FILE_DIR:${TEST_TARGET_NAME}>)

target_include_directories(${TEST_TARGET_NAME} PRIVATE $<TARGET_PROPERTY:${PROJECT_NAME_STATIC},INCLUDE_DIRECTORIES>)                                                      

Here i Link against the GTest Library which was installed with Conan.

If i run the Build the Results are in the following Folderstructure inside the Build Directory:

+---build
    |
    +---Debug
    |       CommandDispatcherd.dll
    +---tests
    |   |               
    |   \---Debug
    |           CommandDispatcher_TEST.exe

And the GTest DLLs are somewhere down my Userfolder/.conan.

What is a good way to group these things together? E.g. Building the DLL, Test Exe and Dependencies DLL inside a bin/test folder inside the build directory?

My first guess was to add the following to the Conanfile.txt:

[imports]
bin, gmockd.dll -> ./bin/test
bin, gmock_maind.dll -> ./bin/test
bin, gtestd.dll -> ./bin/test
bin, gtest_maind.dll -> ./bin/test

But for this i need to know the name of the builded dlls.

And also adding the following to the Main CMakeFile.txt:

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR}/lib)

Or should i make the changes depending of the target with set_target_properties?

Or is there a better solution out there? ALso for the Conan Part to copy the DLLs to the right place?

Also any improvements for my CMakeFile.txt are welcome =).


Solution

  • But for this i need to know the name of the builded dlls.

    I think you do not need to know name of the dlls. You can setup imports like:

    bin, *.dll -> ./bin/test @ root_package=gtest
    

    Note the root_package parameter, that will tell conan what package to copy dlls from. More info here: https://docs.conan.io/en/latest/reference/conanfile_txt.html#imports

    or in conanfile.py it can be implemented as:

    def imports(self):
        self.copy("*.dll", f"bin\\tests", "bin", root_package='gtest')