Skip to content

Commit 910ef1f

Browse files
authored
Issue #68: use JSR-199 to call Eclipse compiler (#69)
Instead of invoking BatchCompiler with an option to write diagnostics to a temporary XML file and then parsing that call ECJ via the standard JSR-199 interfaces and use a DiagnosticListener. Besides not needing a temporary file this also ensures that all diagnostics are reported correctly. ECJ's BatchCompiler reports diagnostics from APT processors differently than other diagnostics (they're "extra_problems" in the XML), and the XML parser just ignores these extra_problems. Even if it did parse them, ECJ up to at least 3.20.0 neglects to give the source file for such extra_problems in the XML (that's a bug in ECJ), and thus the output would be not exactly helpful. Using JSR-199 to call ECJ, it reports all diagnostics including these "extra_problems" (with source file!) to the DiagnosticListener, and thus no messages are lost. A minor quirk is that when no source level is specified, BatchCompiler compiles with source level 1.3, while the ECJ JSR-199 implementation uses the latest source level it supports (Java 12 for ECJ 3.20.0). To maintain backwards compatibility, set source level 1.3 explicitly in that case. In normal maven usage, <source> is typically set anyway, either explicitly in the user projects' POMs or via property maven.compiler.source. If no JSR-199 interface for ECJ can be found keep using the legacy mechanism using BatchCompiler.
1 parent e916592 commit 910ef1f

File tree

1 file changed

+201
-77
lines changed

1 file changed

+201
-77
lines changed

plexus-compilers/plexus-compiler-eclipse/src/main/java/org/codehaus/plexus/compiler/eclipse/EclipseJavaCompiler.java

Lines changed: 201 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,30 @@
3030
import org.codehaus.plexus.compiler.CompilerMessage;
3131
import org.codehaus.plexus.compiler.CompilerOutputStyle;
3232
import org.codehaus.plexus.compiler.CompilerResult;
33+
import org.codehaus.plexus.util.DirectoryScanner;
3334
import org.codehaus.plexus.util.StringUtils;
3435
import org.eclipse.jdt.core.compiler.CompilationProgress;
3536
import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
3637

3738
import java.io.File;
38-
import java.io.IOException;
3939
import java.io.PrintWriter;
4040
import java.io.StringWriter;
41+
import java.nio.charset.Charset;
4142
import java.util.ArrayList;
42-
import java.util.Collections;
43+
import java.util.Iterator;
4344
import java.util.List;
45+
import java.util.Locale;
4446
import java.util.Map;
4547
import java.util.Map.Entry;
48+
import java.util.ServiceLoader;
4649
import java.util.Set;
4750

51+
import javax.tools.Diagnostic;
52+
import javax.tools.DiagnosticListener;
53+
import javax.tools.JavaCompiler;
54+
import javax.tools.JavaFileObject;
55+
import javax.tools.StandardJavaFileManager;
56+
4857
/**
4958
* @plexus.component role="org.codehaus.plexus.compiler.Compiler" role-hint="eclipse"
5059
*/
@@ -61,6 +70,7 @@ public EclipseJavaCompiler()
6170
// ----------------------------------------------------------------------
6271
boolean errorsAsWarnings = false;
6372

73+
@Override
6474
public CompilerResult performCompile(CompilerConfiguration config )
6575
throws CompilerException
6676
{
@@ -240,97 +250,166 @@ else if(extras.containsKey("-errorsAsWarnings"))
240250
args.add("-classpath");
241251
args.add(getPathString(classpathEntries));
242252

243-
// Compile! Send all errors to xml temp file.
244-
File errorF = null;
245-
try
253+
// Collect sources
254+
List<String> allSources = new ArrayList<>();
255+
for (String source : config.getSourceLocations())
246256
{
247-
errorF = File.createTempFile("ecjerr-", ".xml");
248-
249-
args.add("-log");
250-
args.add(errorF.toString());
251-
252-
// Add all sources.
253-
int argCount = args.size();
254-
for(String source : config.getSourceLocations())
257+
File srcFile = new File(source);
258+
if (srcFile.exists())
255259
{
256-
File srcFile = new File(source);
257-
if(srcFile.exists())
258-
{
259-
Set<String> ss = getSourceFilesForSourceRoot(config, source);
260-
args.addAll(ss);
261-
}
260+
Set<String> ss = getSourceFilesForSourceRoot(config, source);
261+
allSources.addAll(ss);
262262
}
263-
args.addAll(extraSourceDirs);
264-
if(args.size() == argCount)
265-
{
266-
//-- Nothing to do -> bail out
267-
return new CompilerResult(true, Collections.EMPTY_LIST);
263+
}
264+
for (String extraSrcDir : extraSourceDirs) {
265+
File extraDir = new File(extraSrcDir);
266+
if (extraDir.isDirectory()) {
267+
addExtraSources(extraDir, allSources);
268268
}
269+
}
270+
List<CompilerMessage> messageList = new ArrayList<>();
271+
if (allSources.isEmpty()) {
272+
// -- Nothing to do -> bail out
273+
return new CompilerResult(true, messageList);
274+
}
269275

270-
getLogger().debug("ecj command line: " + args);
271-
276+
// Compile
277+
try {
272278
StringWriter sw = new StringWriter();
273279
PrintWriter devNull = new PrintWriter(sw);
274-
275-
//BatchCompiler.compile(args.toArray(new String[args.size()]), new PrintWriter(System.err), new PrintWriter(System.out), new CompilationProgress() {
276-
boolean success = BatchCompiler.compile(args.toArray(new String[args.size()]), devNull, devNull, new CompilationProgress() {
277-
@Override
278-
public void begin(int i)
279-
{
280+
JavaCompiler compiler = getEcj();
281+
boolean success = false;
282+
if (compiler != null) {
283+
getLogger().debug("Using JSR-199 EclipseCompiler");
284+
// ECJ JSR-199 compiles against the latest Java version it supports if no source
285+
// version is given explicitly. BatchCompiler uses 1.3 as default. So check
286+
// whether a source version is specified, and if not supply 1.3 explicitly.
287+
String srcVersion = null;
288+
Iterator<String> allArgs = args.iterator();
289+
while (allArgs.hasNext()) {
290+
String option = allArgs.next();
291+
if ("-source".equals(option) && allArgs.hasNext()) {
292+
srcVersion = allArgs.next();
293+
break;
294+
}
280295
}
281-
282-
@Override
283-
public void done()
284-
{
296+
if (srcVersion == null) {
297+
getLogger().debug("ecj: no source level specified, defaulting to Java 1.3");
298+
args.add("-source");
299+
args.add("1.3");
285300
}
301+
final Locale defaultLocale = Locale.getDefault();
302+
final List<CompilerMessage> messages = messageList;
303+
DiagnosticListener<? super JavaFileObject> messageCollector = new DiagnosticListener<JavaFileObject>() {
304+
305+
@Override
306+
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
307+
// Convert to Plexus' CompilerMessage and append to messageList
308+
String fileName = "Unknown source";
309+
try {
310+
JavaFileObject file = diagnostic.getSource();
311+
if (file != null) {
312+
fileName = file.getName();
313+
}
314+
} catch (NullPointerException e) {
315+
// ECJ bug: diagnostic.getSource() may throw an NPE if there is no source
316+
}
317+
long startColumn = diagnostic.getColumnNumber();
318+
// endColumn may be wrong if the endPosition is not on the same line.
319+
long endColumn = startColumn + (diagnostic.getEndPosition() - diagnostic.getStartPosition());
320+
CompilerMessage message = new CompilerMessage(fileName,
321+
convert(diagnostic.getKind()), (int) diagnostic.getLineNumber(), (int) startColumn,
322+
(int) diagnostic.getLineNumber(), (int) endColumn,
323+
diagnostic.getMessage(defaultLocale));
324+
messages.add(message);
325+
}
326+
};
327+
StandardJavaFileManager manager = compiler.getStandardFileManager(messageCollector, defaultLocale,
328+
Charset.defaultCharset());
286329

287-
@Override
288-
public boolean isCanceled()
289-
{
290-
return false;
291-
}
330+
getLogger().debug("ecj command line: " + args);
331+
getLogger().debug("ecj input source files: " + allSources);
292332

293-
@Override
294-
public void setTaskName(String s)
295-
{
333+
Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromStrings(allSources);
334+
try {
335+
success = Boolean.TRUE
336+
.equals(compiler.getTask(devNull, manager, messageCollector, args, null, units).call());
337+
} catch (RuntimeException e) {
338+
throw new EcjFailureException(e.getLocalizedMessage());
296339
}
297-
298-
@Override
299-
public void worked(int i, int i1)
300-
{
340+
getLogger().debug(sw.toString());
341+
} else {
342+
// Use the BatchCompiler and send all errors to xml temp file.
343+
File errorF = null;
344+
try {
345+
errorF = File.createTempFile("ecjerr-", ".xml");
346+
getLogger().debug("Using legacy BatchCompiler; error file " + errorF);
347+
348+
args.add("-log");
349+
args.add(errorF.toString());
350+
args.addAll(allSources);
351+
352+
getLogger().debug("ecj command line: " + args);
353+
354+
success = BatchCompiler.compile(args.toArray(new String[args.size()]), devNull, devNull,
355+
new CompilationProgress() {
356+
@Override
357+
public void begin(int i) {
358+
}
359+
360+
@Override
361+
public void done() {
362+
}
363+
364+
@Override
365+
public boolean isCanceled() {
366+
return false;
367+
}
368+
369+
@Override
370+
public void setTaskName(String s) {
371+
}
372+
373+
@Override
374+
public void worked(int i, int i1) {
375+
}
376+
});
377+
getLogger().debug(sw.toString());
378+
379+
if (errorF.length() < 80) {
380+
throw new EcjFailureException(sw.toString());
381+
}
382+
messageList = new EcjResponseParser().parse(errorF, errorsAsWarnings);
383+
} finally {
384+
if (null != errorF) {
385+
try {
386+
errorF.delete();
387+
} catch (Exception x) {
388+
}
389+
}
301390
}
302-
});
303-
getLogger().debug(sw.toString());
304-
305-
List<CompilerMessage> messageList;
306-
boolean hasError = false;
307-
if(errorF.length() < 80)
308-
{
309-
throw new EcjFailureException(sw.toString());
310391
}
311-
messageList = new EcjResponseParser().parse(errorF, errorsAsWarnings);
312-
313-
for(CompilerMessage compilerMessage : messageList)
314-
{
315-
if(compilerMessage.isError())
316-
{
392+
boolean hasError = false;
393+
for (CompilerMessage compilerMessage : messageList) {
394+
if (compilerMessage.isError()) {
317395
hasError = true;
318396
break;
319397
}
320398
}
321-
if(!hasError && !success && !errorsAsWarnings)
322-
{
323-
CompilerMessage.Kind kind = errorsAsWarnings ? CompilerMessage.Kind.WARNING : CompilerMessage.Kind.ERROR;
324-
325-
//-- Compiler reported failure but we do not seem to have one -> probable exception
326-
CompilerMessage cm = new CompilerMessage("[ecj] The compiler reported an error but has not written it to its logging", kind);
399+
if (!hasError && !success && !errorsAsWarnings) {
400+
CompilerMessage.Kind kind = errorsAsWarnings ? CompilerMessage.Kind.WARNING
401+
: CompilerMessage.Kind.ERROR;
402+
403+
// -- Compiler reported failure but we do not seem to have one -> probable
404+
// exception
405+
CompilerMessage cm = new CompilerMessage(
406+
"[ecj] The compiler reported an error but has not written it to its logging", kind);
327407
messageList.add(cm);
328408
hasError = true;
329409

330-
//-- Try to find the actual message by reporting the last 5 lines as a message
410+
// -- Try to find the actual message by reporting the last 5 lines as a message
331411
String stdout = getLastLines(sw.toString(), 5);
332-
if(stdout.length() > 0)
333-
{
412+
if (stdout.length() > 0) {
334413
cm = new CompilerMessage("[ecj] The following line(s) might indicate the issue:\n" + stdout, kind);
335414
messageList.add(cm);
336415
}
@@ -339,14 +418,58 @@ public void worked(int i, int i1)
339418
} catch(EcjFailureException x) {
340419
throw x;
341420
} catch(Exception x) {
342-
throw new RuntimeException(x); // sigh
343-
} finally {
344-
if(null != errorF) {
345-
try {
346-
errorF.delete();
347-
} catch(Exception x) {}
421+
throw new RuntimeException(x); // sigh
422+
}
423+
}
424+
425+
private JavaCompiler getEcj() {
426+
ServiceLoader<JavaCompiler> javaCompilerLoader = ServiceLoader.load(JavaCompiler.class,
427+
BatchCompiler.class.getClassLoader());
428+
Class<?> c = null;
429+
try {
430+
c = Class.forName("org.eclipse.jdt.internal.compiler.tool.EclipseCompiler", false,
431+
BatchCompiler.class.getClassLoader());
432+
} catch (ClassNotFoundException e) {
433+
// Ignore
434+
}
435+
if (c != null) {
436+
for (JavaCompiler javaCompiler : javaCompilerLoader) {
437+
if (c.isInstance(javaCompiler)) {
438+
return javaCompiler;
439+
}
348440
}
349441
}
442+
getLogger().debug("Cannot find org.eclipse.jdt.internal.compiler.tool.EclipseCompiler");
443+
return null;
444+
}
445+
446+
private void addExtraSources(File dir, List<String> allSources) {
447+
DirectoryScanner scanner = new DirectoryScanner();
448+
scanner.setBasedir(dir.getAbsolutePath());
449+
scanner.setIncludes(new String[] { "**/*.java" });
450+
scanner.scan();
451+
for (String file : scanner.getIncludedFiles()) {
452+
allSources.add(new File(dir, file).getAbsolutePath());
453+
}
454+
}
455+
456+
private CompilerMessage.Kind convert(Diagnostic.Kind kind) {
457+
if (kind == null) {
458+
return CompilerMessage.Kind.OTHER;
459+
}
460+
switch (kind) {
461+
case ERROR:
462+
return errorsAsWarnings ? CompilerMessage.Kind.WARNING : CompilerMessage.Kind.ERROR;
463+
case WARNING:
464+
return CompilerMessage.Kind.WARNING;
465+
case MANDATORY_WARNING:
466+
return CompilerMessage.Kind.MANDATORY_WARNING;
467+
case NOTE:
468+
return CompilerMessage.Kind.NOTE;
469+
case OTHER:
470+
default:
471+
return CompilerMessage.Kind.OTHER;
472+
}
350473
}
351474

352475
private String getLastLines(String text, int lines)
@@ -396,6 +519,7 @@ private boolean isPreJava16(CompilerConfiguration config) {
396519
|| s.startsWith( "1.1" ) || s.startsWith( "1.0" );
397520
}
398521

522+
@Override
399523
public String[] createCommandLine( CompilerConfiguration config )
400524
throws CompilerException
401525
{

0 commit comments

Comments
 (0)