1246 lines
47 KiB
Plaintext
1246 lines
47 KiB
Plaintext
"""ViperBuild —— `viperbuild` 内建库的编译器侧实现(`--src` Pass 0 配置发现)
|
||
|
||
定位:
|
||
`viperbuild` 是编译器内建的**编译期宏库**(与 `t`/`c`/`ctraits` 同级),
|
||
不产生任何运行时代码。编译器在入口文件的**依赖闭包**内识别
|
||
`import viperbuild as vb` / `from viperbuild import Project`,并据此提取
|
||
构建配置(源目录 / 构建根目录 / 编译器 / 链接器 / 目标三元组等)。
|
||
|
||
本模块只负责「配置面」:
|
||
① BFS 展开入口文件的依赖闭包(工程本地模块;解析不到的模块视为 stdlib 跳过);
|
||
② 解析每个文件顶层语句,提取 `vb.Project(...)` / `proj.<字段>=...` /
|
||
`proj.config(...)` / `proj.build(...)`;
|
||
③ 常量求值:字符串字面量、名字引用(`define`/模块级常量)、`+` 拼接、
|
||
`list[str]` 字面量与 `*展开`;
|
||
④ 填充默认值并写回 `Config` 全局字段;
|
||
⑤ 全程 fail-fast:没找到 / 找到但坏,一律报错终止,绝不静默兜底。
|
||
|
||
约束(与 v5 设计一致):
|
||
· 一个依赖闭包内**只允许一个** Project 对象,出现第二个即 fatal。
|
||
· Project 是宏类型,**禁止读取**(当右值用即 fatal)。
|
||
· `source` 缺省 = 入口文件所在目录;显式指定时入口文件必须位于其下。
|
||
· `buildroot` 缺省 = `source + "/.tpv_build"`。
|
||
· `includes` 缺省 = 从 exe 所在目录向上 0..3 层探测 `includes` 目录,命中即用。
|
||
· `build()` 显式 triple/datalayout 与 `selfmachine=True` 互斥(同时给 fatal);
|
||
二者都未给时等价 `selfmachine=True`。
|
||
|
||
内存纪律:
|
||
· AST / lexer / token 等瞬时数据从主 MemBuddy 池分配,**每文件 `reset_to(mark)`**;
|
||
· BFS 工作集(worklist / visited / 模块名 / 常量表)从本模块私有 scratch 池分配,
|
||
不受 `reset_to` 影响;
|
||
· 最终写回 `Config` 的字符串一律 `stdlib.malloc` 深拷贝,跨池存活。
|
||
"""
|
||
import t, c
|
||
from stdint import *
|
||
from w32.win32base import *
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import ast
|
||
import w32.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
|
||
|
||
# kernel32!GetEnvironmentVariableA —— 本模块自声明(与 includes/traceback.vp 同范式;
|
||
# win32base.vp 未声明它,kernel32 反正会链上),仅用于 selfmachine 运行时探测本机架构。
|
||
# DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize);
|
||
extern def GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: CHARPTR, nSize: DWORD) -> DWORD
|
||
|
||
# 路径/源码缓冲上限(与 main.vp 的 SRC_BUF_SIZE 保持一致口径)
|
||
define VB_PATH_CAP = 1024
|
||
define VB_SRC_CAP = 1048576
|
||
# scratch 池大小(BFS 工作集 + 常量表;不参与主池 reset)
|
||
define VB_SCRATCH = 8388608
|
||
# visited 上限(防御成环,闭包规模远超即 fatal)
|
||
define VB_MAX_VISIT = 4096
|
||
# 列表常量的元素分隔符:换行符的**字符码**(源码路径 / 编译 flag 均不含换行)。
|
||
# 注意必须存字符码再 char() 转换——Viper 中 'c' 字面量是**单字符字符串**(i8*),
|
||
# 直接 define 成 '\n' 会得到 i8*,赋给 char 槽位触发"指针隐式转整数已禁止"。
|
||
define VB_SEP_CODE = 10
|
||
|
||
# ============================================================
|
||
# 模块状态
|
||
# ============================================================
|
||
_scratch: memhub.MemBuddy* = None
|
||
_entry_dir: str = None # 入口文件所在目录(source 缺省值;裸文件名时规整成 ".")
|
||
_search_dirs: str = None # 闭包搜索根(空格分隔):入口目录 优先,其次当前目录 "."
|
||
_work: list[str]* = None # BFS 待处理文件路径
|
||
_visited: list[str]* = None # 已处理文件路径
|
||
_pending: list[str]* = None # 单文件收集到的 import 模块名(BFS 待解析)
|
||
_proj_vars: list[str]* = None # 绑定了 vb.Project(...) 实例的变量名
|
||
_alias: str = None # viperbuild 的本地别名(import viperbuild as vb → "vb")
|
||
_ctor_name: str = None # from viperbuild import Project as P → "P"
|
||
|
||
# 单文件常量表(仅在该文件扫描期间有效,池分配)
|
||
_f_str_names: list[str]* = None
|
||
_f_str_vals: list[str]* = None
|
||
_f_list_names: list[str]* = None
|
||
_f_list_vals: list[str]* = None
|
||
|
||
# 常量求值输出槽
|
||
_g_out_str: str = None
|
||
|
||
# 提取出的配置(字符串一律 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
|
||
|
||
# 闭包内是否出现过 `import viperbuild` / `from viperbuild import ...`(仅用于诊断区分)
|
||
_saw_viperbuild: int = 0
|
||
|
||
# 是否已成功应用(供 main.vp 判断用 viperbuild 的 linker.output)
|
||
_applied: int = 0
|
||
|
||
|
||
# ============================================================
|
||
# 基础工具
|
||
# ============================================================
|
||
def _DupStd(s: str) -> str:
|
||
"""stdlib.malloc 深拷贝(跨 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:
|
||
"""scratch 池深拷贝(不受主池 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:
|
||
"""a + "/" + b(a 为空返回 b)"""
|
||
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:
|
||
"""返回 path 的目录(不含结尾分隔符);无分隔符返回 stdlib 分配的 """""
|
||
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:
|
||
"""返回 path 的文件名(含扩展名)"""
|
||
if path is None: return None
|
||
n: sizet = string.strlen(path)
|
||
start: sizet = 0
|
||
i: sizet = 0
|
||
while i < n:
|
||
ch: char = path[i]
|
||
if ch == '/' or ch == '\\': start = i + 1
|
||
i += 1
|
||
return _DupStd(path + start)
|
||
|
||
|
||
def _IsDir(path: str) -> int:
|
||
"""路径是否为已存在目录(1=是)"""
|
||
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:
|
||
"""s 是否以 prefix 开头(1=是;prefix 为 None/空/"." 视为真)
|
||
|
||
两侧各自剥掉一个前导 "./",避免 `--src App/main.vp` 与 `proj.source="./App"`
|
||
因写法差异被判成"入口不在 source 之下";prefix 为 "." 时(入口是裸文件名,
|
||
source 规整成当前目录)恒真。
|
||
"""
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# 常量表
|
||
# ============================================================
|
||
def _FindInList(names: list[str]*, name: str) -> int:
|
||
"""在 names 中线性查找 name,返回下标;未找到返回 -1"""
|
||
if names is None or name is None: return -1
|
||
n: sizet = names.__len__()
|
||
i: sizet = 0
|
||
while i < n:
|
||
s: str = names.get(i)
|
||
if s is not None:
|
||
if string.strcmp(s, name) == 0: return int(i)
|
||
i += 1
|
||
return -1
|
||
|
||
|
||
def _LookupStrConst(name: str) -> int:
|
||
"""名字 → 字符串常量(写 _g_out_str);0=失败"""
|
||
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:
|
||
"""名字 → 列表常量(按 VB_SEP 拆分后写入 out);0=失败"""
|
||
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:
|
||
"""把 VB_SEP_CODE 连接的串拆成元素写入 out(空串表示空列表)"""
|
||
if out is None or joined is None: return 0
|
||
n: sizet = string.strlen(joined)
|
||
if n == 0: return 1
|
||
start: sizet = 0
|
||
i: sizet = 0
|
||
while i <= n:
|
||
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
|
||
i += 1
|
||
return 1
|
||
|
||
|
||
def _JoinSep(lst: list[str]*) -> str:
|
||
"""list[str] → VB_SEP_CODE 连接串(池分配;空列表返回 None)"""
|
||
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 = _scratch.alloc(total + 1)
|
||
if buf is None: return None
|
||
pos: sizet = 0
|
||
i = 0
|
||
while i < 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
|
||
i += 1
|
||
buf[pos] = '\0'
|
||
return buf
|
||
|
||
|
||
def _JoinSpace(lst: list[str]*) -> str:
|
||
"""list[str] → 空格连接串(stdlib 分配;空列表返回 None)"""
|
||
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
|
||
i = 0
|
||
while i < 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
|
||
i += 1
|
||
buf[pos] = '\0'
|
||
return buf
|
||
|
||
|
||
def _FirstSpaceTok(joined: str) -> str:
|
||
"""取空格分隔串的首个 token(stdlib 分配);空串/None 返回 None"""
|
||
if joined is None: return None
|
||
n: sizet = string.strlen(joined)
|
||
if n == 0: return None
|
||
sp: sizet = n
|
||
i: sizet = 0
|
||
while i < n:
|
||
if joined[i] == ' ':
|
||
sp = i
|
||
break
|
||
i += 1
|
||
seg: str = stdlib.malloc(sp + 1)
|
||
if seg is None: return None
|
||
string.strncpy(seg, joined, sp)
|
||
seg[sp] = '\0'
|
||
return seg
|
||
|
||
|
||
# ============================================================
|
||
# 常量表达式求值
|
||
# ============================================================
|
||
def _EvalStr(node: ast.AST*) -> int:
|
||
"""字符串常量表达式求值(结果写 _g_out_str);0=失败
|
||
|
||
支持:字符串字面量、名字引用(同文件先定义的 str 常量 / define 常量)、
|
||
`+` 拼接(左/右递归)。
|
||
"""
|
||
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:
|
||
"""字符串拼接(stdlib 分配)"""
|
||
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:
|
||
"""list[str] 常量表达式求值(元素写入 out);0=失败
|
||
|
||
支持:list 字面量(元素为 str 常量表达式)、`*名字` 展开(引用 list 常量)、
|
||
`+` 拼接(左/右递归)。
|
||
"""
|
||
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__()
|
||
i: sizet = 0
|
||
while i < n:
|
||
e: ast.AST* = lst.elts.get(i)
|
||
if e is None: return 0
|
||
if e.kind() == ast.ASTKind.Starred:
|
||
st: ast.Starred* = ast.Starred*(e)
|
||
if st is None or st.value is None: return 0
|
||
if _EvalList(st.value, out) == 0: return 0
|
||
else:
|
||
if _EvalStr(e) == 0: return 0
|
||
out.append(_DupScratch(_g_out_str))
|
||
i += 1
|
||
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
|
||
|
||
|
||
def _EvalBool(node: ast.AST*) -> int:
|
||
"""布尔常量求值;-1=非布尔常量"""
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# 宏引用检测(禁止读取)
|
||
# ============================================================
|
||
def _IsProjRef(node: ast.AST*) -> int:
|
||
"""节点是否直接引用 Project 宏对象(Name 或 Attribute 的 base);1=是"""
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# 单文件扫描
|
||
# ============================================================
|
||
def _ResetFileTables(pool: memhub.MemBuddy*) -> int:
|
||
"""重建单文件常量表(池分配)"""
|
||
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:
|
||
"""把待解析模块名记入 BFS 队列(scratch 池深拷贝)"""
|
||
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:
|
||
"""处理 `import X [as Y]`:识别 viperbuild,其余模块登记入 BFS 队列"""
|
||
global _alias, _saw_viperbuild
|
||
if stmt is None or stmt.names is None: return 0
|
||
n: sizet = stmt.names.__len__()
|
||
i: sizet = 0
|
||
while i < 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")
|
||
else: _AddPending(al.name)
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _HandleImportFrom(stmt: ast.ImportFrom*) -> int:
|
||
"""处理 `from X import Y`:识别 `from viperbuild import Project`,其余登记 BFS"""
|
||
global _ctor_name, _saw_viperbuild
|
||
if stmt is None: return 0
|
||
if stmt.level != 0:
|
||
# 相对导入不参与工程本地闭包展开(无包上下文,跳过)
|
||
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__()
|
||
i: sizet = 0
|
||
while i < 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")
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _IsProjectCtor(vnode: ast.AST*) -> int:
|
||
"""判断表达式是否为 `vb.Project(...)` / `Project(...)`;1=是"""
|
||
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:
|
||
"""`vb.Project("Test", "1.0.0")` / `vb.Project(name=, version=)` → _c_name / _c_version
|
||
|
||
位置参数与关键字参数都支持(位置在前:args[0]=name, args[1]=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(...) 位置参数最多 2 个(name, version)", "viperbuild")
|
||
return 1
|
||
if an >= 1:
|
||
if _EvalStr(cl.args.get(0)) == 0:
|
||
VLogger.error("viperbuild: Project(name) 必须是字符串常量", "viperbuild")
|
||
return 1
|
||
_c_name = _DupStd(_g_out_str)
|
||
if an >= 2:
|
||
if _EvalStr(cl.args.get(1)) == 0:
|
||
VLogger.error("viperbuild: Project(version) 必须是字符串常量", "viperbuild")
|
||
return 1
|
||
_c_version = _DupStd(_g_out_str)
|
||
if cl.keywords is None: return 0
|
||
n: sizet = cl.keywords.__len__()
|
||
i: sizet = 0
|
||
while i < 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=...) 必须是字符串常量", "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=...) 必须是字符串常量", "viperbuild")
|
||
return 1
|
||
_c_version = _DupStd(_g_out_str)
|
||
else:
|
||
VLogger.error(f"viperbuild: Project(...) 不支持的关键字 {kw.arg}", "viperbuild")
|
||
return 1
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _SetField(fname: str, vnode: ast.AST*) -> int:
|
||
"""`proj.<fname> = <vnode>` / `config(<fname>=<vnode>)`;0=成功"""
|
||
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
|
||
# ---- 字符串字段 ----
|
||
if string.strcmp(fname, "source") == 0:
|
||
if _EvalStr(vnode) == 0:
|
||
VLogger.error("viperbuild: source 必须是字符串常量表达式", "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 必须是字符串常量表达式", "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 必须是字符串常量表达式", "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 必须是字符串常量表达式", "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 必须是字符串常量表达式", "viperbuild")
|
||
return 1
|
||
_c_version = _DupStd(_g_out_str)
|
||
return 0
|
||
# ---- 列表字段 ----
|
||
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: 未知字段 proj.{fname}", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _SetListField(slot: int, vnode: ast.AST*) -> int:
|
||
"""列表字段赋值(slot: 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[str] 常量表达式", "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:
|
||
"""处理赋值语句:常量定义 / `proj = vb.Project(...)` / `proj.<字段> = ...`"""
|
||
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:
|
||
"""处理注解赋值(含 `define NAME = <字面量>` 脱糖出的 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:
|
||
"""`NAME = <value>`:Project 实例绑定 / 字符串常量 / 列表常量"""
|
||
global _c_has_project
|
||
if tgt is None or vnode is None or tgt.id is None: return 0
|
||
# 右侧读取 Project 宏对象 ⇒ fatal(禁止读取)
|
||
if _IsProjRef(vnode) != 0:
|
||
VLogger.error(f"viperbuild: 禁止读取 Project 宏对象({tgt.id} = proj ...)", "viperbuild")
|
||
return 1
|
||
# Project 实例绑定
|
||
if _IsProjectCtor(vnode) != 0:
|
||
if _c_has_project != 0:
|
||
VLogger.error("viperbuild: 一个依赖闭包内只允许一个 Project 对象", "viperbuild")
|
||
return 1
|
||
_c_has_project = 1
|
||
_proj_vars.append(_DupScratch(tgt.id))
|
||
cl: ast.Call* = ast.Call*(vnode)
|
||
return _ApplyCtorArgs(cl)
|
||
# 字符串常量
|
||
if _EvalStr(vnode) != 0:
|
||
_f_str_names.append(tgt.id)
|
||
_f_str_vals.append(_DupScratch(_g_out_str))
|
||
return 0
|
||
# 列表常量
|
||
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 _HandleAttrAssign(tgt: ast.Attribute*, vnode: ast.AST*) -> int:
|
||
"""`proj.<字段> = <常量表达式>`"""
|
||
if tgt is None or tgt.attr is None or tgt.value is None: return 0
|
||
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: 禁止读取 Project 宏对象", "viperbuild")
|
||
return 1
|
||
return _SetField(tgt.attr, vnode)
|
||
|
||
|
||
def _HandleConfigCall(cl: ast.Call*) -> int:
|
||
"""`proj.config(kw=...)`"""
|
||
if cl is None or cl.keywords is None: return 0
|
||
n: sizet = cl.keywords.__len__()
|
||
i: sizet = 0
|
||
while i < 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
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _HandleBuildCall(cl: ast.Call*) -> int:
|
||
"""`proj.build(triple=, datalayout=, selfmachine=)`"""
|
||
global _c_triple, _c_datalayout, _c_selfmachine, _c_has_build
|
||
if cl is None: return 1
|
||
if _c_has_build != 0:
|
||
VLogger.error("viperbuild: proj.build() 只能调用一次", "viperbuild")
|
||
return 1
|
||
_c_has_build = 1
|
||
if cl.keywords is None: return 0
|
||
n: sizet = cl.keywords.__len__()
|
||
i: sizet = 0
|
||
while i < 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=...) 必须是字符串常量", "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=...) 必须是字符串常量", "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=...) 必须是布尔常量", "viperbuild")
|
||
return 1
|
||
_c_selfmachine = b
|
||
else:
|
||
VLogger.error(f"viperbuild: build(...) 不支持的关键字 {kw.arg}", "viperbuild")
|
||
return 1
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _CallArgsHaveProjRef(cl: ast.Call*) -> int:
|
||
"""调用的实参/关键字里是否引用了 Project 宏对象(读即 fatal);1=是"""
|
||
if cl is None: return 0
|
||
if cl.args is not None:
|
||
n: sizet = cl.args.__len__()
|
||
i: sizet = 0
|
||
while i < n:
|
||
if _IsProjRef(cl.args.get(i)) != 0: return 1
|
||
i += 1
|
||
if cl.keywords is not None:
|
||
n = cl.keywords.__len__()
|
||
i = 0
|
||
while i < 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
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _HandleExprStmt(stmt: ast.Expr*) -> int:
|
||
"""表达式语句:识别 `proj.config(...)` / `proj.build(...)`;其余检查读取"""
|
||
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
|
||
# 非 Project 方法调用:若实参里读了宏对象 ⇒ fatal(禁止读取)
|
||
if cl.func.kind() != ast.ASTKind.Attribute:
|
||
if _CallArgsHaveProjRef(cl) != 0:
|
||
VLogger.error("viperbuild: 禁止读取 Project 宏对象", "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: 禁止读取 Project 宏对象", "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: 禁止读取 Project 宏对象", "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: Project 对象不支持的方法 {at.attr}()", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _ScanFile(pool: memhub.MemBuddy*, path: str) -> int:
|
||
"""解析并扫描单个 .vp 文件;0=成功,1=失败(已报错),2=可跳过(不可读/非 .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:
|
||
# 非 .vp:不参与闭包(.vpi/.vpnci 对编译器不可见)
|
||
return 2
|
||
|
||
src_buf: bytes = Utils.read_file_to_buf(path, VB_SRC_CAP)
|
||
if src_buf is None:
|
||
VLogger.warning(f"viperbuild: 无法读取依赖文件 {path}", "viperbuild")
|
||
return 2
|
||
|
||
if _ResetFileTables(pool) != 0:
|
||
stdlib.free(src_buf)
|
||
VLogger.error("viperbuild: 常量表分配失败", "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 分配失败", "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: 词法失败 {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: 语法失败 {path}", "viperbuild")
|
||
return 1
|
||
|
||
body: list[ast.AST*]* = tree.children
|
||
rc: int = 0
|
||
if body is not None:
|
||
cn: sizet = len(body)
|
||
ci: sizet = 0
|
||
while ci < 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: rc = _HandleAssign(ast.Assign*(child))
|
||
elif kd == ast.ASTKind.AnnAssign: rc = _HandleAnnAssign(ast.AnnAssign*(child))
|
||
elif kd == ast.ASTKind.Expr: rc = _HandleExprStmt(ast.Expr*(child))
|
||
if rc != 0: break
|
||
ci += 1
|
||
|
||
pool.reset_to(mark)
|
||
stdlib.free(src_buf)
|
||
return rc
|
||
|
||
|
||
# ============================================================
|
||
# 依赖闭包 BFS
|
||
# ============================================================
|
||
def _ResolveModuleToFile(module_name: str) -> str:
|
||
"""把模块名解析为工程本地文件路径(搜索根 = 入口文件所在目录);未命中返回 None
|
||
|
||
输出槽契约与 StubMergerBuild 一致:槽为 8 字节缓冲,写侧 sizet(ptr)、
|
||
读侧 rawptr(slot[0]);ResolveModuleToPath 返回的 abs/rel/inc 串由调用方 free。
|
||
"""
|
||
if module_name is None or _search_dirs is None: return None
|
||
if string.strlen(module_name) == 0: return None
|
||
out_abs: t.CPtr = stdlib.malloc(8)
|
||
out_rel: t.CPtr = stdlib.malloc(8)
|
||
out_inc: t.CPtr = 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:
|
||
"""把文件路径加入 BFS 队列(worklist/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:
|
||
"""从入口文件出发展开依赖闭包;0=成功"""
|
||
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: 依赖闭包超过 {VB_MAX_VISIT} 个文件,疑似成环", "viperbuild")
|
||
return 1
|
||
_visited.append(_DupScratch(cur))
|
||
|
||
_pending = list[str](_scratch, 8)
|
||
if _pending is None:
|
||
VLogger.error("viperbuild: BFS 队列分配失败", "viperbuild")
|
||
return 1
|
||
rc: int = _ScanFile(pool, cur)
|
||
if rc == 1: return 1
|
||
# 解析本文件收集到的模块名
|
||
pn: sizet = _pending.__len__()
|
||
pi: sizet = 0
|
||
while pi < pn:
|
||
mod: str = _pending.get(pi)
|
||
if mod is not None:
|
||
dep: str = _ResolveModuleToFile(mod)
|
||
if dep is not None: _Enqueue(dep)
|
||
pi += 1
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# 默认值 / 校验 / 应用
|
||
# ============================================================
|
||
def _DetectHost() -> int:
|
||
"""运行时探测本机 triple / datalayout(写 _c_triple / _c_datalayout);0=成功
|
||
|
||
读 PROCESSOR_ARCHITECTURE 环境变量判定本机架构。当前仅支持 AMD64
|
||
(编译器自身的目标平台 x86_64-pc-windows-gnu);其余架构 fail-fast。
|
||
"""
|
||
global _c_triple, _c_datalayout
|
||
buf: CHARPTR = stdlib.malloc(64)
|
||
if buf is None:
|
||
VLogger.error("viperbuild: selfmachine 探测缓冲分配失败", "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 探测失败(PROCESSOR_ARCHITECTURE 不可用)", "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 暂不支持本机架构(当前仅支持 AMD64)", "viperbuild")
|
||
return 1
|
||
|
||
|
||
def _ProbeIncludes() -> str:
|
||
"""按 exe 所在目录向上 0..3 层探测 includes 目录;命中返回绝对路径,否则 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
|
||
k: int = 0
|
||
while k <= 3:
|
||
base: str = _DupStd(exe_dir)
|
||
j: int = 0
|
||
while j < k:
|
||
base = _Join2(base, "..")
|
||
j += 1
|
||
cand: str = _Join2(base, "includes")
|
||
if _IsDir(cand) != 0: return cand
|
||
k += 1
|
||
return None
|
||
|
||
|
||
def _DeriveOutputFromEntry(entry_path: str) -> str:
|
||
"""从入口文件名推导默认输出名(去扩展名 + ".exe")"""
|
||
base: str = _BaseNameOf(entry_path)
|
||
if base is None: return "app.exe"
|
||
n: sizet = string.strlen(base)
|
||
cut: sizet = n
|
||
i: sizet = 0
|
||
while i < n:
|
||
if base[i] == '.':
|
||
cut = i
|
||
break
|
||
i += 1
|
||
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:
|
||
"""填充默认值 + 校验 + 写回 Config;0=成功"""
|
||
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: 依赖闭包内未找到 vb.Project(...) 定义", "viperbuild")
|
||
return 1
|
||
if _c_has_build == 0:
|
||
VLogger.error("viperbuild: 未调用 proj.build(...),无法确定构建目标", "viperbuild")
|
||
return 1
|
||
|
||
# ---- 默认值 ----
|
||
if _c_source is None: _c_source = _DupStd(_entry_dir)
|
||
if _c_buildroot is None: _c_buildroot = _Join2(_c_source, ".tpv_build")
|
||
if _c_includes is None:
|
||
probed: str = _ProbeIncludes()
|
||
if probed is None:
|
||
VLogger.error("viperbuild: 未能探测到 includes 目录(exe 目录向上 0..3 层均无 includes)", "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:
|
||
# 内置默认 = 本仓 TransPyV/project.vpj 那份**已验证可用**的链接参数。
|
||
# 注意:不要带 -lgcc —— msys64/mingw64 + lld 下没有 libgcc,
|
||
# 会以 "lld: error: unable to find library -lgcc" 直接链接失败。
|
||
_c_linker = _DupStd("clang++ -fuse-ld=lld -lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -Wl,--allow-multiple-definition -Wl,--stack,0x1000000 -Wl,--no-insert-timestamp -Wl,--export-all-symbols -g")
|
||
if _c_output is None: _c_output = _DeriveOutputFromEntry(entry_path)
|
||
|
||
# ---- 入口文件必须位于 source 之下 ----
|
||
if _HasPrefix(entry_path, _c_source) == 0:
|
||
VLogger.error(f"viperbuild: 入口文件不在 source 目录之下\n 入口: {entry_path}\n source: {_c_source}", "viperbuild")
|
||
return 1
|
||
|
||
# ---- 目标平台 ----
|
||
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) 与显式 triple/datalayout 互斥", "viperbuild")
|
||
return 1
|
||
if explicit_triple != 0:
|
||
if _c_triple is None or _c_datalayout is None:
|
||
VLogger.error("viperbuild: build() 的 triple 与 datalayout 必须成对给出", "viperbuild")
|
||
return 1
|
||
else:
|
||
# 都未给 或 selfmachine=True ⇒ 运行时探测本机
|
||
if _DetectHost() != 0: return 1
|
||
|
||
# ---- 写回 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:
|
||
"""把 `["cmd", flag...]` 空格连接串拆成 Config 的 Cmd/Flags(which: 0=compiler 1=linker)"""
|
||
if joined is None: return
|
||
n: sizet = string.strlen(joined)
|
||
sp: sizet = n
|
||
i: sizet = 0
|
||
while i < n:
|
||
if joined[i] == ' ':
|
||
sp = i
|
||
break
|
||
i += 1
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# 入口
|
||
# ============================================================
|
||
def _ResetAll() -> None:
|
||
"""重置模块状态(每次 DiscoverAndApply 调用前)"""
|
||
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
|
||
global _c_triple, _c_datalayout, _c_selfmachine, _c_has_project, _c_has_build
|
||
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
|
||
_c_output = None
|
||
_c_triple = None
|
||
_c_datalayout = None
|
||
_c_selfmachine = 0
|
||
_c_has_project = 0
|
||
_c_has_build = 0
|
||
|
||
|
||
def DiscoverAndApply(mb: memhub.MemBuddy*, entry_path: str) -> int:
|
||
"""`--src` Pass 0:发现并应用 viperbuild 配置;0=成功,非 0=失败(已报错)
|
||
|
||
Args:
|
||
mb: 主 MemBuddy(AST 用;每文件 reset_to 回收)
|
||
entry_path: 入口 .vp 文件路径(--src)
|
||
|
||
Returns:
|
||
0 成功(Config 已填充);1 失败(fail-fast,调用方须终止编译)
|
||
"""
|
||
global _scratch, _work, _visited, _pending, _proj_vars, _entry_dir, _applied
|
||
global _search_dirs
|
||
|
||
if mb is None or entry_path is None:
|
||
VLogger.error("viperbuild: 参数为空", "viperbuild")
|
||
return 1
|
||
|
||
_ResetAll()
|
||
|
||
# 入口目录(source 缺省值 + 闭包搜索根)
|
||
_entry_dir = _DirOf(entry_path)
|
||
if _entry_dir is None:
|
||
VLogger.error(f"viperbuild: 无法解析入口文件目录 {entry_path}", "viperbuild")
|
||
return 1
|
||
# 裸文件名(无目录分隔符)→ _DirOf 返回空串;必须规整成 ".",否则
|
||
# ResolveModuleToPath("") 一个候选目录都不会尝试,入口同目录的依赖
|
||
# (往往是 viperbuild 配置所在模块)永远解析不到 ⇒ 误报"未找到 import viperbuild"。
|
||
if string.strlen(_entry_dir) == 0: _entry_dir = _DupStd(".")
|
||
# 闭包搜索根:入口目录优先,其次当前目录 "."(配置模块可能不在入口目录内)
|
||
_search_dirs = _ConcatStd(_DupStd(_entry_dir), _DupStd(" ."))
|
||
|
||
# scratch 池(BFS 工作集,不参与主池 reset)
|
||
arena: bytes = stdlib.malloc(VB_SCRATCH)
|
||
if arena is None:
|
||
VLogger.error("viperbuild: scratch 内存分配失败", "viperbuild")
|
||
return 1
|
||
_scratch = memhub.MemBuddy(arena, VB_SCRATCH, 0, 0)
|
||
if _scratch is None:
|
||
VLogger.error("viperbuild: scratch 池初始化失败", "viperbuild")
|
||
return 1
|
||
|
||
_work = list[str](_scratch, 8)
|
||
_visited = list[str](_scratch, 8)
|
||
_pending = list[str](_scratch, 8)
|
||
_proj_vars = list[str](_scratch, 8)
|
||
if _work is None or _visited is None or _pending is None or _proj_vars is None:
|
||
VLogger.error("viperbuild: 工作集分配失败", "viperbuild")
|
||
return 1
|
||
|
||
# 关键字/操作符表须在主池 reset 窗口之前建立(否则被回收 → 悬空崩溃)
|
||
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`(入口: {entry_path};已扫描 {_visited.__len__()} 个文件)", "viperbuild")
|
||
else: VLogger.error(f"viperbuild: 找到 `import viperbuild` 但依赖闭包内没有 `proj = vb.Project(...)` 绑定(入口: {entry_path})", "viperbuild")
|
||
return 1
|
||
|
||
if _FinalizeCfg(entry_path) != 0: return 1
|
||
|
||
_applied = 1
|
||
return 0
|
||
|
||
|
||
def IsApplied() -> int:
|
||
"""本次进程是否已成功应用 viperbuild 配置(供 main.vp 判断 linker.output 来源)"""
|
||
return _applied
|