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)
|
||||
|
||||
@@ -37,6 +37,9 @@ Sha1SliceLevel: int = 0 # SHA1 slice level (directory dispersion depth)
|
||||
StrictMode: int = 0
|
||||
IncludesDir: str = None # includes dir path (first element of includes array, single-dir compat)
|
||||
IncludesDirs: str = None # all includes dirs (space-separated, multi-dir support)
|
||||
Qt6Path: str = None # runtime.qt6_path: Qt6 install root (optional)
|
||||
Qt6Dlls: str = None # runtime.qt6_dlls joined (space-separated DLL names; optional)
|
||||
Qt6Plugins: str = None # runtime.qt6_plugins joined (space-separated paths relative to <qt6_path>/plugins; optional)
|
||||
|
||||
# includes prebuilt cache dir suffix (asan mode uses .binary_asan to isolate instrumented obj)
|
||||
_inc_bin_suffix: str = ".binary"
|
||||
@@ -51,6 +54,7 @@ def Load_project_config(path: str) -> int:
|
||||
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs, LinkerDynamicLibs
|
||||
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
|
||||
global IncludesDir, IncludesDirs
|
||||
global Qt6Path, Qt6Dlls, Qt6Plugins
|
||||
|
||||
if path is None: return 1
|
||||
if _mbuddy is None: return 1
|
||||
@@ -161,6 +165,17 @@ def Load_project_config(path: str) -> int:
|
||||
IncludesDirs = inc_buf
|
||||
if IncludesDir is None: IncludesDir = inc_buf
|
||||
|
||||
# Read runtime sub-object (optional): declared runtime deps deployed next to the exe.
|
||||
# qt6_dlls / qt6_plugins join via _join_flags_array (space-separated, same口径 as linker.static_libs).
|
||||
runtime: JsonValue* = root["runtime"]
|
||||
if runtime is not None and runtime.is_object():
|
||||
qp_val: JsonValue* = runtime["qt6_path"]
|
||||
Qt6Path = qp_val.as_string() if qp_val is not None else None
|
||||
qd_val: JsonValue* = runtime["qt6_dlls"]
|
||||
Qt6Dlls = _join_flags_array(qd_val)
|
||||
qpl_val: JsonValue* = runtime["qt6_plugins"]
|
||||
Qt6Plugins = _join_flags_array(qpl_val)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
+11
@@ -24,6 +24,11 @@ import w32.win32base as w32base
|
||||
import w32.fileio as fileio
|
||||
import sys
|
||||
import ast
|
||||
import os
|
||||
import os._win32
|
||||
import os._posix
|
||||
import os.path
|
||||
import shutil
|
||||
import mlirlite
|
||||
import subprocess
|
||||
import viperlib
|
||||
@@ -106,6 +111,12 @@ def main() -> int:
|
||||
Phase1._mbuddy = mb
|
||||
Phase2._mbuddy = mb
|
||||
subprocess._mbuddy = mb
|
||||
# Runtime-dep deploy primitives (BuildPipeline.deploy_runtime_deps): os.mkdir + shutil.copyfile.
|
||||
os._mbuddy = mb
|
||||
os._win32._mbuddy = mb
|
||||
os._posix._mbuddy = mb
|
||||
os.path._mbuddy = mb
|
||||
shutil._mbuddy = mb
|
||||
# hashlib._mbuddy = (mb)
|
||||
VLogger._mbuddy = mb
|
||||
lib._mbuddy = mb
|
||||
|
||||
Reference in new issue
Block a user