现有组件功能与Amesim组件比较对齐,补全功能;仿真服务启动环境检测,旧版json工程文件适配读取

This commit is contained in:
ljz committed 2026-09-13 20:01:56 +08:00
1 parent b6c22a54f8
commit 6fc9afe41d
119 files changed
+260114 -351

No files matched your search

+71 -17
View File
@@ -5,6 +5,7 @@ from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from hashlib import sha256
import json
import logging
import os
from pathlib import Path
import re
@@ -17,8 +18,20 @@ from typing import Any, Callable
from .compiler import NativeProgram
from .modules import component_modules
from .processes import command_environment, is_driver_launch_failure, is_transient_process_error, LAUNCH_RETRY_DELAYS
from .cache_storage import acquire_cache_lease, touch_cache_entry, prune_cache
LOGGER = logging.getLogger(__name__)
WINDOWS = os.name == 'nt'
class NativeCommandError(RuntimeError):
def __init__(self, message, command, *, exit_code=None, stdout=b'', stderr='', error_type='CalledProcessError', attempts=1):
super().__init__(message)
self.details = {'command': list(command), 'cwd': str(Path.cwd()), 'exitCode': exit_code,
'stdout': stdout.decode('utf-8',errors='replace')[-8000:], 'stderr': stderr[-8000:],
'errorType': error_type, 'attempts': attempts}
ROOT = Path(__file__).resolve().parents[3]
NATIVE = ROOT / "native"
CACHE = ROOT / "app/data/native-builds"
@@ -135,12 +148,38 @@ def _publish(stage: Path, target: Path, key_name: str, key: str, version: int, r
return manifest
def _command(command: list[str], *, log: list[str], timeout: float = 120) -> bytes:
result = subprocess.run(command, capture_output=True, timeout=timeout)
log.append(" ".join(command) + "\n" + result.stderr.decode("utf-8", errors="replace"))
if result.returncode:
raise RuntimeError("Native compilation failed: " + result.stderr.decode("utf-8", errors="replace")[-3000:])
return result.stdout
def _command(command: list[str], *, log: list[str], timeout: float = 120,
require_stdout: bool = False) -> bytes:
environment = command_environment(command[0])
for attempt in range(len(LAUNCH_RETRY_DELAYS) + 1):
try:
result = subprocess.run(command, capture_output=True, timeout=timeout,
stdin=subprocess.DEVNULL, close_fds=True, env=environment,
creationflags=subprocess.CREATE_NO_WINDOW if WINDOWS else 0)
except OSError as exc:
if not is_transient_process_error(exc) or attempt == len(LAUNCH_RETRY_DELAYS):
raise
reason = f'{type(exc).__name__}: {exc}'
else:
stderr = result.stderr.decode('utf-8', errors='replace')
log.append(' '.join(command) + '\n' + stderr)
empty = result.returncode == 0 and require_stdout and not result.stdout.strip()
transient = is_driver_launch_failure(stderr) or (empty and WINDOWS)
if not result.returncode and not empty:
return result.stdout
reason = 'Compiler returned no identification output.' if empty else stderr[-3000:]
if not transient or attempt == len(LAUNCH_RETRY_DELAYS):
prefix = ('GCC 已启动,但编译子进程启动失败(已完成短间隔重试);'
if is_driver_launch_failure(stderr) else 'Native compilation failed: ')
raise NativeCommandError(prefix + reason, command, exit_code=result.returncode,
stdout=result.stdout, stderr=stderr, error_type='EmptyCompilerOutput' if empty else 'CalledProcessError',
attempts=attempt+1)
delay = LAUNCH_RETRY_DELAYS[attempt]
log.append(f'Compiler launch retry {attempt+1}/{len(LAUNCH_RETRY_DELAYS)} after {delay:g}s: {reason}')
LOGGER.warning('Compiler process launch failed; retry %d/%d in %.2fs: %s',
attempt+1, len(LAUNCH_RETRY_DELAYS), delay, reason.strip())
time.sleep(delay)
raise AssertionError('Unreachable compiler retry state')
def toolchain() -> tuple[str, Path, str]:
@@ -156,16 +195,14 @@ def toolchain() -> tuple[str, Path, str]:
base = next((path for path in candidates if (path / "include/cvode/cvode.h").is_file()), candidates[0])
if not (base / "include/cvode/cvode.h").is_file():
raise RuntimeError("SUNDIALS C development files not found; set SUNDIALS_ROOT.")
version = subprocess.run([compiler, "--version"], capture_output=True, text=True, check=True, timeout=15).stdout.splitlines()[0]
if os.name == 'nt':
compiler = str(Path(shutil.which(compiler) or compiler).resolve())
version = _command([compiler, '--version'], log=[], timeout=15, require_stdout=True).decode('utf-8', errors='replace').strip().splitlines()[0]
return compiler, base, version
def build_native(program: NativeProgram, *, cache_dir: Path | None = None,
progress_callback: Callable[[str], None] | None = None) -> NativeBuild:
started = time.perf_counter()
if progress_callback:
progress_callback("native-cache-check")
compiler, sundials, compiler_version = toolchain()
def platform_build_inputs(sundials: Path) -> tuple[list[str], list[Path], list[Path], str]:
"""Share production flags and dependencies with the startup smoke check."""
flags = list(COMPILER_FLAGS)
executable_name = "model.exe" if os.name == "nt" else "model"
if os.name == "nt":
@@ -182,6 +219,23 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None,
raise RuntimeError("SUNDIALS static libraries not found; set SUNDIALS_ROOT.")
libraries = [directory / f"libsundials_{name}.a" for name in LIBRARIES]
dlls = []
return flags, libraries, dlls, executable_name
def link_library_arguments(libraries: list[Path]) -> list[str]:
arguments = list(map(str, libraries))
if sys.platform.startswith("linux"):
arguments = ["-Wl,--start-group", *arguments, "-Wl,--end-group"]
return arguments
def build_native(program: NativeProgram, *, cache_dir: Path | None = None,
progress_callback: Callable[[str], None] | None = None) -> NativeBuild:
started = time.perf_counter()
if progress_callback:
progress_callback("native-cache-check")
compiler, sundials, compiler_version = toolchain()
flags, libraries, dlls, executable_name = platform_build_inputs(sundials)
cache = (cache_dir or CACHE).resolve()
cache.mkdir(parents=True, exist_ok=True)
for kind in ("models", "objects"):
@@ -205,7 +259,7 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None,
(stage / "model.h").write_text(program.header, encoding="utf-8", newline="\n")
compiler_path = Path(shutil.which(compiler) or compiler).resolve()
compiler_identity = {"version": compiler_version, "binarySha256": _hash(compiler_path),
"target": _command([compiler, "-dumpmachine"], log=logs).decode().strip(),
"target": _command([compiler, "-dumpmachine"], log=logs, require_stdout=True).decode().strip(),
"flags": flags, "platform": sys.platform}
runtime = _runtime_sources(program)
sources = [stage / "model.c", *runtime]
@@ -252,6 +306,7 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None,
"unitCount": len(units), "objectCacheHits": 0, "objectCompilations": 0,
"preprocessSeconds": preprocessing_seconds, "compileSeconds": 0.0,
"compileWallSeconds": 0.0, "linkSeconds": 0.0, "modelCacheHit": manifest is not None}
details['compilerLaunchRetries'] = sum(line.startswith('Compiler launch retry ') for line in logs)
if manifest is not None:
if progress_callback:
progress_callback("native-cache-hit")
@@ -302,14 +357,13 @@ def build_native(program: NativeProgram, *, cache_dir: Path | None = None,
details["objectCompilations"] = len(objects) - details["objectCacheHits"]
for item in objects:
logs.extend(item["log"])
link_libraries = list(map(str, libraries))
if sys.platform.startswith("linux"):
link_libraries = ["-Wl,--start-group", *link_libraries, "-Wl,--end-group"]
link_libraries = link_library_arguments(libraries)
if progress_callback:
progress_callback("native-linking")
before_link = time.perf_counter()
_command([compiler, *flags, *[str(item["path"]) for item in objects], *link_libraries,
"-lm", "-o", str(stage / executable_name)], log=logs)
details['compilerLaunchRetries'] = sum(line.startswith('Compiler launch retry ') for line in logs)
details["linkSeconds"] = time.perf_counter() - before_link
if {p.name: _hash(p) for p in libraries + dlls} != dependencies:
raise RuntimeError("Native dependencies changed during linking; retry with a stable toolchain.")