Files

481 lines
26 KiB
Plaintext

import lib.core.Gen.GenApi as GenApi
import lib.core.VLogger as VLogger
import lib.core.Repl as Repl
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.core.IncludesScanner as IncludesScanner
import lib.Stub.StubMerger as StubMerger
import lib.Phase.Phase1 as Phase1
import lib.Phase.Phase2 as Phase2
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.Generator as GenGenerator
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import lib.core.builtin.ViperBuild as ViperBuild
import lib
from stdint import *
import stdio
import string
import stdlib
import memhub
import argparse
import win32.win32console as win32cmd
import win32.win32base as win32base
import sys
import ast
import os
import os._win32
import os._posix
import os.path
import shutil
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。)
define POOL_SIZE = 1073741824
# Permanent memory pool size: 2^29 + 264 = 512MB usable, then change to 1 GiB
define PERM_POOL_SIZE = 1073741824
# Code page: UTF-8 (Windows 65001)
define CODE_PAGE = 65001
# Source code buffer size (1MB)
define SRC_BUF_SIZE = 1048576
# Version (dev/beta/rc)
V_VPC_VERSION: __attribute__(section(".PRVERSN")) []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"
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)
define REPL_PROJ_PATH_CAP = 1024
define REPL_PROJ_DIAG_CAP = 2048
static a: int
# main: Command-line entry
def main() -> int:
win32cmd.SetConsoleOutputCP(CODE_PAGE)
win32cmd.SetConsoleCP(CODE_PAGE)
detect_flag: int = 0
cmd_line_d: str = win32base.GetCommandLineA()
if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection") is not None: detect_flag = 1
# --detection-deep: heavy diagnostics (per-alloc probe + periodic full-table scan; extremely slow, off by default); read via raw command-line strstr before MemBuddy
deep_detect_flag: int = 0
if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection-deep") is not None: deep_detect_flag = 1
# Initialize the mbuddy memory pool
arena: bytes = stdlib.malloc(POOL_SIZE)
if arena is None:
stdio.printf("FAIL: malloc for arena failed\n")
return 1
# Create a global permanent memory pool (perm_pool, 512MB)
perm_arena: bytes = stdlib.malloc(PERM_POOL_SIZE)
if perm_arena is None:
stdio.printf("FAIL: malloc for perm_pool arena failed\n")
return 1
mb: memhub.MemBuddy* = memhub.MemBuddy(arena, POOL_SIZE, detect_flag, deep_detect_flag)
if mb is None:
stdio.printf("FAIL: MemBuddy init failed\n")
return 1
perm_pool: memhub.MemBuddy* = memhub.MemBuddy(perm_arena, PERM_POOL_SIZE, detect_flag, deep_detect_flag)
if perm_pool is None:
stdio.printf("FAIL: perm_pool init failed\n")
return 1
CompilerFactory.factory = CompilerFactory.CompilerFactory(mb, perm_pool)
# set the global mbuddy pointer (needed by sys and argparse)
sys._mbuddy = mb
argparse._mbuddy = mb
ast._mbuddy = mb
lib._mbuddy = mb
Config._mbuddy = mb
Utils._mbuddy = mb
HandlesTranslator._mbuddy = mb
BuildPipeline._mbuddy = mb
IncludesScanner._mbuddy = mb
StubMerger._mbuddy = mb
Phase1._mbuddy = mb
Phase2._mbuddy = mb
subprocess._mbuddy = mb
# Runtime-dep deploy primitives (BuildPipeline.deploy_runtime_deps): os.mkdir + shutil.copyfile.
os._mbuddy = mb
os._win32._mbuddy = mb
os._posix._mbuddy = mb
os.path._mbuddy = mb
shutil._mbuddy = mb
# hashlib._mbuddy = (mb)
VLogger._mbuddy = mb
lib._mbuddy = mb
# P2: create the project-level Generator singleton (reused across Phase1/Phase2/BuildPipeline;
gen: GenGenerator.Generator* = mb.alloc(GenGenerator.Generator.__sizeof__())
if gen is None:
stdio.printf("FAIL: Generator alloc failed\n")
return 1
gen.__before_init__()
gen.__init__(mb)
GenGenerator._g_gen = gen
# P2-P5: inject module-level Handles singletons (Phase1 calls their state funcs); module globals carry sha1 prefixes, assign each
GenApi._g_gen = gen
# P6: inject statement/assign/var/import/translator-driver module singletons
# initialize VLogger and print the startup log
VLogger.init_logger()
log: VLogger.Logger = VLogger.logger
if log is None: log.error("ViperC Failed to parse parameters")
# initialize command-line args (Windows: GetCommandLineA, POSIX: /proc/self/cmdline)
sys._init_argv()
# create the argument parser
parser: argparse.ArgumentParser = argparse.ArgumentParser("ViperC", "ViperC command-line parameter parser", pool=mb)
# register arguments (matching Projectrans.py main())
parser.add_argument("--project", None, argparse.STRING, 0, None, False, argparse.STORE, "Project VPJ path, by default it looks in the current directory")
parser.add_argument("--src", None, argparse.STRING, 0, None, False, argparse.STORE, "Source directory")
parser.add_argument("--temp", None, argparse.STRING, 0, None, False, argparse.STORE, "Declare a temporary directory for the interface")
parser.add_argument("--output", None, argparse.STRING, 0, None, False, argparse.STORE, "Output directory")
parser.add_argument("--phase", None, argparse.STRING, 0, None, False, argparse.STORE, "Phase: 1 = Generate statement, 2 = Translate and compile, all = Everything")
parser.add_argument("--cc", None, argparse.STRING, 0, None, False, argparse.STORE, "LLVM compiler command")
parser.add_argument("--clean", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clean output and temp directories")
parser.add_argument("--run", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Run the generated executable after compilation")
parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Delete the includes.binary precompiled cache and rebuild all the includes")
parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clear .transpyc_cache global cache")
parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)")
parser.add_argument("--traceback", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Traceback mode: force-compile includes/traceback.vp + Install() ctor, llc -frame-pointer=all, link -ldbghelp, export <exe>.stackmap")
parser.add_argument("--phase2command", None, argparse.STRING, 0, None, False, argparse.STORE, "Do not run llc/link: write Phase2 llc(opt+llc) and clang link commands to the given .bat path")
parser.add_argument("--nowarn-register", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Suppress Phase A dependency WARN")
parser.add_argument("--detection", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable MemBuddy allocation detection (0xCDCD markers + record-table probe for out-of-bounds writes; applies to both mb and perm_pool)")
# Registered only so argparse accepts the flag: the value is read via raw
# command-line strstr before MemBuddy construction, same as --detection.
parser.add_argument("--detection-deep", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Deep detection: probe every allocation and enable the periodic full-table scan (implies --detection; extremely slow, diagnostics only)")
parser.add_argument("--show-args", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show all parsed arguments")
parser.add_argument("--repl", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Start the interactive REPL (M2 session variables + M3 cross-fragment functions: fragment -> DLL -> call)")
parser.add_argument("--build-dir", None, argparse.STRING, 0, None, False, argparse.STORE, "Override project.vpj build_dir (default ./__vpcbuild__; relative paths resolve against the project dir)")
parser.add_argument("--no-llvmpeephole", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Disable all local mlirlite peephole passes (T2 store/load forward & ret-zero, T3 no-op bitcast) in FunctionPrint serialization")
parser.add_argument("--version", "-v", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show version and exit")
parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show help and exit")
# parse command-line arguments
args: argparse.ParsedArgs = parser.parse_args(sys._argc, sys._argv)
if args is None:
log.error("Failed to parse arguments", "argparse")
return 1
if args.get_bool("help"):
parser.print_help()
return 0
if args.get_bool("version"):
stdio.printf("""Viper %s
Copyright (C) 2026 Viper Project
License MIT <https://opensource.org/licenses/MIT>
GVSDS(TM) Viper SE Runtime Environment and Library (VRTE) %s
Viper Frontend Compiler ViperC %s
Author: terminexus@gvsds.com
Tpv commit: %s
Tpc commit: %s
VRTE commit: %s
Target: %s
""", V_VIPER_VERSION, V_RUNTIME_VERSION, V_VPC_VERSION, V_VPC_COMMIT, V_TPC_COMMIT, V_RUNTIME_COMMIT, V_TARGET, V_LLVM)
return 0
# --repl guard: mutually exclusive with --phase2command / --asan, checked first
# before their global-state/cleanup side effects; both would break fragment DLLs.
repl_flag: bool = args.get_bool("repl")
if repl_flag:
if args.get_str("phase2command") is not None:
stdio.printf("FAIL: --repl and --phase2command are mutually exclusive\n")
stdio.printf(" Reason: under --phase2command, compile_ll_to_obj only writes the bat and does not run llc,\n")
stdio.printf(" so the fragment .obj is never produced and the subsequent clang++ -shared necessarily fails\n")
stdio.printf(" Fix: drop --phase2command (REPL must produce and load the fragment DLL immediately)\n")
argparse.release(args)
return 2
if args.get_bool("asan"):
stdio.printf("FAIL: --repl and --asan are mutually exclusive\n")
stdio.printf(" Reason: --asan first instruments the fragment .ll with ASan, but the fragment artifact is a runtime\n")
stdio.printf(" DLL loaded by LoadLibraryA, and the host process does not link the ASan runtime; instrumentation semantics are undefined\n")
stdio.printf(" Fix: drop --asan (to debug memory issues, build an ASan ViperC in project mode)\n")
argparse.release(args)
return 2
# --traceback is also rejected under --repl: the fragment is a runtime-loaded
# DLL (DI metadata / startup ctor semantics undefined) and it has no <exe>.stackmap.
if args.get_bool("traceback"):
stdio.printf("FAIL: --repl and --traceback are mutually exclusive\n")
stdio.printf(" Reason: --traceback relies on startup-ctor injection + a per-module DWARF side file,\n")
stdio.printf(" but the fragment is a runtime LoadLibraryA-loaded DLL; the two semantics are undefined\n")
stdio.printf(" Fix: drop --traceback (for crash stacks, build a --traceback ViperC in project mode)\n")
argparse.release(args)
return 2
# === --repl: M1 interactive loop (expression fragment -> DLL -> echo). Runs
# after project-config load and before Phase1/Phase2, then returns.
# print parse result
# log.banner("ViperC argument parse result")
# string arguments
project: str = args.get_str("project")
src: str = args.get_str("src")
temp: str = args.get_str("temp")
output: str = args.get_str("output")
phase: str = args.get_str("phase")
cc: str = args.get_str("cc")
build_dir: str = args.get_str("build-dir")
# === --project and --src are mutually exclusive: --project uses project.vpj,
# --src uses viperbuild built-in config; giving both fails fast.
if project is not None and src is not None:
stdio.printf("FAIL: --project and --src are mutually exclusive\n")
stdio.printf(" Reason: --project uses project.vpj project mode, --src uses the entry file's built-in viperbuild config\n")
stdio.printf(" Fix: choose one; in --src mode the build config goes in 'import viperbuild' within the entry file's dependency closure\n")
argparse.release(args)
return 2
_gb_clean: bool = args.get_bool("clean")
show_args: bool = args.get_bool("show-args")
if show_args:
if project is not None: stdio.printf(" --project: %s\n", project)
else: stdio.printf(" --project: (not specified)\n")
if src is not None: stdio.printf(" --src: %s\n", src)
else: stdio.printf(" --src: (not specified)\n")
if temp is not None: stdio.printf(" --temp: %s\n", temp)
else: stdio.printf(" --temp: (not specified)\n")
if output is not None: stdio.printf(" --output: %s\n", output)
else: stdio.printf(" --output: (not specified)\n")
if phase is not None: stdio.printf(" --phase: %s\n", phase)
else: stdio.printf(" --phase: (not specified, default all)\n")
if cc is not None: stdio.printf(" --cc: %s\n", cc)
else: stdio.printf(" --cc: (not specified)\n")
if _gb_clean: stdio.printf(" --clean: True\n")
else: stdio.printf(" --clean: False\n")
if args.get_bool("run"): stdio.printf(" --run: True\n")
else: stdio.printf(" --run: False\n")
if args.get_bool("rebuild-includes"): stdio.printf(" --rebuild-includes: True\n")
else: stdio.printf(" --rebuild-includes: False\n")
if args.get_bool("clear-cache"): stdio.printf(" --clear-cache: True\n")
else: stdio.printf(" --clear-cache: False\n")
stdio.printf("Argument parsing complete.\n\n")
# Mode switches: isolate includes caches by suffix so mode-specific objects do
# not mix (ASan/traceback objects are incompatible); suffix combines both flags.
gb_asan: bool = args.get_bool("asan")
gb_traceback: bool = args.get_bool("traceback")
if gb_asan: BuildPipeline._asan_mode = True
else: BuildPipeline._asan_mode = False
if gb_traceback: BuildPipeline._traceback_mode = True
else: BuildPipeline._traceback_mode = False
if gb_asan and gb_traceback: Config._inc_bin_suffix = ".binary_traceback_asan"
elif gb_traceback: Config._inc_bin_suffix = ".binary_traceback"
elif gb_asan: Config._inc_bin_suffix = ".binary_asan"
else: Config._inc_bin_suffix = ".binary"
if gb_asan: log.info("ASan mode enabled (opt -passes=asan instrumentation; runtime libs must be declared in project.vpj)", "asan")
if gb_traceback: log.info("Traceback mode enabled (llc keep frame pointer + link dbghelp + Install ctor + <exe>.stackmap)", "traceback")
# Enabling either mode clears that suffix's includes cache to force a rebuild
if gb_asan or gb_traceback:
mf_inc_bin_mode: str = Config.get_includes_binary_dir()
if mf_inc_bin_mode is not None:
n_mode: int = Utils.CleanDir(mf_inc_bin_mode)
log.info(f"Mode cache cleanup: {mf_inc_bin_mode} deleted {n_mode} files (will rebuild with {Config._inc_bin_suffix})", "mode")
# === --phase2command: only write llc/link commands to a bat, do not execute them ===
p2c: str = args.get_str("phase2command")
if p2c is not None:
BuildPipeline._phase2_cmd_path = p2c
log.info(f"--phase2command: not running llc/link, commands written to {p2c}", "phase2cmd")
# --nowarn-register: suppress Phase A dependency WARNs (cross-module text.ll not yet generated)
if args.get_bool("nowarn-register"):
VLogger._nowarn_register = 1
log.info("--nowarn-register enabled: suppress register-class WARN output", "vlogger")
# --no-llvmpeephole: disable all local peepholes (T2/T3); FunctionPrint serializes directly
if args.get_bool("no-llvmpeephole"):
mlirlite.G_PeepEnable = 0
log.info("--no-llvmpeephole enabled: disable FunctionPrint local peepholes (RenumberSSA kept)", "peep")
# === --repl convenience: without --project, search cwd then the exe dir for
# repl.vpj and feed it to Config.Load_project_config; miss fails fast.
proj_loaded: int = 0
proj_path: str = project
if repl_flag and proj_path is None:
repl_proj_buf: bytes = stdlib.malloc(REPL_PROJ_PATH_CAP)
repl_diag_buf: bytes = stdlib.malloc(REPL_PROJ_DIAG_CAP)
if repl_proj_buf is None or repl_diag_buf is None:
stdio.printf("FAIL: --repl failed to allocate the path buffer for locating repl.vpj\n")
argparse.release(args)
return 1
if Repl.ReplFindProject(repl_proj_buf, REPL_PROJ_PATH_CAP, repl_diag_buf, REPL_PROJ_DIAG_CAP) != 0:
proj_path = repl_proj_buf
log.info(f"--repl: --project not given, using the located template project {proj_path}", "repl")
else:
stdio.printf("FAIL: --repl could not find the project file repl.vpj\n")
stdio.printf("%s", repl_diag_buf)
stdio.printf(" You can also pass --project to specify the project file explicitly (e.g. ViperC/project.vpj in this repo)\n")
argparse.release(args)
return 1
# load project config (explicit --project / repl.vpj found by --repl)
if proj_path is not None:
log.banner("Project config")
cfg_ret: int = Config.Load_project_config(proj_path)
if cfg_ret == 0: proj_loaded = 1
else: log.warning(f"Failed to load project.vpj: {proj_path}", "config")
else:
# try the default project.vpj (current and parent dir); --src mode must not
# implicitly load project.vpj (it would pollute the config source).
if src is None:
proj_path = "project.vpj"
if Config.Load_project_config(proj_path) == 0: proj_loaded = 1
else:
proj_path = "../project.vpj"
if Config.Load_project_config(proj_path) == 0: proj_loaded = 1
if proj_loaded == 1:
# extract project.vpj's dir so relative paths resolve against it
proj_len: sizet = string.strlen(proj_path)
slash_pos: sizet = proj_len
for i in range(int(proj_len)):
idx: sizet = proj_len - 1 - i
ch: char = proj_path[idx]
if ch == '/' or ch == '\\':
slash_pos = idx
break
proj_dir: str = ""
if slash_pos > 0 and slash_pos < proj_len:
proj_dir = stdlib.malloc(slash_pos + 1)
if proj_dir is not None:
string.memcpy(proj_dir, proj_path, slash_pos)
proj_dir[slash_pos] = '\0'
# [--build-dir] overrides build_dir before resolve_paths so paths stay project_dir-based
if build_dir is not None: Config.BuildDir = build_dir
Config.resolve_paths(proj_dir)
Config.print_config()
# === --src: viperbuild built-in config discovery/apply (Pass 0), before the
# switches read Config; a miss or bad config fails fast.
if src is not None:
log.banner("viperbuild")
if ViperBuild.DiscoverAndApply(mb, src) != 0:
argparse.release(args)
return 1
Config.print_config()
# === --repl: M1 interactive loop (fragment -> IR -> llc -> clang++ -shared ->
# DLL -> echo), after config load and before Phase1/Phase2.
if args.get_bool("repl"):
Repl._perm_pool = perm_pool # jmp_buf/parse-table/session buffers must live across calls; must use PermPool
if log is not None:
log.set_jit_flag(True) # errors longjmp back to REPL (reset to False when Repl.run exits)
rc_repl: int = Repl.run(mb)
argparse.release(args)
return rc_repl
# --asan requires the project to declare the ASan runtime in project.vpj linker.static_libs (no hardcoded paths); missing fails fast
if BuildPipeline._asan_mode != 0 and Config.asan_runtime_declared() == 0:
log.error("Enabling --asan requires declaring the ASan runtime in project.vpj's linker.static_libs:", "asan")
log.error(" <MSYS2>/clang64/lib/clang/<ver>/lib/windows/libclang_rt.asan_dynamic-x86_64.dll.a", "asan")
log.error(" <MSYS2>/clang64/lib/clang/<ver>/lib/windows/libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", "asan")
log.error("The clang64 root is auto-detected from PATH (see README section 8.1)", "asan")
return 1
# === --clean: remove temp, output and includes.binary directories ===
if _gb_clean:
log.info("Cleaning temp directory...", "clean")
if Config.TempDir is not None:
n: int = Utils.CleanDir(Config.TempDir)
log.info(f"{Config.TempDir}: deleted {n} files", "clean")
if Config.OutputDir is not None:
n2: int = Utils.CleanDir(Config.OutputDir)
log.info(f"{Config.OutputDir}: deleted {n2} files", "clean")
# Clean the includes.binary dir (stale .obj files would skip recompilation)
mf_inc_bin_clean: str = Config.get_includes_binary_dir()
if mf_inc_bin_clean is not None:
n3: int = Utils.CleanDir(mf_inc_bin_clean)
log.info(f"{mf_inc_bin_clean}: deleted {n3} files", "clean")
# === --rebuild-includes: delete the includes.binary precompile cache ===
# Forces all includes to recompile next time (temp/output untouched)
if args.get_bool("rebuild-includes"):
log.info("Rebuilding includes cache...", "rebuild-includes")
mf_inc_bin_rb: str = Config.get_includes_binary_dir()
if mf_inc_bin_rb is not None:
n_rb: int = Utils.CleanDir(mf_inc_bin_rb)
log.info(f"{mf_inc_bin_rb}: deleted {n_rb} files (includes will be recompiled)", "rebuild-includes")
# === --clear-cache: clear the temp dir cache (.vpi/.stub.ll/.text.ll) ===
# Forces Phase1 to regenerate stubs next time (output/includes.binary untouched)
if args.get_bool("clear-cache"):
log.info("Clearing temp cache...", "clear-cache")
if Config.TempDir is not None:
n_cc: int = Utils.CleanDir(Config.TempDir)
log.info(f"{Config.TempDir}: deleted {n_cc} files (.vpi/.stub.ll cache)", "clear-cache")
# === --phase: 1=stub split only; 2=stub merge+compile only; all=both (default) ===
phase_mode: str = phase
if phase_mode is None: phase_mode = "all"
do_phase1: int = 0
do_phase2: int = 0
# Note: use string.strcmp instead of == to avoid the compiler == semantics bug
if string.strcmp(phase_mode, "1") == 0 or string.strcmp(phase_mode, "all") == 0: do_phase1 = 1
if string.strcmp(phase_mode, "2") == 0 or string.strcmp(phase_mode, "all") == 0: do_phase2 = 1
log.info(f"Mode: {phase_mode} (phase1={do_phase1} phase2={do_phase2})", "phase")
# === Phase1: scan includes (on-demand translation) ===
# For each .vp/.py file under includes, translate and split the stub if absent
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:
VLogger.error("Phase1 failed, aborting compilation", "Phase1")
return 1
# Phase1-only (no Phase2): exit right away
if do_phase2 == 0:
log.info("Phase1 complete, exiting", "phase")
return 0
# AST parse: when --src or --project is given, read the file and parse it into an AST
src_path: str = args.get_str("src")
# No project config and no --src: fail fast (uninitialized Config fields are garbage pointers -> crash)
if proj_loaded == 0 and src_path is None:
log.error("project.vpj not found (current/parent dir) and --src not given; cannot build", "config")
argparse.release(args)
return 1
# Project mode (multi-file): --project scans source_dir for all .vp; --src + viperbuild
# reuses it (Pass 0 writes config back, SourceDir as root); the two are mutually exclusive.
if Config.SourceDir is not None and (project is not None or ViperBuild.IsApplied() != 0):
mf_temp: str = Config.TempDir if Config.TempDir is not None else "."
mf_output: str = Config.OutputDir if Config.OutputDir is not None else "."
mf_cc: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
mf_cc_flags: str = Config.CompilerFlags if Config.CompilerFlags is not None else "-filetype=obj -relocation-model=pic"
mf_linker: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
mf_linker_flags: str = Config.LinkerFlags if Config.LinkerFlags is not None else "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc"
mf_linker_out: str = Config.LinkerOutput if Config.LinkerOutput is not None else "app.exe"
mf_static_libs: str = Config.LinkerStaticLibs
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)
argparse.release(args)
log.success("ViperC complete")
return mf_ret
# Reaching here means no buildable project root (SourceDir missing): fail fast, no silent "done"
log.error("No buildable project root: project.vpj is missing source_dir, or viperbuild provided no source", "config")
argparse.release(args)
return 1