I have a build script that weaves java classes at ...
# community-support
j
I have a build script that weaves java classes at the end of the
compileJava
task so that gradle caches the weaved classes as task output automatically. Weaving is done using
compileJava { doLast { project.javaexec {.... } } }
. Because
project.javaexec
is deprecated I migrated it to
def execOutput = providers.javaexec
but now I am unable to get the process output/error. It always seems to be empty. Any Idea?
Copy code
compileJava {
  doLast {
    def execOutput = providers.javaexec {
        ...
    }
    // trigger javaexec
    def result = execOutput.getResult().get()
    println execOutput.getStandardOutput().getAsText().get()
    println execOutput.getStandardError().getAsText().get()
    result.assertNormalExitValue()
  }
}
Ok if weaving fails and the process exists with code 1 then `execOutput.getResult().get()`will actually throw an exception. If I catch that and then try to access the error stream I get another thrown exception. Not very useful.
v
Can you shown an MCVE?
j
Copy code
plugins {
    id 'java-library'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(23)
    }
}

dependencies {
    implementation "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.15"
}

// old version
//compileJava {
//    doLast {
//        project.javaexec {
//            executable = javaToolchains.launcherFor {
//                languageVersion = JavaLanguageVersion.of(23)
//            }.map { it.executablePath }.get()
//            mainClass = "org.eclipse.persistence.tools.weaving.jpa.StaticWeave"
//            classpath = configurations.compileClasspath
//        }
//    }
//}

// migrated version
compileJava {

    def execOutput = providers.javaexec {
        executable = javaToolchains.launcherFor {
            languageVersion = JavaLanguageVersion.of(23)
        }.map { it.executablePath }.get()
        mainClass = "org.eclipse.persistence.tools.weaving.jpa.StaticWeave"
        classpath = configurations.compileClasspath
    }

    doLast {
        try {
            println "trigger javaexec"
            def result = execOutput.getResult().get()
        } catch (Exception e) {
            println "trigger javaexec: exception caught: " + e.getMessage()
            e.printStackTrace()
            try {
                def stdOut = execOutput.getStandardOutput().getAsText().get()
                println stdOut
            } catch (Exception e2) {
                // "e2" is same instance as "e" as noted in the JavaDoc of ExecOutput.getResult()
                println "trigger javaexec: Cannot access standard out of process: " + e2.getMessage()
            }
            try {
                def stdErr = execOutput.getStandardError().getAsText().get()
                println stdErr
            } catch (Exception e2) {
                // "e2" is same instance as "e" as noted in the JavaDoc of ExecOutput.getResult()
                println "trigger javaexec: Cannot access standard error of process: " + e2.getMessage()
            }
        }
        println "trigger javaexec: done"
    }
}
If you switch compileJava to the old version then you see help output of StaticWeave class. The migrated version just throws an exception saying that StaticWeave has exited with code 1 without any further information being available.
I don't really want to make a dedicated JavaExec task because then I cannot do an in-place weaving as it would invalidated the output cache of compileJava once I override the class files with the weaved class file. So I am not really sure how to migrate without loosing stdout and stderr.
v
Set
ignoreExitValue
to
true
. Then you can check
result
for the exit value and access stdout and stderr without problems or also let it rethrow the exception.
j
Ok that works, thanks. But I kind of see it as a workaround. Shouldn't Gradle log the output of the process if Gradle manages the exit code?
v
Probably not 🤷‍♂️
Ask the Gradle folks by opening an issue with your request 🙂
You can also configure custom output streams
j
I just found an example in the documentation how to get a reference to
ExecOperations
in an ad-hoc task. Using that one for
javaexec
behaves the same as
project.javaexec
even without the
ignoreExitValue
. Looks like
ExecOperations
is meant to be the drop-in replacement although it is a bit clunky to get a reference of it for ad-hoc tasks.
v
The other way around.
project.javaexec { ... }
is just a convenience method for using
ExecOperations
when you are not in a plugin or similar.
So yes, those behave exactly the same
This would be the way in Kotlin DSL:
Copy code
interface ExecOperationsProvider {
    @get:Inject
    val execOperations: ExecOperations
}
objects.newInstance<ExecOperationsProvider>().execOperations.javaexec {
    // ...
}
Or this in Groovy DSL if you are still bound to that (poor you)
Copy code
interface ExecOperationsProvider {
   @Inject
   ExecOperations getExecOperations()
}
objects.newInstance(ExecOperationsProvider).execOperations.javaexec {
   // ...
}
j
Yeah poor me did the last one. Not sure if Kotlin DSL is so much better and if its worth converting the build files.
v
By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio.