feat(build): 实现 Qt 运行时依赖部署(runtime.qt6_path / qt6_dlls / qt6_plugins)

背景:project.vpj 里的 runtime 块此前只是声明(编译器不读取),Qr 程序运行时报 Could not find the Qt platform plugin "windows"。

实现:链接成功后(Phase2 的 PhaseC 成功点)调用 BuildPipeline.deploy_runtime_deps(exe_path)——把 qt6_dlls 各项从 <qt6_path>/bin 复制到 exe 同目录;把新增的可选字段 qt6_plugins 各项从 <qt6_path>/plugins 复制到 <exe 目录>/<相对路径>(父目录不存在则 mkdir)。Config.vp 新增 Qt6Path/Qt6Dlls/Qt6Plugins 读取(口径同 LinkerStaticLibs)。

fail-fast:声明了但源文件不存在 / 复制失败 / mkdir 失败 => VLogger.error 并中止构建;三字段全未声明则整段跳过。

验证:重编 SUCCESS(210.11s);ViperMNIST 产物含 7 个 Qt6*.dll + platforms/qwindows.dll,实跑 stderr 无插件报错;ViperAIChat / ViperQtHelloWorld 同样 OK 并完成部署。
This commit is contained in:
TermiNexus committed 2026-10-07 09:08:35 +08:00
1 parent 848dc0ab38
commit 36d23d2f42
4 files changed
+155 -1

No files matched your search

+121 -1
View File
@@ -15,6 +15,8 @@ import w32.win32sync as win32sync
import subprocess
import viperlib
import ast
import os as oslib
import shutil
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HandlesTranslator
import lib.core.Gen.DebugInfoGen as DebugInfoGen
@@ -2399,4 +2401,122 @@ def run_pipeline(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, m
return result
result.Success = 1
return result
return result
# =============================================================================
# Runtime dependency deployment (project.vpj runtime.*)
# After a successful link, copy the declared runtime deps next to the exe.
# Optional: no-op unless runtime.qt6_path / qt6_dlls / qt6_plugins are declared.
# Fail-fast: a declared source that is missing, or a failed copy/dir-create, aborts the build.
# =============================================================================
def _deploy_dir_of(path: str) -> str:
"""Return the directory portion of path (text before the last / or backslash); None if no separator."""
if path is None: return None
plen: sizet = string.strlen(path)
i: sizet = plen
while i > 0:
if path[i - 1] == '/' or path[i - 1] == '\\': break
i -= 1
if i == 0: return None
buf: str = _mbuddy.alloc(i)
if buf is None: return None
string.strncpy(buf, path, i - 1)
buf[i - 1] = '\0'
return buf
def _deploy_join(base: str, name: str) -> str:
"""Join base and name with '/'; mbuddy-allocated."""
if base is None or name is None: return None
bl: sizet = string.strlen(base)
nl: sizet = string.strlen(name)
buf: str = _mbuddy.alloc(bl + nl + 2)
if buf is None: return None
viperlib.snprintf(buf, bl + nl + 2, "%s/%s", base, name)
return buf
def _deploy_token(s: str, start: sizet, end: sizet) -> str:
"""Copy s[start:end] into an mbuddy-allocated NUL-terminated string; None on OOM."""
n: sizet = end - start
buf: str = _mbuddy.alloc(n + 1)
if buf is None: return None
string.strncpy(buf, s + start, n)
buf[n] = '\0'
return buf
def deploy_runtime_deps(exe_path: str) -> int:
"""Deploy declared runtime dependencies (project.vpj runtime.*) next to the exe; 0=ok, 1=fail.
No-op when runtime.qt6_path / qt6_dlls / qt6_plugins are undeclared. Fail-fast otherwise."""
if exe_path is None: return 0
qt6_path: str = Config.Qt6Path
if qt6_path is None: return 0
dlls: str = Config.Qt6Dlls
plugins: str = Config.Qt6Plugins
if dlls is None and plugins is None: return 0
exe_dir: str = _deploy_dir_of(exe_path)
if exe_dir is None:
VLogger.error(f"runtime deploy: cannot determine exe directory from {exe_path}", "runtime")
return 1
bin_dir: str = _deploy_join(qt6_path, "bin")
plug_root: str = _deploy_join(qt6_path, "plugins")
# runtime.qt6_dlls: <qt6_path>/bin/<dll> -> <exe_dir>/<dll>
if dlls is not None:
total: sizet = string.strlen(dlls)
i: sizet = 0
while i < total:
while i < total and dlls[i] == ' ': i += 1
if i >= total: break
start: sizet = i
while i < total and dlls[i] != ' ': i += 1
if i > start:
name: str = _deploy_token(dlls, start, i)
if name is None:
VLogger.error("runtime deploy: DLL name allocation failed (OOM)", "runtime")
return 1
src: str = _deploy_join(bin_dir, name)
dst: str = _deploy_join(exe_dir, name)
if not oslib.isfile(src):
VLogger.error(f"runtime deploy: declared DLL not found: {src}", "runtime")
return 1
if shutil.copyfile(src, dst) != 0:
VLogger.error(f"runtime deploy: failed to copy {src} -> {dst}", "runtime")
return 1
VLogger.info(f"runtime deploy: {name}", "runtime")
# runtime.qt6_plugins: <qt6_path>/plugins/<rel> -> <exe_dir>/<rel> (ensure parent dir)
if plugins is not None:
total_p: sizet = string.strlen(plugins)
ip: sizet = 0
while ip < total_p:
while ip < total_p and plugins[ip] == ' ': ip += 1
if ip >= total_p: break
start_p: sizet = ip
while ip < total_p and plugins[ip] != ' ': ip += 1
if ip > start_p:
rel: str = _deploy_token(plugins, start_p, ip)
if rel is None:
VLogger.error("runtime deploy: plugin path allocation failed (OOM)", "runtime")
return 1
src_p: str = _deploy_join(plug_root, rel)
dst_p: str = _deploy_join(exe_dir, rel)
parent: str = _deploy_dir_of(dst_p)
if parent is not None:
if not oslib.isdir(parent):
if oslib.mkdir(parent) != 0:
VLogger.error(f"runtime deploy: failed to create dir {parent}", "runtime")
return 1
if not oslib.isfile(src_p):
VLogger.error(f"runtime deploy: declared plugin not found: {src_p}", "runtime")
return 1
if shutil.copyfile(src_p, dst_p) != 0:
VLogger.error(f"runtime deploy: failed to copy {src_p} -> {dst_p}", "runtime")
return 1
VLogger.info(f"runtime deploy: {rel}", "runtime")
return 0
+8
View File
@@ -1506,6 +1506,14 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou
if lret == 0:
VLogger.success(f"output: {exe_path}", "PhaseC")
# Deploy declared runtime deps (project.vpj runtime.*) next to the exe; fail-fast on error.
# Skipped under --phase2command (the exe is not linked yet).
if BuildPipeline._phase2_cmd_path is None:
if BuildPipeline.deploy_runtime_deps(exe_path) != 0:
VLogger.error("runtime dependency deployment failed, aborting compilation", "runtime")
stdlib.free(entries)
stdlib.free(asm_entries)
return 1
# --traceback: export <exe>.stackmap (skipped under --phase2command,
# where nothing was actually linked).
if BuildPipeline._traceback_mode != 0 and BuildPipeline._phase2_cmd_path is None: BuildPipeline.WriteStackMap(mb, exe_path)