I am running into various problems when trying to run android start-server
from within Java as external process. The Java is called by Gradle. Let me describe to you what is exactly happening in various scenarios:
Environment
Assumption
adb daemon is killed and will start up when calling adb start-server
.
This code:
DefaultExecutor executor = new org.apache.commons.exec.DefaultExecutor();
executor.execute(org.apache.commons.exec.CommandLine.parse("adb start-server"));
log.info("Checkpoint!");
When run from Gradle run
task of the application plugin, will display the start-server output, i.e.:
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
and then it will hang, i.e. "Checkpoint!" will never be logged. Killing the adb.exe
process manually will cause the code to continue execution.
Why this call blocks? When adb start-server
command is run from terminal, after a couple of seconds the control is returned to the terminal, so why it doesn't happen in the code?
If instead I use directly the Java runtime like so:
Runtime.getRuntime().exec(new String[]{"adb", "start-server"});
log.info("Checkpoint!");
System.exit(0);
If calling from Gradle as previously, the "Checkpoint!" will be logged. However, the execution will hang on System.exit(0)
. Killing adb.exe
manually will again make Gradle call finish.
In this case no output of adb start-server
is displayed.
Interesting thing is that when, instead of Gradle, I run the application from IntelliJ IDEA with build setup mimicking that of Gradle's, everything works fine and the application finishes properly.
Why Gradle hangs on System.exit(0)
and IntelliJ doesn't? Is this somehow related to the fact that Gradle itself is a process that internally calls Java and in case of IntelliJ, Java is called immediately without any indirection? Why does it matter?
Ultimately, I want to be able to run this from Gradle without hangs of any kind. Logging output of adb start-server
would be a bonus. I would greatly appreciate any hints how to do this.
Ultimately I solved the problem by doing the following:
Inside my program, for calling adb start-server
, I no longer use org.apache.commons.exec.DefaultExecutor.execute("adb start-server")
nor Java's Runtime.getRuntime().exec("adb start-server")
.
Instead, I use java.lang.ProcessBuilder("adb start-server").inheritIO().start().waitFor()
. Note the inheritIO()
is added in Java 7 and allows for online reading of the started process stdout, among others.
EDIT The inheritIO()
in Gradle has no effect when Gradle is called from CLI directly as opposed to being called by IntelliJ IDEA. See this question for details.
Probably implementing StreamGobbler
as described here would fix the problem.
In Gradle, instead of Gradle's application plugin's run
task, I again use ProcessBuilder
reusing run
tasks variables. It looks like so:
apply plugin: 'java'
apply plugin: 'application'
sourceCompatibility = '1.7'
targetCompatibility = '1.7'
// configuration-time of the original run task
run {
main = com.example.MainClass; // without this, our custom run will try to run "null"
}
task myRun(dependsOn: build) {
// execution-time of our custom run task.
doFirst {
ProcessBuilder pb = new ProcessBuilder(tasks['run'].commandLine);
pb.directory(tasks['run'].workingDir);
// works when the gradle command is executed from IntelliJ IDEA, has no effect when executed from standalone CLI interface.
pb.inheritIO();
Process proc = pb.start();
proc.waitFor();
}
}
As for the reason of the strange behaviors, I don't have any definite answer, so I refrain from further comments.
EDIT 9 July 2013
This question seems to point out the answer to Question 2 is: on windows, the parent process waits for child process before it terminates. Unfortunately, the solutions proposed there do not work because of problem stated in Question 1.
Hope this helps,
Konrad