Files
ViperC/App/lib/Projectrans/Config.vp
T
2026-08-06 18:08:09 +08:00

488 lines
17 KiB
Plaintext
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:
return 1
if _mbuddy is None:
return 1
# 打开文件
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
if f is None:
return 1
if f.closed:
return 1
# 分配读取缓冲区
CFG_BUF_SIZE: t.CSizeT = 8192
buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE)
if buf is None:
f.close()
return 1
bytes_read: t.CInt64T = f.read_all(buf, CFG_BUF_SIZE)
f.close()
if bytes_read <= 0:
return 1
# 解析 JSON
root: JsonValue | t.CPtr = json_parse(_mbuddy, buf)
if root is None:
return 1
if not root.is_object():
return 1
# 读取顶层字段(使用显式 __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
拼接结果经 _normalize_path 规范化:./Test/../includes → ./includes,
消除 .. 段,保证路径显示干净且相对寻址正确。
"""
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 _normalize_path(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 _normalize_path(buf)
def _normalize_path(path: str) -> str:
"""规范化路径:解析 . 和 .. 段,合并多余分隔符
例: ./test/BuddyTest/../../includes → ./includes
./test/BuddyTest/App → ./test/BuddyTest/App
绝对路径(/ 或 \\ 开头,含盘符 C:)保持不变。
返回 _mbuddy 分配的字符串;失败时返回原路径。
"""
if path is None:
return None
plen: t.CSizeT = string.strlen(path)
if plen == 0:
return path
# 绝对路径(/ 或 \ 开头,或盘符如 C:)不规范化
if path[0] == '/' or path[0] == '\\':
return path
if plen >= 2 and path[1] == ':':
return path
# 输出缓冲(最坏情况 = 输入长度 + 可能的前缀 ./)
out: str = _mbuddy.alloc(plen + 3)
if out is None:
return path
seg_start: t.CSizeT = 0
out_len: t.CSizeT = 0
has_seg: int = 0
i: t.CSizeT = 0
while i <= plen:
ch: t.CChar = '\0'
if i < plen:
ch = path[i]
if ch == '/' or ch == '\\' or ch == '\0':
seg_len: t.CSizeT = i - seg_start
if seg_len > 0:
# 单段 ".":跳过
if seg_len == 1 and path[seg_start] == '.':
pass
# 单段 "..":弹出上一段(无段可弹时保留,如 ../includes)
elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.':
if out_len > 0:
# 回退到上一个分隔符
k: t.CSizeT = out_len
while k > 0 and out[k - 1] != '/' and out[k - 1] != '\\':
k -= 1
if k > 0:
out_len = k - 1
else:
out_len = 0
# 仍有剩余段时保持分隔符逻辑
if out_len > 0:
has_seg = 1
else:
has_seg = 0
else:
# 无段可弹:保留 ..
out[0] = '.'
out[1] = '.'
out_len = 2
has_seg = 1
else:
# 普通段:追加分隔符 + 段
if has_seg > 0:
out[out_len] = '/'
out_len += 1
jj: t.CSizeT = 0
while jj < seg_len:
out[out_len] = path[seg_start + jj]
out_len += 1
jj += 1
has_seg = 1
seg_start = i + 1
i += 1
if has_seg == 0:
# 全部段被消掉(如 ./..):返回当前目录
return "./"
# 相对路径补回 ./ 前缀(与原路径风格一致;以 .. 开头的除外)
if out[0] != '.' and out[0] != '/':
# 右移 2 位留出 "./"(从后往前拷贝,避免覆盖源字符)
mv: t.CSizeT = out_len
while mv > 0:
mv -= 1
out[mv + 2] = out[mv]
out[0] = '.'
out[1] = '/'
out_len += 2
out[out_len] = '\0'
return out
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