Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ArseniyShestakov opened this issue Aug 16, 2017 · 22 comments
Labels
category:vcpkg-feature The issue is a new capability of the tool that doesn’t already exist and we haven’t committed

Comments

@ArseniyShestakov
Copy link
Contributor

ArseniyShestakov commented Aug 16, 2017

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.

@ArseniyShestakov ArseniyShestakov changed the title Cmake: provide option to deploy DLLs on install() like VCPKG_APPLOCAL_DEPS CMake: provide option to deploy DLLs on install() like VCPKG_APPLOCAL_DEPS Aug 16, 2017
@cmazakas
Copy link
Contributor

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.

@ArseniyShestakov
Copy link
Contributor Author

ArseniyShestakov commented Aug 16, 2017

@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.

@dstephan1
Copy link

dstephan1 commented Apr 13, 2018

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")

@NancyLi1013 NancyLi1013 added the category:vcpkg-feature The issue is a new capability of the tool that doesn’t already exist and we haven’t committed label Feb 20, 2019
@MikeGitb
Copy link
Contributor

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

@jumpinjackie
Copy link
Contributor

jumpinjackie commented Oct 22, 2019

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.

@johannesstricker
Copy link

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.

@sandercox
Copy link
Contributor

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.

BillyONeal pushed a commit that referenced this issue Oct 13, 2020
…13011)

Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
@Shatur
Copy link
Contributor

Shatur commented Oct 19, 2020

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!

@Shatur
Copy link
Contributor

Shatur commented Oct 19, 2020

If I replace _install with install - it works.

@Shatur
Copy link
Contributor

Shatur commented Oct 19, 2020

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}>\"

@sandercox
Copy link
Contributor

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.

@Shatur
Copy link
Contributor

Shatur commented Oct 20, 2020

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.

@sandercox
Copy link
Contributor

sandercox commented Oct 20, 2020

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}")

@Shatur
Copy link
Contributor

Shatur commented Oct 20, 2020

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

@Shatur
Copy link
Contributor

Shatur commented Oct 20, 2020

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

@Shatur
Copy link
Contributor

Shatur commented Oct 22, 2020

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

@sandercox
Copy link
Contributor

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.

@JackBoosY
Copy link
Contributor

Close this issue via #14243 merged.

@dennisameling
Copy link

For anyone reading this: the first line needs to be set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON), with the X_ in front of it (experimental feature). See the relevant PR for details: #14243

Can confirm this works - git-for-windows/git#2971 🎉

Thanks @sandercox!

@Be-ing
Copy link
Contributor

Be-ing commented Jan 25, 2021

X_VCPKG_APPLOCAL_DEPS_INSTALL depends on CMake policy CMP0087: #15874

if(POLICY CMP0087)
    cmake_policy(SET CMP0087 NEW)
endif()

@mcraveiro
Copy link
Contributor

mcraveiro commented Apr 8, 2022

Not sure if anything has changed since @Be-ing 's post but I now see:

CMake Warning (dev) at vcpkg/scripts/buildsystems/vcpkg.cmake:50 (option):
  Policy CMP0077 is not set: option() honors normal variables.  Run "cmake
  --help-policy CMP0077" for policy details.  Use the cmake_policy command to
  set the policy and suppress this warning.

I wonder if the policy number changed since then. I eventually made the warning go away as follows:

set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON)

@JackBoosY
Copy link
Contributor

@mcraveiro Can you please make a PR to fix that?

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category:vcpkg-feature The issue is a new capability of the tool that doesn’t already exist and we haven’t committed
Projects
None yet
Development

No branches or pull requests