import os import subprocess from pathlib import Path from runners.runner import BuildResult, RunResult class CobolRunner: """COBOL 程序编译·执行器。 非DB程序(KIN系)使用 ``compile_with_links()`` + ``run_file_based()``。 DB程序/旧式调用保持 ``compile()`` + ``run()`` 不变。 """ # ── 旧式(orchestrator.py 互換)── def compile(self, src: str, dialect="ibm", gcov: bool = False) -> BuildResult: """旧式编译(-std=ibm-strict)。orchestrator.py 用,不修改。""" stem = Path(src).stem out = str(Path(src).parent / stem) cmd = ["cobc", "-x", f"-std={dialect}-strict", "-o", out, src] if gcov: cmd = ["cobc", "-x", f"-std={dialect}-strict", "--coverage", "-o", out, src] p = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return BuildResult(success=p.returncode == 0, artifact_path=out, log=p.stdout + p.stderr) def run(self, binary: str, input_path: str, output_path: str) -> RunResult: """旧式执行(stdin→stdout)。orchestrator.py 用。""" with open(input_path, "rb") as f: data = f.read() p = subprocess.run([binary], input=data, capture_output=True, timeout=30) Path(output_path).write_bytes(p.stdout) return RunResult(success=p.returncode == 0) # ── 新式(非DB KIN系向け)── def compile_with_links(self, src: str, work_dir: str, copybook_dirs: list[str] | None = None, sub_objects: list[str] | None = None, gcov: bool = False) -> BuildResult: """编译主程序 + 链接 SUB.o,不使用 -std=ibm-strict。 Args: src: COBOL 源文件路径。 work_dir: 编译工作目录(.gcno 产出位置,必须提前存在)。 copybook_dirs: COPYBOOK 搜索路径列表(映射为 -I 参数)。 sub_objects: SUB*.o 文件路径列表(静态链接)。 gcov: 是否启用 ``--coverage``。 Returns: BuildResult,artifact_path 指向 .exe 的绝对路径。 """ work_path = Path(work_dir).resolve() work_path.mkdir(parents=True, exist_ok=True) stem = Path(src).stem exe_path = work_path / f'{stem}.exe' cmd = ["cobc", "-x", "-g"] if gcov: cmd.append("--coverage") for cpy in (copybook_dirs or []): cmd.extend(["-I", str(cpy)]) cmd.extend(["-o", str(exe_path), str(src)]) cmd.extend(str(o) for o in (sub_objects or [])) orig = os.getcwd() try: os.chdir(str(work_path)) p = subprocess.run(cmd, capture_output=True, text=True, timeout=120) finally: os.chdir(orig) return BuildResult( success=p.returncode == 0, artifact_path=str(exe_path), log=(p.stdout + p.stderr)[:2000], ) def run_file_based(self, binary: str, run_dir: str, input_files: dict[str, str] | None = None, timeout: int = 60) -> RunResult: """基于文件的执行。将输入文件复制到 run_dir 后启动程序。 Args: binary: 可执行文件路径。 run_dir: 执行目录(CWD,输入文件在此,输出文件也在此)。 input_files: {assign_name: source_path} 的映射。 如 ``{"KIN04R01": "/path/to/input/KIN04R01"}``。 timeout: 超时秒数。 Returns: RunResult。 """ run_path = Path(run_dir).resolve() run_path.mkdir(parents=True, exist_ok=True) # 入力ファイル配置 for assign_name, src_path in (input_files or {}).items(): src = Path(src_path) if src.exists(): dst = run_path / assign_name import shutil shutil.copy2(str(src), str(dst)) orig = os.getcwd() try: os.chdir(str(run_path)) p = subprocess.run([str(binary)], capture_output=True, text=True, timeout=timeout) finally: os.chdir(orig) return RunResult( success=p.returncode == 0, log=(p.stdout + p.stderr)[:2000], )