You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何将Maven中ECJ插件的输出传递给自定义插件?

如何在自定义Maven插件中获取ECJ的错误与警告(无需文件输出)

与其绕弯子去读取文件输出,其实有几种更直接、更优雅的方式来获取ECJ的诊断信息,下面是我实践过的几个靠谱方案:

1. 直接集成ECJ编译API,主动捕获诊断信息

这是最灵活可控的方式——不用依赖现成的Maven ECJ插件,直接在你的自定义插件里调用ECJ核心API编译代码,同时收集所有错误和警告细节。

步骤示例:

首先在插件的pom.xml里引入ECJ依赖:

<dependency>
    <groupId>org.eclipse.jdt</groupId>
    <artifactId>org.eclipse.jdt.core</artifactId>
    <version>3.36.0</version> <!-- 替换为最新稳定版 -->
</dependency>

然后编写代码调用ECJ,自定义监听器收集诊断:

import org.eclipse.jdt.core.compiler.Problem;
import org.eclipse.jdt.core.compiler.IDiagnosticListener;
import org.eclipse.jdt.internal.compiler.*;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.Mojo;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

// 自定义诊断监听器,直接捕获ECJ的Problem对象
class EcjDiagnosticCollector implements IDiagnosticListener {
    private final List<Problem> diagnostics = new ArrayList<>();

    @Override
    public void reportProblem(Problem problem) {
        diagnostics.add(problem);
        // 可直接区分错误/警告:problem.isError() / problem.isWarning()
    }

    public List<Problem> getDiagnostics() {
        return diagnostics;
    }
}

@Mojo(name = "ecj-analyze")
public class EcjAnalyzerMojo extends AbstractMojo {
    @Parameter(defaultValue = "${project.basedir}/src/main/java", required = true)
    private File sourceDir;

    @Override
    public void execute() {
        EcjDiagnosticCollector collector = new EcjDiagnosticCollector();

        // 配置ECJ编译参数(比如指定Java版本)
        CompilerOptions options = new CompilerOptions();
        options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_17);
        options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_17);
        options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_17);

        // 初始化编译器(这里需要实现名称环境和请求器,可参考ECJ官方示例或现有插件代码)
        INameEnvironment nameEnv = createNameEnvironment();
        ICompilerRequestor requestor = (unit, classFiles) -> {}; // 忽略编译结果,只关心诊断

        Compiler compiler = new Compiler(
                nameEnv,
                DefaultErrorHandlingPolicies.exitOnFirstError(),
                options,
                requestor,
                collector,
                new DefaultProblemFactory()
        );

        // 扫描源码文件并触发编译
        List<ICompilationUnit> units = scanSourceFiles(sourceDir);
        compiler.compile(units.toArray(new ICompilationUnit[0]));

        // 处理收集到的诊断信息
        for (Problem problem : collector.getDiagnostics()) {
            getLog().info(String.format("[%s] %s - %s 第%d行",
                    problem.isError() ? "ERROR" : "WARNING",
                    problem.getMessage(),
                    problem.getOriginatingFileName(),
                    problem.getSourceLineNumber()
            ));
            // 这里可以加入你的自定义解析逻辑
        }
    }

    // 辅助方法:扫描源码目录生成编译单元列表(需自行实现)
    private List<ICompilationUnit> scanSourceFiles(File sourceDir) {
        // 遍历目录,把.java文件转为ICompilationUnit实例
        return new ArrayList<>();
    }

    // 辅助方法:创建名称环境(处理类路径、依赖等,需自行实现)
    private INameEnvironment createNameEnvironment() {
        // 可参考org.eclipse.jdt.internal.compiler.env.NameEnvironment的实现
        return null;
    }
}

这种方式的核心优势是直接拿到ECJ的Problem对象,包含错误类型、位置、详细消息等结构化数据,完全不需要解析字符串,可靠性拉满。

2. 通过Maven BuildListener监听ECJ插件日志

如果一定要复用现有的Maven ECJ插件,你可以注册一个BuildListener,实时捕获ECJ输出的日志信息,再做解析。

步骤示例:

先实现一个BuildListener:

import org.apache.maven.execution.AbstractExecutionListener;
import org.apache.maven.execution.ExecutionEvent;
import org.codehaus.plexus.component.annotations.Component;
import org.apache.maven.BuildListener;

import java.util.ArrayList;
import java.util.List;

@Component(role = BuildListener.class)
public class EcjLogCollector extends AbstractExecutionListener {
    private final List<String> ecjMessages = new ArrayList<>();

    @Override
    public void logEvent(ExecutionEvent event) {
        String msg = event.getMessage();
        // 过滤ECJ输出的错误/警告日志(通常带[ERROR]/[WARNING]前缀)
        if (msg != null && (msg.startsWith("[ERROR]") || msg.startsWith("[WARNING]"))) {
            ecjMessages.add(msg);
        }
    }

    public List<String> getEcjMessages() {
        return ecjMessages;
    }
}

然后在你的Mojo里注入这个监听器,获取收集到的日志:

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.LifecyclePhase;

@Mojo(name = "ecj-log-parse", defaultPhase = LifecyclePhase.COMPILE)
public class EcjLogParserMojo extends AbstractMojo {
    @Component
    private EcjLogCollector logCollector;

    @Override
    public void execute() {
        for (String msg : logCollector.getEcjMessages()) {
            // 解析日志字符串,提取错误/警告信息
            getLog().info("解析到ECJ消息:" + msg);
        }
    }
}

这种方式的缺点是需要解析日志字符串,容易受日志格式变更影响,但好处是可以直接复用现有ECJ插件的配置。

3. 扩展Maven ECJ插件,自定义问题报告器

如果用的是Tycho的ECJ插件或者Apache的maven-compiler-plugin(指定ECJ为编译器),可以实现ECJ的IProblemReporter,让插件直接把诊断信息传给你。

核心思路:

  1. 实现IProblemReporter接口,在reportProblem方法中收集问题;
  2. 通过Maven插件配置,让ECJ插件使用你的自定义报告器;
  3. 在你的插件中获取报告器实例,拿到收集到的问题列表。

这种方式需要修改ECJ插件的配置,相对复杂,但能深度集成现有插件流程。


总的来说,直接集成ECJ API的方式是最优解,它不依赖外部插件的输出,直接获取结构化的诊断信息,解析成本最低也最可靠。

内容的提问来源于stack exchange,提问作者Jobinary

火山引擎 最新活动