- new_builder 的 CurBlock 初值为 None,_emit 会静默丢弃指令,导致 thunk 生成空函数体(mlir-opt: empty block) - 补 create_block + position_at_end,随后再把全部静默 return None 改为 fatal_error - 移除两处 [ffiprobe] 调试探针
1580 lines
62 KiB
Plaintext
1580 lines
62 KiB
Plaintext
"""Compiler-side implementation of the `viperbuild` built-in macro library (--src Pass 0)."""
|
||
from stdint import *
|
||
from win32.win32base import *
|
||
import memhub
|
||
import string
|
||
import stdlib
|
||
import ast
|
||
import win32.win32file as win32file
|
||
import lib.Projectrans.Utils as Utils
|
||
import lib.Projectrans.Config as Config
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.IncludesScanner as IncludesScanner
|
||
import stdio
|
||
|
||
# kernel32!GetEnvironmentVariableA (DWORD(LPCSTR, LPSTR, DWORD)) - declared here, same as includes/traceback.vp;
|
||
# win32base.vp omits it but kernel32 is linked anyway; only for selfmachine host detection.
|
||
extern def GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: CHARPTR, nSize: DWORD) -> DWORD
|
||
|
||
# Path/source buffer caps (same convention as main.vp's SRC_BUF_SIZE)
|
||
define VB_PATH_CAP = 1024
|
||
define VB_SRC_CAP = 1048576
|
||
# Scratch pool size (BFS working set + constant tables; not part of main-pool reset)
|
||
define VB_SCRATCH = 8388608
|
||
# Visited cap (cycle defense; fatal if the closure greatly exceeds it)
|
||
define VB_MAX_VISIT = 4096
|
||
# Element separator for list constants: the char code of newline (source paths/flags never contain newlines).
|
||
# Must store the code then char(): in Viper 'c' literals are single-char strings (i8*), not assignable to a char slot.
|
||
define VB_SEP_CODE = 10
|
||
|
||
# Module state
|
||
_scratch: memhub.MemBuddy* = None
|
||
_entry_dir: str = None # entry file dir (default source; normalized to "." for bare filenames)
|
||
_search_dirs: str = None # closure search roots (space-separated): entry dir first, then "."
|
||
_work: list[str]* = None # BFS pending file paths
|
||
_visited: list[str]* = None # processed file paths
|
||
_pending: list[str]* = None # import module names collected in one file (pending BFS)
|
||
_proj_vars: list[str]* = None # variable names bound to a vb.Project(...) instance
|
||
_alias: str = None # local alias of viperbuild (import viperbuild as vb -> "vb")
|
||
# ffilib 的本地别名(import ffilib / import ffilib as F);ffilib.CDLL(...) 是纯构建配置
|
||
_ffi_alias: str = None
|
||
# ffilib.CDLL 句柄表(两个平行列表):变量名 / runtime(1=运行时加载, 0=链接期)
|
||
# 供 `extern <lib> name(...)` 校验句柄来源与选择行为。
|
||
_ffi_handles_names: list[str]* = None
|
||
_ffi_handles_rt: list[int]* = None
|
||
# 句柄对应的库路径(runtime=True 时 thunk 直接用绝对路径 LoadLibraryA)
|
||
_ffi_handles_path: list[str]* = None
|
||
_ctor_name: str = None # from viperbuild import Project as P -> "P"
|
||
|
||
# Per-file constant tables (valid only while scanning that file; pool-allocated)
|
||
_f_str_names: list[str]* = None
|
||
_f_str_vals: list[str]* = None
|
||
_f_list_names: list[str]* = None
|
||
_f_list_vals: list[str]* = None
|
||
|
||
# Constant-eval output slot
|
||
_g_out_str: str = None
|
||
|
||
# Extracted config (all strings stdlib.malloc)
|
||
_c_name: str = None
|
||
_c_version: str = None
|
||
_c_source: str = None
|
||
_c_buildroot: str = None
|
||
_c_includes: str = None
|
||
_c_compiler: str = None
|
||
_c_linker: str = None
|
||
_c_static_libs: str = None
|
||
_c_dynamic_libs: str = None
|
||
_c_output: str = None
|
||
_c_triple: str = None
|
||
_c_datalayout: str = None
|
||
_c_selfmachine: int = 0
|
||
_c_has_project: int = 0
|
||
_c_has_build: int = 0
|
||
# proj.advanced.* 高级开关(等价 CLI 的 --asan / --traceback)
|
||
_c_adv_asan: int = 0
|
||
_c_adv_traceback: int = 0
|
||
# proj.clean.* 细粒度清理(等价 --clean 的子集;build_dir = 整个 __vpcbuild__)
|
||
_c_clean_all: int = 0
|
||
_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
|
||
|
||
# Whether applied successfully (for main.vp to decide viperbuild's linker.output)
|
||
_applied: int = 0
|
||
|
||
|
||
# Basic helpers
|
||
def _DupStd(s: str) -> str:
|
||
"""Deep-copy a string with stdlib.malloc so it survives reset_to."""
|
||
if s is None: return None
|
||
n: sizet = string.strlen(s)
|
||
buf: str = stdlib.malloc(n + 1)
|
||
if buf is None: return None
|
||
string.strcpy(buf, s)
|
||
return buf
|
||
|
||
|
||
def _DupScratch(s: str) -> str:
|
||
"""Deep-copy a string into the scratch pool, unaffected by main-pool reset_to."""
|
||
if s is None or _scratch is None: return None
|
||
n: sizet = string.strlen(s)
|
||
buf: str = _scratch.alloc(n + 1)
|
||
if buf is None: return None
|
||
string.strcpy(buf, s)
|
||
return buf
|
||
|
||
|
||
def _Join2(a: str, b: str) -> str:
|
||
"""Join a and b with a slash; returns b when a is empty."""
|
||
if a is None: return b
|
||
if b is None: return a
|
||
al: sizet = string.strlen(a)
|
||
if al == 0: return b
|
||
bl: sizet = string.strlen(b)
|
||
buf: str = stdlib.malloc(al + 1 + bl + 1)
|
||
if buf is None: return None
|
||
string.strcpy(buf, a)
|
||
buf[al] = '/'
|
||
string.strcpy(buf + al + 1, b)
|
||
return buf
|
||
|
||
|
||
def _DirOf(path: str) -> str:
|
||
"""Return the directory part of path (no trailing separator); empty string if none."""
|
||
if path is None: return None
|
||
n: sizet = string.strlen(path)
|
||
i: sizet = n
|
||
while i > 0:
|
||
i -= 1
|
||
ch: char = path[i]
|
||
if ch == '/' or ch == '\\':
|
||
buf: str = stdlib.malloc(i + 1)
|
||
if buf is None: return None
|
||
string.strncpy(buf, path, i)
|
||
buf[i] = '\0'
|
||
return buf
|
||
return _DupStd("")
|
||
|
||
|
||
def _BaseNameOf(path: str) -> str:
|
||
"""Return the file name of path, including its extension."""
|
||
if path is None: return None
|
||
n: sizet = string.strlen(path)
|
||
start: sizet = 0
|
||
for i in range(int(n)):
|
||
ch: char = path[i]
|
||
if ch == '/' or ch == '\\': start = i + 1
|
||
return _DupStd(path + start)
|
||
|
||
|
||
def _IsDir(path: str) -> int:
|
||
"""Whether path is an existing directory; 1=yes."""
|
||
if path is None: return 0
|
||
attr: ULONG = win32file.GetFileAttributesA(path)
|
||
if attr == 0xFFFFFFFF: return 0
|
||
if (attr & win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: return 1
|
||
return 0
|
||
|
||
|
||
def _HasPrefix(s: str, prefix: str) -> int:
|
||
"""Whether s starts with prefix; 1=yes (None/empty/"." counts as true)."""
|
||
if s is None: return 0
|
||
if prefix is None: return 1
|
||
if string.strcmp(prefix, ".") == 0: return 1
|
||
if string.strncmp(s, "./", 2) == 0: s = s + 2
|
||
if string.strncmp(prefix, "./", 2) == 0: prefix = prefix + 2
|
||
pl: sizet = string.strlen(prefix)
|
||
if pl == 0: return 1
|
||
sl: sizet = string.strlen(s)
|
||
if sl < pl: return 0
|
||
if string.strncmp(s, prefix, pl) == 0: return 1
|
||
return 0
|
||
|
||
|
||
# Constant tables
|
||
def _FindInList(names: list[str]*, name: str) -> int:
|
||
"""Linear-search names for name; return its index, or -1 if not found."""
|
||
if names is None or name is None: return -1
|
||
n: sizet = names.__len__()
|
||
for i in range(int(n)):
|
||
s: str = names.get(i)
|
||
if s is not None:
|
||
if string.strcmp(s, name) == 0: return int(i)
|
||
return -1
|
||
|
||
|
||
def _LookupStrConst(name: str) -> int:
|
||
"""Resolve a name to a string constant in _g_out_str; 0=failure."""
|
||
global _g_out_str
|
||
idx: int = _FindInList(_f_str_names, name)
|
||
if idx < 0: return 0
|
||
v: str = _f_str_vals.get(sizet(idx))
|
||
if v is None: return 0
|
||
_g_out_str = v
|
||
return 1
|
||
|
||
|
||
def _LookupListConst(name: str, out: list[str]*) -> int:
|
||
"""Resolve a name to a list constant, split into out; 0=failure."""
|
||
idx: int = _FindInList(_f_list_names, name)
|
||
if idx < 0: return 0
|
||
v: str = _f_list_vals.get(sizet(idx))
|
||
if v is None: return 0
|
||
return _SplitInto(out, v)
|
||
|
||
|
||
def _SplitInto(out: list[str]*, joined: str) -> int:
|
||
"""Split a VB_SEP_CODE-joined string into out; empty string means empty list."""
|
||
if out is None or joined is None: return 0
|
||
n: sizet = string.strlen(joined)
|
||
if n == 0: return 1
|
||
start: sizet = 0
|
||
for i in range(int(n + 1)):
|
||
if i == n or joined[i] == char(VB_SEP_CODE):
|
||
seg: str = stdlib.malloc(i - start + 1)
|
||
if seg is None: return 0
|
||
string.strncpy(seg, joined + start, i - start)
|
||
seg[i - start] = '\0'
|
||
out.append(_DupScratch(seg))
|
||
stdlib.free(seg)
|
||
start = i + 1
|
||
return 1
|
||
|
||
|
||
def _JoinSep(lst: list[str]*) -> str:
|
||
"""Join a list[str] with VB_SEP_CODE (pool-allocated); None for an empty list."""
|
||
if lst is None: return None
|
||
n: sizet = lst.__len__()
|
||
if n == 0: return None
|
||
# Measure total length first
|
||
total: sizet = 0
|
||
i: sizet = 0
|
||
while i < n:
|
||
s: str = lst.get(i)
|
||
if s is not None: total += string.strlen(s) + 1
|
||
i += 1
|
||
if total == 0: return None
|
||
buf: str = _scratch.alloc(total + 1)
|
||
if buf is None: return None
|
||
pos: sizet = 0
|
||
for i in range(int(n)):
|
||
s = lst.get(i)
|
||
if s is not None:
|
||
sl: sizet = string.strlen(s)
|
||
if pos > 0:
|
||
buf[pos] = char(VB_SEP_CODE)
|
||
pos += 1
|
||
string.strcpy(buf + pos, s)
|
||
pos += sl
|
||
buf[pos] = '\0'
|
||
return buf
|
||
|
||
|
||
def _JoinSpace(lst: list[str]*) -> str:
|
||
"""Join a list[str] with spaces (stdlib-allocated); None for an empty list."""
|
||
if lst is None: return None
|
||
n: sizet = lst.__len__()
|
||
if n == 0: return None
|
||
total: sizet = 0
|
||
i: sizet = 0
|
||
while i < n:
|
||
s: str = lst.get(i)
|
||
if s is not None: total += string.strlen(s) + 1
|
||
i += 1
|
||
if total == 0: return None
|
||
buf: str = stdlib.malloc(total + 1)
|
||
if buf is None: return None
|
||
pos: sizet = 0
|
||
for i in range(int(n)):
|
||
s = lst.get(i)
|
||
if s is not None:
|
||
sl: sizet = string.strlen(s)
|
||
if pos > 0:
|
||
buf[pos] = ' '
|
||
pos += 1
|
||
string.strcpy(buf + pos, s)
|
||
pos += sl
|
||
buf[pos] = '\0'
|
||
return buf
|
||
|
||
|
||
def _FirstSpaceTok(joined: str) -> str:
|
||
"""Return the first space-separated token (stdlib-allocated); None if empty."""
|
||
if joined is None: return None
|
||
n: sizet = string.strlen(joined)
|
||
if n == 0: return None
|
||
sp: sizet = n
|
||
for i in range(int(n)):
|
||
if joined[i] == ' ':
|
||
sp = i
|
||
break
|
||
seg: str = stdlib.malloc(sp + 1)
|
||
if seg is None: return None
|
||
string.strncpy(seg, joined, sp)
|
||
seg[sp] = '\0'
|
||
return seg
|
||
|
||
|
||
# Constant expression evaluation
|
||
def _EvalStr(node: ast.AST*) -> int:
|
||
"""Evaluate a string constant expression into _g_out_str; 0=failure."""
|
||
global _g_out_str
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
if cn is None or cn.const_kind != ast.CONST_STR: return 0
|
||
_g_out_str = cn.str_val
|
||
return 1
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm is None or nm.id is None: return 0
|
||
return _LookupStrConst(nm.id)
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(node)
|
||
if bop is None or bop.op != ast.OpKind.Add: return 0
|
||
if _EvalStr(bop.left) == 0: return 0
|
||
lv: str = _g_out_str
|
||
if _EvalStr(bop.right) == 0: return 0
|
||
rv: str = _g_out_str
|
||
_g_out_str = _ConcatStd(lv, rv)
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def _ConcatStd(a: str, b: str) -> str:
|
||
"""Concatenate two strings with stdlib allocation."""
|
||
if a is None: return b
|
||
if b is None: return a
|
||
al: sizet = string.strlen(a)
|
||
bl: sizet = string.strlen(b)
|
||
buf: str = stdlib.malloc(al + bl + 1)
|
||
if buf is None: return None
|
||
string.strcpy(buf, a)
|
||
string.strcpy(buf + al, b)
|
||
return buf
|
||
|
||
|
||
def _EvalList(node: ast.AST*, out: list[str]*) -> int:
|
||
"""Evaluate a list[str] constant expression into out; 0=failure."""
|
||
if node is None or out is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm is None or nm.id is None: return 0
|
||
return _LookupListConst(nm.id, out)
|
||
if k == ast.ASTKind.List:
|
||
lst: ast.List* = ast.List*(node)
|
||
if lst is None or lst.elts is None: return 0
|
||
n: sizet = lst.elts.__len__()
|
||
for i in range(int(n)):
|
||
e: ast.AST* = lst.elts.get(i)
|
||
if e is None: return 0
|
||
if _IsListUnpack(e) != 0:
|
||
inner: ast.AST* = _ListUnpackOperand(e)
|
||
if inner is None: return 0
|
||
if _EvalList(inner, out) == 0: return 0
|
||
else:
|
||
if _EvalStr(e) == 0: return 0
|
||
out.append(_DupScratch(_g_out_str))
|
||
return 1
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(node)
|
||
if bop is None or bop.op != ast.OpKind.Add: return 0
|
||
if _EvalList(bop.left, out) == 0: return 0
|
||
if _EvalList(bop.right, out) == 0: return 0
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# Macro-list unpacking: `[*COMMON_FLAGS]` / `A + [*B]`.
|
||
# The parser has no Starred node in list displays: a leading `*` in an element position is
|
||
# parsed as a deref UnaryOp (expression-position `*x` is pointer deref). Inside a compile-time
|
||
# macro list the only meaningful interpretation is unpacking, so both shapes are accepted here.
|
||
# Keep in sync with _ListUnpackOperand.
|
||
def _IsListUnpack(node: ast.AST*) -> int:
|
||
"""Whether a list element is an unpacking form (`*X`); 1=yes / 0=no."""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Starred: return 1
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uo: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uo is not None and uo.op == ast.OpKind.Deref: return 1
|
||
return 0
|
||
|
||
|
||
def _ListUnpackOperand(node: ast.AST*) -> ast.AST*:
|
||
"""Operand of an unpacking element (`*X` -> X); None when node is not an unpacking form."""
|
||
if _IsListUnpack(node) == 0: return None
|
||
if node.kind() == ast.ASTKind.Starred:
|
||
st: ast.Starred* = ast.Starred*(node)
|
||
if st is None: return None
|
||
return st.value
|
||
uo: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uo is None: return None
|
||
return uo.operand
|
||
|
||
|
||
def _EvalBool(node: ast.AST*) -> int:
|
||
"""Evaluate a bool constant; -1 means it is not a bool constant."""
|
||
if node is None: return -1
|
||
if node.kind() != ast.ASTKind.Constant: return -1
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
if cn is None or cn.const_kind != ast.CONST_BOOL: return -1
|
||
if cn.int_val != 0: return 1
|
||
return 0
|
||
|
||
|
||
# Macro-reference detection (reading is forbidden)
|
||
def _IsProjRef(node: ast.AST*) -> int:
|
||
"""Whether a node directly references a Project macro object; 1=yes."""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm is None or nm.id is None: return 0
|
||
if _FindInList(_proj_vars, nm.id) >= 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(node)
|
||
if at is None or at.value is None: return 0
|
||
return _IsProjRef(at.value)
|
||
return 0
|
||
|
||
|
||
# Single-file scan
|
||
def _ResetFileTables(pool: memhub.MemBuddy*) -> int:
|
||
"""Rebuild the per-file constant tables (pool-allocated)."""
|
||
global _f_str_names, _f_str_vals, _f_list_names, _f_list_vals
|
||
_f_str_names = list[str](pool, 8)
|
||
_f_str_vals = list[str](pool, 8)
|
||
_f_list_names = list[str](pool, 8)
|
||
_f_list_vals = list[str](pool, 8)
|
||
if _f_str_names is None or _f_str_vals is None: return 1
|
||
if _f_list_names is None or _f_list_vals is None: return 1
|
||
return 0
|
||
|
||
|
||
def _AddPending(mod_name: str) -> None:
|
||
"""Append a module name to the BFS queue (deep-copied into the scratch pool)."""
|
||
if mod_name is None or _pending is None: return
|
||
if string.strlen(mod_name) == 0: return
|
||
_pending.append(_DupScratch(mod_name))
|
||
|
||
|
||
def _HandleImport(stmt: ast.Import*) -> int:
|
||
"""Handle `import X [as Y]`: detect viperbuild/ffilib, queue other modules for BFS."""
|
||
global _alias, _ffi_alias, _saw_viperbuild
|
||
if stmt is None or stmt.names is None: return 0
|
||
n: sizet = stmt.names.__len__()
|
||
for i in range(int(n)):
|
||
al: ast.Alias* = ast.Alias*(stmt.names.get(i))
|
||
if al is not None and al.name is not None:
|
||
if string.strcmp(al.name, "viperbuild") == 0:
|
||
_saw_viperbuild = 1
|
||
if al.asname is not None: _alias = _DupScratch(al.asname)
|
||
else: _alias = _DupScratch("viperbuild")
|
||
elif string.strcmp(al.name, "ffilib") == 0:
|
||
# 宏库:不参与 BFS,只记别名(CDLL 语句由 _HandleCDLL 抽取)。
|
||
# _ffilib 的依赖只在真正出现 runtime=True 的 CDLL 时才加(见 _RegisterFfiHandle),
|
||
# 纯链接期(runtime=False)的工程不需要编译这个运行时库。
|
||
if al.asname is not None: _ffi_alias = _DupScratch(al.asname)
|
||
else: _ffi_alias = _DupScratch("ffilib")
|
||
else: _AddPending(al.name)
|
||
return 0
|
||
|
||
|
||
def _HandleImportFrom(stmt: ast.ImportFrom*) -> int:
|
||
"""Handle `from X import Y`: detect viperbuild's Project, queue other modules."""
|
||
global _ctor_name, _saw_viperbuild
|
||
if stmt is None: return 0
|
||
if stmt.level != 0:
|
||
# Relative imports are not part of the local closure (no package context: skip)
|
||
return 0
|
||
if stmt.module is None: return 0
|
||
if string.strcmp(stmt.module, "viperbuild") != 0:
|
||
_AddPending(stmt.module)
|
||
return 0
|
||
_saw_viperbuild = 1
|
||
if stmt.names is None: return 0
|
||
n: sizet = stmt.names.__len__()
|
||
for i in range(int(n)):
|
||
al: ast.Alias* = ast.Alias*(stmt.names.get(i))
|
||
if al is not None and al.name is not None:
|
||
if string.strcmp(al.name, "Project") == 0:
|
||
if al.asname is not None: _ctor_name = _DupScratch(al.asname)
|
||
else: _ctor_name = _DupScratch("Project")
|
||
return 0
|
||
|
||
|
||
def _IsProjectCtor(vnode: ast.AST*) -> int:
|
||
"""Whether an expression is a vb.Project(...) or Project(...) call; 1=yes."""
|
||
if vnode is None: return 0
|
||
if vnode.kind() != ast.ASTKind.Call: return 0
|
||
cl: ast.Call* = ast.Call*(vnode)
|
||
if cl is None or cl.func is None: return 0
|
||
fk: int = cl.func.kind()
|
||
if fk == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at is None or at.attr is None or at.value is None: return 0
|
||
if at.value.kind() != ast.ASTKind.Name: return 0
|
||
bn: ast.Name* = ast.Name*(at.value)
|
||
if bn is None or bn.id is None or _alias is None: return 0
|
||
if string.strcmp(bn.id, _alias) != 0: return 0
|
||
if string.strcmp(at.attr, "Project") == 0: return 1
|
||
return 0
|
||
if fk == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(cl.func)
|
||
if nm is None or nm.id is None or _ctor_name is None: return 0
|
||
if string.strcmp(nm.id, _ctor_name) == 0: return 1
|
||
return 0
|
||
|
||
|
||
def _ApplyCtorArgs(cl: ast.Call*) -> int:
|
||
"""Extract name/version from Project(...) args into _c_name / _c_version."""
|
||
global _c_name, _c_version
|
||
if cl is None: return 0
|
||
if cl.args is not None:
|
||
an: sizet = cl.args.__len__()
|
||
if an > 2:
|
||
VLogger.error("viperbuild: Project(...) accepts at most 2 positional arguments (name, version)", "viperbuild")
|
||
return 1
|
||
if an >= 1:
|
||
if _EvalStr(cl.args.get(0)) == 0:
|
||
VLogger.error("viperbuild: Project(name) must be a string constant", "viperbuild")
|
||
return 1
|
||
_c_name = _DupStd(_g_out_str)
|
||
if an >= 2:
|
||
if _EvalStr(cl.args.get(1)) == 0:
|
||
VLogger.error("viperbuild: Project(version) must be a string constant", "viperbuild")
|
||
return 1
|
||
_c_version = _DupStd(_g_out_str)
|
||
if cl.keywords is None: return 0
|
||
n: sizet = cl.keywords.__len__()
|
||
for i in range(int(n)):
|
||
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
|
||
if kw is not None and kw.arg is not None and kw.value is not None:
|
||
if string.strcmp(kw.arg, "name") == 0:
|
||
if _EvalStr(kw.value) == 0:
|
||
VLogger.error("viperbuild: Project(name=...) must be a string constant", "viperbuild")
|
||
return 1
|
||
_c_name = _DupStd(_g_out_str)
|
||
elif string.strcmp(kw.arg, "version") == 0:
|
||
if _EvalStr(kw.value) == 0:
|
||
VLogger.error("viperbuild: Project(version=...) must be a string constant", "viperbuild")
|
||
return 1
|
||
_c_version = _DupStd(_g_out_str)
|
||
else:
|
||
VLogger.error(f"viperbuild: unsupported keyword {kw.arg} for Project(...)", "viperbuild")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def _SetField(fname: str, vnode: ast.AST*) -> int:
|
||
"""Apply a proj.<fname> assignment or config(<fname>=...) keyword; 0=success."""
|
||
global _c_source, _c_buildroot, _c_includes, _c_compiler, _c_linker
|
||
global _c_static_libs, _c_dynamic_libs, _c_output, _c_name, _c_version
|
||
if fname is None or vnode is None: return 1
|
||
# ---- String fields ----
|
||
if string.strcmp(fname, "source") == 0:
|
||
if _EvalStr(vnode) == 0:
|
||
VLogger.error("viperbuild: source must be a string constant expression", "viperbuild")
|
||
return 1
|
||
_c_source = _DupStd(_g_out_str)
|
||
return 0
|
||
if string.strcmp(fname, "buildroot") == 0:
|
||
if _EvalStr(vnode) == 0:
|
||
VLogger.error("viperbuild: buildroot must be a string constant expression", "viperbuild")
|
||
return 1
|
||
_c_buildroot = _DupStd(_g_out_str)
|
||
return 0
|
||
if string.strcmp(fname, "output") == 0:
|
||
if _EvalStr(vnode) == 0:
|
||
VLogger.error("viperbuild: output must be a string constant expression", "viperbuild")
|
||
return 1
|
||
_c_output = _DupStd(_g_out_str)
|
||
return 0
|
||
if string.strcmp(fname, "name") == 0:
|
||
if _EvalStr(vnode) == 0:
|
||
VLogger.error("viperbuild: name must be a string constant expression", "viperbuild")
|
||
return 1
|
||
_c_name = _DupStd(_g_out_str)
|
||
return 0
|
||
if string.strcmp(fname, "version") == 0:
|
||
if _EvalStr(vnode) == 0:
|
||
VLogger.error("viperbuild: version must be a string constant expression", "viperbuild")
|
||
return 1
|
||
_c_version = _DupStd(_g_out_str)
|
||
return 0
|
||
# ---- List fields ----
|
||
if string.strcmp(fname, "includes") == 0: return _SetListField(0, vnode)
|
||
if string.strcmp(fname, "compiler") == 0: return _SetListField(1, vnode)
|
||
if string.strcmp(fname, "linker") == 0: return _SetListField(2, vnode)
|
||
if string.strcmp(fname, "static_libs") == 0: return _SetListField(3, vnode)
|
||
if string.strcmp(fname, "dynamic_libs") == 0: return _SetListField(4, vnode)
|
||
VLogger.error(f"viperbuild: unknown field proj.{fname}", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _SetListField(slot: int, vnode: ast.AST*) -> int:
|
||
"""Assign a list field (0=includes, 1=compiler, 2=linker, 3=static_libs, 4=dynamic_libs)."""
|
||
global _c_includes, _c_compiler, _c_linker, _c_static_libs, _c_dynamic_libs
|
||
if _scratch is None: return 1
|
||
tmp: list[str]* = list[str](_scratch, 8)
|
||
if tmp is None: return 1
|
||
if _EvalList(vnode, tmp) == 0:
|
||
VLogger.error("viperbuild: list fields must be list[str] constant expressions", "viperbuild")
|
||
return 1
|
||
joined: str = _JoinSpace(tmp)
|
||
if slot == 0: _c_includes = joined
|
||
elif slot == 1: _c_compiler = joined
|
||
elif slot == 2: _c_linker = joined
|
||
elif slot == 3: _c_static_libs = joined
|
||
else: _c_dynamic_libs = joined
|
||
return 0
|
||
|
||
|
||
def _HandleAssign(stmt: ast.Assign*) -> int:
|
||
"""Handle an assignment: constant, Project instance, or proj field."""
|
||
if stmt is None or stmt.targets is None or stmt.value is None: return 0
|
||
if stmt.targets.__len__() != 1: return 0
|
||
tgt: ast.AST* = stmt.targets.get(0)
|
||
if tgt is None: return 0
|
||
tk: int = tgt.kind()
|
||
if tk == ast.ASTKind.Name: return _HandleNameBind(ast.Name*(tgt), stmt.value)
|
||
if tk == ast.ASTKind.Attribute: return _HandleAttrAssign(ast.Attribute*(tgt), stmt.value)
|
||
return 0
|
||
|
||
|
||
def _HandleAnnAssign(stmt: ast.AnnAssign*) -> int:
|
||
"""Handle an annotated assignment (including define-desugared AnnAssign)."""
|
||
if stmt is None or stmt.target is None or stmt.value is None: return 0
|
||
if stmt.target.kind() != ast.ASTKind.Name: return 0
|
||
return _HandleNameBind(ast.Name*(stmt.target), stmt.value)
|
||
|
||
|
||
def _HandleNameBind(tgt: ast.Name*, vnode: ast.AST*) -> int:
|
||
"""Handle NAME = value: Project binding, string constant, or list constant."""
|
||
global _c_has_project
|
||
if tgt is None or vnode is None or tgt.id is None: return 0
|
||
# Reading a Project macro object on the RHS is fatal (reading forbidden)
|
||
if _IsProjRef(vnode) != 0:
|
||
VLogger.error(f"viperbuild: reading the Project macro object is forbidden ({tgt.id} = proj ...)", "viperbuild")
|
||
return 1
|
||
# Project instance binding
|
||
if _IsProjectCtor(vnode) != 0:
|
||
if _c_has_project != 0:
|
||
VLogger.error("viperbuild: only one Project object is allowed per dependency closure", "viperbuild")
|
||
return 1
|
||
_c_has_project = 1
|
||
_proj_vars.append(_DupScratch(tgt.id))
|
||
cl: ast.Call* = ast.Call*(vnode)
|
||
return _ApplyCtorArgs(cl)
|
||
# String constant
|
||
if _EvalStr(vnode) != 0:
|
||
_f_str_names.append(tgt.id)
|
||
_f_str_vals.append(_DupScratch(_g_out_str))
|
||
return 0
|
||
# List constant
|
||
if _scratch is not None:
|
||
tmp: list[str]* = list[str](_scratch, 8)
|
||
if tmp is not None:
|
||
if _EvalList(vnode, tmp) != 0:
|
||
_f_list_names.append(tgt.id)
|
||
_f_list_vals.append(_JoinSep(tmp))
|
||
return 0
|
||
tmp.clear()
|
||
return 0
|
||
|
||
|
||
def _SetAdvancedField(fname: str, vnode: ast.AST*) -> int:
|
||
"""Apply proj.advanced.<fname> = <bool constant>; 0=success."""
|
||
global _c_adv_asan, _c_adv_traceback
|
||
if fname is None or vnode is None: return 1
|
||
b: int = _EvalBool(vnode)
|
||
if b < 0:
|
||
VLogger.error(f"viperbuild: proj.advanced.{fname} must be a boolean constant (True/False)", "viperbuild")
|
||
return 1
|
||
if string.strcmp(fname, "asan") == 0:
|
||
_c_adv_asan = b
|
||
return 0
|
||
if string.strcmp(fname, "traceback") == 0:
|
||
_c_adv_traceback = b
|
||
return 0
|
||
VLogger.error(f"viperbuild: unknown field proj.advanced.{fname} (supported: asan / traceback)", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _SetCleanField(fname: str, vnode: ast.AST*) -> int:
|
||
"""Apply proj.clean.<fname> = <bool constant>; 0=success."""
|
||
global _c_clean_all, _c_clean_output, _c_clean_temp, _c_clean_build_dir, _c_clean_includes
|
||
if fname is None or vnode is None: return 1
|
||
b: int = _EvalBool(vnode)
|
||
if b < 0:
|
||
VLogger.error(f"viperbuild: proj.clean.{fname} must be a boolean constant (True/False)", "viperbuild")
|
||
return 1
|
||
if string.strcmp(fname, "all") == 0:
|
||
_c_clean_all = b
|
||
return 0
|
||
if string.strcmp(fname, "output") == 0:
|
||
_c_clean_output = b
|
||
return 0
|
||
if string.strcmp(fname, "temp") == 0:
|
||
_c_clean_temp = b
|
||
return 0
|
||
if string.strcmp(fname, "build_dir") == 0:
|
||
_c_clean_build_dir = b
|
||
return 0
|
||
if string.strcmp(fname, "includes") == 0:
|
||
_c_clean_includes = b
|
||
return 0
|
||
VLogger.error(f"viperbuild: unknown field proj.clean.{fname} (supported: all / output / temp / build_dir / includes)", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _HandleAttrAssign(tgt: ast.Attribute*, vnode: ast.AST*) -> int:
|
||
"""Handle proj.<field> = <const>, proj.advanced.<field> / proj.clean.<field> = <bool>."""
|
||
if tgt is None or tgt.attr is None or tgt.value is None: return 0
|
||
# Nested form: proj.advanced.{asan,traceback} / proj.clean.{output,temp,build_dir,includes}
|
||
if tgt.value.kind() == ast.ASTKind.Attribute:
|
||
mid: ast.Attribute* = ast.Attribute*(tgt.value)
|
||
if mid is None or mid.attr is None or mid.value is None: return 0
|
||
if mid.value.kind() != ast.ASTKind.Name: return 0
|
||
mn: ast.Name* = ast.Name*(mid.value)
|
||
if mn is None or mn.id is None: return 0
|
||
if _FindInList(_proj_vars, mn.id) < 0: return 0
|
||
if string.strcmp(mid.attr, "advanced") != 0 and string.strcmp(mid.attr, "clean") != 0:
|
||
VLogger.error(f"viperbuild: unknown nested field proj.{mid.attr}.{tgt.attr}", "viperbuild")
|
||
return 1
|
||
if _IsProjRef(vnode) != 0:
|
||
VLogger.error("viperbuild: reading the Project macro object is forbidden", "viperbuild")
|
||
return 1
|
||
if string.strcmp(mid.attr, "clean") == 0: return _SetCleanField(tgt.attr, vnode)
|
||
return _SetAdvancedField(tgt.attr, vnode)
|
||
if tgt.value.kind() != ast.ASTKind.Name: return 0
|
||
bn: ast.Name* = ast.Name*(tgt.value)
|
||
if bn is None or bn.id is None: return 0
|
||
if _FindInList(_proj_vars, bn.id) < 0: return 0
|
||
if _IsProjRef(vnode) != 0:
|
||
VLogger.error("viperbuild: reading the Project macro object is forbidden", "viperbuild")
|
||
return 1
|
||
return _SetField(tgt.attr, vnode)
|
||
|
||
|
||
def _HandleConfigCall(cl: ast.Call*) -> int:
|
||
"""Handle a proj.config(kw=...) call."""
|
||
if cl is None or cl.keywords is None: return 0
|
||
n: sizet = cl.keywords.__len__()
|
||
for i in range(int(n)):
|
||
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
|
||
if kw is not None and kw.arg is not None and kw.value is not None:
|
||
if _SetField(kw.arg, kw.value) != 0: return 1
|
||
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=, 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")
|
||
return 1
|
||
_c_has_build = 1
|
||
if cl.keywords is None: return 0
|
||
n: sizet = cl.keywords.__len__()
|
||
for i in range(int(n)):
|
||
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
|
||
if kw is not None and kw.arg is not None and kw.value is not None:
|
||
if string.strcmp(kw.arg, "triple") == 0:
|
||
if _EvalStr(kw.value) == 0:
|
||
VLogger.error("viperbuild: build(triple=...) must be a string constant", "viperbuild")
|
||
return 1
|
||
_c_triple = _DupStd(_g_out_str)
|
||
elif string.strcmp(kw.arg, "datalayout") == 0:
|
||
if _EvalStr(kw.value) == 0:
|
||
VLogger.error("viperbuild: build(datalayout=...) must be a string constant", "viperbuild")
|
||
return 1
|
||
_c_datalayout = _DupStd(_g_out_str)
|
||
elif string.strcmp(kw.arg, "selfmachine") == 0:
|
||
b: int = _EvalBool(kw.value)
|
||
if b < 0:
|
||
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
|
||
return 0
|
||
|
||
|
||
def _CallArgsHaveProjRef(cl: ast.Call*) -> int:
|
||
"""Whether any call argument references a Project macro object; 1=yes."""
|
||
if cl is None: return 0
|
||
if cl.args is not None:
|
||
n: sizet = cl.args.__len__()
|
||
for i in range(int(n)):
|
||
if _IsProjRef(cl.args.get(i)) != 0: return 1
|
||
if cl.keywords is not None:
|
||
n = cl.keywords.__len__()
|
||
for i in range(int(n)):
|
||
kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i))
|
||
if kw is not None and kw.value is not None:
|
||
if _IsProjRef(kw.value) != 0: return 1
|
||
return 0
|
||
|
||
|
||
def _HandleExprStmt(stmt: ast.Expr*) -> int:
|
||
"""Handle an expression statement: proj.config/build calls, else check reads."""
|
||
if stmt is None or stmt.value is None: return 0
|
||
v: ast.AST* = stmt.value
|
||
if v.kind() != ast.ASTKind.Call: return 0
|
||
cl: ast.Call* = ast.Call*(v)
|
||
if cl is None or cl.func is None: return 0
|
||
# Non-Project method call: if an argument reads the macro object -> fatal (reading forbidden)
|
||
if cl.func.kind() != ast.ASTKind.Attribute:
|
||
if _CallArgsHaveProjRef(cl) != 0:
|
||
VLogger.error("viperbuild: reading the Project macro object is forbidden", "viperbuild")
|
||
return 1
|
||
return 0
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at is None or at.attr is None or at.value is None: return 0
|
||
if at.value.kind() != ast.ASTKind.Name:
|
||
if _CallArgsHaveProjRef(cl) != 0:
|
||
VLogger.error("viperbuild: reading the Project macro object is forbidden", "viperbuild")
|
||
return 1
|
||
return 0
|
||
bn: ast.Name* = ast.Name*(at.value)
|
||
if bn is None or bn.id is None: return 0
|
||
if _FindInList(_proj_vars, bn.id) < 0:
|
||
if _CallArgsHaveProjRef(cl) != 0:
|
||
VLogger.error("viperbuild: reading the Project macro object is forbidden", "viperbuild")
|
||
return 1
|
||
return 0
|
||
if string.strcmp(at.attr, "config") == 0: return _HandleConfigCall(cl)
|
||
if string.strcmp(at.attr, "build") == 0: return _HandleBuildCall(cl)
|
||
VLogger.error(f"viperbuild: unsupported method {at.attr}() on Project object", "viperbuild")
|
||
return 1
|
||
|
||
|
||
# ---------------- ffilib.CDLL(纯构建配置) ----------------
|
||
|
||
# _BoolConst - 布尔字面量 -> 1(True) / 0(False) / -1(不是布尔常量)
|
||
def _BoolConst(node: ast.AST*) -> int:
|
||
"""布尔常量求值;非布尔常量返回 -1"""
|
||
if node is None or node.kind() != ast.ASTKind.Constant: return -1
|
||
c: ast.Constant* = ast.Constant*(node)
|
||
if c is None or c.const_kind != ast.CONST_BOOL: return -1
|
||
if c.int_val != 0: return 1
|
||
return 0
|
||
|
||
|
||
# _AppendLibPath - 把路径追加到 static_libs(slot=0) / dynamic_libs(slot=1) 的空格拼接串
|
||
def _AppendLibPath(slot: int, path: str) -> int:
|
||
"""追加库路径;0=成功"""
|
||
global _c_static_libs, _c_dynamic_libs
|
||
if path is None: return 1
|
||
cur: str = _c_static_libs
|
||
if slot == 1: cur = _c_dynamic_libs
|
||
pl: sizet = string.strlen(path)
|
||
if pl == 0: return 0
|
||
ol: sizet = 0
|
||
if cur is not None: ol = string.strlen(cur)
|
||
buf: str = stdlib.malloc(ol + pl + 2)
|
||
if buf is None: return 1
|
||
pos: sizet = 0
|
||
if ol > 0:
|
||
string.strcpy(buf, cur)
|
||
buf[ol] = ' '
|
||
pos = ol + 1
|
||
string.strcpy(buf + pos, path)
|
||
if slot == 1: _c_dynamic_libs = buf
|
||
else: _c_static_libs = buf
|
||
return 0
|
||
|
||
|
||
# _IsCDLLCall - node 是否是 ffilib.CDLL(...)(别名/全名都认)
|
||
def _IsCDLLCall(node: ast.AST*) -> int:
|
||
"""是 ffilib.CDLL 调用时返回 1"""
|
||
if node is None or node.kind() != ast.ASTKind.Call: return 0
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl is None or cl.func is None: return 0
|
||
if cl.func.kind() != ast.ASTKind.Attribute: return 0
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at is None or at.attr is None or string.strcmp(at.attr, "CDLL") != 0: return 0
|
||
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
|
||
nm: ast.Name* = ast.Name*(at.value)
|
||
if nm.id is None: return 0
|
||
if _ffi_alias is not None and string.strcmp(nm.id, _ffi_alias) == 0: return 1
|
||
if string.strcmp(nm.id, "ffilib") == 0: return 1
|
||
return 0
|
||
|
||
|
||
# _RegisterFfiHandle - 登记 CDLL 句柄(变量名 -> runtime + 库路径)
|
||
def _RegisterFfiHandle(handle: str, runtime: int, path: str) -> None:
|
||
"""登记 ffilib.CDLL 绑定的变量名、runtime 标志与库路径;句柄名为 None 时忽略"""
|
||
global _ffi_handles_names, _ffi_handles_rt, _ffi_handles_path
|
||
if handle is None or _ffi_handles_names is None or _ffi_handles_rt is None or _ffi_handles_path is None: return
|
||
nm: str = _DupScratch(handle)
|
||
if nm is None: return
|
||
pth: str = _DupScratch(path)
|
||
_ffi_handles_names.append(nm)
|
||
_ffi_handles_rt.append(runtime)
|
||
_ffi_handles_path.append(pth)
|
||
# runtime=True:thunk 运行期要调 _ffilib.Load/Sym,把该运行时库加入依赖闭包
|
||
# (按需可达性扫描只收录被 import 的模块,否则 _resolve_module_sha1("_ffilib") 为 None)。
|
||
if runtime != 0: _AddPending("_ffilib")
|
||
|
||
|
||
# _AssignTargetName - 取 `x = ...` 的赋值目标名;非简单 Name 时返回 None
|
||
def _AssignTargetName(asn: ast.Assign*) -> str:
|
||
"""赋值目标名;多目标/非 Name 返回 None"""
|
||
if asn is None or asn.targets is None: return None
|
||
if len(asn.targets) != 1: return None
|
||
t: ast.AST* = asn.targets.get(0)
|
||
if t is None or t.kind() != ast.ASTKind.Name: return None
|
||
tn: ast.Name* = ast.Name*(t)
|
||
if tn is None: return None
|
||
return tn.id
|
||
|
||
|
||
# _HandleCDLL - 按实参形态写入静态/动态库表;list 回退形式本期 fail-fast
|
||
def _HandleCDLL(node: ast.AST*, handle: str) -> int:
|
||
"""抽取 ffilib.CDLL(...):0=成功"""
|
||
if node is None or node.kind() != ast.ASTKind.Call: return 0
|
||
call: ast.Call* = ast.Call*(node)
|
||
args: list[ast.AST*]* = call.args
|
||
if args is None or len(args) < 1:
|
||
VLogger.error("ffilib: CDLL(...) requires at least one argument", "ffilib")
|
||
return 1
|
||
a0: ast.AST* = args.get(0)
|
||
if a0 is None: return 1
|
||
|
||
# 1) tag 形式:Project.StaticLib(path) / Project.DynamicLib(path)
|
||
if a0.kind() == ast.ASTKind.Call:
|
||
tag: ast.Call* = ast.Call*(a0)
|
||
is_static: int = 0
|
||
is_dynamic: int = 0
|
||
if tag is not None and tag.func is not None and tag.func.kind() == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(tag.func)
|
||
if at is not None and at.attr is not None:
|
||
if string.strcmp(at.attr, "StaticLib") == 0: is_static = 1
|
||
elif string.strcmp(at.attr, "DynamicLib") == 0: is_dynamic = 1
|
||
if is_static == 0 and is_dynamic == 0:
|
||
VLogger.error("ffilib: CDLL(tag) expects Project.StaticLib(path) or Project.DynamicLib(path)", "ffilib")
|
||
return 1
|
||
if tag.args is None or len(tag.args) < 1:
|
||
VLogger.error("ffilib: StaticLib/DynamicLib requires a path argument", "ffilib")
|
||
return 1
|
||
p0: ast.AST* = tag.args.get(0)
|
||
if p0 is None or p0.kind() != ast.ASTKind.Constant:
|
||
VLogger.error("ffilib: library path must be a string constant", "ffilib")
|
||
return 1
|
||
c0: ast.Constant* = ast.Constant*(p0)
|
||
if c0 is None or c0.const_kind != ast.CONST_STR:
|
||
VLogger.error("ffilib: library path must be a string constant", "ffilib")
|
||
return 1
|
||
if is_static != 0:
|
||
rc_t: int = _AppendLibPath(0, c0.str_val)
|
||
if rc_t == 0: _RegisterFfiHandle(handle, 0, c0.str_val)
|
||
return rc_t
|
||
rc_td: int = _AppendLibPath(1, c0.str_val)
|
||
if rc_td == 0: _RegisterFfiHandle(handle, 1, c0.str_val)
|
||
return rc_td
|
||
|
||
# 2) 回退列表(预留):需要 _ffilib 运行时库,本期编译期拦截
|
||
if a0.kind() == ast.ASTKind.List:
|
||
VLogger.error("ffilib: CDLL(list[str], runtime) fallback-list form is not implemented yet (requires the _ffilib runtime library)", "ffilib")
|
||
return 1
|
||
|
||
# 3) 字符串 + runtime(位置参数或 runtime= 关键字)
|
||
if a0.kind() != ast.ASTKind.Constant:
|
||
VLogger.error("ffilib: CDLL(path, runtime) path must be a string constant", "ffilib")
|
||
return 1
|
||
c1: ast.Constant* = ast.Constant*(a0)
|
||
if c1 is None or c1.const_kind != ast.CONST_STR:
|
||
VLogger.error("ffilib: CDLL path must be a string constant", "ffilib")
|
||
return 1
|
||
rt: int = -1
|
||
if len(args) >= 2: rt = _BoolConst(args.get(1))
|
||
if rt < 0 and call.keywords is not None:
|
||
kn: sizet = len(call.keywords)
|
||
for ki in range(int(kn)):
|
||
kw: ast.Keyword* = ast.Keyword*(call.keywords.get(ki))
|
||
if kw is None or kw.arg is None: continue
|
||
if string.strcmp(kw.arg, "runtime") == 0:
|
||
rt = _BoolConst(kw.value)
|
||
break
|
||
if rt < 0:
|
||
VLogger.error("ffilib: CDLL(path) needs a constant runtime=True/False, or use Project.StaticLib/DynamicLib", "ffilib")
|
||
return 1
|
||
if rt == 1:
|
||
# runtime=True:不进链接输入(运行期由 thunk 用绝对路径 LoadLibraryA),
|
||
# 只登记句柄与路径;否则会同时生成导入表依赖,与 thunk 重复。
|
||
_RegisterFfiHandle(handle, 1, c1.str_val)
|
||
return 0
|
||
rc_s: int = _AppendLibPath(0, c1.str_val)
|
||
if rc_s == 0: _RegisterFfiHandle(handle, 0, c1.str_val)
|
||
return rc_s
|
||
|
||
|
||
# IsCdlLDeclCall - 供 Gen 层判断"这条模块级赋值是 ffilib.CDLL 声明"(纯编译期配置,不能生成全局)
|
||
def IsCdlLDeclCall(node: ast.AST*) -> int:
|
||
"""node 是 ffilib.CDLL(...) 调用时返回 1"""
|
||
return _IsCDLLCall(node)
|
||
|
||
|
||
# HasRuntimeFfiHandle - 是否存在 runtime=True 的 CDLL 句柄(决定是否要引入 _ffilib 依赖)
|
||
def HasRuntimeFfiHandle() -> int:
|
||
"""至少一个句柄 runtime=1 时返回 1"""
|
||
global _ffi_handles_rt
|
||
if _ffi_handles_rt is None: return 0
|
||
n: sizet = _ffi_handles_rt.__len__()
|
||
for i in range(int(n)):
|
||
if _ffi_handles_rt.get(i) != 0: return 1
|
||
return 0
|
||
|
||
|
||
# FfiHandlePath - 查询 extern <lib> name(...) 的库路径(runtime=True 的 thunk 用它 LoadLibraryA)
|
||
def FfiHandlePath(name: str) -> str:
|
||
"""库路径;未登记返回 None"""
|
||
global _ffi_handles_names, _ffi_handles_path
|
||
if name is None or _ffi_handles_names is None or _ffi_handles_path is None: return None
|
||
n: sizet = _ffi_handles_names.__len__()
|
||
for i in range(int(n)):
|
||
nm: str = _ffi_handles_names.get(i)
|
||
if nm is not None and string.strcmp(nm, name) == 0: return _ffi_handles_path.get(i)
|
||
return None
|
||
|
||
|
||
# FfiHandleRuntime - 查询 extern <lib> name(...) 的库句柄标志
|
||
def FfiHandleRuntime(name: str) -> int:
|
||
"""1=运行时加载 / 0=链接期 / -1=未登记(不是 ffilib.CDLL 绑定的变量)"""
|
||
global _ffi_handles_names, _ffi_handles_rt
|
||
if name is None or _ffi_handles_names is None or _ffi_handles_rt is None: return -1
|
||
n: sizet = _ffi_handles_names.__len__()
|
||
for i in range(int(n)):
|
||
nm: str = _ffi_handles_names.get(i)
|
||
if nm is not None and string.strcmp(nm, name) == 0: return _ffi_handles_rt.get(i)
|
||
return -1
|
||
|
||
|
||
def _ScanFile(pool: memhub.MemBuddy*, path: str) -> int:
|
||
"""Parse and scan one .vp file; 0=ok, 1=failed, 2=skippable (unreadable/not .vp)."""
|
||
if path is None: return 2
|
||
sl: sizet = string.strlen(path)
|
||
if sl == 0: return 2
|
||
if Utils.is_vp_at(path, 0, sl) == 0:
|
||
# Not .vp: excluded from the closure (.vpi/.vpnci invisible to the compiler)
|
||
return 2
|
||
|
||
src_buf: bytes = Utils.read_file_to_buf(path, VB_SRC_CAP)
|
||
if src_buf is None:
|
||
VLogger.warning(f"viperbuild: cannot read dependency file {path}", "viperbuild")
|
||
return 2
|
||
|
||
if _ResetFileTables(pool) != 0:
|
||
stdlib.free(src_buf)
|
||
VLogger.error("viperbuild: constant table allocation failed", "viperbuild")
|
||
return 1
|
||
|
||
mark: sizet = pool.mark()
|
||
lx: ast.Lexer* = ast.new_lexer(pool)
|
||
if lx is None:
|
||
pool.reset_to(mark)
|
||
stdlib.free(src_buf)
|
||
VLogger.error("viperbuild: lexer allocation failed", "viperbuild")
|
||
return 1
|
||
ast._lexer_init(lx, src_buf, pool)
|
||
tokens: ast.Token* = ast.tokenize(lx)
|
||
if tokens is None:
|
||
pool.reset_to(mark)
|
||
stdlib.free(src_buf)
|
||
VLogger.error(f"viperbuild: tokenization failed {path}", "viperbuild")
|
||
return 1
|
||
tree: ast.AST* = ast.parse_tokens(pool, tokens)
|
||
if tree is None:
|
||
pool.reset_to(mark)
|
||
stdlib.free(src_buf)
|
||
VLogger.error(f"viperbuild: parsing failed {path}", "viperbuild")
|
||
return 1
|
||
|
||
body: list[ast.AST*]* = tree.children
|
||
rc: int = 0
|
||
if body is not None:
|
||
cn: sizet = len(body)
|
||
for ci in range(int(cn)):
|
||
child: ast.AST* = body.get(ci)
|
||
if child is not None:
|
||
kd: int = child.kind()
|
||
if kd == ast.ASTKind.Import: _HandleImport(ast.Import*(child))
|
||
elif kd == ast.ASTKind.ImportFrom: _HandleImportFrom(ast.ImportFrom*(child))
|
||
elif kd == ast.ASTKind.Assign:
|
||
asn_f: ast.Assign* = ast.Assign*(child)
|
||
if asn_f is not None and _IsCDLLCall(asn_f.value) != 0: rc = _HandleCDLL(asn_f.value, _AssignTargetName(asn_f))
|
||
else: rc = _HandleAssign(asn_f)
|
||
elif kd == ast.ASTKind.AnnAssign: rc = _HandleAnnAssign(ast.AnnAssign*(child))
|
||
elif kd == ast.ASTKind.Expr:
|
||
ex_f: ast.Expr* = ast.Expr*(child)
|
||
if ex_f is not None and _IsCDLLCall(ex_f.value) != 0: rc = _HandleCDLL(ex_f.value, None)
|
||
else: rc = _HandleExprStmt(ex_f)
|
||
if rc != 0: break
|
||
|
||
pool.reset_to(mark)
|
||
stdlib.free(src_buf)
|
||
return rc
|
||
|
||
|
||
# Dependency closure BFS
|
||
def _ResolveModuleToFile(module_name: str) -> str:
|
||
"""Resolve a module name to a local project file path; None if not found."""
|
||
if module_name is None or _search_dirs is None: return None
|
||
if string.strlen(module_name) == 0: return None
|
||
out_abs: rawptr = stdlib.malloc(8)
|
||
out_rel: rawptr = stdlib.malloc(8)
|
||
out_inc: rawptr = stdlib.malloc(8)
|
||
if out_abs is None or out_rel is None or out_inc is None:
|
||
if out_abs is not None: stdlib.free(out_abs)
|
||
if out_rel is not None: stdlib.free(out_rel)
|
||
if out_inc is not None: stdlib.free(out_inc)
|
||
return None
|
||
abs_slot: sizet* = (sizet*)(out_abs)
|
||
rel_slot: sizet* = (sizet*)(out_rel)
|
||
inc_slot: sizet* = (sizet*)(out_inc)
|
||
abs_slot[0] = 0
|
||
rel_slot[0] = 0
|
||
inc_slot[0] = 0
|
||
rcode: int = IncludesScanner.ResolveModuleToPath(_search_dirs, module_name, out_abs, out_rel, out_inc)
|
||
result: str = None
|
||
if rcode == 0 and abs_slot[0] != 0:
|
||
abs_path: str = rawptr(abs_slot[0])
|
||
if abs_path is not None: result = _DupScratch(abs_path)
|
||
if abs_slot[0] != 0: stdlib.free(rawptr(abs_slot[0]))
|
||
if rel_slot[0] != 0: stdlib.free(rawptr(rel_slot[0]))
|
||
if inc_slot[0] != 0: stdlib.free(rawptr(inc_slot[0]))
|
||
stdlib.free(out_abs)
|
||
stdlib.free(out_rel)
|
||
stdlib.free(out_inc)
|
||
return result
|
||
|
||
|
||
def _Enqueue(path: str) -> None:
|
||
"""Add a file path to the BFS queue (deduped against worklist and visited)."""
|
||
if path is None or _work is None: return
|
||
if _FindInList(_visited, path) >= 0: return
|
||
if _FindInList(_work, path) >= 0: return
|
||
_work.append(_DupScratch(path))
|
||
|
||
|
||
def _RunBfs(pool: memhub.MemBuddy*, entry_path: str) -> int:
|
||
"""Expand the dependency closure from the entry file; 0=success."""
|
||
global _pending
|
||
_work.append(_DupScratch(entry_path))
|
||
while _work.__len__() > 0:
|
||
cur: str = _work.pop()
|
||
if cur is None: continue
|
||
if _FindInList(_visited, cur) >= 0: continue
|
||
if _visited.__len__() >= VB_MAX_VISIT:
|
||
VLogger.error(f"viperbuild: dependency closure exceeds {VB_MAX_VISIT} files, likely a cycle", "viperbuild")
|
||
return 1
|
||
_visited.append(_DupScratch(cur))
|
||
|
||
_pending = list[str](_scratch, 8)
|
||
if _pending is None:
|
||
VLogger.error("viperbuild: BFS queue allocation failed", "viperbuild")
|
||
return 1
|
||
rc: int = _ScanFile(pool, cur)
|
||
if rc == 1: return 1
|
||
# Resolve module names collected from this file
|
||
pn: sizet = _pending.__len__()
|
||
for pi in range(int(pn)):
|
||
mod: str = _pending.get(pi)
|
||
if mod is not None:
|
||
dep: str = _ResolveModuleToFile(mod)
|
||
if dep is not None: _Enqueue(dep)
|
||
return 0
|
||
|
||
|
||
# Defaults / validation / apply
|
||
def _DetectHost() -> int:
|
||
"""Detect the host triple/datalayout via PROCESSOR_ARCHITECTURE; 0=success."""
|
||
global _c_triple, _c_datalayout
|
||
buf: CHARPTR = stdlib.malloc(64)
|
||
if buf is None:
|
||
VLogger.error("viperbuild: selfmachine probe buffer allocation failed", "viperbuild")
|
||
return 1
|
||
buf[0] = char(0)
|
||
n: DWORD = GetEnvironmentVariableA("PROCESSOR_ARCHITECTURE", buf, DWORD(64))
|
||
if n == 0 or n >= DWORD(64):
|
||
stdlib.free(buf)
|
||
VLogger.error("viperbuild: selfmachine probe failed (PROCESSOR_ARCHITECTURE unavailable)", "viperbuild")
|
||
return 1
|
||
buf[int(n)] = char(0)
|
||
# "AMD64\0" (A=65 M=77 D=68 6=54 4=52)
|
||
is_amd64: int = 0
|
||
if buf[0] == char(65) and buf[1] == char(77) and buf[2] == char(68) and buf[3] == char(54) and buf[4] == char(52) and buf[5] == char(0): is_amd64 = 1
|
||
if is_amd64 != 0:
|
||
_c_triple = "x86_64-pc-windows-gnu"
|
||
_c_datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||
stdlib.free(buf)
|
||
return 0
|
||
stdlib.free(buf)
|
||
VLogger.error("viperbuild: selfmachine does not support the host architecture yet (currently only AMD64)", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _ProbeIncludes() -> str:
|
||
"""Probe up to 3 levels above the exe dir for an includes directory; else None."""
|
||
exe_buf: bytes = stdlib.malloc(VB_PATH_CAP)
|
||
if exe_buf is None: return None
|
||
exe_buf[0] = '\0'
|
||
n_exe: ULONG = win32file.GetModuleFileNameA(None, exe_buf, ULONG(VB_PATH_CAP))
|
||
if n_exe <= 0:
|
||
stdlib.free(exe_buf)
|
||
return None
|
||
el: int = int(n_exe)
|
||
if el >= VB_PATH_CAP: el = VB_PATH_CAP - 1
|
||
exe_buf[el] = '\0'
|
||
exe_dir: str = _DirOf(exe_buf)
|
||
stdlib.free(exe_buf)
|
||
if exe_dir is None: return None
|
||
for k in range(int(3 + 1)):
|
||
base: str = _DupStd(exe_dir)
|
||
for j in range(int(k)):
|
||
base = _Join2(base, "..")
|
||
cand: str = _Join2(base, "includes")
|
||
if _IsDir(cand) != 0: return cand
|
||
return None
|
||
|
||
|
||
def _DeriveOutputFromEntry(entry_path: str) -> str:
|
||
"""Derive the default output name from the entry file name."""
|
||
base: str = _BaseNameOf(entry_path)
|
||
if base is None: return "app.exe"
|
||
n: sizet = string.strlen(base)
|
||
cut: sizet = n
|
||
for i in range(int(n)):
|
||
if base[i] == '.':
|
||
cut = i
|
||
break
|
||
head: str = stdlib.malloc(cut + 5)
|
||
if head is None: return "app.exe"
|
||
string.strncpy(head, base, cut)
|
||
head[cut] = '.'
|
||
head[cut + 1] = 'e'
|
||
head[cut + 2] = 'x'
|
||
head[cut + 3] = 'e'
|
||
head[cut + 4] = '\0'
|
||
return head
|
||
|
||
|
||
def _FinalizeCfg(entry_path: str) -> int:
|
||
"""Fill defaults, validate and write back into Config; 0=success."""
|
||
global _c_source, _c_buildroot, _c_includes, _c_compiler, _c_linker
|
||
global _c_static_libs, _c_dynamic_libs, _c_output, _c_triple, _c_datalayout
|
||
global _c_selfmachine
|
||
|
||
if _c_has_project == 0:
|
||
VLogger.error("viperbuild: no vb.Project(...) definition found in the dependency closure", "viperbuild")
|
||
return 1
|
||
if _c_has_build == 0:
|
||
VLogger.error("viperbuild: proj.build(...) was not called, cannot determine the build target", "viperbuild")
|
||
return 1
|
||
|
||
# ---- Defaults ----
|
||
if _c_source is None: _c_source = _DupStd(_entry_dir)
|
||
if _c_buildroot is None: _c_buildroot = _Join2(_c_source, "__vpcbuild__")
|
||
if _c_includes is None:
|
||
probed: str = _ProbeIncludes()
|
||
if probed is None:
|
||
VLogger.error("viperbuild: could not probe an includes directory (no includes up to 0..3 levels above the exe directory)", "viperbuild")
|
||
return 1
|
||
_c_includes = probed
|
||
if _c_compiler is None: _c_compiler = _DupStd("llc -filetype=obj -relocation-model=pic")
|
||
if _c_linker is None:
|
||
# Built-in default = the verified link args from this repo's ViperC/project.vpj.
|
||
# Do NOT add -lgcc: msys64/mingw64 + lld has no libgcc and fails with "unable to find library -lgcc".
|
||
_c_linker = _DupStd("clang++ -fuse-ld=lld -lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -Wl,--stack,0x1000000 -Wl,--no-insert-timestamp -Wl,--export-all-symbols -g")
|
||
if _c_output is None: _c_output = _DeriveOutputFromEntry(entry_path)
|
||
|
||
# ---- Entry file must be under source ----
|
||
if _HasPrefix(entry_path, _c_source) == 0:
|
||
VLogger.error(f"viperbuild: entry file is not under the source directory\n entry: {entry_path}\n source: {_c_source}", "viperbuild")
|
||
return 1
|
||
|
||
# ---- Target platform ----
|
||
explicit_triple: int = 0
|
||
if _c_triple is not None or _c_datalayout is not None: explicit_triple = 1
|
||
if _c_selfmachine != 0 and explicit_triple != 0:
|
||
VLogger.error("viperbuild: build(selfmachine=True) is mutually exclusive with an explicit triple/datalayout", "viperbuild")
|
||
return 1
|
||
if explicit_triple != 0:
|
||
if _c_triple is None or _c_datalayout is None:
|
||
VLogger.error("viperbuild: build() triple and datalayout must be given as a pair", "viperbuild")
|
||
return 1
|
||
else:
|
||
# Neither given, or selfmachine=True: detect the host at runtime
|
||
if _DetectHost() != 0: return 1
|
||
|
||
# ---- Write back to Config ----
|
||
Config.ProjectName = _c_name
|
||
Config.ProjectVersion = _c_version
|
||
Config.SourceDir = _c_source
|
||
Config.BuildDir = _c_buildroot
|
||
Config.IncludesDirs = _c_includes
|
||
Config.IncludesDir = _FirstSpaceTok(_c_includes)
|
||
if Config.IncludesDir is None: Config.IncludesDir = _c_includes
|
||
Config.TargetTriple = _c_triple
|
||
Config.TargetDataLayout = _c_datalayout
|
||
Config.LinkerStaticLibs = _c_static_libs
|
||
Config.LinkerDynamicLibs = _c_dynamic_libs
|
||
Config.LinkerOutput = _c_output
|
||
Config.Sha1SliceLevel = 1
|
||
Config.StrictMode = 1
|
||
_SplitToolCmd(_c_compiler, 0)
|
||
_SplitToolCmd(_c_linker, 1)
|
||
|
||
Config.resolve_paths(_c_source)
|
||
return 0
|
||
|
||
|
||
def _SplitToolCmd(joined: str, which: int) -> None:
|
||
"""Split a space-joined tool command into Config Cmd/Flags (0=compiler, 1=linker)."""
|
||
if joined is None: return
|
||
n: sizet = string.strlen(joined)
|
||
sp: sizet = n
|
||
for i in range(int(n)):
|
||
if joined[i] == ' ':
|
||
sp = i
|
||
break
|
||
cmd: str = stdlib.malloc(sp + 1)
|
||
if cmd is not None:
|
||
string.strncpy(cmd, joined, sp)
|
||
cmd[sp] = '\0'
|
||
flags: str = None
|
||
if sp < n: flags = _DupStd(joined + sp + 1)
|
||
if which == 0:
|
||
Config.CompilerCmd = cmd
|
||
Config.CompilerFlags = flags
|
||
else:
|
||
Config.LinkerCmd = cmd
|
||
Config.LinkerFlags = flags
|
||
|
||
|
||
# Entry point
|
||
def _ResetAll() -> None:
|
||
"""Reset module state before each DiscoverAndApply call."""
|
||
global _alias, _ctor_name, _g_out_str, _applied, _entry_dir, _search_dirs
|
||
global _c_name, _c_version, _c_source, _c_buildroot, _c_includes
|
||
global _c_compiler, _c_linker, _c_static_libs, _c_dynamic_libs, _c_output, _ffi_alias
|
||
global _ffi_handles_names, _ffi_handles_rt, _ffi_handles_path
|
||
global _c_triple, _c_datalayout, _c_selfmachine, _c_has_project, _c_has_build
|
||
global _c_adv_asan, _c_adv_traceback
|
||
global _c_clean_all, _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
|
||
_g_out_str = None
|
||
_applied = 0
|
||
_entry_dir = None
|
||
_search_dirs = None
|
||
_saw_viperbuild = 0
|
||
_c_name = None
|
||
_c_version = None
|
||
_c_source = None
|
||
_c_buildroot = None
|
||
_c_includes = None
|
||
_c_compiler = None
|
||
_c_linker = None
|
||
_c_static_libs = None
|
||
_c_dynamic_libs = None
|
||
_ffi_alias = None
|
||
_ffi_handles_names = None
|
||
_ffi_handles_rt = None
|
||
_ffi_handles_path = None
|
||
_c_output = None
|
||
_c_triple = None
|
||
_c_datalayout = None
|
||
_c_selfmachine = 0
|
||
_c_has_project = 0
|
||
_c_has_build = 0
|
||
_c_adv_asan = 0
|
||
_c_adv_traceback = 0
|
||
_c_clean_all = 0
|
||
_c_clean_output = 0
|
||
_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:
|
||
"""Pass 0 for --src: discover and apply viperbuild config; 0=success, else failed."""
|
||
global _scratch, _work, _visited, _pending, _proj_vars, _entry_dir, _applied
|
||
global _search_dirs
|
||
global _ffi_handles_path, _ffi_handles_names, _ffi_handles_rt
|
||
|
||
if mb is None or entry_path is None:
|
||
VLogger.error("viperbuild: argument is null", "viperbuild")
|
||
return 1
|
||
|
||
_ResetAll()
|
||
|
||
# Entry dir (default source + closure search root)
|
||
_entry_dir = _DirOf(entry_path)
|
||
if _entry_dir is None:
|
||
VLogger.error(f"viperbuild: cannot resolve the entry file directory {entry_path}", "viperbuild")
|
||
return 1
|
||
# Bare filename (no separator) -> _DirOf returns ""; must normalize to ".", otherwise
|
||
# ResolveModuleToPath("") tries no candidate and same-dir deps (often the viperbuild config) never resolve.
|
||
if string.strlen(_entry_dir) == 0: _entry_dir = _DupStd(".")
|
||
# Closure search roots: entry dir first, then "." (the config module may live elsewhere)
|
||
_search_dirs = _ConcatStd(_DupStd(_entry_dir), _DupStd(" ."))
|
||
|
||
# Scratch pool (BFS working set; not affected by main-pool reset)
|
||
arena: bytes = stdlib.malloc(VB_SCRATCH)
|
||
if arena is None:
|
||
VLogger.error("viperbuild: scratch memory allocation failed", "viperbuild")
|
||
return 1
|
||
# Heap-allocate the manager: `_scratch` is a module-level global, so a frame-local
|
||
# `MemBuddy` temp would hand out a dangling address once this frame returns (LifetimeError).
|
||
# `hlife` is explicit so NLL frame-demotion cannot apply.
|
||
_scratch = new heap hlife memhub.MemBuddy(arena, VB_SCRATCH, 0, 0)
|
||
if _scratch is None:
|
||
VLogger.error("viperbuild: scratch pool initialization failed", "viperbuild")
|
||
return 1
|
||
|
||
_work = list[str](_scratch, 8)
|
||
_visited = list[str](_scratch, 8)
|
||
_pending = list[str](_scratch, 8)
|
||
_proj_vars = list[str](_scratch, 8)
|
||
_ffi_handles_names = list[str](_scratch, 8)
|
||
_ffi_handles_rt = list[int](_scratch, 8)
|
||
_ffi_handles_path = list[str](_scratch, 8)
|
||
if _work is None or _visited is None or _pending is None or _proj_vars is None:
|
||
VLogger.error("viperbuild: working set allocation failed", "viperbuild")
|
||
return 1
|
||
if _ffi_handles_names is None or _ffi_handles_rt is None or _ffi_handles_path is None:
|
||
VLogger.error("viperbuild: ffilib handle table allocation failed", "viperbuild")
|
||
return 1
|
||
|
||
# Keyword/operator tables must be built before the main-pool reset window (else freed -> dangling crash)
|
||
ast._init_tables(mb)
|
||
|
||
if _RunBfs(mb, entry_path) != 0: return 1
|
||
|
||
if _proj_vars.__len__() == 0:
|
||
if _saw_viperbuild == 0: VLogger.error(f"viperbuild: `import viperbuild` not found in the entry file dependency closure (entry: {entry_path}; scanned {_visited.__len__()} files)", "viperbuild")
|
||
else: VLogger.error(f"viperbuild: found `import viperbuild` but the dependency closure has no `proj = vb.Project(...)` binding (entry: {entry_path})", "viperbuild")
|
||
return 1
|
||
|
||
if _FinalizeCfg(entry_path) != 0: return 1
|
||
|
||
_applied = 1
|
||
return 0
|
||
|
||
|
||
def IsApplied() -> int:
|
||
"""Whether viperbuild config was applied in this process; 1=yes."""
|
||
return _applied
|
||
|
||
|
||
def AdvancedAsan() -> int:
|
||
"""Whether viperbuild requested ASan mode (proj.advanced.asan = True); 1=yes / 0=no."""
|
||
return _c_adv_asan
|
||
|
||
|
||
def AdvancedTraceback() -> int:
|
||
"""Whether viperbuild requested traceback mode (proj.advanced.traceback = True); 1=yes / 0=no."""
|
||
return _c_adv_traceback
|
||
|
||
|
||
def CleanAll() -> int:
|
||
"""Whether viperbuild requested a full clean (proj.clean.all: buildroot + includes); 1=yes / 0=no."""
|
||
return _c_clean_all
|
||
|
||
|
||
def CleanOutput() -> int:
|
||
"""Whether viperbuild requested cleaning the output dir (proj.clean.output); 1=yes / 0=no."""
|
||
return _c_clean_output
|
||
|
||
|
||
def CleanTemp() -> int:
|
||
"""Whether viperbuild requested cleaning the temp dir (proj.clean.temp); 1=yes / 0=no."""
|
||
return _c_clean_temp
|
||
|
||
|
||
def CleanBuildDir() -> int:
|
||
"""Whether viperbuild requested cleaning the whole build dir (proj.clean.build_dir); 1=yes / 0=no."""
|
||
return _c_clean_build_dir
|
||
|
||
|
||
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
|