Capturing and handling non-zero JavaExec result code


(Michael Brand) #1

I’m writing a plugin for Compass/SASS and need to provide my own error handling for a non-zero JavaExec result code. There are two reasons for this:

  1. When an error occurs the relevant information is written to stdout instead of stderr. I capture stdout in a ByteArrayOutputStream and write it to logger.info in my doLast{} block. This doLast{} block is not executed when there’s a non-zero exit value. What I’d like to do is write stdout to logger.error if there is a non-zero exit value, but write it to logger.info if the exit value is zero.

  2. When errors occur they error messages are written to the generated files. I’d like to parse these files and display the error messages in logger.error (this has already caused some confusion.

I’ve looked at the JavaExec code, but as far as I can tell it swallows the ExecResult. It seems like this should be stored as a property in the JavaExec task instead. We might also introduce an onError{} bock to the JavaExec task where you can write code that will be executed if a non-zero result code is encountered.

I wanted to run this by this list before submitting it as a defect.


(Peter Niederwieser) #2

I recommend to write your own task (class) that makes use of ‘Project.exec()’. ‘LoggingManager’ (available via ‘Task.logging’) and/or ‘(Base)ExecSpec’ can help with handling standard out/err.


(Michael Brand) #3

Thanks for your response, Peter.

My first thought was to run the java command through ant. But I don’t think these are the best solutions because they require non-Gradle solutions and special knowledge. It’s far more natural to have access to the result code in a block that is executed after the result is returned.

I’ve locally modified the JavaExec code with the following:

public class JavaExec1 extends ConventionTask implements JavaExecSpec {
    private JavaExecAction javaExecHandleBuilder;
    private ExecResult result = null;
      public JavaExec1() {
        FileResolver fileResolver = getServices().get(FileResolver.class);
        javaExecHandleBuilder = new DefaultJavaExecAction(fileResolver);
        getOutputs().upToDateWhen(Specs.<Object>satisfyNone());
    }
      @TaskAction
    void exec() {
        setMain(getMain()); // make convention mapping work (at least for 'main')
        result = javaExecHandleBuilder.execute();
    }
    ...
   public ExecResult getResult() {
  return result;
 }
   public void setResult(ExecResult result) {
  this.result = result;
 }

My build.gradle compileSass definition in SassPlugin.groovy file contains the block:

doLast {
 if (result.exitValue) {
  project.logger.error "\n\n" + project.sass.outputStream.toString().split("\n").
    collect{it.trim()}.findAll{it.startsWith("error")}.join("\n")
  project.logger.error "\nNote: SASS compiles CSS files even if errors occur and writes error messages \n" +
    "
 into the generated CSS file as comments.
Generated CSS files are located under \n" +
    "
 ${project.file(project.sass.cssDir)}"
        throw new GradleException("Compile failed for SASS files.
See output for details.")
 } else {
  project.logger.info(project.sass.outputStream.toString())
 }
}

Is there a reason that the return code is not retained as an attribute for the JavaExec task?

(Modified to add accessors and doLast{} lines in response to Peter’s answer below)


(Peter Niederwieser) #4

What do you mean by “my build.gradle file contains the block”? Where exactly? Do you make sure this block runs after the task? Also, ‘result’ in the task class is private, so it looks like you don’t want anyone else to access it (although Groovy currently won’t stop you).

Why does the task class implement ‘JavaExecSpec’? Can’t it be more tailored to the job at hand, rather than acting as a generic exec-like task?


(Michael Brand) #5

The build.gradle is inside a “doLast{}” block. I’m not sure if this is ideal, but it works.

The “result” attribute has accessors that weren’t in my code snippet.

After the original post I decided to see how hard it would be to make the change. After a couple of wrong turns (e.g. trying to use Groovy), I took the following steps:

  1. I copied the file JavaExec.java from the 1.0-milestone8a build and copied it into a file in my buildSrc directory. 2. I renamed the class to JavaExec1 to avoid a naming collision. Otherwise I started with an exact copy of JavaExec.java. 3. I added the “result” attribute getResult() and setResult() methods (setResult() is probably unnecessary, but it’s more Groovy-like). 4. I added "result = " in front of “javaExecHandleBuilder.execute();” in the “exec()” method. 5. I modified the sassCompile task definition in my SassPlugin.groovy to use JavaExec1 as its type instead of JavaExec. 6. I added “ignoreExitValue = true” to the task configuration. 7. I added a new “doLast{}” that contained the second block of code in my previous post.

Based on this, I think this should be a feature request. It looks like making the result code available would be a very minor change to JavaExec.java and I don’t see any downside.

My very limited understanding of the lifecycle it suggests that doLast{} consistently execute after exec() is called. But I really like the idea of providing an “onError{}” block instead that, if it exists, is called whenever there is a non-zero result code.

If it helps I can post the entire code for SassPlugin.groovy and JavaExec1.java.