feat(viperbuild): build(run=, args=) 支持编译后直接运行
- ViperBuild:解析 run(bool) / args(list[str]);非空 args 隐含 run,含空格元素自动加引号,内嵌双引号 fatal - Phase2:链接成功后按配置运行产物(命令行形式 "exe" args...),CLI --run 仍生效,phase2command 模式跳过
This commit is contained in:
1 parent
22e4fc5d50
commit
024e61ce45
2 files changed
+107
-7
No files matched your search
@@ -18,6 +18,7 @@ import lib.core.Gen.Translator as HandlesTranslator
|
||||
import lib.Phase.BuildPipeline as BuildPipeline
|
||||
import lib.core.IncludesScanner as IncludesScanner
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.core.builtin.ViperBuild as ViperBuild
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
import lib.Projectrans.Utils as Utils
|
||||
import lib.Projectrans.Config as Config
|
||||
@@ -1512,13 +1513,30 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou
|
||||
# --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)
|
||||
if args.get_bool("run") and BuildPipeline._phase2_cmd_path is not None:
|
||||
# --run / viperbuild build(run=True[, args=[...]]): run the linked exe after linking.
|
||||
# viperbuild 的 args 已在 Pass 0 拼好(含按需加引号),这里只负责拼命令行。
|
||||
vb_run_wanted: int = 0
|
||||
vb_run_args: str = None
|
||||
if ViperBuild.IsApplied() != 0:
|
||||
vb_run_wanted = ViperBuild.RunAfterBuild()
|
||||
vb_run_args = ViperBuild.RunArgs()
|
||||
run_wanted: bool = args.get_bool("run") or vb_run_wanted != 0
|
||||
if run_wanted and BuildPipeline._phase2_cmd_path is not None:
|
||||
# phase2command mode: the exe is not linked yet (the command is in the bat); skip --run
|
||||
VLogger.info("phase2command mode: skipping --run (exe not linked yet)", "phase2cmd")
|
||||
elif args.get_bool("run"):
|
||||
VLogger.info(f"run: {exe_path}", "run")
|
||||
rp: subprocess.CompletedProcess* = subprocess.run(exe_path, False, False)
|
||||
if rp is not None: VLogger.info(f"exit code: {rp.returncode}", "run")
|
||||
elif run_wanted:
|
||||
run_cmd_len: sizet = len(exe_path) + 8
|
||||
if vb_run_args is not None: run_cmd_len += len(vb_run_args) + 1
|
||||
run_cmd: bytes = stdlib.malloc(run_cmd_len)
|
||||
if run_cmd is not None:
|
||||
if vb_run_args is not None and string.strlen(vb_run_args) > 0:
|
||||
viperlib.snprintf(run_cmd, run_cmd_len, "\"%s\" %s", exe_path, vb_run_args)
|
||||
else:
|
||||
viperlib.snprintf(run_cmd, run_cmd_len, "\"%s\"", exe_path)
|
||||
VLogger.info(f"run: {run_cmd}", "run")
|
||||
rp: subprocess.CompletedProcess* = subprocess.run(run_cmd, False, False)
|
||||
if rp is not None: VLogger.info(f"exit code: {rp.returncode}", "run")
|
||||
stdlib.free(run_cmd)
|
||||
else:
|
||||
VLogger.error("link failed", "PhaseC")
|
||||
stdlib.free(entries)
|
||||
|
||||
@@ -70,6 +70,9 @@ _c_clean_output: int = 0
|
||||
_c_clean_temp: int = 0
|
||||
_c_clean_build_dir: int = 0
|
||||
_c_clean_includes: int = 0
|
||||
# build(run=, args=):编译后直接运行(args 非空时隐含 run)
|
||||
_c_run: int = 0
|
||||
_c_run_args: str = None
|
||||
|
||||
# Whether `import viperbuild` / `from viperbuild import ...` appeared in the closure (diagnostics only)
|
||||
_saw_viperbuild: int = 0
|
||||
@@ -738,9 +741,54 @@ def _HandleConfigCall(cl: ast.Call*) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _JoinRunArgs(lst: list[str]*) -> str:
|
||||
"""Join run args for CreateProcess: elements containing blanks are wrapped in double quotes.
|
||||
|
||||
Embedded double quotes are rejected (fail-fast) — Windows argv quoting rules would make the
|
||||
actual argv ambiguous, and macro config should not silently alter the user's arguments.
|
||||
"""
|
||||
if lst is None: return None
|
||||
n: sizet = len(lst)
|
||||
if n == 0: return None
|
||||
total: sizet = 0
|
||||
for i in range(int(n)):
|
||||
s: str = lst.get(sizet(i))
|
||||
if s is None: continue
|
||||
total += string.strlen(s) + 3
|
||||
buf: str = stdlib.malloc(total + 1)
|
||||
if buf is None: return None
|
||||
pos: sizet = 0
|
||||
for i in range(int(n)):
|
||||
s = lst.get(sizet(i))
|
||||
if s is None: continue
|
||||
sl: sizet = string.strlen(s)
|
||||
need_quote: int = 0
|
||||
j: sizet = 0
|
||||
while j < sl:
|
||||
if s[j] == ' ' or s[j] == '\t':
|
||||
need_quote = 1
|
||||
if s[j] == '"':
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
j += 1
|
||||
if pos > 0:
|
||||
buf[pos] = ' '
|
||||
pos += 1
|
||||
if need_quote != 0:
|
||||
buf[pos] = '"'
|
||||
pos += 1
|
||||
string.strcpy(buf + pos, s)
|
||||
pos += sl
|
||||
if need_quote != 0:
|
||||
buf[pos] = '"'
|
||||
pos += 1
|
||||
buf[pos] = '\0'
|
||||
return buf
|
||||
|
||||
|
||||
def _HandleBuildCall(cl: ast.Call*) -> int:
|
||||
"""Handle a proj.build(triple=, datalayout=, selfmachine=) call."""
|
||||
global _c_triple, _c_datalayout, _c_selfmachine, _c_has_build
|
||||
"""Handle a proj.build(triple=, datalayout=, selfmachine=, run=, args=) call."""
|
||||
global _c_triple, _c_datalayout, _c_selfmachine, _c_has_build, _c_run, _c_run_args
|
||||
if cl is None: return 1
|
||||
if _c_has_build != 0:
|
||||
VLogger.error("viperbuild: proj.build() may be called only once", "viperbuild")
|
||||
@@ -767,6 +815,27 @@ def _HandleBuildCall(cl: ast.Call*) -> int:
|
||||
VLogger.error("viperbuild: build(selfmachine=...) must be a boolean constant", "viperbuild")
|
||||
return 1
|
||||
_c_selfmachine = b
|
||||
elif string.strcmp(kw.arg, "run") == 0:
|
||||
rb: int = _EvalBool(kw.value)
|
||||
if rb < 0:
|
||||
VLogger.error("viperbuild: build(run=...) must be a boolean constant", "viperbuild")
|
||||
return 1
|
||||
_c_run = rb
|
||||
elif string.strcmp(kw.arg, "args") == 0:
|
||||
if _scratch is None: return 1
|
||||
tmp_args: list[str]* = list[str](_scratch, 8)
|
||||
if tmp_args is None: return 1
|
||||
if _EvalList(kw.value, tmp_args) == 0:
|
||||
VLogger.error("viperbuild: build(args=...) must be a list[str] constant expression", "viperbuild")
|
||||
return 1
|
||||
joined_args: str = _JoinRunArgs(tmp_args)
|
||||
# None + non-empty list = element rejected (embedded quote); empty list is legal.
|
||||
if joined_args is None and len(tmp_args) > 0:
|
||||
VLogger.error("viperbuild: build(args=...) has an element that cannot be passed through (embedded double quote); quote it in code or avoid it", "viperbuild")
|
||||
return 1
|
||||
_c_run_args = joined_args
|
||||
# Non-empty args imply run: `build(args=["--a"])` == run the exe with --a.
|
||||
if _c_run_args is not None and string.strlen(_c_run_args) > 0: _c_run = 1
|
||||
else:
|
||||
VLogger.error(f"viperbuild: unsupported keyword {kw.arg} for build(...)", "viperbuild")
|
||||
return 1
|
||||
@@ -1127,6 +1196,7 @@ def _ResetAll() -> None:
|
||||
global _c_triple, _c_datalayout, _c_selfmachine, _c_has_project, _c_has_build
|
||||
global _c_adv_asan, _c_adv_traceback
|
||||
global _c_clean_output, _c_clean_temp, _c_clean_build_dir, _c_clean_includes
|
||||
global _c_run, _c_run_args
|
||||
global _saw_viperbuild
|
||||
_alias = None
|
||||
_ctor_name = None
|
||||
@@ -1156,6 +1226,8 @@ def _ResetAll() -> None:
|
||||
_c_clean_temp = 0
|
||||
_c_clean_build_dir = 0
|
||||
_c_clean_includes = 0
|
||||
_c_run = 0
|
||||
_c_run_args = None
|
||||
|
||||
|
||||
def DiscoverAndApply(mb: memhub.MemBuddy*, entry_path: str) -> int:
|
||||
@@ -1250,3 +1322,13 @@ def CleanBuildDir() -> int:
|
||||
def CleanIncludes() -> int:
|
||||
"""Whether viperbuild requested cleaning the includes cache (proj.clean.includes); 1=yes / 0=no."""
|
||||
return _c_clean_includes
|
||||
|
||||
|
||||
def RunAfterBuild() -> int:
|
||||
"""Whether viperbuild requested running the exe after a successful build; 1=yes / 0=no."""
|
||||
return _c_run
|
||||
|
||||
|
||||
def RunArgs() -> str:
|
||||
"""Space-joined run arguments for build(args=[...]) (already quoted where needed); None = none."""
|
||||
return _c_run_args
|
||||
Reference in new issue
Block a user