H3D
H3D

Reputation: 11

CMake - How Can I link to an external library that I have packed with my project?

How Can I link to external library that I have packed with my project?

Here is how my project is set up:

I have a folder called App and that is where my main.cpp sits.

C:\Raph\src\App

main.cpp

I also have a folder called "ExternalLibrary" and that is where I have bundled Qt Library that I need to use in my project:

C:\Raph\src\ExternalLibrary\Platform\Windows\Qt5.6\VS2013_64bit

It contains 3 folders:

**bin**
    moc.exe
    rcc.exe
    uic.exe
    ......
    bunch of Qt dll files

**include**
    bunch of Qt header files

**lib**
    bunch of Qt lib files 

I need to setup Cmake to do three things:

  1. Link to Qt library that I have packed in "ExternalLibrary" dynamically.

  2. Automatically performs moc.exe, uic.exe, rcc.exe every time I add a Qt class or a resource in the project. I also dont need any of the generated moc_myClassname.cpp to show up in the project.

  3. I want All the Qt dlls to end up in my bin folder where the executable sits. e.g:

    C:\Raph\build\Raph\Windows\x64\Debug\bin

    Raph.exe + all necessary Qt Dlls

Upvotes: 1

Views: 978

Answers (1)

bweber
bweber

Reputation: 4092

(1) You can find the Qt Library the following way:

find_package(Qt5Core [...] 5.6 REQUIRED)
add_executable(someExe file1.cpp)
target_link_libraries(someExe Qt5::Core)

Note that you should keep the original directory stucture of Qt, also containing the Qt cmake scripts, e.g. lib/cmake/Qt5/Qt5Config.cmake

If you want a specific directory to be included when searching for Qt, you can do (before you search for Qt):

set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "./your/path")

This can for example be the directory you supply.

(2) You can use cmake's automoc feature. Just add

set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

at the beginning of your cmake script. CMake also supports AUTOUIC and AUTORCC. I haven't tried them yet, but the probably work similar.

(3) You can for example add a custom target to your project, that copies the dlls. When you build that target, all the dlls will be copied. The paths of the dlls you can get from the targets that the Qt find script defines (like Qt5::Core).

get_target_property(LOC_R Qt5::Core LOCATION_RELEASE)
get_target_property(LOC_D Qt5::Core LOCATION_DEBUG)

However, you should also scan these target for dependencies (other dlls they depend on). You could write a macro that scans a whole list of targets for the correspondig dlls and adds them to a list, let's call these RELEASE_DLLS and DEBUG_DLLS:

macro(copydlls RELEASE_DLLS DEBUG_DLLS MODULELIST)
    foreach(ELEMENT ${${MODULELIST}})
        get_target_property(LOC_R ${ELEMENT} LOCATION_RELEASE)
        get_target_property(LOC_D ${ELEMENT} LOCATION_DEBUG)
        list(APPEND ${RELEASE_DLLS} ${LOC_R})
        list(APPEND ${DEBUG_DLLS} ${LOC_D})


        get_target_property(DEPENDENCIES_RELEASE ${ELEMENT} IMPORTED_LINK_DEPENDENT_LIBRARIES_RELEASE)
        foreach(DEPENDENCY ${DEPENDENCIES_RELEASE})
            if(TARGET ${DEPENDENCY})
                get_target_property(LOC_R ${DEPENDENCY} LOCATION_RELEASE)
                if(${LOC_R} MATCHES ".dll$")
                    list(APPEND ${RELEASE_DLLS} ${LOC_R})
                endif()
            endif()
        endforeach()    

        get_target_property(DEPENDENCIES_DEBUG ${ELEMENT} IMPORTED_LINK_DEPENDENT_LIBRARIES_DEBUG)
        foreach(DEPENDENCY ${DEPENDENCIES_DEBUG})
            if(TARGET ${DEPENDENCY})
                get_target_property(LOC_D ${DEPENDENCY} LOCATION_DEBUG)
                if(${LOC_D} MATCHES ".dll$")
                    list(APPEND ${DEBUG_DLLS} ${LOC_D})
                endif()         
            endif()
        endforeach()    

        get_target_property(DEPENDENCIES_RELEASE ${ELEMENT} IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE)
        foreach(DEPENDENCY ${DEPENDENCIES_RELEASE})
            if(TARGET ${DEPENDENCY})
                get_target_property(LOC_R ${DEPENDENCY} LOCATION_RELEASE)
                if(${LOC_R} MATCHES ".dll$")
                    list(APPEND ${RELEASE_DLLS} ${LOC_R})
                endif()
            endif()
        endforeach()    

        get_target_property(DEPENDENCIES_DEBUG ${ELEMENT} IMPORTED_LINK_INTERFACE_LIBRARIES_DEBUG)
        foreach(DEPENDENCY ${DEPENDENCIES_DEBUG})
            if(TARGET ${DEPENDENCY})
                get_target_property(LOC_D ${DEPENDENCY} LOCATION_DEBUG)
                if(${LOC_D} MATCHES ".dll$")
                    list(APPEND ${DEBUG_DLLS} ${LOC_D})
                endif()
            endif()
        endforeach()

    endforeach()
endmacro()

Then you could get all the Qt dlls in a list, by calling this macro with:

IF(MSVC)
    set(QT_MODULES "Qt5::Core" "Qt5::Gui" "Qt5::Widgets")
    set(RELEASE_DLLS)
    set(DEBUG_DLLS)
    copydlls(RELEASE_DLLS DEBUG_DLLS QT_MODULES)    
ENDIF(MSVC)

When you've retrieved that information, you can create your custom target the following way. Let's assume you have all your dll paths in the lists RELEASE_DLLS and DEBUG_DLLS and your executable names as a list in TARGETS. Then you could do something like this:

if(MSVC)

    set(COPY_COMMAND_RELEASE "-E copy_if_different ")
    set(COPY_COMMAND_DEBUG "-E copy_if_different ")

    list(REMOVE_DUPLICATES RELEASE_DLLS)
    list(REMOVE_DUPLICATES DEBUG_DLLS)

    foreach(DLL ${RELEASE_DLLS})
        string(CONCAT COPY_COMMAND_RELEASE "${COPY_COMMAND_RELEASE} \"${DLL}\" ")
    endforeach()
    foreach(DLL ${DEBUG_DLLS})
        string(CONCAT COPY_COMMAND_DEBUG "${COPY_COMMAND_DEBUG} \"${DLL}\" ")
    endforeach()

    string(CONCAT COPY_COMMAND_RELEASE ${COPY_COMMAND_RELEASE} "\"${CMAKE_CURRENT_BINARY_DIR}/Release\" ")
    string(CONCAT COPY_COMMAND_DEBUG ${COPY_COMMAND_DEBUG} "\"${CMAKE_CURRENT_BINARY_DIR}/Debug\" ")

    add_custom_target(COPY_DLLS)

    foreach(EXECUTABLE ${TARGETS})
        add_custom_command(TARGET COPY_DLLS PRE_BUILD COMMAND ${CMAKE_COMMAND} ${COPY_COMMAND_RELEASE} COMMENT "Copying dlls to executable directory...")   
        add_custom_command(TARGET COPY_DLLS PRE_BUILD COMMAND ${CMAKE_COMMAND} ${COPY_COMMAND_DEBUG} COMMENT "Copying dlls to executable directory...")
    endforeach()

endif()

If you want this target to be executed every time one of your other targets is built, you can do:

foreach(EXECUTABLE ${TARGETS})
    add_dependencies(${EXECUTABLE} COPY_DLLS)
endforeach()

Don't foget that you must also copy the platforms directory from plugins to your executable folder, for example by adding it to the copy dll target:

add_custom_command(TARGET COPY_DLLS PRE_BUILD 
    COMMAND ${CMAKE_COMMAND} -E copy_directory ${PATH_QT_ROOT}/plugins/platforms ${CMAKE_CURRENT_BINARY_DIR}/Debug/platforms
    COMMAND ${CMAKE_COMMAND} -E copy_directory ${PATH_QT_ROOT}/plugins/platforms ${CMAKE_CURRENT_BINARY_DIR}/Release/platforms
    COMMENT "Copying Qt platforms to executable directory...")

Upvotes: 1

Related Questions