microsoft / vcpkg

C++ Library Manager for Windows, Linux, and MacOS
MIT License
23.09k stars 6.37k forks source link

Unused Qt5Quick.dll causes deployment failure for QML applications #10520

Open rpjohnst opened 4 years ago

rpjohnst commented 4 years ago

Describe the bug I'm building a Qt QML application with the following CMake:

find_package(Qt5 5.12 REQUIRED COMPONENTS Quick)
...
target_link_libraries(main PRIVATE Qt5::Quick)

This is enough to produce a linker command line containing the installed Qt5Quick.lib. However, my application does not directly use any APIs from that library, so the executable does not actually depend on Qt5Quick.dll. This causes applocal.ps1/qtdeploy.ps1 not to copy it into the output directory.

However, my application does use APIs from Qt5Qml, which expects to be able to load Qt5Quick.dll dynamically, so it fails on startup. If I artificially use an API from Qt5Quick, the necessary DLLs are copied and everything works.

(For the curious, I'm loading QML via QQmlApplicationEngine from Qt5Qml, and my workaround is to force a dependency on Qt5Quick by manually setting a style via QQuickStyle.)

Environment

Expected behavior Ideally, there would be some way for vcpkg's qt5-declarative port to force linking and/or force copying Qt5Quick.dll even in scenarios like this.

As a side note, I am not entirely sure how the linker is making this decision. Its documentation claims that its default behavior is to enable /opt:ref, but that this is disabled with /debug, and I still see this problem in builds that pass /debug to the linker. Perhaps that doesn't matter because I'm not even referencing the symbols?

JackBoosY commented 4 years ago

@Neumann-A Could you please have a look?

Thanks.

Neumann-A commented 4 years ago

@JackBoosY: That is a deployment problem of vcpkg. It uses dumpbin to detect the dependencies and deploy them. If it is a dynamically loaded plugin, the plugin gets not detect and that's it. If windeployqt (https://doc.qt.io/qt-5/windows-deployment.html) is available (qt5-tools required) we could use this instead of dumpbin.

heydojo commented 4 years ago

@rpjohnst as @Neumann-A mentions: vcpkg's windeployqt executable might be of use. I've had some success with it. Although not with anything like your use case AFAIK.

rpjohnst commented 4 years ago

Unfortunately, qtdeploy.ps1 is already basically just a port of windeployqt to PowerShell. They both read the executable's imports to determine its dependencies, and so they both fail exactly the same way here.

heydojo commented 4 years ago

Unfortunately, qtdeploy.ps1 is already basically just a port of windeployqt to PowerShell. They both read the executable's imports to determine its dependencies, and so they both fail exactly the same way here.

The difference between .ps1 files and .exe files is that dot ps1 files are scripts (used in conjunction with the powershell scripting environment) and a dot exe file is compiled executable code. This means that while the two things could be mistaken to be equivalents, in terms of vcpkg at least, they are actually unlikely to be and in this case they are not.

I'd like to mention again that I have had success using vcpkg's windeployqt.exe executable found within vcpkg's qt5-tools port. I encourage you to take a second look.

https://doc.qt.io/qt-5/windows-deployment.html

Qt libraries can be added by passing their name (-xml) or removed by passing the name prepended by --no- (--no-xml). Available libraries: bluetooth concurrent core declarative designer designercomponents enginio gamepad gui qthelp multimedia multimediawidgets multimediaquick network nfc opengl positioning printsupport qml qmltooling quick quickparticles quickwidgets script scripttools sensors serialport sql svg test webkit webkitwidgets websockets widgets winextras xml xmlpatterns webenginecore webengine webenginewidgets 3dcore 3drenderer 3dquick 3dquickrenderer 3dinput 3danimation 3dextras geoservices webchannel texttospeech serialbus webview

So:

SETLOCAL
SET SRCROOTDIR=H:\somedir
SET VCPKGDIR=%SRCROOTDIR%\windows\vcpkg
SET QTDIR=%VCPKGDIR%\installed\x64-windows
SET INSTDIR=%SRCROOTDIR%\windows\binary

%QTDIR%\tools\qt5-tools\bin\windeployqt ^
-winextras ^
-quick ^
--libdir %INSTDIR% ^
--plugindir %INSTDIR% ^
--release ^
--no-opengl-sw ^
--no-angle ^
--no-compiler-runtime ^
--no-translations ^
%INSTDIR%

Gets you the Qt5Quick.dll copied into your project's release binary directory.

Neumann-A commented 4 years ago

Maybe the *.ps script should call <platform>deployqt if it is available (meaning qt5-tools is installed)

rpjohnst commented 4 years ago

@heydojo You're passing -quick to windeployqt there to override its automatic dependency detection. I could just as easily add a command to my CMake script to copy Qt5Quick.dll directly. The issue here is that this should already be covered by target_link_libraries(main PRIVATE Qt5::Quick).

If the vcpkg port were to store that already-specified dependency on Qt5Quick somewhere other than the executable's imports, it could do that itself, either in applocal.ps1/qtdeploy.ps1, or by reusing windeployqt. It doesn't really matter how- it just needs to happen in the port.

Neumann-A commented 4 years ago

should already be covered by target_link_libraries(main PRIVATE Qt5::Quick)

That is not how CMake works. Here you are telling CMake that Qt5::Quick is a build requirement which it definitively is not since it is a runtime dependency. You really want Qt5::Qml there.

vcpkg port were to store that already-specified dependency on Qt5Quick somewhere other

Sry that is not on the VCPKG side. If you want that behavior feel free to write your own vcpkg toolchain which does that or you could also add a custom command to Qt5Quick which copies the IMPORTED_LOCATION_<CONFIG> somewhere (If you want that behavior this would be an upstream change).

rpjohnst commented 4 years ago

Interesting, there is probably a misunderstanding somewhere. (Probably mine :) )

The way I see it, Qt5::Quick is just a CMake target of some kind (which one exactly shouldn't matter), provided to me by way of the vcpkg port of Qt, such that I expect a dependency on it to make the slightly more-abstract "Qt Quick" available to my program. The fact that my actual executable does not happen to link to Qt5Quick.dll directly is merely an implementation detail of Qt, to which my build system should not be exposed.

The primary reason I see things this way is that, at the more-abstract level, I understand "Qt Quick" to depend on "Qt QML", not the other way around:

The Qt Quick module is the standard library for writing QML applications. While the Qt QML module provides the QML engine and language infrastructure, the Qt Quick module provides all the basic types necessary for creating user interfaces with QML.

I am not trying to mess with the QML language implementation itself, but "writing QML applications!" Therefore "Qt QML" is a transitive dependency of my program, by way of "Qt Quick." I don't see why I would want Qt5::Qml there.

This view is reinforced by the fact that, while qtdeploy.ps1 and windeployqt do automatically copy all the plugins for any linked modules, they don't treat Qt5Quick.dll as one of "Qt QML's" plugins- that is, Quick doesn't seem to be loaded dynamically as a QML plugin, but for some other reason. (This part is admittedly pretty fuzzy, I'm just trying to describe my thought process.)

If the CMake targets provided by upstream Qt do not accomplish what I'm asking for, perhaps that is an upstream bug as well. But vcpkg is the component providing the applocal deployment implementation in applocal.ps1/qtdeploy.ps1- if vcpkg wants to be responsible for that, it ought to do it right and copy the stuff required by the dependency I specified!

Am I getting something wrong here? Is there a better, recommended, or more-idiomatic "modern CMake" way to do this that I haven't found? (If yes to either of these questions, it could perhaps be better-documented.) Adding an extra command to every QtQuick-using application's build system seems... wrong, architecturally, to me.

nokutu commented 3 years ago

A way to bypass this issue for now is to add something from QTQuick to your code so it forces the dll to be copied. For example:

QSGEngine e; // Forces QTQuick to be included