Vcpkg: CMake: provide option to deploy DLLs on install() like VCPKG_APPLOCAL_DEPS

Created on 16 Aug 2017  路  17Comments  路  Source: microsoft/vcpkg

While Vcpkg copy all dependency DLLs include one for Qt properly it's there is no option to do that for install. Such option would be very useful for most of projects using CMake that create their installers using CPack+NSIS.

Of course I can simply copy DLLs and Qt plugins from output directory to install directory, but this would be very ugly hack that will likely to break at any moment. Another alternative is using BundleUtilities, but it's not handle Qt plugins and winqtdeploy is pretty broken in Vcpkg.

vcpkg-feature

Most helpful comment

Any updates on this? I'd really love to see this feature.

All 17 comments

You know what's funny, I made an issue exactly like this but then closed it.

When you CMake install something, it's ideally going to be in the users /usr/bin, /usr/lib, what have you.

Basically, your user should've already had all dependent DLLs/.sos installed so linking to them should be left as an exercise to the consumer, in my opinion.

@LeonineKing1199 I seen your issue and even thought to mention it, but after decide that don't make any sense. Basically what you talking about is deployment on Linux. It's my primary platform and on Linux you just prepare your binaries for package manager to come and pack them for you or install them appropriately. Also there are solutions for standalone apps such as snap or flatpack that do everything on their own and not require changes in CMakeLists.

Still when you need to deploy your application for Mac and Windows this is not the case. Mac expect all dependencies to be bundled inside DMG and Windows users expect installer and both can be created with CPack. This is why on these platforms install() used for completely different purposes: to prepare package structure that will be then packed into DMG or Windows installer (not yet checked UWP).

PS: Yeah to be fair it's not that different on Linux except for the dependencies management. CPack even have deb and rpm generators, but I never seen anyone actually using them and there no reason since each distribution have own way to delivery the software and packages not designed to be standalone anyway.

I'm awful with CMake, but here's a workaround I came up with. This my situation:

  • on windows, want dll's installed
  • have tests in the release folder that I don't want installed
  • have assets and things I do want installed with install()
  • using git bash -> dumbin isn't in path -> applocal doesn't work

I just copied the add_executable macro from vcpkg.cmake. To get the environment right you can use a custom target. To run after installation you use install(CODE ...).

  add_custom_target(InstallDLLs
      COMMAND powershell -noprofile -executionpolicy Bypass -file ${_VCPKG_TOOLCHAIN_DIR}/msbuild/applocal.ps1
      -targetBinary ${CMAKE_INSTALL_PREFIX}/yourapphere.exe
      -installedDir "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$<CONFIG:Debug>:/debug>/bin"
      -OutVariable out
  )
  set_target_properties(InstallDLLs PROPERTIES EXCLUDE_FROM_ALL TRUE)
  # runs this target after installation
  install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" --build . --target InstallDLLs --config RELEASE)")

Edit:
The previous way doesn't really work with cpack. Here's another hacky solution.

install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/
  DESTINATION .
  FILES_MATCHING PATTERN "*.dll")

Any updates on this? I'd really love to see this feature.

For Windows, my workaround is to have a template CopyDeps.bat which leverages the existing applocal.ps1 provided by vcpkg and looks like this:

@echo off
SET _TARGET_DIR=%1
SET _TARGET=@TARGET_OUTPUT_NAME@
SET _CONFIG=%2

if ("%_CONFIG%"=="Debug") (
    SET _TARGET=@TARGET_OUTPUT_NAME@d
    SET _VCPKG_ROOT=@VCPKG_INSTALL_ROOT@/debug/bin
) else (
    SET _VCPKG_ROOT=@VCPKG_INSTALL_ROOT@/bin
)

echo [install]: Copying deps for (%_TARGET%, %_CONFIG%)
powershell -noprofile -executionpolicy Bypass -file "@VCPKG_APPLOCAL@" -targetBinary "%_TARGET_DIR%/%_CONFIG%/Bin/%_TARGET%.dll" -installedDir "%_VCPKG_ROOT%" -OutVariable out

Which is used in this macro to generate a specialized CopyDeps.bat for any given target:

macro(install_target_deps target)
        # Get target output name
        get_target_property(TARGET_OUTPUT_NAME ${target} OUTPUT_NAME)
        if (TARGET_OUTPUT_NAME STREQUAL "TARGET_OUTPUT_NAME-NOTFOUND")
            # It means no custom output name was specified, so just use target name
            set(TARGET_OUTPUT_NAME "${target}")
        endif()
        configure_file(${CMAKE_CONFIGS_PATH}/CopyDeps.bat.in ${CMAKE_BINARY_DIR}/CopyDeps_${TARGET_OUTPUT_NAME}.bat @ONLY)
        install(CODE "execute_process(COMMAND \${CMAKE_BINARY_DIR}/CopyDeps_${TARGET_OUTPUT_NAME}.bat \${CMAKE_INSTALL_PREFIX} \${CMAKE_INSTALL_CONFIG_NAME})")
 endmacro()

And then invoke the macro after the target install() directive..

The only requirement then is that you detect and specify the:

  • Path to applocal.ps1: VCPKG_APPLOCAL
  • Root directory of the vcpkg triplet: VCPKG_INSTALL_ROOT

Hope this helps anyone having a similar problem.

I was really confused to see that this functionality is not included with vcpkg and I think this makes using vcpkg to create Windows installers more difficult than it should be. I would really appreciate if this functionality came with vcpkg.

Thanks for the inspiration on all of this. I think I have found a way to solve this by overloading the install command when VCPKG_APPINSTALL_DEPS is set to on (new feature flag)

Happy to hear your thoughts on this as I might have missed some edge cases.

Thanks! But I can't get it work.
It says:

CMake Error at C:/ProgramData/vcpkg/scripts/buildsystems/vcpkg.cmake:456 (_install):
  Unknown CMake command "_install".
Call Stack (most recent call first):
  CMakeLists.txt:181 (x_vcpkg_install_local_dependencies)


-- Configuring incomplete, errors occurred!

If I replace _install with install - it works.

It also not works with CPack. To make it work you should replace the following line in scripts\buildsystems\vcpkg.cmake:

-targetBinary \"${__VCPKG_APPINSTALL_DESTINATION}/$<TARGET_FILE_NAME:${TARGET}>\"

with this:

-targetBinary \"\${CMAKE_INSTALL_PREFIX}/${__VCPKG_APPINSTALL_DESTINATION}/$<TARGET_FILE_NAME:${TARGET}>\"

If I replace _install with install - it works.

Good catch. I've been testing it only with a custom install function as only then would this be transparent.

So I have this inside my own CMakeLists.txt - part of the original patch on splitting the aruguments for the install function.

function(install)
    _install(${ARGV})

    if(COMMAND x_vcpkg_install_local_dependencies)
        if(${ARGV0} STREQUAL "TARGETS")
            # Will contain the list of targets
            set(PARSED_TARGETS "")

            # Destination - [RUNTIME] DESTINATION argument overrides this
            set(DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")

            # Parse arguments given to the install function to find targets and (runtime) destination
            set(MODIFIER "") # Modifier for the command in the argument
            set(LAST_COMMAND "") # Last command we found to process
            foreach(ARG ${ARGN})
                if(${ARG} MATCHES "ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE")
                    set(MODIFIER ${ARG})
                    continue()
                endif()

                if("${ARG}" MATCHES "TARGETS|DESTINATION|PERMISSIONS|CONFIGURATIONS|COMPONENT|NAMELINK_COMPONENT|OPTIONAL|EXCLUDE_FROM_ALL|NAMELINK_ONLY|NAMELINK_SKIP")
                    set(LAST_COMMAND ${ARG})
                    continue()
                endif()

                if("${LAST_COMMAND}" STREQUAL "TARGETS")
                    list(APPEND PARSED_TARGETS "${ARG}")
                endif()

                if("${LAST_COMMAND}" STREQUAL "DESTINATION" AND ("${MODIFIER}" STREQUAL "" OR "${MODIFIER}" STREQUAL "RUNTIME"))
                    set(DESTINATION "${CMAKE_INSTALL_PREFIX}/${ARG}")
                endif()
            endforeach()

            x_vcpkg_install_local_dependencies(TARGETS ${PARSED_TARGETS} DESTINATION ${DESTINATION})
        endif()
    endif()
endfunction()

Unfortunately this part of the patch wasn't accepted by the VCPKG team.

It also not works with CPack. To make it work you should replace the following line in scripts\buildsystems\vcpkg.cmake:

-targetBinary \"${__VCPKG_APPINSTALL_DESTINATION}/$<TARGET_FILE_NAME:${TARGET}>\"

with this:

-targetBinary \"\${CMAKE_INSTALL_PREFIX}/${__VCPKG_APPINSTALL_DESTINATION}/$<TARGET_FILE_NAME:${TARGET}>\"

Awesome. Glad you could test that as I have no experience with that part of cmake myself.

I'll make a new pull request with these fixes.

Unfortunately this part of the patch wasn't accepted by the VCPKG team.

Last time was the problem with CPack, but now it works correctly. Maybe they accept a new version with an option to override install command as you did?
I'll test it as soon as you open the PR.

After the new PR #14129 the custom install command should change to:

function(install)
    _install(${ARGV})

    if(COMMAND x_vcpkg_install_local_dependencies)
        if(${ARGV0} STREQUAL "TARGETS")
            # Will contain the list of targets
            set(PARSED_TARGETS "")

            # Destination - [RUNTIME] DESTINATION argument overrides this
            set(DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")

            # Parse arguments given to the install function to find targets and (runtime) destination
            set(MODIFIER "") # Modifier for the command in the argument
            set(LAST_COMMAND "") # Last command we found to process
            foreach(ARG ${ARGN})
                if(${ARG} MATCHES "ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE")
                    set(MODIFIER ${ARG})
                    continue()
                endif()

                if("${ARG}" MATCHES "TARGETS|DESTINATION|PERMISSIONS|CONFIGURATIONS|COMPONENT|NAMELINK_COMPONENT|OPTIONAL|EXCLUDE_FROM_ALL|NAMELINK_ONLY|NAMELINK_SKIP")
                    set(LAST_COMMAND ${ARG})
                    continue()
                endif()

                if("${LAST_COMMAND}" STREQUAL "TARGETS")
                    list(APPEND PARSED_TARGETS "${ARG}")
                endif()

                if("${LAST_COMMAND}" STREQUAL "DESTINATION" AND ("${MODIFIER}" STREQUAL "" OR "${MODIFIER}" STREQUAL "RUNTIME"))
                    set(DESTINATION "${ARG}")
                endif()
            endforeach()

            x_vcpkg_install_local_dependencies(TARGETS ${PARSED_TARGETS} DESTINATION ${DESTINATION})
        endif()
    endif()
endfunction()

Changing:

set(DESTINATION "${CMAKE_INSTALL_PREFIX}/${ARG}")

to:

set(DESTINATION "${ARG}")

Thanks! CPack just not works correctly if ${CMAKE_INSTALL_PREFIX} specified outside the x_vcpkg_install_local_dependencies.
But now it works :)

I also noticed that Qt translations (.qm files) are not copied. But I think that this is a vcpkg issue.

@sandercox, could you open the PR with install function override? I would be happy to test it.

Here you go. Initially I was a bit biased on your request as I was also providing some extra changes on my install command that do not make sense to be part of vcpkg, but I found a way to only optionally perform this override, meaning I can still copy this code as a base for my private projects and add whatever I need.

So now you can use it with:

set(VCPKG_APPLOCAL_DEPS_INSTALL ON)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")

Enabling the option before including the toolchain, like you can also do for overlay ports and triplets.

Quite happy with this result - hoping this will also be accepted by the maintainers.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jasjuang picture jasjuang  路  3Comments

jack17529 picture jack17529  路  3Comments

ghost picture ghost  路  3Comments

cskrisz picture cskrisz  路  3Comments

cjvaijo picture cjvaijo  路  3Comments