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 加载) # 默认 None/0:未加载 project.vpj 时字段为 null,调用方 is not None # 检查才能正确兜底(否则垃圾指针通过检查 → 用非法路径 → 崩溃, # 尤其非工作区执行无配置时)。 SourceDir: str = None BuildDir: str = None TempDir: str = None # = {BuildDir}/temp(自动计算) OutputDir: str = None # = {BuildDir}/output(自动计算) ProjectName: str = None ProjectVersion: str = None CompilerCmd: str = None CompilerFlags: str = None # 编译器参数(空格分隔,从 compiler.flags 数组拼接) LinkerCmd: str = None LinkerFlags: str = None # 链接器参数(空格分隔,从 linker.flags 数组拼接) LinkerOutput: str = None TargetTriple: str = None TargetDataLayout: str = None Sha1SliceLevel: t.CInt = 0 # SHA1 切片层数(目录分散等级) StrictMode: t.CInt = 0 IncludesDir: str = None # includes 目录路径(从 includes 数组首元素,兼容单目录场景) IncludesDirs: str = None # 全部 includes 目录(空格分隔,多 includes 目录支持) # includes 预编译缓存目录后缀(asan 模式用 .binary_asan 隔离,避免 # asan 插桩 obj 与非 asan obj 混链:asan obj 引用 __asan_init 等符号, # 非 asan 链接不提供 asan 库 → undefined reference) _inc_bin_suffix: str = ".binary" def set_inc_bin_suffix(suffix: str) -> None: """设置 includes.binary 缓存目录后缀(如 ".binary_asan")""" global _inc_bin_suffix _inc_bin_suffix = suffix 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, IncludesDirs 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 数组(支持多目录:首元素存 IncludesDir,全部拼接存 IncludesDirs) 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__() inc_buf: bytes = stdlib.malloc(2048) if inc_buf is not None: inc_buf[0] = '\0' inc_pos: t.CSizeT = 0 for inc_i in range(arr_len): inc_item: JsonValue | t.CPtr = includes_val.get_item(inc_i) if inc_item is None or not inc_item.is_string(): continue inc_str: str = inc_item.as_string() inc_slen: t.CSizeT = string.strlen(inc_str) if inc_i == 0: IncludesDir = inc_str # 拼接进 IncludesDirs(空格分隔) if inc_pos + inc_slen + 1 < 2048: if inc_pos > 0: inc_buf[inc_pos] = ' ' inc_pos += 1 string.strcpy(inc_buf + inc_pos, inc_str) inc_pos += inc_slen inc_buf[inc_pos] = '\0' IncludesDirs = inc_buf if IncludesDir is None: IncludesDir = inc_buf 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, IncludesDirs 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 解析 # IncludesDir(首元素)与 IncludesDirs(全部,空格分隔)逐项规范化 if IncludesDirs is not None: id_total: t.CSizeT = string.strlen(IncludesDirs) id_out: str = _mbuddy.alloc(id_total * 3 + 64) if id_out is not None: id_out[0] = '\0' id_pos: t.CSizeT = 0 id_i: t.CSizeT = 0 while id_i < id_total: while id_i < id_total and IncludesDirs[id_i] == ' ': id_i += 1 if id_i >= id_total: break id_start: t.CSizeT = id_i while id_i < id_total and IncludesDirs[id_i] != ' ': id_i += 1 seg_len: t.CSizeT = id_i - id_start if seg_len > 0: seg_buf: str = _mbuddy.alloc(seg_len + 1) if seg_buf is not None: string.strncpy(seg_buf, IncludesDirs + id_start, seg_len) seg_buf[seg_len] = '\0' joined: str = _join_path(seg_buf, project_dir) if joined is not None: jl: t.CSizeT = string.strlen(joined) if id_pos > 0: id_out[id_pos] = ' ' id_pos += 1 string.strcpy(id_out + id_pos, joined) id_pos += jl id_out[id_pos] = '\0' IncludesDirs = id_out if IncludesDir is not None: IncludesDir = _join_path(IncludesDir, project_dir) return 0 def get_includes_binary_dir() -> str: """获取 includes.binary 目录路径(includes 目录 + 缓存后缀) includes 目录如 ../includes,默认返回 ../includes.binary; asan 模式(set_inc_bin_suffix(".binary_asan"))返回 ../includes.binary_asan, 隔离 asan 插桩 obj,避免与非 asan obj 混链。 """ if IncludesDir is None: return None inc_len: t.CSizeT = string.strlen(IncludesDir) suf_len: t.CSizeT = string.strlen(_inc_bin_suffix) buf: str = _mbuddy.alloc(inc_len + suf_len + 1) if buf is None: return None viperlib.snprintf(buf, inc_len + suf_len + 1, "%s%s", IncludesDir, _inc_bin_suffix) return buf def print_config(): """打印当前配置""" buf: str = 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