Files

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