import os import subprocess, json, shutil from pathlib import Path from runners.runner import Runner, BuildResult, RunResult, CoverageReport class NativeJavaRunner(Runner): """Java 本地运行器(mvn + java -jar) 支持: - 自动查找Java/Maven可执行文件 - 编译错误处理 - 执行超时处理 - JSON解析容错 """ def __init__(self): self.java = "java" self.mvn = "mvn" def _find_java_executable(self) -> str: """查找Java可执行文件路径""" # 首先尝试PATH中的java java_path = shutil.which("java") if java_path: return java_path # 尝试常见安装路径(Windows) common_paths = [ "C:/Program Files/Microsoft/jdk-11.0.32.101-hotspot/bin/java.exe", "C:/Program Files/Java/jdk-11/bin/java.exe", "C:/Program Files/Java/jdk-17/bin/java.exe", "C:/Program Files/Eclipse Adoptium/jdk-11.0.21.9-hotspot/bin/java.exe", ] for path in common_paths: if Path(path).exists(): return path # 尝试JAVA_HOME环境变量 java_home = os.environ.get("JAVA_HOME") if java_home: java_exe = Path(java_home) / "bin" / "java.exe" if java_exe.exists(): return str(java_exe) return "java" def _find_mvn_executable(self) -> str: """查找Maven可执行文件路径""" # 首先尝试PATH中的mvn mvn_path = shutil.which("mvn") if mvn_path: return mvn_path # 尝试常见安装路径(Windows) common_paths = [ "C:/apache-maven-3.9.6/bin/mvn.cmd", "C:/Program Files/apache-maven-3.9.6/bin/mvn.cmd", ] for path in common_paths: if Path(path).exists(): return path # 尝试MAVEN_HOME环境变量 maven_home = os.environ.get("MAVEN_HOME") if maven_home: mvn_cmd = Path(maven_home) / "bin" / "mvn.cmd" if mvn_cmd.exists(): return str(mvn_cmd) return "mvn" def compile(self, source_dir: str) -> BuildResult: """编译Java项目 Args: source_dir: Java源代码目录(包含pom.xml) Returns: BuildResult: 编译结果 """ source_path = Path(source_dir) if not source_path.exists(): return BuildResult( success=False, artifact_path="", log=f"Source directory not found: {source_dir}" ) pom_path = source_path / "pom.xml" if not pom_path.exists(): return BuildResult( success=False, artifact_path="", log=f"pom.xml not found in {source_dir}" ) try: mvn = self._find_mvn_executable() p = subprocess.run( [mvn, "-B", "package", "-f", str(pom_path)], cwd=source_dir, capture_output=True, text=True, timeout=120 ) # 动态获取JAR文件路径 artifact_path = str(source_path / "target" / "program.jar") if not Path(artifact_path).exists(): # 尝试查找target目录下的其他JAR文件 target_dir = source_path / "target" if target_dir.exists(): jar_files = list(target_dir.glob("*.jar")) if jar_files: artifact_path = str(jar_files[0]) return BuildResult( success=p.returncode == 0, artifact_path=artifact_path, log=p.stdout + p.stderr ) except subprocess.TimeoutExpired: return BuildResult( success=False, artifact_path="", log="Maven build timed out after 120 seconds" ) except Exception as e: return BuildResult( success=False, artifact_path="", log=f"Build error: {str(e)}" ) def run(self, artifact: str, input_path: str, output_path: str) -> RunResult: """执行Java程序 Args: artifact: JAR文件路径 input_path: 输入文件路径 output_path: 输出目录路径 Returns: RunResult: 执行结果 """ artifact_path = Path(artifact) if not artifact_path.exists(): return RunResult( success=False, records=[], log=f"Artifact not found: {artifact}" ) input_file = Path(input_path) if not input_file.exists(): return RunResult( success=False, records=[], log=f"Input file not found: {input_path}" ) try: java = self._find_java_executable() with open(input_path, 'r', encoding='utf-8') as f: data = f.read() p = subprocess.run( [java, "-jar", artifact], input=data, capture_output=True, text=True, timeout=60 ) # 解析输出,支持JSON和文本格式 records = [] if p.stdout.strip(): for line in p.stdout.strip().split("\n"): line = line.strip() if line: try: record = json.loads(line) records.append(record) except json.JSONDecodeError: # 如果不是JSON格式,作为文本记录处理 records.append({"raw": line}) return RunResult( success=p.returncode == 0, records=records, log=p.stdout + p.stderr ) except subprocess.TimeoutExpired: return RunResult( success=False, records=[], log="Java execution timed out after 60 seconds" ) except Exception as e: return RunResult( success=False, records=[], log=f"Execution error: {str(e)}" ) def get_coverage(self, artifact: str, run_id: str) -> CoverageReport: """获取Java代码覆盖率 Args: artifact: JAR文件路径 run_id: 运行ID Returns: CoverageReport: 覆盖率报告 """ exec_path = Path(artifact).parent / "jacoco.exec" if exec_path.exists(): # TODO: 解析JaCoCo覆盖率报告 return CoverageReport(branch_rate=0.85, verdict="PASS") return CoverageReport(verdict="FAIL")