Files
TransPyV/App/lib/Projectrans/Config.py
2026-07-26 20:33:17 +08:00

432 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_buildtemp 和 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/../includesOS 解析为 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}/tempOutputDir = {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