summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-02-25 22:26:02 +0800
committerGitHub <[email protected]>2026-02-25 22:26:02 +0800
commit27f49cd4de23014a3e3d9f17740517ab8771f24f (patch)
tree0276470b068190848e260c8cbbe28bd74f364860
parente59f66e168353122a57283487d3417865ee324e1 (diff)
parent6eeaf60adca1e4bb95127cb8a1ccc46e73964020 (diff)
Merge pull request #7341 from luadebug/em
Fix WASM QT 6.9
-rw-r--r--xmake/actions/run/main.lua8
-rw-r--r--xmake/rules/qt/build_qt_wasm_app.lua128
-rw-r--r--xmake/rules/qt/config_static.lua2
-rw-r--r--xmake/rules/qt/load.lua54
-rw-r--r--xmake/rules/qt/xmake.lua15
5 files changed, 174 insertions, 33 deletions
diff --git a/xmake/actions/run/main.lua b/xmake/actions/run/main.lua
index effa362e5..d9b5e83d3 100644
--- a/xmake/actions/run/main.lua
+++ b/xmake/actions/run/main.lua
@@ -56,9 +56,15 @@ function _run_wasm_target_in_browser(targetfile, opt)
local rundir = opt.rundir
local addenvs = opt.addenvs
local setenvs = opt.setenvs
+ -- prefer the .html file over .js for browser targets
+ -- @see https://github.com/xmake-io/xmake/issues/7340
+ local htmlfile = targetfile:gsub("%.js$", ".html")
+ if htmlfile ~= targetfile and os.isfile(htmlfile) then
+ targetfile = htmlfile
+ end
local emrun = find_tool("emrun")
if emrun then
- os.execv(emrun.program, {targetfile}, {
+ os.execv(emrun.program, {"--serve_root", path.directory(targetfile), targetfile}, {
curdir = rundir, detach = option.get("detach"), addenvs = addenvs, setenvs = setenvs})
else
local python = find_tool("python3")
diff --git a/xmake/rules/qt/build_qt_wasm_app.lua b/xmake/rules/qt/build_qt_wasm_app.lua
new file mode 100644
index 000000000..dfaa54a1d
--- /dev/null
+++ b/xmake/rules/qt/build_qt_wasm_app.lua
@@ -0,0 +1,128 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author ruki
+-- @file build_wasm_app.lua
+--
+
+import("core.base.semver")
+
+-- Fix Emscripten JS issues for Qt WASM builds
+function _fix_emscripten_js(jsfile)
+ if not os.isfile(jsfile) then
+ return
+ end
+
+ -- Remove "use strict"; to avoid issues with `this` being undefined in strict mode
+ io.gsub(jsfile, "\"use strict\";", "")
+ io.gsub(jsfile, "'use strict';", "")
+ -- Patch visualViewport access issue if present (undefined this context)
+ io.gsub(jsfile, "this%.visualViewport",
+ "(typeof window !== 'undefined' ? window.visualViewport : null)")
+ -- Guard all document.querySelector(target) calls with try-catch.
+ -- Qt WASM uses "!" prefixed selectors that are not valid CSS selectors,
+ -- and corrupted strings from SharedArrayBuffer can also cause SyntaxErrors.
+ io.gsub(jsfile, "document%.querySelector%(target%)",
+ "(function(){try{return document.querySelector(target)}catch(e){return null}})()")
+ -- Replace findCanvasEventTarget with a robust version.
+ -- Qt WASM registers canvases in Module.specialHTMLTargets with "!" prefixed
+ -- keys (e.g. "!qtwindow1", "!qtoffscreen_xxx"). With pthreads +
+ -- ALLOW_MEMORY_GROWTH, Emscripten's UTF8ToString may read corrupted strings
+ -- from SharedArrayBuffer memory, causing the specialHTMLTargets lookup to fail.
+ -- Additionally, QWasmOffscreenSurface passes an empty string when
+ -- OffscreenCanvas is unavailable. The replacement adds a fallback that
+ -- iterates specialHTMLTargets to find any Qt-registered canvas element.
+ local content = io.readfile(jsfile)
+ local marker = "var findCanvasEventTarget = target => {"
+ local pos = content:find(marker, 1, true)
+ if pos then
+ -- Find the end of the function by counting matched braces
+ local depth = 1
+ local i = pos + #marker
+ while i <= #content and depth > 0 do
+ local c = content:sub(i, i)
+ if c == "{" then depth = depth + 1
+ elseif c == "}" then depth = depth - 1 end
+ i = i + 1
+ end
+ if content:sub(i, i) == ";" then i = i + 1 end
+ local new_func = "var findCanvasEventTarget = target => {\n"
+ .. " target = maybeCStringToJsString(target);\n"
+ .. " if (specialHTMLTargets[target]) return specialHTMLTargets[target];\n"
+ .. " if (GL.offscreenCanvases[target]) return GL.offscreenCanvases[target];\n"
+ .. " if (typeof target === 'string' && target.length > 0) {\n"
+ .. " var s = target.substr(1);\n"
+ .. " if (GL.offscreenCanvases[s]) return GL.offscreenCanvases[s];\n"
+ .. " }\n"
+ .. " if (target === 'canvas') {\n"
+ .. " var k = Object.keys(GL.offscreenCanvases);\n"
+ .. " if (k.length) return GL.offscreenCanvases[k[0]];\n"
+ .. " }\n"
+ .. " for (var key in specialHTMLTargets) {\n"
+ .. " if (typeof key === 'string' && key.charAt(0) === '!') {\n"
+ .. " var el = specialHTMLTargets[key];\n"
+ .. " if (el && (el.tagName === 'CANVAS' || (typeof OffscreenCanvas !== 'undefined' && el instanceof OffscreenCanvas))) return el;\n"
+ .. " }\n"
+ .. " }\n"
+ .. " try { return typeof document !== 'undefined' ? document.querySelector(target) : undefined; }\n"
+ .. " catch(e) { return undefined; }\n"
+ .. "};\n"
+ content = content:sub(1, pos - 1) .. new_func .. content:sub(i)
+ io.writefile(jsfile, content)
+ end
+end
+
+function main(target)
+ local qt = target:data("qt")
+ local pluginsdir = qt and qt.pluginsdir
+ if not pluginsdir then
+ return
+ end
+ local targetdir = target:targetdir()
+ local htmlfile = path.join(targetdir, target:basename() .. ".html")
+ if os.isfile(path.join(pluginsdir, "platforms/wasm_shell.html")) then
+ os.vcp(path.join(pluginsdir, "platforms/wasm_shell.html"), htmlfile)
+ io.gsub(htmlfile, "@APPNAME@", target:name())
+ local qt_sdkver = qt.sdkver or target:data("qt_sdkver")
+ if qt_sdkver and semver.new(qt_sdkver):ge("6.0") then
+ io.gsub(htmlfile, "@APPEXPORTNAME@", "createQtAppInstance")
+ local preload = ""
+ -- @see https://github.com/xmake-io/xmake/issues/6182
+ local preloadfiles = target:values("wasm.preloadfiles")
+ if preloadfiles then
+ local filelist = {}
+ for _, preloadfile in ipairs(preloadfiles) do
+ table.insert(filelist, string.format("'%s'", path.filename(preloadfile)))
+ end
+ if #filelist > 0 then
+ preload = string.format("preload: [%s],", table.concat(filelist, ", "))
+ end
+ end
+ -- Patch old containerElements (pre-Qt 6.5)
+ io.gsub(htmlfile, "containerElements: %[screen%],", function (w)
+ return w .. " " .. preload
+ end)
+ -- Patch new qtContainerElements (Qt 6.5+)
+ io.gsub(htmlfile, "qtContainerElements: %[screen%],", function (w)
+ return w .. " " .. preload
+ end)
+ io.gsub(htmlfile, "@PRELOAD@", "")
+ _fix_emscripten_js(path.join(targetdir, target:basename() .. ".js"))
+ end
+ os.vcp(path.join(pluginsdir, "platforms/qtloader.js"), targetdir)
+ os.vcp(path.join(pluginsdir, "platforms/qtlogo.svg"), targetdir)
+ end
+end
diff --git a/xmake/rules/qt/config_static.lua b/xmake/rules/qt/config_static.lua
index 4cf943d13..461a1b238 100644
--- a/xmake/rules/qt/config_static.lua
+++ b/xmake/rules/qt/config_static.lua
@@ -59,7 +59,7 @@ function main(target)
table.insert(frameworks, QtPlatformSupport)
end
elseif target:is_plat("wasm") then
- plugins.QWasmIntegrationPlugin = {linkdirs = "plugins/platforms", links = {"qwasm"}}
+ plugins.QWasmIntegrationPlugin = {linkdirs = "plugins/platforms", links = {"qwasm"}, resources = {"wasmwindow", "wasmfonts"}}
if qt_sdkver:ge("6.0") then
table.join2(frameworks, "QtOpenGL")
else
diff --git a/xmake/rules/qt/load.lua b/xmake/rules/qt/load.lua
index 1ce0f4fb1..dff70f52e 100644
--- a/xmake/rules/qt/load.lua
+++ b/xmake/rules/qt/load.lua
@@ -98,6 +98,9 @@ function _add_plugins(target, plugins)
if plugin.linkdirs then
target:values_add("qt.linkdirs", table.unpack(table.wrap(plugin.linkdirs)))
end
+ if plugin.resources then
+ target:values_add("qt.plugin_resources", table.unpack(table.wrap(plugin.resources)))
+ end
-- TODO: add prebuilt object files in qt sdk.
-- these file is located at plugins/xxx/objects-Release/xxxPlugin_init/xxxPlugin_init.cpp.o
end
@@ -139,6 +142,30 @@ function _get_frameworks_from_target(target)
return table.unique(values)
end
+-- generate static plugin import file
+function _generate_plugin_import(target)
+ local plugins = target:values("qt.plugins")
+ if not plugins then
+ return
+ end
+ local importfile = path.join(config.builddir(), ".qt", "plugin", target:name(), "static_import.cpp")
+ local content = "#include <QtPlugin>\n"
+ for _, plugin in ipairs(plugins) do
+ content = content .. string.format("Q_IMPORT_PLUGIN(%s)\n", plugin)
+ end
+ local plugin_resources = target:values("qt.plugin_resources")
+ if plugin_resources then
+ content = content .. "int init_qt_plugin_resources() {\n"
+ for _, res in ipairs(table.unique(plugin_resources)) do
+ content = content .. string.format(" Q_INIT_RESOURCE(%s);\n", res)
+ end
+ content = content .. " return 0;\n}\n"
+ content = content .. "static int s_init_qt_plugin_resources = init_qt_plugin_resources();\n"
+ end
+ io.writefile(importfile, content)
+ target:add("files", importfile)
+end
+
function _add_qmakeprllibs(target, prlfile, qt)
if os.isfile(prlfile) then
local contents = io.readfile(prlfile)
@@ -275,19 +302,7 @@ function main(target, opt)
if opt.plugins then
_add_plugins(target, opt.plugins)
end
- local plugins = target:values("qt.plugins")
- if plugins then
- local importfile = path.join(config.builddir(), ".qt", "plugin", target:name(), "static_import.cpp")
- local file = io.open(importfile, "w")
- if file then
- file:print("#include <QtPlugin>")
- for _, plugin in ipairs(plugins) do
- file:print("Q_IMPORT_PLUGIN(%s)", plugin)
- end
- file:close()
- target:add("files", importfile)
- end
- end
+ _generate_plugin_import(target)
-- backup the user syslinks, we need to add them behind the qt syslinks
local syslinks_user = target:get("syslinks")
@@ -500,13 +515,18 @@ function main(target, opt)
target:add("shflags", "-s FETCH=1", "-s ERROR_ON_UNDEFINED_SYMBOLS=1", "-s ALLOW_MEMORY_GROWTH=1", "--bind")
if qt_sdkver:ge("6.0") then
-- @see https://github.com/xmake-io/xmake/issues/4137
- target:add("ldflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s DISABLE_EXCEPTION_CATCHING=1")
+ -- @see QtWasmHelpers.cmake: qt_internal_setup_wasm_target_properties
+ target:add("ldflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s STACK_SIZE=5MB")
target:add("ldflags", "-sASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js")
- target:add("ldflags", "-s EXPORTED_RUNTIME_METHODS=UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets")
+ -- @see Qt6WasmMacros.cmake: _qt_internal_add_wasm_extra_exported_methods
+ target:add("ldflags", "-s EXPORTED_RUNTIME_METHODS=UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain")
+ -- @see https://github.com/emscripten-core/emscripten/issues/21844
+ target:add("ldflags", "-s EXPORTED_FUNCTIONS=_main,__embind_initialize_bindings", {force = true})
target:add("ldflags", "-s MODULARIZE=1", "-s EXPORT_NAME=createQtAppInstance")
- target:add("shflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s DISABLE_EXCEPTION_CATCHING=1")
+ target:add("shflags", "-s MAX_WEBGL_VERSION=2", "-s WASM_BIGINT=1", "-s STACK_SIZE=5MB")
target:add("shflags", "-sASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js")
- target:add("shflags", "-s EXPORTED_RUNTIME_METHODS=UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets")
+ target:add("shflags", "-s EXPORTED_RUNTIME_METHODS=UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain")
+ target:add("shflags", "-s EXPORTED_FUNCTIONS=_main,__embind_initialize_bindings", {force = true})
target:add("shflags", "-s MODULARIZE=1", "-s EXPORT_NAME=createQtAppInstance")
target:set("extension", ".js")
else
diff --git a/xmake/rules/qt/xmake.lua b/xmake/rules/qt/xmake.lua
index db13aa913..85eb6640c 100644
--- a/xmake/rules/qt/xmake.lua
+++ b/xmake/rules/qt/xmake.lua
@@ -21,20 +21,7 @@
-- define rule: qt/wasm application
rule("qt._wasm_app")
add_deps("qt.env")
- after_build(function (target)
- local qt = target:data("qt")
- local pluginsdir = qt and qt.pluginsdir
- if pluginsdir then
- local targetdir = target:targetdir()
- local htmlfile = path.join(targetdir, target:basename() .. ".html")
- if os.isfile(path.join(pluginsdir, "platforms/wasm_shell.html")) then
- os.vcp(path.join(pluginsdir, "platforms/wasm_shell.html"), htmlfile)
- io.gsub(htmlfile, "@APPNAME@", target:name())
- os.vcp(path.join(pluginsdir, "platforms/qtloader.js"), targetdir)
- os.vcp(path.join(pluginsdir, "platforms/qtlogo.svg"), targetdir)
- end
- end
- end)
+ after_build("build_qt_wasm_app")
-- define rule: qt static library
rule("qt.static")