diff options
| author | ruki <[email protected]> | 2026-02-25 22:26:02 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-25 22:26:02 +0800 |
| commit | 27f49cd4de23014a3e3d9f17740517ab8771f24f (patch) | |
| tree | 0276470b068190848e260c8cbbe28bd74f364860 | |
| parent | e59f66e168353122a57283487d3417865ee324e1 (diff) | |
| parent | 6eeaf60adca1e4bb95127cb8a1ccc46e73964020 (diff) | |
Merge pull request #7341 from luadebug/em
Fix WASM QT 6.9
| -rw-r--r-- | xmake/actions/run/main.lua | 8 | ||||
| -rw-r--r-- | xmake/rules/qt/build_qt_wasm_app.lua | 128 | ||||
| -rw-r--r-- | xmake/rules/qt/config_static.lua | 2 | ||||
| -rw-r--r-- | xmake/rules/qt/load.lua | 54 | ||||
| -rw-r--r-- | xmake/rules/qt/xmake.lua | 15 |
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") |
