432 lines
15 KiB
Python
432 lines
15 KiB
Python
import t, c
|
||
from stdint import *
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import lib.core.VLogger as VLogger
|
||
from json.__parser import parse as json_parse
|
||
from json import JsonValue, JSON_OBJECT, JSON_INT
|
||
import w32.fileio as fileio
|
||
|
||
|
||
# ============================================================
|
||
# Config - 工程配置
|
||
# ============================================================
|
||
|
||
# 全局 mbuddy 指针
|
||
_mbuddy: memhub.MemManager | t.CPtr
|
||
|
||
# 配置值(从 project.vpj 加载)
|
||
SourceDir: str
|
||
BuildDir: str
|
||
TempDir: str # = {BuildDir}/temp(自动计算)
|
||
OutputDir: str # = {BuildDir}/output(自动计算)
|
||
ProjectName: str
|
||
ProjectVersion: str
|
||
CompilerCmd: str
|
||
CompilerFlags: str # 编译器参数(空格分隔,从 compiler.flags 数组拼接)
|
||
LinkerCmd: str
|
||
LinkerFlags: str # 链接器参数(空格分隔,从 linker.flags 数组拼接)
|
||
LinkerOutput: str
|
||
TargetTriple: str
|
||
TargetDataLayout: str
|
||
Sha1SliceLevel: t.CInt # SHA1 切片层数(目录分散等级)
|
||
StrictMode: t.CInt
|
||
IncludesDir: str # includes 目录路径(从 includes 数组首元素)
|
||
|
||
|
||
def Load_project_config(path: str) -> int:
|
||
"""加载工程配置文件(project.vpj)
|
||
|
||
Args:
|
||
path: 配置文件路径
|
||
|
||
Returns:
|
||
0 表示成功,非 0 表示失败
|
||
"""
|
||
global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion
|
||
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput
|
||
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
|
||
global IncludesDir
|
||
|
||
if path is None:
|
||
stdio.printf("[CFG] FAIL: path is None\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
if _mbuddy is None:
|
||
stdio.printf("[CFG] FAIL: _mbuddy is None\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
|
||
# 打开文件
|
||
stdio.printf("[CFG] before File ctor path=%s\n", path)
|
||
stdio.fflush(0)
|
||
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
|
||
f_closed_flag: int = 1
|
||
if f is not None:
|
||
if f.closed:
|
||
f_closed_flag = 1
|
||
else:
|
||
f_closed_flag = 0
|
||
stdio.printf("[CFG] after File ctor f=%p closed=%d\n", f, f_closed_flag)
|
||
stdio.fflush(0)
|
||
if f is None:
|
||
stdio.printf("[CFG] FAIL: File ctor returned None\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
if f.closed:
|
||
stdio.printf("[CFG] FAIL: f.closed is True\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
|
||
# 分配读取缓冲区
|
||
CFG_BUF_SIZE: t.CSizeT = 8192
|
||
buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE)
|
||
if buf is None:
|
||
stdio.printf("[CFG] FAIL: alloc buf failed\n")
|
||
stdio.fflush(0)
|
||
f.close()
|
||
return 1
|
||
|
||
stdio.printf("[CFG] before read_all\n")
|
||
stdio.fflush(0)
|
||
bytes_read: t.CInt64T = f.read_all(buf, CFG_BUF_SIZE)
|
||
f.close()
|
||
stdio.printf("[CFG] after read_all bytes_read=%d\n", bytes_read)
|
||
stdio.fflush(0)
|
||
if bytes_read <= 0:
|
||
stdio.printf("[CFG] FAIL: bytes_read <= 0\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
|
||
# 解析 JSON
|
||
stdio.printf("[CFG] before json_parse\n")
|
||
stdio.fflush(0)
|
||
root: JsonValue | t.CPtr = json_parse(_mbuddy, buf)
|
||
stdio.printf("[CFG] after json_parse root=%p\n", root)
|
||
stdio.fflush(0)
|
||
if root is None:
|
||
stdio.printf("[CFG] FAIL: json_parse returned None\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
if not root.is_object():
|
||
stdio.printf("[CFG] FAIL: root is not object\n")
|
||
stdio.fflush(0)
|
||
return 1
|
||
stdio.printf("[CFG] root is object OK\n")
|
||
stdio.fflush(0)
|
||
|
||
# 读取顶层字段(使用显式 __getitem__ 调用,兼容旧编译器二进制)
|
||
sd_val: JsonValue | t.CPtr = root.__getitem__("source_dir")
|
||
SourceDir = sd_val.as_string() if sd_val is not None else None
|
||
# build_dir: 统一构建目录(默认 ./.tpv_build),temp 和 output 作为其子目录
|
||
bd_val: JsonValue | t.CPtr = root.__getitem__("build_dir")
|
||
if bd_val is not None and bd_val.is_string():
|
||
BuildDir = bd_val.as_string()
|
||
else:
|
||
BuildDir = "./.tpv_build"
|
||
name_val: JsonValue | t.CPtr = root.__getitem__("name")
|
||
ProjectName = name_val.as_string() if name_val is not None else None
|
||
ver_val: JsonValue | t.CPtr = root.__getitem__("version")
|
||
ProjectVersion = ver_val.as_string() if ver_val is not None else None
|
||
|
||
# 读取 compiler 子对象
|
||
compiler: JsonValue | t.CPtr = root.__getitem__("compiler")
|
||
if compiler is not None and compiler.is_object():
|
||
cc_cmd_val: JsonValue | t.CPtr = compiler.__getitem__("cmd")
|
||
CompilerCmd = cc_cmd_val.as_string() if cc_cmd_val is not None else None
|
||
# 读取 compiler.flags 数组,拼接成空格分隔的字符串
|
||
cc_flags_val: JsonValue | t.CPtr = compiler.__getitem__("flags")
|
||
CompilerFlags = _join_flags_array(cc_flags_val)
|
||
|
||
# 读取 linker 子对象
|
||
linker: JsonValue | t.CPtr = root.__getitem__("linker")
|
||
if linker is not None and linker.is_object():
|
||
ld_cmd_val: JsonValue | t.CPtr = linker.__getitem__("cmd")
|
||
LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None
|
||
ld_out_val: JsonValue | t.CPtr = linker.__getitem__("output")
|
||
LinkerOutput = ld_out_val.as_string() if ld_out_val is not None else None
|
||
# 读取 linker.flags 数组,拼接成空格分隔的字符串
|
||
ld_flags_val: JsonValue | t.CPtr = linker.__getitem__("flags")
|
||
LinkerFlags = _join_flags_array(ld_flags_val)
|
||
|
||
# 读取 target 子对象
|
||
target: JsonValue | t.CPtr = root.__getitem__("target")
|
||
if target is not None and target.is_object():
|
||
tgt_triple_val: JsonValue | t.CPtr = target.__getitem__("triple")
|
||
TargetTriple = tgt_triple_val.as_string() if tgt_triple_val is not None else None
|
||
tgt_dl_val: JsonValue | t.CPtr = target.__getitem__("datalayout")
|
||
TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None
|
||
|
||
# 读取 options 子对象
|
||
# Sha1SliceLevel: SHA1 切片层数(默认 1,每层取 SHA1 前 2 字符作为子目录)
|
||
# level=0 → 不切片,文件直接放 base_dir
|
||
# level=1 → b7/{sha1}.ext
|
||
# level=2 → b7/90/{sha1}.ext
|
||
options: JsonValue | t.CPtr = root.__getitem__("options")
|
||
Sha1SliceLevel = 1
|
||
StrictMode = 1
|
||
if options is not None and options.is_object():
|
||
sl: JsonValue | t.CPtr = options.__getitem__("sha1_slice_level")
|
||
if sl is not None and sl.is_int():
|
||
Sha1SliceLevel = sl.as_int()
|
||
sm: JsonValue | t.CPtr = options.__getitem__("strict_mode")
|
||
if sm is not None and sm.is_bool():
|
||
StrictMode = 1 if sm.as_bool() else 0
|
||
|
||
# 读取 includes 数组(取首个元素作为 includes 目录)
|
||
includes_val: JsonValue | t.CPtr = root.__getitem__("includes")
|
||
if includes_val is not None and includes_val.is_array():
|
||
arr_len: t.CSizeT = includes_val.__len__()
|
||
if arr_len > 0:
|
||
first_inc: JsonValue | t.CPtr = includes_val.get_item(0)
|
||
if first_inc is not None and first_inc.is_string():
|
||
IncludesDir = first_inc.as_string()
|
||
|
||
return 0
|
||
|
||
|
||
def _join_flags_array(flags_val: JsonValue | t.CPtr) -> str:
|
||
"""将 JSON 字符串数组拼接成空格分隔的字符串
|
||
|
||
用于 linker.flags 和 compiler.flags 数组。
|
||
用 while + 索引遍历,避免 list 迭代器 bug。
|
||
|
||
Args:
|
||
flags_val: JsonValue,应为字符串数组
|
||
|
||
Returns:
|
||
空格分隔的字符串,或 None 如果 flags_val 为 None 或非数组或空数组
|
||
"""
|
||
if flags_val is None:
|
||
return None
|
||
if not flags_val.is_array():
|
||
return None
|
||
|
||
arr_len: t.CSizeT = flags_val.__len__()
|
||
if arr_len == 0:
|
||
return None
|
||
|
||
# 第一遍:计算总长度(所有 flag 长度 + 空格分隔符)
|
||
total_len: t.CSizeT = 0
|
||
i: t.CSizeT = 0
|
||
while i < arr_len:
|
||
item: JsonValue | t.CPtr = flags_val.get_item(i)
|
||
if item is not None and item.is_string():
|
||
s: str = item.as_string()
|
||
if s is not None:
|
||
total_len += string.strlen(s) + 1 # +1 for space
|
||
i += 1
|
||
|
||
if total_len == 0:
|
||
return None
|
||
|
||
# 分配缓冲区(total_len 已含每个 flag 后的空格,最后一个是 '\0')
|
||
buf: str = _mbuddy.alloc(total_len + 1)
|
||
if buf is None:
|
||
return None
|
||
|
||
# 第二遍:拼接(flag 之间用空格分隔)
|
||
pos: t.CSizeT = 0
|
||
i = 0
|
||
while i < arr_len:
|
||
item = flags_val.get_item(i)
|
||
if item is not None and item.is_string():
|
||
s = item.as_string()
|
||
if s is not None:
|
||
slen: t.CSizeT = string.strlen(s)
|
||
if pos > 0:
|
||
buf[pos] = ' '
|
||
pos += 1
|
||
string.strcpy(buf + pos, s)
|
||
pos += slen
|
||
i += 1
|
||
buf[pos] = '\0'
|
||
return buf
|
||
|
||
|
||
def _join_path(rel: str, project_dir: str) -> str:
|
||
"""将相对路径与 project_dir 拼接为绝对路径
|
||
|
||
- ./App + Test → Test/App
|
||
- ./temp + Test → Test/temp
|
||
- ../includes + Test → Test/../includes(OS 解析为 includes)
|
||
- 绝对路径保持不变
|
||
- None 返回 None
|
||
"""
|
||
if rel is None:
|
||
return None
|
||
# 绝对路径(以 / 或 \ 开头)保持不变
|
||
if rel[0] == '/' or rel[0] == '\\':
|
||
return rel
|
||
|
||
rel_len: t.CSizeT = string.strlen(rel)
|
||
dir_len: t.CSizeT = string.strlen(project_dir)
|
||
|
||
# 处理 ../ 或 ..\\ 前缀:拼接 project_dir/../...
|
||
if rel_len >= 3 and rel[0] == '.' and rel[1] == '.':
|
||
if rel[2] == '/' or rel[2] == '\\':
|
||
if dir_len == 0:
|
||
return rel
|
||
total_len: t.CSizeT = dir_len + 1 + rel_len + 1
|
||
buf: str = _mbuddy.alloc(total_len)
|
||
if buf is None:
|
||
return rel
|
||
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel)
|
||
return buf
|
||
|
||
# 处理 ./ 或 .\\ 前缀:拼接 project_dir + rel[2:]
|
||
rel_off: t.CSizeT = 0
|
||
if rel_len >= 2 and rel[0] == '.':
|
||
if rel[1] == '/' or rel[1] == '\\':
|
||
rel_off = 2
|
||
else:
|
||
return rel # 不是 ./ 开头,保持不变
|
||
else:
|
||
return rel # 不是 . 开头,保持不变
|
||
|
||
if dir_len == 0:
|
||
# project_dir 为空(当前目录),去掉 ./ 前缀即可
|
||
return rel + rel_off
|
||
# 拼接: project_dir + "/" + rel[rel_off:]
|
||
total_len = dir_len + 1 + (rel_len - rel_off) + 1
|
||
buf = _mbuddy.alloc(total_len)
|
||
if buf is None:
|
||
return rel # 分配失败,返回原路径
|
||
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off)
|
||
return buf
|
||
|
||
|
||
def resolve_paths(project_dir: str) -> int:
|
||
"""解析并规范化工程路径(src/build/includes)
|
||
|
||
将 project.vpj 中的相对路径(以 ./ 开头)转换为基于 project_dir 的路径。
|
||
BuildDir 解析后,TempDir = {BuildDir}/temp,OutputDir = {BuildDir}/output。
|
||
|
||
Args:
|
||
project_dir: 工程根目录(project.vpj 所在目录)
|
||
|
||
Returns:
|
||
0 表示成功,非 0 表示失败
|
||
"""
|
||
global SourceDir, BuildDir, TempDir, OutputDir, IncludesDir
|
||
if project_dir is None:
|
||
return 1
|
||
if _mbuddy is None:
|
||
return 1
|
||
|
||
SourceDir = _join_path(SourceDir, project_dir)
|
||
BuildDir = _join_path(BuildDir, project_dir)
|
||
|
||
# TempDir = {BuildDir}/temp
|
||
if BuildDir is not None:
|
||
bd_len: t.CSizeT = string.strlen(BuildDir)
|
||
temp_buf: str = _mbuddy.alloc(bd_len + 8)
|
||
if temp_buf is not None:
|
||
viperlib.snprintf(temp_buf, bd_len + 8, "%s/temp", BuildDir)
|
||
TempDir = temp_buf
|
||
# OutputDir = {BuildDir}/output
|
||
output_buf: str = _mbuddy.alloc(bd_len + 8)
|
||
if output_buf is not None:
|
||
viperlib.snprintf(output_buf, bd_len + 8, "%s/output", BuildDir)
|
||
OutputDir = output_buf
|
||
|
||
# includes 路径可能是 ../includes 形式,需要基于 project_dir 解析
|
||
if IncludesDir is not None:
|
||
IncludesDir = _join_path(IncludesDir, project_dir)
|
||
return 0
|
||
|
||
|
||
def get_includes_binary_dir() -> str:
|
||
"""获取 includes.binary 目录路径(includes 目录 + .binary 后缀)
|
||
|
||
includes 目录如 ../includes,则 includes.binary 为 ../includes.binary
|
||
"""
|
||
if IncludesDir is None:
|
||
return None
|
||
inc_len: t.CSizeT = string.strlen(IncludesDir)
|
||
buf: str = _mbuddy.alloc(inc_len + 8)
|
||
if buf is None:
|
||
return None
|
||
viperlib.snprintf(buf, inc_len + 8, "%s.binary", IncludesDir)
|
||
return buf
|
||
|
||
|
||
def print_config():
|
||
"""打印当前配置"""
|
||
buf: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||
if buf is None:
|
||
return
|
||
if ProjectName is not None:
|
||
viperlib.snprintf(buf, 1024, "project: %s v%s", ProjectName,
|
||
ProjectVersion if ProjectVersion is not None else "?")
|
||
VLogger.info(buf, "config")
|
||
if SourceDir is not None:
|
||
viperlib.snprintf(buf, 1024, "source_dir: %s", SourceDir)
|
||
VLogger.info(buf, "config")
|
||
if BuildDir is not None:
|
||
viperlib.snprintf(buf, 1024, "build_dir: %s", BuildDir)
|
||
VLogger.info(buf, "config")
|
||
if TempDir is not None:
|
||
viperlib.snprintf(buf, 1024, "temp_dir: %s", TempDir)
|
||
VLogger.info(buf, "config")
|
||
if OutputDir is not None:
|
||
viperlib.snprintf(buf, 1024, "output_dir: %s", OutputDir)
|
||
VLogger.info(buf, "config")
|
||
if CompilerCmd is not None:
|
||
viperlib.snprintf(buf, 1024, "compiler: %s", CompilerCmd)
|
||
VLogger.info(buf, "config")
|
||
if LinkerCmd is not None:
|
||
viperlib.snprintf(buf, 1024, "linker: %s -> %s", LinkerCmd,
|
||
LinkerOutput if LinkerOutput is not None else "?")
|
||
VLogger.info(buf, "config")
|
||
if IncludesDir is not None:
|
||
viperlib.snprintf(buf, 1024, "includes: %s", IncludesDir)
|
||
VLogger.info(buf, "config")
|
||
viperlib.snprintf(buf, 1024, "sha1_slice_level: %d", Sha1SliceLevel)
|
||
VLogger.info(buf, "config")
|
||
|
||
|
||
# ============================================================
|
||
# slice_subdir - 根据 SHA1 和切片层数返回子目录路径
|
||
#
|
||
# 每层取 SHA1 的 2 个字符作为子目录名:
|
||
# level=0 → None(不切片)
|
||
# level=1 → "b7"
|
||
# level=2 → "b7/90"
|
||
# level=3 → "b7/90/23"
|
||
#
|
||
# Args:
|
||
# sha1: SHA1 字符串(至少 2*level 字符)
|
||
# level: 切片层数
|
||
#
|
||
# Returns:
|
||
# 子目录路径字符串(mbuddy 分配,无需释放),level<=0 时返回 None
|
||
# ============================================================
|
||
def slice_subdir(sha1: str, level: int) -> str:
|
||
"""根据 SHA1 和切片层数返回子目录路径"""
|
||
if level <= 0 or sha1 is None:
|
||
return None
|
||
if _mbuddy is None:
|
||
return None
|
||
# 每层 2 字符 + 分隔符,最后无分隔符 + null
|
||
# buf 大小: level * 2 + (level - 1) + 1 = level * 3
|
||
buf_len: t.CSizeT = t.CSizeT(level * 3)
|
||
buf: str = _mbuddy.alloc(buf_len)
|
||
if buf is None:
|
||
return None
|
||
pos: int = 0
|
||
i: int = 0
|
||
while i < level:
|
||
if i > 0:
|
||
buf[pos] = '/'
|
||
pos += 1
|
||
idx: int = i * 2
|
||
buf[pos] = sha1[idx]
|
||
buf[pos + 1] = sha1[idx + 1]
|
||
pos += 2
|
||
i += 1
|
||
buf[pos] = '\0'
|
||
return buf |