610 lines
28 KiB
Plaintext
610 lines
28 KiB
Plaintext
from stdint import *
|
|
import memhub
|
|
import string
|
|
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 win32.fileio as fileio
|
|
|
|
|
|
# Config - project configuration
|
|
|
|
# Global mbuddy pointer
|
|
_mbuddy: memhub.MemManager*
|
|
|
|
# Config values (loaded from project.vpj); default None/0: callers must guard with `is not None` (else garbage pointer crash)
|
|
SourceDir: str = None
|
|
BuildDir: str = None
|
|
TempDir: str = None # = {BuildDir}/temp (auto-computed)
|
|
OutputDir: str = None # = {BuildDir}/output (auto-computed)
|
|
ProjectName: str = None
|
|
ProjectVersion: str = None
|
|
CompilerCmd: str = None
|
|
CompilerFlags: str = None # compiler flags (space-separated, joined from compiler.flags array)
|
|
LinkerCmd: str = None
|
|
LinkerFlags: str = None # linker flags (space-separated, joined from linker.flags array)
|
|
LinkerOutput: str = None
|
|
LinkerStaticLibs: str = None # explicit static libs (space-separated abs paths, from linker.static_libs array)
|
|
LinkerDynamicLibs: str = None # explicit dynamic libs (space-separated abs paths, from linker.dynamic_libs array, .dll/.so)
|
|
TargetTriple: str = None
|
|
TargetDataLayout: str = None
|
|
ProjectDir: str = None # directory containing project.vpj (set by resolve_paths)
|
|
Sha1SliceLevel: int = 0 # SHA1 slice level (directory dispersion depth)
|
|
StrictMode: int = 0
|
|
IncludesDir: str = None # includes dir path (first element of includes array, single-dir compat)
|
|
IncludesDirs: str = None # all includes dirs (space-separated, multi-dir support)
|
|
Qt6Path: str = None # runtime.qt6_path: Qt6 install root (optional)
|
|
Qt6Dlls: str = None # runtime.qt6_dlls joined (space-separated DLL names; optional)
|
|
Qt6Plugins: str = None # runtime.qt6_plugins joined (space-separated paths relative to <qt6_path>/plugins; optional)
|
|
|
|
# includes prebuilt cache dir suffix (asan mode uses .binary_asan to isolate instrumented obj)
|
|
_inc_bin_suffix: str = ".binary"
|
|
|
|
# Emission backend: single-track MLIR (since 2026-10-03).
|
|
# The tree now uses includes/mlirlite; BuildPipeline always runs mlir-opt -> mlir-translate to lower back to LLVM IR.
|
|
|
|
|
|
def Load_project_config(path: str) -> int:
|
|
"""Load the project config file (project.vpj); 0=success, non-zero=failure."""
|
|
global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion
|
|
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs, LinkerDynamicLibs
|
|
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
|
|
global IncludesDir, IncludesDirs
|
|
global Qt6Path, Qt6Dlls, Qt6Plugins
|
|
|
|
if path is None: return 1
|
|
if _mbuddy is None: return 1
|
|
|
|
# Open file
|
|
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
|
if f is None: return 1
|
|
if f.closed: return 1
|
|
|
|
# Allocate read buffer
|
|
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
|
|
|
|
# Parse JSON
|
|
root: JsonValue* = json_parse(_mbuddy, buf)
|
|
if root is None: return 1
|
|
if not root.is_object(): return 1
|
|
|
|
# Read top-level fields (explicit __getitem__ calls for old compiler binaries)
|
|
sd_val: JsonValue* = root["source_dir"]
|
|
SourceDir = sd_val.as_string() if sd_val is not None else None
|
|
# build_dir: unified build dir (default ./__vpcbuild__); temp and output are subdirs
|
|
bd_val: JsonValue* = root["build_dir"]
|
|
if bd_val is not None and bd_val.is_string(): BuildDir = bd_val.as_string()
|
|
else: BuildDir = "./__vpcbuild__"
|
|
name_val: JsonValue* = root["name"]
|
|
ProjectName = name_val.as_string() if name_val is not None else None
|
|
ver_val: JsonValue* = root["version"]
|
|
ProjectVersion = ver_val.as_string() if ver_val is not None else None
|
|
|
|
# Read compiler sub-object
|
|
compiler: JsonValue* = root["compiler"]
|
|
if compiler is not None and compiler.is_object():
|
|
cc_cmd_val: JsonValue* = compiler["cmd"]
|
|
CompilerCmd = cc_cmd_val.as_string() if cc_cmd_val is not None else None
|
|
# Read compiler.flags array, join into a space-separated string
|
|
cc_flags_val: JsonValue* = compiler["flags"]
|
|
CompilerFlags = _join_flags_array(cc_flags_val)
|
|
|
|
# Read linker sub-object
|
|
linker: JsonValue* = root["linker"]
|
|
if linker is not None and linker.is_object():
|
|
ld_cmd_val: JsonValue* = linker["cmd"]
|
|
LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None
|
|
ld_out_val: JsonValue* = linker["output"]
|
|
LinkerOutput = ld_out_val.as_string() if ld_out_val is not None else None
|
|
# Read linker.flags array, join into a space-separated string
|
|
ld_flags_val: JsonValue* = linker["flags"]
|
|
LinkerFlags = _join_flags_array(ld_flags_val)
|
|
# mingw-w64:同时链接 -lmsvcrt 与 -lucrt 时,GNU ld 会从两个 import 库各拉出一个都定义
|
|
# __imp_<name> 的成员(现场:__imp_ldexp 同时出现在 libmsvcrt 与
|
|
# libapi-ms-win-crt-math),直接报 "multiple definition"。lld 按需解析 import 库不会冲突,
|
|
# 仓库默认链接命令也一直是 lld;项目配置漏写 -fuse-ld 时在这里补齐,避免逐个测试改配置。
|
|
if LinkerCmd is not None and LinkerFlags is not None and string.strstr(LinkerCmd, "clang") is not None:
|
|
if string.strstr(LinkerFlags, "-fuse-ld") is None and string.strstr(LinkerFlags, "-lucrt") is not None:
|
|
lf_cap: sizet = string.strlen(LinkerFlags) + 32
|
|
lf_new: str = _mbuddy.alloc(lf_cap)
|
|
if lf_new is not None:
|
|
viperlib.snprintf(lf_new, lf_cap, "-fuse-ld=lld %s", LinkerFlags)
|
|
LinkerFlags = lf_new
|
|
# Read linker.static_libs array: explicit static libs (.a/.obj); joined raw here,
|
|
# then resolved to abs paths against project_dir in resolve_paths.
|
|
sl_val: JsonValue* = linker["static_libs"]
|
|
LinkerStaticLibs = _join_flags_array(sl_val)
|
|
# Read linker.dynamic_libs array: explicit dynamic libs (.dll/.so); joined raw here,
|
|
# then resolved to abs paths against project_dir in resolve_paths.
|
|
dl_val: JsonValue* = linker["dynamic_libs"]
|
|
LinkerDynamicLibs = _join_flags_array(dl_val)
|
|
|
|
# Read target sub-object
|
|
target: JsonValue* = root["target"]
|
|
if target is not None and target.is_object():
|
|
tgt_triple_val: JsonValue* = target["triple"]
|
|
TargetTriple = tgt_triple_val.as_string() if tgt_triple_val is not None else None
|
|
tgt_dl_val: JsonValue* = target["datalayout"]
|
|
TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None
|
|
|
|
# Read options sub-object
|
|
# Sha1SliceLevel: SHA1 slice level (default 1; each level uses 2 SHA1 chars: 0=none, 1=b7/, 2=b7/90/)
|
|
options: JsonValue* = root["options"]
|
|
Sha1SliceLevel = 1
|
|
StrictMode = 1
|
|
if options is not None and options.is_object():
|
|
sl: JsonValue* = options["sha1_slice_level"]
|
|
if sl is not None and sl.is_int(): Sha1SliceLevel = int(sl.as_int())
|
|
sm: JsonValue* = options["strict_mode"]
|
|
if sm is not None and sm.is_bool(): StrictMode = 1 if sm.as_bool() else 0
|
|
|
|
# Read includes array (multi-dir: first element -> IncludesDir, all joined -> IncludesDirs)
|
|
includes_val: JsonValue* = 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(int(arr_len)):
|
|
inc_item: JsonValue* = 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
|
|
# Append into IncludesDirs (space-separated)
|
|
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
|
|
|
|
# Read runtime sub-object (optional): declared runtime deps deployed next to the exe.
|
|
# qt6_dlls / qt6_plugins join via _join_flags_array (space-separated, same口径 as linker.static_libs).
|
|
runtime: JsonValue* = root["runtime"]
|
|
if runtime is not None and runtime.is_object():
|
|
qp_val: JsonValue* = runtime["qt6_path"]
|
|
Qt6Path = qp_val.as_string() if qp_val is not None else None
|
|
qd_val: JsonValue* = runtime["qt6_dlls"]
|
|
Qt6Dlls = _join_flags_array(qd_val)
|
|
qpl_val: JsonValue* = runtime["qt6_plugins"]
|
|
Qt6Plugins = _join_flags_array(qpl_val)
|
|
|
|
return 0
|
|
|
|
|
|
def _join_flags_array(flags_val: JsonValue*) -> str:
|
|
"""Join a JSON string array into a space-separated string; None if empty/invalid."""
|
|
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
|
|
|
|
# First pass: compute total length (all flags + space separators)
|
|
total_len: sizet = 0
|
|
for i in range(int(arr_len)):
|
|
item: JsonValue* = 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
|
|
|
|
# Allocate buffer (total_len includes a trailing space per flag; last one becomes '\0')
|
|
buf: str = _mbuddy.alloc(total_len + 1)
|
|
if buf is None: return None
|
|
|
|
# Second pass: join (flags separated by spaces)
|
|
pos: sizet = 0
|
|
for i in range(int(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:
|
|
"""Join a relative path with project_dir, expanding env vars and normalizing it."""
|
|
if rel is None: return None
|
|
# Env var expansion: only when '%' present (fileio.ExpandEnvironmentPath returns None on
|
|
# failure/no '%'); on failure keep the original string unchanged.
|
|
ep: str = fileio.ExpandEnvironmentPath(rel)
|
|
if ep is not None: rel = ep
|
|
# Absolute paths (starting with / or \) stay unchanged
|
|
if rel[0] == '/' or rel[0] == '\\': return rel
|
|
# Skip joining when rel already has the project_dir prefix (avoids double paths); boundary at separator or end
|
|
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)
|
|
|
|
# Handle ../ or ..\ prefix: join 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)
|
|
|
|
# Handle ./ or .\ prefix: join 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 # not starting with ./: keep unchanged
|
|
else:
|
|
return rel # not starting with .: keep unchanged
|
|
|
|
if dir_len == 0:
|
|
# project_dir empty (current dir): just strip the ./ prefix
|
|
return rel + rel_off
|
|
# Join: 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 # allocation failed: return original path
|
|
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off)
|
|
return _normalize_path(buf)
|
|
|
|
|
|
def _normalize_path(path: str) -> str:
|
|
"""Normalize a path by resolving . and .. segments; absolute paths pass through."""
|
|
if path is None: return None
|
|
plen: sizet = string.strlen(path)
|
|
if plen == 0: return path
|
|
# Absolute paths (/ or \ start, or drive letter like C:) are not normalized
|
|
if path[0] == '/' or path[0] == '\\': return path
|
|
if plen >= 2 and path[1] == ':': return path
|
|
# Output buffer (worst case = input length + possible ./ prefix)
|
|
out: str = _mbuddy.alloc(plen + 3)
|
|
if out is None: return path
|
|
seg_start: sizet = 0
|
|
out_len: sizet = 0
|
|
has_seg: int = 0
|
|
for i in range(int(plen + 1)):
|
|
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:
|
|
# Single "." segment: skip
|
|
if seg_len == 1 and path[seg_start] == '.': pass
|
|
# Single ".." segment: pop previous; leading ".." with nothing to pop must be kept (like Python normpath)
|
|
elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.':
|
|
# Whether the last segment is already a leading ".." (standalone: at start or preceded by a separator)
|
|
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:
|
|
# Back up to the previous separator
|
|
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
|
|
# Keep separator logic when segments remain
|
|
if out_len > 0: has_seg = 1
|
|
else: has_seg = 0
|
|
else:
|
|
# No normal segment to pop: append ".." as a normal segment (keep leading ..)
|
|
if has_seg > 0:
|
|
out[out_len] = '/'
|
|
out_len += 1
|
|
out[out_len] = '.'
|
|
out[out_len + 1] = '.'
|
|
out_len += 2
|
|
has_seg = 1
|
|
else:
|
|
# Normal segment: append separator + segment
|
|
if has_seg > 0:
|
|
out[out_len] = '/'
|
|
out_len += 1
|
|
for jj in range(int(seg_len)):
|
|
out[out_len] = path[seg_start + jj]
|
|
out_len += 1
|
|
has_seg = 1
|
|
seg_start = i + 1
|
|
if has_seg == 0:
|
|
# All segments consumed (e.g. ./..): return current dir
|
|
return "./"
|
|
# Re-add ./ prefix for relative paths (matches original style; except those starting with ..)
|
|
if out[0] != '.' and out[0] != '/':
|
|
# Shift right by 2 to make room for "./" (copy back-to-front to avoid overwriting)
|
|
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:
|
|
"""Resolve and normalize project paths (src/build/includes); 0=success."""
|
|
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; OutputDir = {BuildDir}/output
|
|
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 paths may be of the form ../includes, resolved against project_dir.
|
|
# Normalize IncludesDir (first element) and IncludesDirs (all, space-separated) item by item.
|
|
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 explicit paths resolved segment-by-segment to abs paths against 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 explicit files (.dll/.so) likewise resolved to abs paths against 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
|
|
# -T <script> paths in linker.flags also resolved against project_dir, else ld.lld misses them
|
|
_resolve_linker_t_paths(project_dir)
|
|
return 0
|
|
|
|
|
|
def _join_path_abs(rel: str, project_dir: str) -> str:
|
|
"""Join a relative path under project_dir into an absolute path; absolutes unchanged."""
|
|
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:
|
|
"""Resolve relative `-T <script>` paths in LinkerFlags against project_dir."""
|
|
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 # whether the previous token is -T
|
|
while i < fl_total:
|
|
# Skip leading spaces
|
|
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
|
|
# Output separator (after the previous 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:
|
|
# Script path after -T: join against project_dir if not absolute
|
|
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:
|
|
"""Return the includes binary cache dir (includes dir + cache and MLIR suffixes)."""
|
|
if IncludesDir is None: return None
|
|
inc_len: sizet = string.strlen(IncludesDir)
|
|
suf_len: sizet = string.strlen(_inc_bin_suffix)
|
|
be_suf: str = mlir_dir_suffix()
|
|
be_suf_len: sizet = string.strlen(be_suf)
|
|
buf: str = _mbuddy.alloc(inc_len + suf_len + be_suf_len + 1)
|
|
if buf is None: return None
|
|
viperlib.snprintf(buf, inc_len + suf_len + be_suf_len + 1, "%s%s%s", IncludesDir, _inc_bin_suffix, be_suf)
|
|
return buf
|
|
|
|
|
|
# Directory isolation suffix for the includes binary cache only.
|
|
# TempDir/OutputDir are plain {BuildDir}/temp and {BuildDir}/output (same layout as TPC and as
|
|
# the project scripts expect). The includes cache keeps "_mlir": it holds MLIR-era artifacts
|
|
# (sha1 subdirectories) while TPC's includes cache at {IncludesDir}.binary/ holds flat
|
|
# "<sha1>.<triple>.pic.obj" files, so sharing one directory would mix the two layouts.
|
|
def mlir_dir_suffix() -> str:
|
|
"""Directory isolation suffix for the includes cache of the MLIR backend (always "_mlir")."""
|
|
return "_mlir"
|
|
|
|
|
|
def asan_runtime_declared() -> int:
|
|
"""Whether project.vpj declares an ASan runtime; 1=declared."""
|
|
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():
|
|
"""Print the current configuration."""
|
|
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 - use 2 SHA1 chars per level as subdir names (level=0 none, 1->"b7", 2->"b7/90").
|
|
# Args: sha1 (>= 2*level chars) / level. Returns mbuddy-allocated subdir path, or None if level<=0.
|
|
def slice_subdir(sha1: str, level: int) -> str:
|
|
"""Return the SHA1 slice subdir path for the given level."""
|
|
if level <= 0 or sha1 is None: return None
|
|
if _mbuddy is None: return None
|
|
# 2 chars per level + separators; no trailing separator, then null
|
|
# buf size: 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
|
|
for i in range(int(level)):
|
|
if i > 0:
|
|
buf[pos] = '/'
|
|
pos += 1
|
|
idx: int = int(i * 2)
|
|
buf[pos] = sha1[idx]
|
|
buf[pos + 1] = sha1[idx + 1]
|
|
pos += 2
|
|
buf[pos] = '\0'
|
|
return buf |