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:
1 parent
848dc0ab38
commit
36d23d2f42
4 files changed
+155
-1
No files matched your search
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user