Files
ViperC/App/lib/core/SelfBuild.vp
T
TermiNexus 782b4d63ff feat(build): 增加 --build-self 与 copyself
- 新增 copyself:延迟后删除目标并复制文件/目录
- ViperC 新增 --build-self:自举到 __vpcbuild_2__ 后由 copyself 替换 __vpcbuild__
- 同步当前 ViperC 工作区已有的 Gen/Diag/Stub 改动
- .gitignore 忽略 __vpcbuild_* 与 _sha1_map.txt
2026-10-26 02:09:45 +08:00

210 lines
7.4 KiB
Plaintext

from stdint import *
import stdio
import stdlib
import string
import viperlib
import os
import os.path
import win32.win32base
import win32.win32file
import win32.win32process
import lib.Phase.BuildPipeline as BuildPipeline
define SELF_EXE_CAP = 4096
define SELF_BUILD_DIR = "__vpcbuild_2__"
define COPY_DELAY_SECONDS = 5
def _GetSelfExe() -> str:
buf: bytes = stdlib.malloc(sizet(SELF_EXE_CAP))
if buf is None:
return None
n: uint32 = win32.win32file.GetModuleFileNameA(None, buf, uint32(SELF_EXE_CAP))
if int(n) <= 0:
stdlib.free(buf)
return None
ni: int = int(n)
if ni >= SELF_EXE_CAP:
ni = SELF_EXE_CAP - 1
buf[ni] = 0
return str(buf)
def _TryRoot(dir: str) -> str:
if dir is None or len(dir) == 0:
return None
root: str = os.path.normpath(dir)
if root is None:
return None
project: str = os.path.join(root, "project.vpj")
copyself_project: str = os.path.join(os.path.join(root, "copyself"), "project.vpj")
if project is not None and copyself_project is not None:
if os.isfile(project) and os.isfile(copyself_project):
return root
return None
def _FindViperCRoot(self_exe: str) -> str:
base: str = os.path.dirname(self_exe)
if base is None:
base = ""
parent1: str = os.path.dirname(base)
parent2: str = os.path.dirname(parent1)
# Executable-derived candidates first: cwd is normally the workspace root,
# not the compiler tree.
root: str = _TryRoot(base)
if root is not None:
return root
root = _TryRoot(parent1)
if root is not None:
return root
root = _TryRoot(parent2)
if root is not None:
return root
root = _TryRoot(os.path.join(base, "ViperC"))
if root is not None:
return root
root = _TryRoot(os.path.join(base, "../ViperC"))
if root is not None:
return root
root = _TryRoot(os.path.join(base, "../../ViperC"))
if root is not None:
return root
# Fallback only when the executable is detached from the compiler tree.
cwd: str = os.getcwd()
root = _TryRoot(cwd)
if root is not None:
return root
if cwd is not None:
root = _TryRoot(os.path.join(cwd, "ViperC"))
if root is not None:
return root
root = _TryRoot(os.path.join(os.path.dirname(cwd), "ViperC"))
if root is not None:
return root
return None
def _FormatSelfBuildCommand(self_exe: str, project: str, build_dir: str) -> str:
need: sizet = len(self_exe) + len(project) + len(build_dir) + 64
buf: bytes = stdlib.malloc(need)
if buf is None:
return None
viperlib.snprintf(buf, need, "\"%s\" --project \"%s\" --clean --build-dir \"%s\"",
self_exe, project, build_dir)
return str(buf)
def _FindCopySelfExe(root: str) -> str:
base: str = os.path.join(root, "copyself")
standard: str = os.path.join(os.path.join(os.path.join(base, "__vpcbuild__"), "output"), "copyself.exe")
if os.isfile(standard):
return standard
legacy: str = os.path.join(os.path.join(base, "output"), "copyself.exe")
if os.isfile(legacy):
return legacy
return None
def _FormatCopySelfCommand(copyself_exe: str) -> str:
need: sizet = len(copyself_exe) + 128
buf: bytes = stdlib.malloc(need)
if buf is None:
return None
viperlib.snprintf(buf, need, "\"%s\" %d ../../__vpcbuild_2__ ../../__vpcbuild__",
copyself_exe, COPY_DELAY_SECONDS)
return str(buf)
def _SpawnDetached(cmd: str, cwd: str) -> int:
if cmd is None or cwd is None:
stdio.printf("FAIL: --build-self cannot launch copyself without a command and working directory\n")
return -1
clen: sizet = len(cmd)
cmd_line: bytes = stdlib.malloc(clen + 1)
if cmd_line is None:
stdio.printf("FAIL: --build-self failed to allocate the copyself command line\n")
return -1
string.strcpy(cmd_line, cmd)
si: win32.win32process.STARTUPINFOA = win32.win32process.STARTUPINFOA()
pi: win32.win32process.PROCESS_INFORMATION = win32.win32process.PROCESS_INFORMATION()
string.memset(&si, 0, win32.win32process.STARTUPINFOA.__sizeof__())
string.memset(&pi, 0, win32.win32process.PROCESS_INFORMATION.__sizeof__())
si.cb = win32.win32process.STARTUPINFOA.__sizeof__()
ok: int = win32.win32process.CreateProcessA(None, cmd_line, None, None, 1, 0,
None, cwd, &si, &pi)
if ok == 0:
err: uint32 = win32.win32base.GetLastError()
stdio.printf("FAIL: --build-self could not launch copyself (Win32 error %d)\n", int(err))
stdio.printf(" command: %s\n", cmd)
stdlib.free(cmd_line)
return -1
stdio.printf("build-self: copyself launched (pid=%d)\n", int(pi.dwProcessId))
win32.win32base.CloseHandle(pi.hProcess)
win32.win32base.CloseHandle(pi.hThread)
stdlib.free(cmd_line)
return 0
def RunSelfBuild() -> int:
self_exe: str = _GetSelfExe()
if self_exe is None:
stdio.printf("FAIL: --build-self could not resolve the compiler executable path\n")
return 1
root: str = _FindViperCRoot(self_exe)
if root is None:
stdio.printf("FAIL: --build-self could not locate the ViperC root\n")
stdio.printf(" expected both: project.vpj and copyself/project.vpj\n")
return 1
project: str = os.path.join(root, "project.vpj")
build_dir: str = os.path.join(root, SELF_BUILD_DIR)
copyself_exe: str = _FindCopySelfExe(root)
if copyself_exe is None:
stdio.printf("FAIL: copyself.exe not found under %s\n", os.path.join(root, "copyself"))
stdio.printf(" expected: output/copyself.exe or __vpcbuild__/output/copyself.exe\n")
stdio.printf(" build it first: viperc --project %s --clean\n",
os.path.join(os.path.join(root, "copyself"), "project.vpj"))
return 1
cmd: str = _FormatSelfBuildCommand(self_exe, project, build_dir)
if cmd is None:
stdio.printf("FAIL: --build-self failed to build the compiler command line\n")
return 1
stdio.printf("build-self: %s\n", cmd)
rc: int = BuildPipeline._run_cmd_wait(cmd)
if rc != 0:
stdio.printf("FAIL: --build-self compilation failed (exit=%d)\n", rc)
return 1
built_exe: str = os.path.join(os.path.join(build_dir, "output"), "viperc.exe")
if not os.isfile(built_exe):
stdio.printf("FAIL: --build-self did not produce %s\n", built_exe)
return 1
# The requested arguments are relative to ViperC/copyself/output. Use that
# directory as the launch cwd even when copyself.exe lives under
# copyself/__vpcbuild__/output.
copyself_cwd: str = os.path.join(os.path.join(root, "copyself"), "output")
if not os.isdir(copyself_cwd):
if os.mkdir(copyself_cwd) != 0:
if not os.isdir(copyself_cwd):
err_mkdir: ULONG = win32.win32base.GetLastError()
stdio.printf("FAIL: --build-self could not create %s (Win32 error %d)\n",
copyself_cwd, int(err_mkdir))
return 1
copy_cmd: str = _FormatCopySelfCommand(copyself_exe)
if copy_cmd is None:
stdio.printf("FAIL: --build-self failed to build the copyself command line\n")
return 1
if _SpawnDetached(copy_cmd, copyself_cwd) != 0:
return 1
stdio.printf("build-self: __vpcbuild__ will be replaced after %d seconds\n", COPY_DELAY_SECONDS)
return 0