729 lines
30 KiB
Plaintext
729 lines
30 KiB
Plaintext
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: rawptr[memhub.MemManager]
|
||
|
||
# 配置值(从 project.vpj 加载);默认 None/0:未加载时字段为 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 混链)
|
||
_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: rawptr[fileio.File] = 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 字符作为子目录:0=不切片, 1=b7/, 2=b7/90/)
|
||
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,
|
||
消除 .. 段,保证路径显示干净且相对寻址正确。
|
||
|
||
含 %VAR% 的路径(如 repl.vpj 的 build_dir = %temp%/viperepl/.tpv_build)
|
||
先经 fileio 路径层展开为实路径:TempDir/OutputDir 等由 BuildDir 派生,
|
||
且后续消费者不止文件打开(ensure_dir 直接调 CreateDirectoryA、
|
||
collect_obj_files 调 FindFirstFileA、命令串走 CreateProcessA),
|
||
故展开必须发生在**配置路径设置层**而不是逐个调用点。
|
||
"""
|
||
if rel is None:
|
||
return None
|
||
# 环境变量展开:仅在含 '%' 时生效(fileio.ExpandEnvironmentPath 自带
|
||
# "失败/无 '%' 返回 None" 契约),展开失败时保持原串不变
|
||
ep: str = fileio.ExpandEnvironmentPath(rel)
|
||
if ep is not None:
|
||
rel = ep
|
||
# 绝对路径(以 / 或 \ 开头)保持不变
|
||
if rel[0] == '/' or rel[0] == '\\':
|
||
return rel
|
||
# 路径已含 project_dir 前缀时不再拼接,避免双份路径(./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
|
||
# 单段 "..":弹出上一段;前导 ".." 无段可弹时必须保留(对齐 Python os.path.normpath)
|
||
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 解析,否则 ld.lld 按进程 cwd 找不到脚本
|
||
_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 的 2 字符作为子目录名(level=0 不切片, 1→"b7", 2→"b7/90")
|
||
# Args: 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 |