Closed lem0nify closed 3 months ago
Hi @lem0nify, thank you for taking the time to write this issue.
The entire build process with CXX-Qt is rather complicated, so issues with cross-compiling are unfortunately not unexpected.
The solution to provide a custom path to QMAKE is indeed correct. I don't think there's another way we can detect where to look for those, just by using the target triple.
If I recall correctly, we detect the exact dlls to link to from the .prl
files that come with your Qt installation.
So that is not really in our control.
Cxx-Qt-lib has some features that you can use to specify which Qt modules it should link to (qt_gui
, qt_qml
, qt_quickcontrols
).
Depending on what you're doing, using those could reduce the number of linked dlls.
However, for the counter example, you'll still need the QuickControls libraries, which are the ones you want to get rid of.
We also link to Network
by default because of some issues on macOS, which tends to static-link with some installations.
But you can get rid of that in the cargo_without_cmake
build script.
@ahayzen-kdab I guess we could investigate dumping out symlinks to the dlls we link to within the CXX_QT_EXPORT_DIR at least :thinking: That should at least allow you to find them more easily. Doing so in the target directory will be tough, as we would need to manually find that in our build scripts, which we've tried to avoid, as it's not really compatible with cargos build model.
However, for the counter example, you'll still need the QuickControls libraries, which are the ones you want to get rid of.
No, I didn't want to avoid linking with QuickControls. I mean, I don't understand why BasicStyle and FusionStyle are there if I'm building an app exclusively with the native Windows style. Isn't WindowsStyle enough? Or maybe I can somehow manually limit the build to the native style?
We also link to Network by default because of some issues on macOS, which tends to static-link with some installations.
Can't we use conditional compilation or something to make this only apply to macOS builds?
But you can get rid of that in the cargo_without_cmake build script.
I didn't quite understand what script you're talking about and how to use it. I don't use CMake, I have a regular Cargo project with only Cargo.toml
and build.rs
of the following contents:
[package]
name = "qcounter"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
cxx = "1"
cxx-qt = "0.6"
cxx-qt-lib = "0.6"
[build-dependencies]
cxx-qt-build = "0.6"
use cxx_qt_build::{CxxQtBuilder, QmlModule};
fn main() {
CxxQtBuilder::new()
.qml_module(QmlModule {
uri: "org.lem0nify.counter",
rust_files: &["src/bridge.rs"],
qml_files: &["qml/main.qml"],
..Default::default()
})
.build();
}
CXX-Qt cannot tell which style you're using, so it needs to link to all of them. I don't know if there are some Qt tools that can determine this. We would have to check whether Qt can determine this in a normal CMake build and see how that works. Though I doubt that Qt can tell either.
Can't we use conditional compilation or something to make this only apply to macOS builds?
Sure, you can use #[cfg(macos)]
to disable the call to .qt_module("Network")
.
I meant the example here: https://github.com/KDAB/cxx-qt/blob/1d1af2f29c5160e484c1f94ab8ab5e92b2dda5a1/examples/cargo_without_cmake/build.rs#L16
We don't use conditional compilation in our examples as those are supposed to be as simple as possible and linking to "Network" usually doesn't hurt.
However, you don't actually have the call to "Network" in your build script, so it's indeed strange that it shows up as a dependency :thinking: It may be listed as a dependency of another Qt module somewhere. We extract the list of DLLs to link from the prl files that come with each Qt installation. So maybe that's listed there somewhere...
We extract the list of DLLs to link from the prl files that come with each Qt installation. So maybe that's listed there somewhere...
Here are all .prl files containing "Qt6Network" string in my Qt installation:
lem0nify@arch ~/src/mxe/usr/x86_64-w64-mingw32.shared % find . -name '*.prl' -exec awk '/Qt6Network/{print gensub(/.*\//,"",1,FILENAME); nextfile}' {} +
Qt6Network.prl
Qt6QmlDebug.prl
Qt6QmlDom.prl
Qt6QuickTestUtils.prl
Qt6QuickControlsTestUtils.prl
Contents of Qt6QmlDom.prl
file:
QMAKE_PRL_TARGET = libQt6QmlDom.a
QMAKE_PRL_CONFIG = static
QMAKE_PRL_VERSION = 6.7.2
QMAKE_PRL_LIBS = $$[QT_INSTALL_LIBS]/libQt6QmlBuiltins.a $$[QT_INSTALL_LIBS]/libQt6Qml.a $$[QT_INSTALL_LIBS]/libQt6QmlCompiler.a $$[QT_INSTALL_LIBS]/libQt6Qml.a $$[QT_INSTALL_LIBS]/libQt6QmlBuiltins.a $$[QT_INSTALL_LIBS]/libQt6Network.a $$[QT_INSTALL_LIBS]/libQt6Core.a -lmpr -luserenv -lws2_32 -lshell32
QMAKE_PRL_LIBS_FOR_CMAKE = $$[QT_INSTALL_LIBS]/libQt6QmlBuiltins.a;$$[QT_INSTALL_LIBS]/libQt6Qml.a;$$[QT_INSTALL_LIBS]/libQt6QmlCompiler.a;$$[QT_INSTALL_LIBS]/libQt6Qml.a;$$[QT_INSTALL_LIBS]/libQt6QmlBuiltins.a;$$[QT_INSTALL_LIBS]/libQt6Network.a;$$[QT_INSTALL_LIBS]/libQt6Core.a;-lmpr;-luserenv;-lws2_32;-lshell32
Does it mean that my application is linked statically against it?
By the way, it's interesting that MXE does some static linkages with x86_64-w64-mingw32.shared
target. It will break LGPL oligations if I sell my application. 🥲
If I understand the code in qt-build-utils correctly (I haven't written it and am not too familiar with it), it will detect all libraries that are listed under QMAKE_PRL_LIBS and instruct cargo to link to those libraries.
For this it strips away the lib
prefix and the file extension .a
.
So cargo will be instructed to link to Qt6Network
.
Whether it does so statically or dynamically seems to be up to the linker at that point, as we're not passing in the KIND to link :thinking:
If you want to investigate this yourself, take a look at crates/qt-build-utils/src/lib.rs
and the cargo_link_libraries
function.
This is what sets up the way we link to Qt with Cargo.
I investigated this and found out that CXX-QT actually links my application only to Qt6Core.dll
, Qt6Gui.dll
and Qt6Qml.dll
, since none of the .prl files of these libraries for Windows contain reference to Qt6Network. At first I thought that CXX-QT for some reason parses .prl files from my host system (libQt6Qml.prl
there indeed contains it), despite the fact that I specify the path to cross-qmake, but no.
It turns out that Qt6Qml.dll
itself is linked to Qt6Network.dll
, unfortunately. 😢 I discovered this when I was browsing the import sections of the binaries with CFF Explorer in a Windows VM.
Yeah, seems it will be hard for you guys to implement the creation of a bundle with all the required libraries when cross-compiling.
But you could at least bundle the qml
directory with the required QML plugins (or whatever is required, I'm not very familiar with Qt terminology). Unlike the error messages about missing libraries, the application doesn't properly report missing required qml
directory contents, so I had to just copy the whole thing into the application folder from the Qt installation.
What qml
files are you referring to exactly?
If you declare a qml_module on the CxxQtBuilder, the files specified there should be compiled into your application so shouldn't need to be distributed separately?
Yes, Qt libraries may link to other Qt libraries or system libs that are required to be distributed with your application, this is a common problem even without Rust for general Qt/C++ and there are tools like windeployqt6 trying to help.
For static vs dynamic linking I would note that static linking by itself is not necessarily a violation of LGPL, it means you need to be able to provide object code for your application though, see https://www.gnu.org/licenses/gpl-faq.en.html#LGPLStaticVsDynamic
Yes, cross compiling in general is tricky when you involve C/C++ and not inside only Rust libraries :-/
For QML modules, if you are using https://docs.rs/cxx-qt-build/latest/cxx_qt_build/struct.CxxQtBuilder.html#method.qml_module then the QML resources and modules are statically linked into the Rust library (and then into your executable) and will be found by qrc (Qt resources) so there should be no need for distributing a qml
folder as they'll be embedded inside the binary.
What qml files are you referring to exactly?
It's easier to show than to explain in words.
Here is folder with *plugin.dll
files which are loaded dynamically (not listed in import sections of binaries) and application doesn't show any error message if they are missing. It just doesn't launch, as you can see.
There are also *.qmltypes
and qmldir
files there but I'm not sure they are needed or not.
If I remove all subdirectories from there except QtQml
and QtQuck
application launches successfully, so for counter example only these two components are needed. How should I figure out that exactly they are needed without trial and error method?
there are tools like windeployqt6 trying to help
windeployqt6
is not a solution. Maybe it is for C++ Qt application but not for CXX-QT rust application, cross-compiled from linux. Here is an environment created with windeployqt6
:
As you can see, it is redundant. It is both redundant and still insufficient to run the application (Qt6QuickControls2*.dll
files are missing and qml
directory here is empty).
Ah that qml
directory, those come from the Qt toolchain/sdk/runtime (whatever you want to call it) and need to be bundled with your application. You may need to use a combination of things like --qmldir --qmlimport --plugindir etc to inform windeployqt to scan for QML dependencies that are used.
But maybe it gets slightly confused as I don't think anyone has tried this yet, so might need some investigation how to set it up correctly.
windeployqt takes an .exe file or a directory that contains an .exe file as an argument, and scans the executable for dependencies. If a directory is passed with the --qmldir argument, windeployqt uses the qmlimportscanner tool to scan QML files inside the directory for QML import dependencies. Identified dependencies are then copied to the executable's directory.
https://doc.qt.io/qt-6/windows-deployment.html#the-windows-deployment-tool
Hm, right, that's data that's not really provided by your application, but rather by the Qt installation, as @ahayzen-kdab noted.
The point here that we will need to know is whether this is an issue with CXX-Qt or with cross-compilation from Linux. If it's a general problem in cross-compiling with Qt, we probably won't be able to help.
Could you test your setup with a simple CMake-based example to check that the issue doesn't exist there and is indeed specific to CXX-Qt?
Could you test your setup with a simple CMake-based example to check that the issue doesn't exist there and is indeed specific to CXX-Qt?
Yeah, windeployqt6.exe
from Qt for Windows toolchain works in the same way for C++ QtQuick helloworld cross-compiled for linux, so it is not CXX-QT issue.
I'm not saying this is a CXX-QT issue at all. I'm not even saying you SHOULD find a solution to this problem on the CXX-QT side. But you COULD try to find it once the main tasks are done, because it would still be a good improvement, wouldn't it?
Even though it would be an improvement, we would also have to maintain such a solution. If not even Qt has the time to solve this issue, we probably won't either.
So for now I'll close this issue and we may revisit it after 1.0, though I doubt we will ever find time to go beyond what Qt can do for cross-compiling. Currently we only aim to get to the same level of support that Qt itself has, and not expand much beyond that. This is an open-source free-to-use project after all.
@LeonMatthesKDAB Well. Excuse me for offtopic, is it planned to add bindings to other Qt modules? I'm particularly interested in vanilla QtWidgets.
Not with the same level of support that we want to provide for QML.
The Widgets API is huge and wrapping it completely is a big undertaking, so we won't be able to do that on our own.
Most types are rather easy to wrap with CXX though, so depending on your task you may be able to do a lot of that yourself.
We hope to support wrapping more types through our new cxx-qt-lib-extras
crate.
The goal for this crate is to have a place where the community can put wrappers for Qt types, even if they're not completetly wrapped and maybe not up to the quality of cxx-qt-lib.
Contributions there will have a much lower bar of entry than e.g. cxx-qt-lib and we won't provide many guarantees about it.
So hopefully we can have many people contribute their bindings to QtWidgets types there, which may fill the gap.
Excuse me, but I haven't found a better place for such questions. I want to compile the simplest application (say, counter application example from CXX-QT book) for windows using linux. How do I do it?
I've tried just specifying target with
--target
cargo flag likecargo build --release --target x86_64-pc-windows-gnu
but here's what I see:Looks like it can't find
QtGui/qwindowdefs_win.h
header. Where can I get it from and how to provide path to it? I've also tried to build Qt cross-compiling toolchain with MXE hoping all needed files will be there but they aren't.UPD: Okay, I've built it like this:
Now the problem is that I have to provide Qt dlls with my executable for windows to run it on my Windows virtual machine. I have found out empirically that libraries needed are:
and also
platforms
dir withqwindows.dll
andqml
dir with QML modules. But it wasn't obvious. It would be obvious if the list was at least likefor such simple application.
Is there way to get whole application distribution for windows in release directory automatically to just zip it and provide to users?
Yes, I know about
windeployqt6.exe
tool, but: