275 lines
10 KiB
Plaintext
275 lines
10 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import memhub
|
||
import string
|
||
import stdlib
|
||
import viperlib
|
||
import hashlib
|
||
import w32.win32file
|
||
import w32.win32base
|
||
import w32.fileio as fileio
|
||
|
||
|
||
# ============================================================
|
||
# Utils - 工程工具函数
|
||
#
|
||
# 提供 SHA1 计算和目录清理等通用工具函数。
|
||
# ============================================================
|
||
|
||
# 全局 mbuddy 指针
|
||
_mbuddy: memhub.MemBuddy | t.CPtr
|
||
|
||
|
||
# ============================================================
|
||
# nibble_to_hex_char - 半字节转十六进制字符
|
||
# ============================================================
|
||
def nibble_to_hex_char(n: int, uppercase: int) -> t.CChar:
|
||
"""0-15 → '0'-'9'/'a'-'f'(uppercase=1 时用 'A'-'F')"""
|
||
if n < 10:
|
||
return '0' + n
|
||
if uppercase != 0:
|
||
return 'A' + (n - 10)
|
||
return 'a' + (n - 10)
|
||
|
||
|
||
# ============================================================
|
||
# bytes_to_hex - 字节序列转十六进制字符串
|
||
# ============================================================
|
||
def bytes_to_hex(pool: memhub.MemBuddy | t.CPtr,
|
||
digest: bytes,
|
||
nbytes: t.CSizeT,
|
||
uppercase: int,
|
||
use_malloc: int) -> str:
|
||
"""将 digest 前 nbytes 字节转为 2*nbytes 个十六进制字符(含 null 终止)
|
||
|
||
use_malloc=1 时用 stdlib.malloc(调用方负责 free),否则 pool.alloc。
|
||
合并自 Utils.compute_sha1 / IncludesScanner.compute_file_sha1 的重复 hex 转换。
|
||
"""
|
||
if digest is None or nbytes <= 0:
|
||
return None
|
||
hex_len: t.CSizeT = nbytes * 2 + 1
|
||
hex_buf: str = None
|
||
if use_malloc != 0:
|
||
hex_buf = stdlib.malloc(hex_len)
|
||
else:
|
||
hex_buf = pool.alloc(hex_len)
|
||
if hex_buf is None:
|
||
return None
|
||
for i in range(nbytes):
|
||
hi: int = (digest[i] >> 4) & 0xF
|
||
lo: int = digest[i] & 0xF
|
||
hex_buf[i * 2] = nibble_to_hex_char(hi, uppercase)
|
||
hex_buf[i * 2 + 1] = nibble_to_hex_char(lo, uppercase)
|
||
hex_buf[nbytes * 2] = '\0'
|
||
return hex_buf
|
||
|
||
|
||
# ============================================================
|
||
# compute_sha1 - 计算字符串的 SHA1,返回前 16 个十六进制字符
|
||
#
|
||
# 使用 includes/hashlib 库的 sha1 类计算摘要,转为十六进制字符串。
|
||
# 参考 Projectrans.py 的 compute_sha1(hashlib.sha1().hexdigest()[:16])。
|
||
# ============================================================
|
||
def compute_sha1(pool: memhub.MemBuddy | t.CPtr, content: str) -> str:
|
||
"""计算字符串的 SHA1,返回前 16 个十六进制字符(用于文件命名)"""
|
||
if content is None or pool is None:
|
||
return None
|
||
|
||
# 构造 sha1 对象(__init__ 会初始化 state/count/buf)
|
||
# ctx 在栈上分配(alloca),函数内使用完即丢弃,无需堆分配
|
||
ctx: hashlib.sha1 | t.CPtr = hashlib.sha1()
|
||
if ctx is None:
|
||
return None
|
||
|
||
# 计算摘要
|
||
ctx.update(content)
|
||
digest: bytes = pool.alloc(hashlib.SHA1_DIGEST_LEN)
|
||
if digest is None:
|
||
return None
|
||
ctx.final(digest)
|
||
|
||
# 转为十六进制字符串(取前 8 字节 = 16 个十六进制字符)
|
||
return bytes_to_hex(pool, digest, 8, 0, 0)
|
||
|
||
|
||
# ============================================================
|
||
# is_vp_or_py_at - 检查路径段是否以 .vp/.py 后缀结尾
|
||
# ============================================================
|
||
def is_vp_or_py_at(p: str, start: t.CSizeT, plen: t.CSizeT) -> int:
|
||
"""检查 p[start, start+plen) 是否以 .vp/.py 后缀结尾(1=是,0=否)"""
|
||
if p is None or plen <= 3:
|
||
return 0
|
||
base: t.CSizeT = start + plen - 3
|
||
if p[base] == '.':
|
||
if p[base + 1] == 'v' and p[base + 2] == 'p':
|
||
return 1
|
||
if p[base + 1] == 'p' and p[base + 2] == 'y':
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# JoinPath - 拼接两个路径段为 a/b
|
||
# ============================================================
|
||
def JoinPath(a: str, b: str) -> str:
|
||
"""拼接路径段为 "a/b"(stdlib.malloc 分配,调用方负责 free),失败返回 None"""
|
||
if a is None or b is None:
|
||
return None
|
||
a_len: t.CSizeT = string.strlen(a)
|
||
b_len: t.CSizeT = string.strlen(b)
|
||
total_len: t.CSizeT = a_len + b_len + 2
|
||
buf: str = stdlib.malloc(total_len)
|
||
if buf is None:
|
||
return None
|
||
viperlib.snprintf(buf, total_len, "%s/%s", a, b)
|
||
return buf
|
||
|
||
|
||
# ============================================================
|
||
# read_file_to_buf - 读取整个文件到缓冲区(null 终止)
|
||
# ============================================================
|
||
def read_file_to_buf(path: str, buf_size: t.CSizeT) -> bytes:
|
||
"""读取文件到 stdlib.malloc 缓冲区并保证 null 终止
|
||
|
||
返回缓冲区(调用方负责 free);打开失败/空文件/分配失败返回 None。
|
||
合并自 Phase2/StubMerger/IncludesScanner 的重复"打开+读取+终止"序列。
|
||
"""
|
||
if path is None or buf_size <= 0:
|
||
return None
|
||
sf: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
|
||
if sf.closed:
|
||
return None
|
||
buf: bytes = stdlib.malloc(buf_size)
|
||
if buf is None:
|
||
sf.close()
|
||
return None
|
||
br: t.CInt64T = sf.read_all(buf, buf_size)
|
||
sf.close()
|
||
if br <= 0:
|
||
stdlib.free(buf)
|
||
return None
|
||
if br < buf_size:
|
||
buf[br] = 0
|
||
else:
|
||
buf[buf_size - 1] = 0
|
||
return buf
|
||
|
||
|
||
# ============================================================
|
||
# PathToModuleName - 文件路径转模块名
|
||
# ============================================================
|
||
def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
|
||
"""将文件路径转换为模块名(stdlib.malloc 分配,调用方负责 free)
|
||
|
||
strip_includes_prefix=1 时跳过 includes/ 前缀(如 includes/ast/__init__.vp → ast)。
|
||
特殊处理 __init__.vp:pkg/__init__.vp → pkg(而非 pkg.__init__)。
|
||
合并自 StubMerger._PathToModuleName / IncludesScanner 内联模块名计算。
|
||
"""
|
||
if path is None:
|
||
return None
|
||
plen: t.CSizeT = string.strlen(path)
|
||
# 跳过 includes/ 或 includes\ 前缀(Windows 反斜杠兼容)
|
||
prefix_len: t.CSizeT = 9
|
||
path_start: t.CSizeT = 0
|
||
if strip_includes_prefix != 0 and plen > prefix_len and string.strncmp(path, "includes", 8) == 0:
|
||
psep_ch: t.CChar = path[8]
|
||
if psep_ch == '/' or psep_ch == '\\':
|
||
path_start = prefix_len
|
||
plen = plen - prefix_len
|
||
# 去掉 .vp 或 .py 后缀(.vp 为 Viper 源,.py 向后兼容)
|
||
if is_vp_or_py_at(path, path_start, plen) != 0:
|
||
plen -= 3
|
||
if plen == 0:
|
||
return None
|
||
# 检查是否以 /__init__ 或 \__init__ 结尾(包的 __init__.vp)
|
||
# 同时支持 / 和 \ 两种分隔符(TPC 写入的路径可能混合使用)
|
||
if plen > 9:
|
||
tail_off: t.CSizeT = path_start + plen - 9
|
||
sep_char: t.CChar = path[tail_off]
|
||
if (sep_char == '/' or sep_char == '\\') \
|
||
and path[tail_off + 1] == '_' and path[tail_off + 2] == '_' \
|
||
and path[tail_off + 3] == 'i' and path[tail_off + 4] == 'n' and path[tail_off + 5] == 'i' \
|
||
and path[tail_off + 6] == 't' and path[tail_off + 7] == '_' and path[tail_off + 8] == '_':
|
||
# 去掉 分隔符+__init__ 后缀,plen 就是包名部分长度
|
||
plen = tail_off - path_start
|
||
if plen == 0:
|
||
return None
|
||
# 复制并替换 / 为 .
|
||
result: str = stdlib.malloc(plen + 1)
|
||
if result is None:
|
||
return None
|
||
for ri in range(plen):
|
||
ch: t.CChar = path[path_start + ri]
|
||
if ch == '/' or ch == '\\':
|
||
result[ri] = '.'
|
||
else:
|
||
result[ri] = ch
|
||
result[plen] = '\0'
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# CleanDir - 递归删除目录中所有文件和子目录(保留 dir_path 本身)
|
||
#
|
||
# 用于 --clean 选项:清理 temp_dir 和 output_dir 中的旧文件。
|
||
# SHA1 分片目录结构(如 temp/1c/、temp/7a/)需要递归清理,
|
||
# 否则旧的截断文件(如 256KB text.ll)会被 Phase1 跳过逻辑复用。
|
||
# ============================================================
|
||
def CleanDir(dir_path: str) -> int:
|
||
"""递归删除目录中所有文件和子目录,保留 dir_path 本身,返回删除的文件数,-1 表示错误"""
|
||
if dir_path is None:
|
||
return -1
|
||
dir_len: t.CSizeT = string.strlen(dir_path)
|
||
pattern: bytes = stdlib.malloc(dir_len + 8)
|
||
if pattern is None:
|
||
return -1
|
||
viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path)
|
||
|
||
find_data: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
||
if find_data is None:
|
||
stdlib.free(pattern)
|
||
return -1
|
||
string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
||
|
||
handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data)
|
||
if handle == w32.win32base.INVALID_HANDLE_VALUE:
|
||
stdlib.free(pattern)
|
||
stdlib.free(find_data)
|
||
return 0
|
||
|
||
deleted: int = 0
|
||
while 1:
|
||
fname: str = find_data.cFileName
|
||
if fname is not None:
|
||
# 跳过 . 和 ..
|
||
if fname[0] == '.':
|
||
if fname[1] == '\0':
|
||
fname = None
|
||
elif fname[1] == '.' and fname[2] == '\0':
|
||
fname = None
|
||
if fname is not None:
|
||
full_path: bytes = JoinPath(dir_path, fname)
|
||
if full_path is not None:
|
||
# 检查是否为目录(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||
is_dir: t.CUInt32T = find_data.dwFileAttributes & w32.win32file.FILE_ATTRIBUTE_DIRECTORY
|
||
if is_dir != 0:
|
||
# 递归清理子目录中的文件
|
||
sub_deleted: int = CleanDir(full_path)
|
||
if sub_deleted > 0:
|
||
deleted += sub_deleted
|
||
# 删除空子目录(RemoveDirectoryA 要求目录为空)
|
||
w32.win32file.RemoveDirectoryA(full_path)
|
||
else:
|
||
# 删除文件
|
||
if w32.win32file.DeleteFileA(full_path) != 0:
|
||
deleted += 1
|
||
stdlib.free(full_path)
|
||
|
||
if w32.win32file.FindNextFileA(handle, find_data) == 0:
|
||
break
|
||
|
||
w32.win32file.FindClose(handle)
|
||
stdlib.free(pattern)
|
||
stdlib.free(find_data)
|
||
return deleted
|