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