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
+154
No files matched your search
@@ -15,6 +15,8 @@ import w32.win32sync as win32sync
|
|||||||
import subprocess
|
import subprocess
|
||||||
import viperlib
|
import viperlib
|
||||||
import ast
|
import ast
|
||||||
|
import os as oslib
|
||||||
|
import shutil
|
||||||
import lib.core.VLogger as VLogger
|
import lib.core.VLogger as VLogger
|
||||||
import lib.core.Gen.Translator as HandlesTranslator
|
import lib.core.Gen.Translator as HandlesTranslator
|
||||||
import lib.core.Gen.DebugInfoGen as DebugInfoGen
|
import lib.core.Gen.DebugInfoGen as DebugInfoGen
|
||||||
@@ -2400,3 +2402,121 @@ def run_pipeline(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, m
|
|||||||
|
|
||||||
result.Success = 1
|
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:
|
if lret == 0:
|
||||||
VLogger.success(f"output: {exe_path}", "PhaseC")
|
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,
|
# --traceback: export <exe>.stackmap (skipped under --phase2command,
|
||||||
# where nothing was actually linked).
|
# where nothing was actually linked).
|
||||||
if BuildPipeline._traceback_mode != 0 and BuildPipeline._phase2_cmd_path is None: BuildPipeline.WriteStackMap(mb, exe_path)
|
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
|
StrictMode: int = 0
|
||||||
IncludesDir: str = None # includes dir path (first element of includes array, single-dir compat)
|
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)
|
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)
|
# includes prebuilt cache dir suffix (asan mode uses .binary_asan to isolate instrumented obj)
|
||||||
_inc_bin_suffix: str = ".binary"
|
_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 CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs, LinkerDynamicLibs
|
||||||
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
|
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
|
||||||
global IncludesDir, IncludesDirs
|
global IncludesDir, IncludesDirs
|
||||||
|
global Qt6Path, Qt6Dlls, Qt6Plugins
|
||||||
|
|
||||||
if path is None: return 1
|
if path is None: return 1
|
||||||
if _mbuddy is None: return 1
|
if _mbuddy is None: return 1
|
||||||
@@ -161,6 +165,17 @@ def Load_project_config(path: str) -> int:
|
|||||||
IncludesDirs = inc_buf
|
IncludesDirs = inc_buf
|
||||||
if IncludesDir is None: IncludesDir = 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
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+11
@@ -24,6 +24,11 @@ import w32.win32base as w32base
|
|||||||
import w32.fileio as fileio
|
import w32.fileio as fileio
|
||||||
import sys
|
import sys
|
||||||
import ast
|
import ast
|
||||||
|
import os
|
||||||
|
import os._win32
|
||||||
|
import os._posix
|
||||||
|
import os.path
|
||||||
|
import shutil
|
||||||
import mlirlite
|
import mlirlite
|
||||||
import subprocess
|
import subprocess
|
||||||
import viperlib
|
import viperlib
|
||||||
@@ -106,6 +111,12 @@ def main() -> int:
|
|||||||
Phase1._mbuddy = mb
|
Phase1._mbuddy = mb
|
||||||
Phase2._mbuddy = mb
|
Phase2._mbuddy = mb
|
||||||
subprocess._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)
|
# hashlib._mbuddy = (mb)
|
||||||
VLogger._mbuddy = mb
|
VLogger._mbuddy = mb
|
||||||
lib._mbuddy = mb
|
lib._mbuddy = mb
|
||||||
|
|||||||
Reference in new issue
Block a user