feat(obs): isolate __obs__ library imports in includes_obs
Each configured include root also maps to an includes_obs sibling: OBS files may only import modules that exist there, and non-OBS files may not import includes_obs-only modules. Scanning and stub resolution use the combined list, while the Gen layer enforces the direction.
This commit is contained in:
1 parent
2e5218abc6
commit
17c7e1f896
3 files changed
+132
-9
No files matched your search
@@ -35,6 +35,12 @@ 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)
|
||||
# __obs__ include roots: the same list with each `includes` component replaced by `includes_obs`.
|
||||
# OBS files may only import library modules that exist here; non-OBS files may not import them.
|
||||
IncludesDirObs: str = None
|
||||
IncludesDirsObs: str = None
|
||||
# Scan/resolve search path = IncludesDirs + IncludesDirsObs (import direction is enforced by the Gen layer).
|
||||
IncludesDirsAll: str = None
|
||||
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)
|
||||
@@ -351,9 +357,33 @@ def _normalize_path(path: str) -> str:
|
||||
return out
|
||||
|
||||
|
||||
def _obs_include_one(dir: str) -> str:
|
||||
"""`.../includes` -> `.../includes_obs`; other directories get `/includes_obs` appended."""
|
||||
if dir is None: return None
|
||||
n: sizet = string.strlen(dir)
|
||||
while n > 0 and (dir[n - 1] == '/' or dir[n - 1] == '\\'): n -= 1
|
||||
if n >= 8:
|
||||
tail: str = dir + (n - 8)
|
||||
if string.strncmp(tail, "includes", 8) == 0:
|
||||
out_obs: str = _mbuddy.alloc(n + 4)
|
||||
if out_obs is not None:
|
||||
string.strncpy(out_obs, dir, n)
|
||||
out_obs[n] = '\0'
|
||||
string.strcat(out_obs, "_obs")
|
||||
return out_obs
|
||||
out_add: str = _mbuddy.alloc(n + 16)
|
||||
if out_add is not None:
|
||||
string.strncpy(out_add, dir, n)
|
||||
out_add[n] = '\0'
|
||||
string.strcat(out_add, "/includes_obs")
|
||||
return out_add
|
||||
return None
|
||||
|
||||
|
||||
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 IncludesDirObs, IncludesDirsObs, IncludesDirsAll
|
||||
global ProjectDir, LinkerStaticLibs, LinkerDynamicLibs
|
||||
if project_dir is None: return 1
|
||||
if _mbuddy is None: return 1
|
||||
@@ -407,6 +437,44 @@ def resolve_paths(project_dir: str) -> int:
|
||||
id_out[id_pos] = '\0'
|
||||
IncludesDirs = id_out
|
||||
if IncludesDir is not None: IncludesDir = _join_path(IncludesDir, project_dir)
|
||||
# OBS include roots: `.../includes` -> `.../includes_obs` for every entry.
|
||||
if IncludesDirs is not None:
|
||||
id_all: sizet = string.strlen(IncludesDirs)
|
||||
obs_out: str = _mbuddy.alloc(id_all * 3 + 96)
|
||||
if obs_out is not None:
|
||||
obs_out[0] = '\0'
|
||||
obs_pos: sizet = 0
|
||||
oi: sizet = 0
|
||||
while oi < id_all:
|
||||
while oi < id_all and IncludesDirs[oi] == ' ': oi += 1
|
||||
if oi >= id_all: break
|
||||
o_start: sizet = oi
|
||||
while oi < id_all and IncludesDirs[oi] != ' ': oi += 1
|
||||
seg_l: sizet = oi - o_start
|
||||
if seg_l > 0:
|
||||
seg_b: str = _mbuddy.alloc(seg_l + 1)
|
||||
if seg_b is not None:
|
||||
string.strncpy(seg_b, IncludesDirs + o_start, seg_l)
|
||||
seg_b[seg_l] = '\0'
|
||||
mapped: str = _obs_include_one(seg_b)
|
||||
if mapped is not None:
|
||||
ml: sizet = string.strlen(mapped)
|
||||
if obs_pos > 0:
|
||||
obs_out[obs_pos] = ' '
|
||||
obs_pos += 1
|
||||
string.strcpy(obs_out + obs_pos, mapped)
|
||||
obs_pos += ml
|
||||
obs_out[obs_pos] = '\0'
|
||||
IncludesDirsObs = obs_out
|
||||
if IncludesDirObs is None and IncludesDir is not None: IncludesDirObs = _obs_include_one(IncludesDir)
|
||||
# Combined search path for scanning/resolution (Gen enforces which side may import what).
|
||||
if IncludesDirsObs is not None and string.strlen(IncludesDirsObs) > 0:
|
||||
all_len: sizet = string.strlen(IncludesDirs) + string.strlen(IncludesDirsObs) + 2
|
||||
all_buf: str = _mbuddy.alloc(all_len)
|
||||
if all_buf is not None:
|
||||
viperlib.snprintf(all_buf, all_len, "%s %s", IncludesDirs, IncludesDirsObs)
|
||||
IncludesDirsAll = all_buf
|
||||
if IncludesDirsAll is None: IncludesDirsAll = IncludesDirs
|
||||
# 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)
|
||||
|
||||
@@ -10,6 +10,8 @@ import stdio
|
||||
import sys
|
||||
import io
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
import lib.core.IncludesScanner as IncludesScanner
|
||||
import lib.Projectrans.Config as Config
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.core.Gen.GenApi as GenApi
|
||||
import lib.core.Gen.Translator as HT
|
||||
@@ -641,6 +643,54 @@ novirtual class ImportsGenMixin:
|
||||
else: self._report_import_error(node, "imported module SHA1 not registered (the includes scan did not find this module)")
|
||||
return 1
|
||||
|
||||
# _obs_import_check - enforce the __obs__ include split for an absolute import.
|
||||
def _obs_import_check(self, trans: HT.Translator*, module_name: str, node: ast.AST*) -> int:
|
||||
"""OBS files may only import includes_obs modules; non-OBS files may not import them."""
|
||||
if trans is None or node is None or module_name is None: return 0
|
||||
if string.strlen(module_name) == 0 or module_name[0] == '.': return 0
|
||||
in_normal: int = self._module_resolves(trans.Pool, Config.IncludesDirs, module_name)
|
||||
in_obs: int = self._module_resolves(trans.Pool, Config.IncludesDirsObs, module_name)
|
||||
if in_normal != 0 and in_obs != 0: return 0
|
||||
if trans.StrictOBS != 0:
|
||||
if in_normal != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"__obs__ files may only import library modules provided by includes_obs: '{module_name}' is not available there", "NameError")
|
||||
return 1
|
||||
else:
|
||||
if in_obs != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"module '{module_name}' is provided by includes_obs and can only be imported from an __obs__ file; add `from __future__ import __obs__`", "NameError")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
# _module_resolves - 1 when the module resolves to a file under the given include dirs.
|
||||
def _module_resolves(self, pool: memhub.MemBuddy*, dirs: str, module_name: str) -> int:
|
||||
if dirs is None or pool is None: return 0
|
||||
oa: rawptr = stdlib.malloc(8)
|
||||
orl: rawptr = stdlib.malloc(8)
|
||||
oi: rawptr = stdlib.malloc(8)
|
||||
if oa is None or orl is None or oi is None:
|
||||
if oa is not None: stdlib.free(oa)
|
||||
if orl is not None: stdlib.free(orl)
|
||||
if oi is not None: stdlib.free(oi)
|
||||
return 0
|
||||
a_s: sizet* = sizet*(oa)
|
||||
r_s: sizet* = sizet*(orl)
|
||||
i_s: sizet* = sizet*(oi)
|
||||
a_s[0] = 0
|
||||
r_s[0] = 0
|
||||
i_s[0] = 0
|
||||
rc: int = IncludesScanner.ResolveModuleToPath(dirs, module_name, oa, orl, oi)
|
||||
a_path: str = rawptr(a_s[0])
|
||||
r_path: str = rawptr(r_s[0])
|
||||
i_path: str = rawptr(i_s[0])
|
||||
if a_path is not None: stdlib.free(a_path)
|
||||
if r_path is not None: stdlib.free(r_path)
|
||||
if i_path is not None: stdlib.free(i_path)
|
||||
stdlib.free(oa)
|
||||
stdlib.free(orl)
|
||||
stdlib.free(oi)
|
||||
if rc == 0: return 1
|
||||
return 0
|
||||
|
||||
# translate_import - handle import statements and update trans._imported_modules
|
||||
def translate_import(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
"""Translate an import statement and update trans._imported_modules."""
|
||||
@@ -655,6 +705,8 @@ novirtual class ImportsGenMixin:
|
||||
for ni in range(int(nn)):
|
||||
alias: ast.Alias* = ast.Alias*(names.get(ni))
|
||||
if alias is not None and alias.name is not None:
|
||||
# OBS include split: enforce which include root may provide this module.
|
||||
if trans._declare_only == 0: self._obs_import_check(trans, alias.name, node)
|
||||
# Validate the module SHA1 at import time (fast-fail, full translation only):
|
||||
# declare_only=1/2 is the pre-registration scan and would false-positive.
|
||||
if trans._declare_only == 0: self._check_import_sha1(node, alias.name)
|
||||
@@ -678,6 +730,9 @@ novirtual class ImportsGenMixin:
|
||||
# Resolve the module name: relative imports need the package prefix
|
||||
resolved: str = self._resolve_relative_module(trans.Pool, trans.CurrentPackage, impf.level, impf.module)
|
||||
if resolved is not None:
|
||||
# OBS include split (absolute imports only; relative ones belong to the app tree).
|
||||
if trans._declare_only == 0 and impf.level == 0 and impf.module is not None:
|
||||
self._obs_import_check(trans, impf.module, node)
|
||||
# Validate the module SHA1 at import time (fast-fail, full translation only). For
|
||||
# `from . import Y` resolved is a package directory, not a file, so skip the check.
|
||||
if trans._declare_only == 0 and impf.module is not None: self._check_import_sha1(node, resolved)
|
||||
|
||||
+9
-9
@@ -38,7 +38,6 @@ import mlirlite
|
||||
import subprocess
|
||||
import lib.Phase.Phase1.Phase1Main as Phase1Main
|
||||
|
||||
|
||||
# Main Pool 1 GiB(不要简单调大:2 GiB 会让 MemBuddy 内部索引/位宽溢出 ——
|
||||
# ASan: access-violation at memhub.vp:858 _fl_push,就在 __init__ 阶段。
|
||||
# 池耗尽的正解是修泄漏:mlirlite 全局数组 dense 渲染的"每元素 ~5KB 不复用",见 includes e9e1933。)
|
||||
@@ -55,12 +54,12 @@ define SRC_BUF_SIZE = 1048576
|
||||
# Version (dev/beta/rc)
|
||||
# Fixed-size so the bytes stay inline in .PRVERSN even when this file is compiled by the
|
||||
# previous bootstrap compiler; the new `[]char` value-array rule infers the same 14 bytes.
|
||||
V_VPC_VERSION: const __attribute__(section(".PRVERSN")) [32]char = "0.0.5-dev.479"
|
||||
define V_VIPER_VERSION = "1.0.5-dev.970 2026-09-28 Non-LTS"
|
||||
define V_RUNTIME_VERSION = "0.2.3+19-Non-LTS"
|
||||
define V_TPC_COMMIT = "e376b949c9"
|
||||
define V_VPC_COMMIT = "ab0f37e1a8"
|
||||
define V_RUNTIME_COMMIT = "f087aa88e2"
|
||||
V_VPC_VERSION: const __attribute__(section(".PRVERSN")) [32]char = "0.1.1-dev.964"
|
||||
define V_VIPER_VERSION = "1.0.6-dev.194 2026-11-03 Non-LTS"
|
||||
define V_RUNTIME_VERSION = "0.2.4+297-Non-LTS"
|
||||
define V_TPC_COMMIT = "077d705609"
|
||||
define V_VPC_COMMIT = "2c65cea97f"
|
||||
define V_RUNTIME_COMMIT = "a0a71e7a85"
|
||||
define V_TARGET = "x86_64-w64-windows-gnu"
|
||||
define V_LLVM = "22.1.8"
|
||||
# --repl template-project lookup buffers (repl.vpj path / miss diagnostic text)
|
||||
@@ -597,7 +596,7 @@ Target: %s
|
||||
if do_phase1 != 0 and Config.IncludesDirs is not None:
|
||||
ph1_temp: str = Config.TempDir
|
||||
if ph1_temp is None: ph1_temp = "."
|
||||
if Phase1Main.RunPhase1(mb, Config.IncludesDirs, ph1_temp, log) != 0:
|
||||
if Phase1Main.RunPhase1(mb, Config.IncludesDirsAll, ph1_temp, log) != 0:
|
||||
VLogger.error("Phase1 failed, aborting compilation", "Phase1")
|
||||
return 1
|
||||
# Phase1-only (no Phase2): exit right away
|
||||
@@ -628,7 +627,8 @@ Target: %s
|
||||
mf_dynamic_libs: str = Config.LinkerDynamicLibs
|
||||
mf_includes_bin: str = Config.get_includes_binary_dir()
|
||||
|
||||
mf_ret: int = Phase2.RunMultiFileProject(mb, Config.SourceDir, mf_temp, mf_output, mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out, mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args, mf_static_libs, mf_dynamic_libs)
|
||||
mf_inc_dirs: str = Config.IncludesDirsAll if Config.IncludesDirsAll is not None else Config.IncludesDirs
|
||||
mf_ret: int = Phase2.RunMultiFileProject(mb, Config.SourceDir, mf_temp, mf_output, mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out, mf_includes_bin, mf_inc_dirs, do_phase1, do_phase2, log, args, mf_static_libs, mf_dynamic_libs)
|
||||
argparse.release(args)
|
||||
log.success("ViperC complete")
|
||||
return mf_ret
|
||||
|
||||
Reference in new issue
Block a user