diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 40c5577..f16224c 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -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 \ No newline at end of file + 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: /bin/ -> / + 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: /plugins/ -> / (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 \ No newline at end of file diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index bc2199f..ca8fd8d 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -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 .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) diff --git a/App/lib/Projectrans/Config.vp b/App/lib/Projectrans/Config.vp index 5ba6751..ea5aa7a 100644 --- a/App/lib/Projectrans/Config.vp +++ b/App/lib/Projectrans/Config.vp @@ -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 /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 diff --git a/App/main.vp b/App/main.vp index 0cd4b9c..013a5bb 100644 --- a/App/main.vp +++ b/App/main.vp @@ -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