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

Add qt_host Option to Enable Cross-Platform Qt Builds Using Host SDK Tools #6097

Merged
merged 1 commit into from
Jan 22, 2025

Conversation

Doekin
Copy link
Contributor

@Doekin Doekin commented Jan 19, 2025

This update introduces an option, qt_host, to facilitate cross-building of Qt desktop applications.

Example Usage

  1. Build a Qt Widgets app for MinGW on Linux
    Using an SDK downloaded by aqt:

    xmake f -c -p mingw --qt=../sdk/6.9.0/llvm-mingw_64 --qt_host=../sdk/6.9.0/gcc_64
  2. Build a Qt Widgets app for Linux on Windows
    Using an SDK downloaded by aqt and the Zig toolchain:

    xmake f -c -p linux --toolchain=zig --qt=../sdk/6.9.0/gcc_64 --qt_host=../sdk/6.9.0/llvm-mingw_64

Caution

  • Ensure the host Qt version matches the target version.
  • The rules qt.deploy and qt.install might cause cross-platform issues, as windeployqt and macdeployqt need to run on their native platforms.

Tested Configurations

  • Cross-Builds:

    • Qt 6.9.0 for Linux built on Windows with the Zig toolchain
    • Qt 6.9.0 for MinGW built on Linux
    • Qt 6.8.0 for Android built on Windows
  • Native Builds (Regression Tests):

    • Qt 6.8.1 for Linux (system-installed)
    • Qt 5.15.16 for Linux (system-installed)
    • Qt 6.9.0 for Linux (downloaded via aqt)
    • Qt 6.9.0 for MinGW

@Doekin
Copy link
Contributor Author

Doekin commented Jan 19, 2025

The -qtconf argument is valid for both qmake5 and qmake6.

Help message for qmake-qt5
$ qmake-qt5 -v
QMake version 3.1
Using Qt version 5.15.16 in /usr/lib

$ qmake-qt5 -help
Usage: qmake-qt5 [mode] [options] [files]

QMake has two modes, one mode for generating project files based on
some heuristics, and the other for generating makefiles. Normally you
shouldn't need to specify a mode, as makefile generation is the default
mode for qmake, but you may use this to test qmake on an existing project

Mode:
  -project       Put qmake into project file generation mode
                 In this mode qmake interprets [files] as files to
                 be added to the .pro file. By default, all files with
                 known source extensions are added.
                 Note: The created .pro file probably will 
                 need to be edited. For example add the QT variable to 
                 specify what modules are required.
  -makefile      Put qmake into makefile generation mode (default)
                 In this mode qmake interprets files as project files to
                 be processed, if skipped qmake will try to find a project
                 file in your current working directory

Warnings Options:
  -Wnone         Turn off all warnings; specific ones may be re-enabled by
                 later -W options
  -Wall          Turn on all warnings
  -Wparser       Turn on parser warnings
  -Wlogic        Turn on logic warnings (on by default)
  -Wdeprecated   Turn on deprecation warnings (on by default)

Options:
   * You can place any variable assignment in options and it will be *
   * processed as if it was in [files]. These assignments will be    *
   * processed before [files] by default.                            *
  -o file        Write output to file
  -d             Increase debug level
  -t templ       Overrides TEMPLATE as templ
  -tp prefix     Overrides TEMPLATE so that prefix is prefixed into the value
  -help          This help
  -v             Version information
  -early         All subsequent variable assignments will be
                 parsed right before default_pre.prf
  -before        All subsequent variable assignments will be
                 parsed right before [files] (the default)
  -after         All subsequent variable assignments will be
                 parsed after [files]
  -late          All subsequent variable assignments will be
                 parsed right after default_post.prf
  -norecursive   Don't do a recursive search
  -recursive     Do a recursive search
  -set <prop> <value> Set persistent property
  -unset <prop>  Unset persistent property
  -query <prop>  Query persistent property. Show all if <prop> is empty.
  -qtconf file   Use file instead of looking for qt.conf
  -cache file    Use file as cache           [makefile mode only]
  -spec spec     Use spec as QMAKESPEC       [makefile mode only]
  -nocache       Don't use a cache file      [makefile mode only]
  -nodepend      Don't generate dependencies [makefile mode only]
  -nomoc         Don't generate moc targets  [makefile mode only]
  -nopwd         Don't look for files in pwd [project mode only]

@Doekin
Copy link
Contributor Author

Doekin commented Jan 19, 2025

It's important to note that qmake reports its own version instead of the SDK version specified by -qtconf. For example, when running:

qmake -query -qtconf ../sdk/6.9.0/llvm-mingw_64/bin/qt.conf

The output shows:

QT_SYSROOT:
QT_INSTALL_PREFIX:/run/media/leemu/D8C48579C4855B20/projects/qt/sdk/6.9.0/llvm-mingw_64
...
QT_VERSION:5.15.16

Notice that QT_VERSION is 5.15.16, not 6.9.0 as expected. This discrepancy may lead to XMake incorrectly identifying the Qt SDK version:

$ xmake f -c -vD --qt=../sdk/6.9.0/llvm-mingw_64 --qt_host=../sdk/6.8.1/gcc_64 -p mingw

The output shows:

checking for Qt SDK version ... 6.8.1

The Qt SDK version should be 6.9.0

@Doekin Doekin marked this pull request as ready for review January 19, 2025 08:30
@Doekin
Copy link
Contributor Author

Doekin commented Jan 19, 2025

This discrepancy may lead to XMake incorrectly identifying the Qt SDK version

It is fixed now.

@waruqi
Copy link
Member

waruqi commented Jan 19, 2025

Why is an additional qt_host needed? I remember that cross-compilation was also supported now.

It can do cross-compilation for ios/android.

local bindir_host = qtenvs.QT_HOST_BINS
if not bindir_host and libexecdir and is_plat("android", "iphoneos") then
local rootdir = path.directory(path.directory(bindir))
if is_host("macosx") then
bindir_host = path.join(rootdir, "macos", "bin")
else
-- TODO
end
end
local libexecdir_host = qtenvs.QT_HOST_LIBEXECS
if not libexecdir_host and libexecdir and is_plat("android", "iphoneos") then
local rootdir = path.directory(path.directory(libexecdir))
if is_host("macosx") then
libexecdir_host = path.join(rootdir, "macos", "libexec")
else
-- TODO
end
end
return {sdkdir = sdkdir, bindir = bindir, bindir_host = bindir_host, libexecdir = libexecdir, libexecdir_host = libexecdir_host, libdir = libdir, includedir = includedir, qmldir = qmldir, pluginsdir = pluginsdir, mkspecsdir = mkspecsdir, sdkver = sdkver}

and did you test qt5base/qt6base packages? it also supports cross-compilation without extra qt_host sdk.

https://github.com/xmake-io/xmake-repo/blob/a266c2344972fd8d7cce4301b1a02500db9afa1c/packages/q/qtbase/xmake.lua#L153

@Doekin
Copy link
Contributor Author

Doekin commented Jan 19, 2025

The current setup doesn't support building Qt applications for Linux on Windows, or vice versa.

Regarding the qt6base package, I've encountered issues where it fails to function properly, with errors like:

$ xmake f -c -vD -p linux -a x86_64 --toolchain=zig
.........
checking for qt6widgets ... no
note: install or modify (m) these packages (pass -y to skip confirm)?
in myrepo:
  -> qt6base 6.8.0 [from:qt6gui,qt6core,qt6widgets, license:LGPL-3]
  -> qt6core 6.8.0 [toolchains:"zig", from:qt6gui,qt6widgets, license:LGPL-3]
  -> zlib v1.3.1 [toolchains:"zig", from:freetype, license:zlib]
  -> freetype 2.13.1 [toolchains:"zig", from:fontconfig,qt6gui, license:BSD]
  -> meson 1.6.1 [host, from:wayland,libxkbcommon,fontconfig, license:Apache-2.0]
  -> expat 2.6.4 [toolchains:"zig", from:fontconfig,wayland, license:MIT]
  -> gperf 3.1 [host, from:fontconfig, license:GPL-3.0-or-later]
  -> fontconfig 2.14.2 [toolchains:"zig", from:qt6gui]
  -> libxml2 v2.13.4 [toolchains:"zig", from:wayland, license:MIT]
  -> libffi 3.4.6 [toolchains:"zig", from:wayland, license:MIT]
  -> wayland 1.23.0 [toolchains:"zig", from:libxkbcommon, license:MIT]
  -> libxkbcommon 1.0.3 [toolchains:"zig", from:qt6gui, license:MIT]
  -> qt6gui 6.8.0 [toolchains:"zig", from:qt6widgets, license:LGPL-3]
  -> qt6widgets 6.8.0 [toolchains:"zig", license:LGPL-3]
please input: y (y/n/m)

checking for ping ... ok
pinging the host(downloads.sourceforge.net) ... 65535 ms
pinging the host(github.com) ... 100 ms
pinging the host(gitlab.freedesktop.org) ... 65535 ms
pinging the host(www.freedesktop.org) ... 218 ms
pinging the host(download.savannah.gnu.org) ... 65535 ms
pinging the host(gitlab.gnome.org) ... 65535 ms
aqt install-qt -O C:\Users\leemu\AppData\Local\.xmake\packages\q\qt6base\6.8.0\c9e1ef6b685d43d6b38c1427181ac297 windows desktop 6.8.0 linux_gcc_64
INFO    : aqtinstall(aqt) v3.1.17 on Python 3.12.4 [CPython MSC v.1940 64 bit (AMD64)]
WARNING : Specified Qt version "6.8.0" did not exist when this version of aqtinstall was released. This may not install properly, but we will try our best.
WARNING : Specified target combination "windows desktop linux_gcc_64" did not exist when this version of aqtinstall was released. This may not install properly, but we will try our best.
ERROR   : The packages ['qt_base'] were not found while parsing XML of package information!
==============================Suggested follow-up:==============================
* Please use 'aqt list-qt windows desktop --arch 6.8.0' to show architectures available.
error: @programdir\core\sandbox\modules\os.lua:378: execv(aqt install-qt -O C:\Users\leemu\AppData\Local\.xmake\packages\q\qt6base\6.8.0\c9e1ef6b685d43d6b38c1427181ac297 windows desktop 6.8.0 linux_gcc_64) failed(1)
stack traceback:
    [C]: in function 'error'
    [@programdir\core\base\os.lua:1075]:
    [@programdir\core\sandbox\modules\os.lua:378]:
    [@programdir\core\sandbox\modules\os.lua:291]: in function 'vrunv'
    [D:\projects\xmake-repo\packages\q\qtbase\xmake.lua:147]:
    [D:\projects\xmake-repo\packages\q\qt6base\xmake.lua:26]: in function 'script'
    [...dir\modules\private\action\require\impl\utils\filter.lua:114]: in function 'call'
    [...\modules\private\action\require\impl\actions\install.lua:452]:

  => install qt6base 6.8.0 .. failed
error: @programdir\core\main.lua:329: @programdir\modules\async\runjobs.lua:325: ...\modules\private\action\require\impl\actions\install.lua:561: install failed!
stack traceback:
    [C]: in function 'error'
    [@programdir\core\base\os.lua:1075]:
    [...\modules\private\action\require\impl\actions\install.lua:561]: in function 'catch'
    [@programdir\core\sandbox\modules\try.lua:123]: in function 'try'
    [...\modules\private\action\require\impl\actions\install.lua:419]:
    [...modules\private\action\require\impl\install_packages.lua:510]: in function 'jobfunc'
    [@programdir\modules\async\runjobs.lua:241]:

@Doekin Doekin changed the title Add cross-platform build support for Qt desktop applications Add qt_host Option to Enable Cross-Platform Qt Builds Using Host SDK Tools Jan 19, 2025
@Doekin Doekin marked this pull request as draft January 19, 2025 17:42
@Doekin

This comment was marked as outdated.

@Doekin Doekin marked this pull request as ready for review January 21, 2025 16:47
@Doekin
Copy link
Contributor Author

Doekin commented Jan 22, 2025

Additional Notes:

The Qt SDK consists of two main components: libraries and tools (e.g., qmake, moc, uic, etc.), where libraries are specific to the target platform.

Since Qt SDKs are not typically configured for cross-platform use, cross-compilation requires two separate SDKs:

  1. Target-Dependent SDK: Includes the libraries the application links against, built for the target platform. It may also contain Qt tools that are incompatible with the build machine.
  2. Host-Dependent SDK: Provides the tools needed for the build process, compatible with the build machine.

moc = path.join(qt.libexecdir_host, is_host("windows") and "moc.exe" or "moc")
local moc
local moc_name = is_host("windows") and "moc.exe" or "moc"
for _, dir in ipairs({qt.bindir_host, qt.libexecdir_host, qt.bindir, qt.libexecdir}) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use lib.detect.find_file

for _, dir in ipairs({qt.bindir_host, qt.libexecdir_host, qt.bindir, qt.libexecdir}) do
if dir then
qmltyperegistrar = path.join(dir, qmltyperegistrar_name)
if os.isexec(qmltyperegistrar) then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

for _, dir in ipairs({qt.bindir_host, qt.libexecdir_host, qt.bindir, qt.libexecdir}) do
if dir then
lupdate = path.join(dir, lupdate_name)
if os.isexec(lupdate) then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

@waruqi waruqi requested a review from SirLynix January 22, 2025 01:36
@waruqi
Copy link
Member

waruqi commented Jan 22, 2025

Did you test for qt5base/qt6base packages?

@Doekin
Copy link
Contributor Author

Doekin commented Jan 22, 2025

qt5base/qt6base does not support cross-compilation well. Cross-compiling for desktop platforms is not supported, and Android targets are not enabled.

https://github.com/xmake-io/xmake-repo/blob/4fd9216b6d1bd108f9211849a4bfd3b1b6f96b3c/packages/q/qt6base/xmake.lua#L25-L27

@Doekin Doekin force-pushed the cross-qt branch 2 times, most recently from 2857d30 to edb2d7f Compare January 22, 2025 02:17
if not os.isexec(uic) and qt.libexecdir_host then
uic = path.join(qt.libexecdir_host, is_host("windows") and "uic.exe" or "uic")
end
local uic = find_file(is_host("windows") and "uic.exe" or "uic", {qt.bindir_host, qt.libexecdir_host, qt.bindir, qt.libexecdir})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if dir is nil, e.g. {dir1, nil, dir2, dir3}, find_file/ipairs will be broken.

local dirs = {}
if dir then
    table.insert(dirs, dir)
end

return sdkver
end
-- Extract the actual SDK version from qconfig.pri
local actual_sdkver = io.readfile(qconfig_path):match("QT_VERSION%s*=%s*(%S+)") -- Expected format: QT_VERSION = x.y.z
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readfile maybe return nil

@Doekin Doekin force-pushed the cross-qt branch 2 times, most recently from ba531a3 to cd5a5d1 Compare January 22, 2025 09:07
androiddeployqt = path.join(qt.bindir_host, "androiddeployqt" .. (is_host("windows") and ".exe" or ""))
end
local search_dirs = {}
if qt.bindir_host then table.insert(search_dirs, qt.bindir_host ) end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove spaces, we don't need alignment

@waruqi waruqi added this to the v2.9.8 milestone Jan 22, 2025
@waruqi
Copy link
Member

waruqi commented Jan 22, 2025

Thanks, can you help to update docs here https://xmake.io/#/guide/project_examples?id=supported-qt-sdks

@waruqi waruqi merged commit 590cb76 into xmake-io:dev Jan 22, 2025
22 checks passed
@Doekin
Copy link
Contributor Author

Doekin commented Jan 22, 2025

OK, I'd be happy to help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants