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 LinkerStaticLibs: str = None # 显式静态链接文件(空格分隔绝对路径,从 linker.static_libs 数组) LinkerDynamicLibs: str = None # 显式动态链接文件(空格分隔绝对路径,从 linker.dynamic_libs 数组,.dll/.so) TargetTriple: str = None TargetDataLayout: str = None ProjectDir: str = None # project.vpj 所在目录(resolve_paths 设置) Sha1SliceLevel: int = 0 # SHA1 切片层数(目录分散等级) StrictMode: int = 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 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, LinkerStaticLibs, LinkerDynamicLibs 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: sizet = 8192 buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE) if buf is None: f.close() return 1 bytes_read: int64 = 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["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["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["name"] ProjectName = name_val.as_string() if name_val is not None else None ver_val: JsonValue | t.CPtr = root["version"] ProjectVersion = ver_val.as_string() if ver_val is not None else None # 读取 compiler 子对象 compiler: JsonValue | t.CPtr = root["compiler"] if compiler is not None and compiler.is_object(): cc_cmd_val: JsonValue | t.CPtr = compiler["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["flags"] CompilerFlags = _join_flags_array(cc_flags_val) # 读取 linker 子对象 linker: JsonValue | t.CPtr = root["linker"] if linker is not None and linker.is_object(): ld_cmd_val: JsonValue | t.CPtr = linker["cmd"] LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None ld_out_val: JsonValue | t.CPtr = linker["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["flags"] LinkerFlags = _join_flags_array(ld_flags_val) # 读取 linker.static_libs 数组:显式静态链接文件(.a/.obj), # 先拼接原样字符串,resolve_paths 中再基于 project_dir 解析绝对路径 sl_val: JsonValue | t.CPtr = linker["static_libs"] LinkerStaticLibs = _join_flags_array(sl_val) # 读取 linker.dynamic_libs 数组:显式动态链接文件(.dll/.so), # 同样先拼接原样字符串,resolve_paths 中基于 project_dir 解析绝对路径 dl_val: JsonValue | t.CPtr = linker["dynamic_libs"] LinkerDynamicLibs = _join_flags_array(dl_val) # 读取 target 子对象 target: JsonValue | t.CPtr = root["target"] if target is not None and target.is_object(): tgt_triple_val: JsonValue | t.CPtr = target["triple"] TargetTriple = tgt_triple_val.as_string() if tgt_triple_val is not None else None tgt_dl_val: JsonValue | t.CPtr = target["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["options"] Sha1SliceLevel = 1 StrictMode = 1 if options is not None and options.is_object(): sl: JsonValue | t.CPtr = options["sha1_slice_level"] if sl is not None and sl.is_int(): Sha1SliceLevel = sl.as_int() sm: JsonValue | t.CPtr = options["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["includes"] if includes_val is not None and includes_val.is_array(): arr_len: sizet = len(includes_val) inc_buf: bytes = stdlib.malloc(2048) if inc_buf is not None: inc_buf[0] = '\0' inc_pos: sizet = 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: sizet = 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: sizet = len(flags_val) if arr_len == 0: return None # 第一遍:计算总长度(所有 flag 长度 + 空格分隔符) total_len: sizet = 0 for i in range(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 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: sizet = 0 for i in range(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: sizet = string.strlen(s) if pos > 0: buf[pos] = ' ' pos += 1 string.strcpy(buf + pos, s) pos += slen 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 # [FIX] 命令行 --build-dir 按进程 cwd 给出的路径(如 ./TransPyV/.tpv_build2) # 已含 project_dir 前缀(./TransPyV)时不再拼接,避免双份路径 # (./TransPyV/TransPyV/.tpv_build2)。边界取到目录分隔符或串尾。 if project_dir is not None: pdl: sizet = string.strlen(project_dir) if pdl > 0 and string.strncmp(rel, project_dir, pdl) == 0: if rel[pdl] == '/' or rel[pdl] == '\\' or rel[pdl] == '\0': return rel rel_len: sizet = string.strlen(rel) dir_len: sizet = 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: sizet = 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: sizet = 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: sizet = 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: sizet = 0 out_len: sizet = 0 has_seg: int = 0 i: sizet = 0 while i <= plen: ch: char = '\0' if i < plen: ch = path[i] if ch == '/' or ch == '\\' or ch == '\0': seg_len: sizet = i - seg_start if seg_len > 0: # 单段 ".":跳过 if seg_len == 1 and path[seg_start] == '.': pass # 单段 "..":弹出上一段;但**前导 ".." 不可弹**,必须保留。 # 旧实现只判 out_len > 0 就回退,于是 "无段可弹时保留" 的那一个 # 前导 ".." 会被下一个 ".." 当成普通段吃掉: # ./VKernel/../../../includes → 缓冲反复清空 → "./includes" # (cwd 在项目目录时,语义应得 "../../includes")。这正是 # ViperOS 各子工程 import 找不到模块的根因。 # 与 Python os.path.normpath / TPC 的行为对齐。 elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.': # 末段是否已经是前导 ".."(独立一段:要么是开头,要么前面是分隔符) last_is_dotdot: int = 0 if out_len >= 2 and out[out_len - 1] == '.' and out[out_len - 2] == '.': if out_len == 2: last_is_dotdot = 1 elif out[out_len - 3] == '/' or out[out_len - 3] == '\\': last_is_dotdot = 1 if out_len > 0 and last_is_dotdot == 0: # 回退到上一个分隔符 k: sizet = 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: # 没有普通段可弹:把 ".." 当普通段追加(前导 .. 累积保留) if has_seg > 0: out[out_len] = '/' out_len += 1 out[out_len] = '.' out[out_len + 1] = '.' out_len += 2 has_seg = 1 else: # 普通段:追加分隔符 + 段 if has_seg > 0: out[out_len] = '/' out_len += 1 jj: sizet = 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: sizet = 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 global ProjectDir, LinkerStaticLibs, LinkerDynamicLibs if project_dir is None: return 1 if _mbuddy is None: return 1 ProjectDir = project_dir SourceDir = _join_path(SourceDir, project_dir) BuildDir = _join_path(BuildDir, project_dir) # TempDir = {BuildDir}/temp if BuildDir is not None: bd_len: sizet = 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: sizet = string.strlen(IncludesDirs) id_out: str = _mbuddy.alloc(id_total * 3 + 64) if id_out is not None: id_out[0] = '\0' id_pos: sizet = 0 id_i: sizet = 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: sizet = id_i while id_i < id_total and IncludesDirs[id_i] != ' ': id_i += 1 seg_len: sizet = 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: sizet = 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) # linker.static_libs 显式静态链接文件路径基于 project_dir 逐段解析为绝对路径 if LinkerStaticLibs is not None: sl_total: sizet = string.strlen(LinkerStaticLibs) sl_out: str = _mbuddy.alloc(sl_total * 3 + 64) if sl_out is not None: sl_out[0] = '\0' sl_pos: sizet = 0 sl_i: sizet = 0 while sl_i < sl_total: while sl_i < sl_total and LinkerStaticLibs[sl_i] == ' ': sl_i += 1 if sl_i >= sl_total: break sl_start: sizet = sl_i while sl_i < sl_total and LinkerStaticLibs[sl_i] != ' ': sl_i += 1 seg_len: sizet = sl_i - sl_start if seg_len > 0: seg_buf: str = _mbuddy.alloc(seg_len + 1) if seg_buf is not None: string.strncpy(seg_buf, LinkerStaticLibs + sl_start, seg_len) seg_buf[seg_len] = '\0' joined: str = _join_path(seg_buf, project_dir) if joined is not None: jl: sizet = string.strlen(joined) if sl_pos > 0: sl_out[sl_pos] = ' ' sl_pos += 1 string.strcpy(sl_out + sl_pos, joined) sl_pos += jl sl_out[sl_pos] = '\0' LinkerStaticLibs = sl_out # linker.dynamic_libs 显式动态链接文件(.dll/.so)同样基于 project_dir # 逐段解析为绝对路径 if LinkerDynamicLibs is not None: dl_total: sizet = string.strlen(LinkerDynamicLibs) dl_out: str = _mbuddy.alloc(dl_total * 3 + 64) if dl_out is not None: dl_out[0] = '\0' dl_pos: sizet = 0 dl_i: sizet = 0 while dl_i < dl_total: while dl_i < dl_total and LinkerDynamicLibs[dl_i] == ' ': dl_i += 1 if dl_i >= dl_total: break dl_start: sizet = dl_i while dl_i < dl_total and LinkerDynamicLibs[dl_i] != ' ': dl_i += 1 seg_len: sizet = dl_i - dl_start if seg_len > 0: seg_buf: str = _mbuddy.alloc(seg_len + 1) if seg_buf is not None: string.strncpy(seg_buf, LinkerDynamicLibs + dl_start, seg_len) seg_buf[seg_len] = '\0' joined: str = _join_path(seg_buf, project_dir) if joined is not None: jl: sizet = string.strlen(joined) if dl_pos > 0: dl_out[dl_pos] = ' ' dl_pos += 1 string.strcpy(dl_out + dl_pos, joined) dl_pos += jl dl_out[dl_pos] = '\0' LinkerDynamicLibs = dl_out # linker.flags 中 `-T <脚本>` 的链接脚本路径同样基于 project_dir 解析, # 否则 subprocess 按进程 cwd(TransPyC/)找 linker.ld → ld.lld 报 # "cannot find linker script linker.ld" _resolve_linker_t_paths(project_dir) return 0 def _join_path_abs(rel: str, project_dir: str) -> str: """将相对路径基于 project_dir 拼接为绝对路径(用于 -T 链接脚本等) 已为绝对路径(/ \\ 开头)或盘符形式(x:)时原样返回。 """ if rel is None: return None rel_len: sizet = string.strlen(rel) if rel_len == 0: return rel if rel[0] == '/' or rel[0] == '\\': return rel if rel_len >= 2 and rel[1] == ':': return rel dir_len: sizet = string.strlen(project_dir) if dir_len == 0: return rel total_len: sizet = 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 def _resolve_linker_t_paths(project_dir: str) -> None: """将 LinkerFlags 中 `-T <相对路径>` 的链接脚本路径解析为绝对路径 ld.lld 的 -T 参数若是相对路径,将按 subprocess 进程 cwd 解析而找不到 脚本。逐 token 扫描 LinkerFlags,遇 -T 其后 token 若非绝对路径则基于 project_dir 拼接(与 resolve_paths 对其他相对路径的处理保持一致)。 """ global LinkerFlags if LinkerFlags is None or project_dir is None: return fl_total: sizet = string.strlen(LinkerFlags) if fl_total == 0: return out: str = _mbuddy.alloc(fl_total * 2 + 512) if out is None: return out[0] = '\0' out_pos: sizet = 0 i: sizet = 0 prev_is_t: int = 0 # 前一 token 是否为 -T while i < fl_total: # 跳过前导空格 while i < fl_total and LinkerFlags[i] == ' ': i += 1 if i >= fl_total: break start: sizet = i while i < fl_total and LinkerFlags[i] != ' ': i += 1 tok_len: sizet = i - start is_t_flag: int = 0 if tok_len == 2 and LinkerFlags[start] == '-' and LinkerFlags[start + 1] == 'T': is_t_flag = 1 # 输出分隔符(前一 token 之后) if out_pos > 0: out[out_pos] = ' ' out_pos += 1 if is_t_flag: string.strcpy(out + out_pos, "-T") out_pos += 2 elif prev_is_t != 0: # -T 后的脚本路径:非绝对路径则基于 project_dir 拼接 tok_buf: str = _mbuddy.alloc(tok_len + 1) if tok_buf is not None: string.memcpy(tok_buf, LinkerFlags + start, tok_len) tok_buf[tok_len] = '\0' abs_path: str = _join_path_abs(tok_buf, project_dir) if abs_path is not None: p_len: sizet = string.strlen(abs_path) string.strcpy(out + out_pos, abs_path) out_pos += p_len else: string.memcpy(out + out_pos, LinkerFlags + start, tok_len) out_pos += tok_len else: string.memcpy(out + out_pos, LinkerFlags + start, tok_len) out_pos += tok_len else: string.memcpy(out + out_pos, LinkerFlags + start, tok_len) out_pos += tok_len prev_is_t = is_t_flag out[out_pos] = '\0' LinkerFlags = out 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: sizet = string.strlen(IncludesDir) suf_len: sizet = 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 asan_runtime_declared() -> int: """project.vpj 是否已声明 ASan 运行库(1 = 声明过) TPV 不再硬编码 clang64 的 libclang_rt 路径:clang 没有给 x86_64-w64-windows-gnu 目标出 ASan 运行库,只能借用 clang64(MSVC ABI) 的那两份(会牵进 MSVC 专有的 .drectve 链接指令),这是工程级选择, 必须由工程自己声明。 三种写法都认(和 openssl 那种「写在 flags 里」的写法兼容): * linker.static_libs 里的 .a / .dll.a * linker.dynamic_libs 里的 .dll / .so * linker.flags 里的 -L… -l:…(如 openssl 的 -lssl -lcrypto) 判定方式:以上任一处出现 "asan" 即视为已声明。 """ if LinkerStaticLibs is not None: if string.strstr(LinkerStaticLibs, "asan") is not None: return 1 if LinkerDynamicLibs is not None: if string.strstr(LinkerDynamicLibs, "asan") is not None: return 1 if LinkerFlags is not None: if string.strstr(LinkerFlags, "asan") is not None: return 1 return 0 def print_config(): """打印当前配置""" if ProjectName is not None: VLogger.info(f"project: {ProjectName} v{ProjectVersion if ProjectVersion is not None else "?"}", "config") if SourceDir is not None: VLogger.info(f"source_dir: {SourceDir}", "config") if BuildDir is not None: VLogger.info(f"build_dir: {BuildDir}", "config") if TempDir is not None: VLogger.info(f"temp_dir: {TempDir}", "config") if OutputDir is not None: VLogger.info(f"output_dir: {OutputDir}", "config") if CompilerCmd is not None: VLogger.info(f"compiler: {CompilerCmd}", "config") if LinkerCmd is not None: VLogger.info(f"linker: {LinkerCmd} -> {LinkerOutput if LinkerOutput is not None else "?"}", "config") if IncludesDir is not None: VLogger.info(f"includes: {IncludesDir}", "config") VLogger.info(f"sha1_slice_level: {Sha1SliceLevel}", "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: sizet = sizet(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