Skip to content

Commit c726899

Browse files
fjalvingholamy
authored andcommitted
Fix issues in ecj compiler's error reporting (#51)
* Fix passing compilerArgs arguments properly so that multi-dash options like --add-modules can be passed; report compiler errors that ecj mumbles about on stdout instead of properly reporting them in the compiler output log. * Obey the "errorsAsWarnings" option for compiler command line errors too. * Add JUnit test for -- arguments and showing errors that are on stdout * Add license.
1 parent 090c592 commit c726899

File tree

3 files changed

+243
-24
lines changed

3 files changed

+243
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.codehaus.plexus.compiler.eclipse;
2+
3+
/**
4+
* @author <a href="mailto:[email protected]">Frits Jalvingh</a>
5+
* Created on 22-4-18.
6+
*/
7+
public class EcjFailureException extends RuntimeException {
8+
private final String ecjOutput;
9+
10+
public EcjFailureException(String ecjOutput) {
11+
super("Failed to run the ecj compiler: " + ecjOutput);
12+
this.ecjOutput = ecjOutput;
13+
}
14+
15+
public String getEcjOutput()
16+
{
17+
return ecjOutput;
18+
}
19+
}

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

Lines changed: 124 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ public EclipseJavaCompiler()
6464
public CompilerResult performCompile(CompilerConfiguration config )
6565
throws CompilerException
6666
{
67-
// Build settings from configuration
6867
List<String> args = new ArrayList<>();
68+
args.add("-noExit"); // Make sure ecj does not System.exit on us 8-/
69+
70+
// Build settings from configuration
6971
if ( config.isDebug() )
7072
{
7173
args.add("-preserveAllLocals");
@@ -146,13 +148,44 @@ else if(extras.containsKey("-errorsAsWarnings"))
146148

147149
for(Entry<String, String> entry : extras.entrySet())
148150
{
151+
/*
152+
* The compiler mojo makes quite a mess of passing arguments, depending on exactly WHICH
153+
* way is used to pass them. The method method using <compilerArguments> uses the tag names
154+
* of its contents to denote option names, and so the compiler mojo happily adds a '-' to
155+
* all of the names there and adds them to the "custom compiler arguments" map as a
156+
* name, value pair where the name always contains a single '-'. The Eclipse compiler (and
157+
* javac too, btw) has options with two dashes (like --add-modules for java 9). These cannot
158+
* be passed using a <compilerArguments> tag.
159+
*
160+
* The other method is to use <compilerArgs>, where each SINGLE argument needs to be passed
161+
* using an <arg>xxxx</arg> tag. In there the xxx is not manipulated by the compiler mojo, so
162+
* if it starts with a dash or more dashes these are perfectly preserved. But of course these
163+
* single <arg> entries are not a pair. So the compiler mojo adds them as pairs of (xxxx, null).
164+
*
165+
* We use that knowledge here: if a pair has a null value then do not mess up the key but
166+
* render it as a single value. This should ensure that something like:
167+
* <compilerArgs>
168+
* <arg>--add-modules</arg>
169+
* <arg>java.se.ee</arg>
170+
* </compilerArgs>
171+
*
172+
* is actually added to the command like as such.
173+
*
174+
* (btw: the above example will still give an error when using ecj <= 4.8M6:
175+
* invalid module name: java.se.ee
176+
* but that seems to be a bug in ecj).
177+
*/
149178
String opt = entry.getKey();
150-
if(! opt.startsWith("-"))
151-
opt = "-" + opt; // compiler mojo apparently messes with this; make sure we are safe
152-
args.add(opt);
153-
String value = entry.getValue();
154-
if(null != value && ! value.isEmpty())
155-
args.add(value);
179+
String optionValue = entry.getValue();
180+
if(null == optionValue) {
181+
//-- We have an option from compilerArgs: use the key as-is as a single option value
182+
args.add(opt);
183+
} else {
184+
if(!opt.startsWith("-"))
185+
opt = "-" + opt;
186+
args.add(opt);
187+
args.add(optionValue);
188+
}
156189
}
157190

158191
// Output path
@@ -209,65 +242,102 @@ else if(extras.containsKey("-errorsAsWarnings"))
209242

210243
// Compile! Send all errors to xml temp file.
211244
File errorF = null;
212-
try {
245+
try
246+
{
213247
errorF = File.createTempFile("ecjerr-", ".xml");
214248

215249
args.add("-log");
216250
args.add(errorF.toString());
217251

218252
// Add all sources.
219253
int argCount = args.size();
220-
for(String source : config.getSourceLocations()) {
254+
for(String source : config.getSourceLocations())
255+
{
221256
File srcFile = new File(source);
222-
if(srcFile.exists()) {
223-
Set<String> ss = getSourceFilesForSourceRoot( config, source );
257+
if(srcFile.exists())
258+
{
259+
Set<String> ss = getSourceFilesForSourceRoot(config, source);
224260
args.addAll(ss);
225261
}
226262
}
227263
args.addAll(extraSourceDirs);
228-
if(args.size() == argCount) {
264+
if(args.size() == argCount)
265+
{
229266
//-- Nothing to do -> bail out
230267
return new CompilerResult(true, Collections.EMPTY_LIST);
231268
}
232269

270+
getLogger().debug("ecj command line: " + args);
271+
233272
StringWriter sw = new StringWriter();
234273
PrintWriter devNull = new PrintWriter(sw);
235274

236275
//BatchCompiler.compile(args.toArray(new String[args.size()]), new PrintWriter(System.err), new PrintWriter(System.out), new CompilationProgress() {
237-
BatchCompiler.compile(args.toArray(new String[args.size()]), devNull, devNull, new CompilationProgress() {
238-
@Override public void begin(int i) {
276+
boolean success = BatchCompiler.compile(args.toArray(new String[args.size()]), devNull, devNull, new CompilationProgress() {
277+
@Override
278+
public void begin(int i)
279+
{
239280
}
240281

241-
@Override public void done() {
282+
@Override
283+
public void done()
284+
{
242285
}
243286

244-
@Override public boolean isCanceled() {
287+
@Override
288+
public boolean isCanceled()
289+
{
245290
return false;
246291
}
247292

248-
@Override public void setTaskName(String s) {
293+
@Override
294+
public void setTaskName(String s)
295+
{
249296
}
250297

251-
@Override public void worked(int i, int i1) {
298+
@Override
299+
public void worked(int i, int i1)
300+
{
252301
}
253302
});
254303
getLogger().debug(sw.toString());
255304

256305
List<CompilerMessage> messageList;
257306
boolean hasError = false;
258-
if(errorF.length() < 80) {
259-
throw new IOException("Failed to run the ECJ compiler:\n" + sw.toString());
307+
if(errorF.length() < 80)
308+
{
309+
throw new EcjFailureException(sw.toString());
260310
}
261311
messageList = new EcjResponseParser().parse(errorF, errorsAsWarnings);
262312

263-
for(CompilerMessage compilerMessage : messageList) {
264-
if(compilerMessage.isError()) {
313+
for(CompilerMessage compilerMessage : messageList)
314+
{
315+
if(compilerMessage.isError())
316+
{
265317
hasError = true;
266318
break;
267319
}
268320
}
269-
return new CompilerResult(! hasError, messageList);
270-
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);
327+
messageList.add(cm);
328+
hasError = true;
329+
330+
//-- Try to find the actual message by reporting the last 5 lines as a message
331+
String stdout = getLastLines(sw.toString(), 5);
332+
if(stdout.length() > 0)
333+
{
334+
cm = new CompilerMessage("[ecj] The following line(s) might indicate the issue:\n" + stdout, kind);
335+
messageList.add(cm);
336+
}
337+
}
338+
return new CompilerResult(!hasError || errorsAsWarnings, messageList);
339+
} catch(EcjFailureException x) {
340+
throw x;
271341
} catch(Exception x) {
272342
throw new RuntimeException(x); // sigh
273343
} finally {
@@ -279,6 +349,36 @@ else if(extras.containsKey("-errorsAsWarnings"))
279349
}
280350
}
281351

352+
private String getLastLines(String text, int lines)
353+
{
354+
List<String> lineList = new ArrayList<>();
355+
text = text.replace("\r\n", "\n");
356+
text = text.replace("\r", "\n"); // make sure eoln is \n
357+
358+
int index = text.length();
359+
while(index > 0) {
360+
int before = text.lastIndexOf('\n', index - 1);
361+
362+
if(before + 1 < index) { // Non empty line?
363+
lineList.add(text.substring(before + 1, index));
364+
lines--;
365+
if(lines <= 0)
366+
break;
367+
}
368+
369+
index = before;
370+
}
371+
372+
StringBuilder sb = new StringBuilder();
373+
for(int i = lineList.size() - 1; i >= 0; i--)
374+
{
375+
String s = lineList.get(i);
376+
sb.append(s);
377+
sb.append(System.getProperty("line.separator")); // 8-/
378+
}
379+
return sb.toString();
380+
}
381+
282382
static private void append(StringBuilder warns, String s) {
283383
if(warns.length() > 0)
284384
warns.append(',');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.codehaus.plexus.compiler.eclipse;
2+
3+
/**
4+
* The MIT License
5+
*
6+
* Copyright (c) 2005, The Codehaus
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
9+
* this software and associated documentation files (the "Software"), to deal in
10+
* the Software without restriction, including without limitation the rights to
11+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12+
* of the Software, and to permit persons to whom the Software is furnished to do
13+
* so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
27+
import org.codehaus.plexus.PlexusTestCase;
28+
import org.codehaus.plexus.compiler.Compiler;
29+
import org.codehaus.plexus.compiler.CompilerConfiguration;
30+
import org.codehaus.plexus.util.FileUtils;
31+
import org.junit.Assert;
32+
33+
import java.io.File;
34+
import java.util.Collections;
35+
import java.util.HashSet;
36+
import java.util.List;
37+
import java.util.Set;
38+
39+
/**
40+
* @author <a href="mailto:[email protected]">Frits Jalvingh</a>
41+
* Created on 22-4-18.
42+
*/
43+
public class EclipseCompilerDashedArgumentsTest extends PlexusTestCase {
44+
45+
public static final String BAD_DOUBLEDASH_OPTION = "--grubbelparkplace";
46+
47+
private CompilerConfiguration getConfig() throws Exception {
48+
String sourceDir = getBasedir() + "/src/test-input/src/main";
49+
50+
List<String> filenames = FileUtils.getFileNames( new File( sourceDir ), "**/*.java", null, false, true );
51+
Collections.sort( filenames );
52+
Set<File> files = new HashSet<>();
53+
for(String filename : filenames)
54+
{
55+
files.add(new File(filename));
56+
}
57+
58+
CompilerConfiguration compilerConfig = new CompilerConfiguration();
59+
compilerConfig.setDebug(false);
60+
compilerConfig.setShowDeprecation(false);
61+
62+
// compilerConfig.setClasspathEntries( getClasspath() );
63+
compilerConfig.addSourceLocation( sourceDir );
64+
compilerConfig.setOutputLocation( getBasedir() + "/target/eclipse/classes");
65+
FileUtils.deleteDirectory( compilerConfig.getOutputLocation() );
66+
// compilerConfig.addInclude( filename );
67+
compilerConfig.setForceJavacCompilerUse(false);
68+
compilerConfig.setSourceFiles(files);
69+
70+
compilerConfig.setTargetVersion("1.8");
71+
compilerConfig.setSourceVersion("1.8");
72+
return compilerConfig;
73+
}
74+
75+
/**
76+
* Start the eclipse compiler with a bad option that has two dashes. It should abort, and the error
77+
* message should show the actual bad option with two dashes. This ensures that both dashes are passed
78+
* to the compiler proper.
79+
*
80+
* This also tests that con-compile errors are shown properly, as the error caused by
81+
* the invalid option is not part of the error output but part of the data sent to stdout/stderr.
82+
*/
83+
public void testDoubleDashOptionsArePassedWithTwoDashes() throws Exception
84+
{
85+
Compiler compiler = (Compiler) lookup( Compiler.ROLE, "eclipse" );
86+
CompilerConfiguration config = getConfig();
87+
config.addCompilerCustomArgument(BAD_DOUBLEDASH_OPTION, "b0rk3d");
88+
89+
try
90+
{
91+
compiler.performCompile(config);
92+
Assert.fail("Expected an exception to be thrown");
93+
} catch(EcjFailureException x) {
94+
String ecjOutput = x.getEcjOutput();
95+
Assert.assertTrue("The output should report the failure with two dashes: " + ecjOutput
96+
, ecjOutput.contains(BAD_DOUBLEDASH_OPTION) && ! ecjOutput.contains("-" + BAD_DOUBLEDASH_OPTION)
97+
);
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)