diff --git a/README.md b/README.md index e8b64ea..33b89f2 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,18 @@ It is also possible to set the name of the pid file allowing more than one proce ready 'Started Application' pidLockFileName '.other.pid.lock' } + +###Log File + +The `SpawnProcessTask` works by directly reading the output stream of the child process it starts, looking for the ready statement. +If you specify a `logFileName` it will instead route the output stream to that file and then tail that file looking for the ready statement. +This is very useful for long lived processes that generate lots of output and would otherwise deadlock (see http://stackoverflow.com/questions/3285408/java-processbuilder-resultant-process-hangs#answer-3285479) + +The log file will be removed by the `SpawnProcessTask` if it exists before spawning the process, effectively truncating it. + + task startServer(type: SpawnProcessTask) { + command "java -jar someScriptHere.sh" + ready 'Started Application' + pidLockFileName '.other.pid.lock' + logFileName = 'myServerLog.txt' + } \ No newline at end of file diff --git a/src/main/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTask.groovy b/src/main/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTask.groovy index effcb03..78744bc 100644 --- a/src/main/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTask.groovy +++ b/src/main/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTask.groovy @@ -8,6 +8,7 @@ class SpawnProcessTask extends DefaultSpawnTask { String command String ready List outputActions = new ArrayList() + String logFileName = null; @Input Map environmentVariables = new HashMap() @@ -24,6 +25,10 @@ class SpawnProcessTask extends DefaultSpawnTask { outputActions.add(outputClosure) } + File getLogFile() { + return logFileName == null ? null : new File(directory, logFileName) + } + @TaskAction void spawn() { if (!(command && ready)) { @@ -32,13 +37,14 @@ class SpawnProcessTask extends DefaultSpawnTask { def pidFile = getPidFile() if (pidFile.exists()) throw new GradleException("Server already running!") + //if (logFile != null && logFile.exists()) logFile.delete() def process = buildProcess(directory, command) waitToProcessReadyOrClosed(process) } private void waitToProcessReadyOrClosed(Process process) { - boolean isReady = waitUntilIsReadyOrEnd(process) + boolean isReady = waitUntilIsReadyOrEnd(logFile, process) if (isReady) { stampLockFile(pidFile, process) } else { @@ -57,18 +63,29 @@ class SpawnProcessTask extends DefaultSpawnTask { } } - private boolean waitUntilIsReadyOrEnd(Process process) { + private boolean waitUntilIsReadyOrEnd(File logFile, Process process) { def line - def reader = new BufferedReader(new InputStreamReader(process.getInputStream())) + def reader = logFile == null ? + new BufferedReader(new InputStreamReader(process.getInputStream())) : + new BufferedReader(new InputStreamReader(new FileInputStream(logFile))) boolean isReady = false - while (!isReady && (line = reader.readLine()) != null) { - logger.quiet line - runOutputActions(line) - if (line.contains(ready)) { - logger.quiet "$command is ready." - isReady = true + while (!isReady) { + line = reader.readLine() + if (line == null){ + if (logFile != null && process.alive) + Thread.sleep(10) + else + break + } else { + logger.quiet line + runOutputActions(line) + if (line.contains(ready)) { + logger.quiet "$command is ready." + isReady = true + } } } + if (logFile != null) reader.close() isReady } @@ -78,9 +95,12 @@ class SpawnProcessTask extends DefaultSpawnTask { } } - private Process buildProcess(String directory, String command) { - def builder = new ProcessBuilder(command.split(' ')) + private Process buildProcess(String directory, String ... command) { + def builder = new ProcessBuilder(command) builder.redirectErrorStream(true) + if (logFile != null){ + builder.redirectOutput(logFile) + } builder.environment().putAll(environmentVariables) builder.directory(new File(directory)) builder.start() diff --git a/src/test/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTaskSpec.groovy b/src/test/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTaskSpec.groovy index 22e96b1..ccbdfd6 100644 --- a/src/test/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTaskSpec.groovy +++ b/src/test/groovy/com/wiredforcode/gradle/spawn/SpawnProcessTaskSpec.groovy @@ -46,6 +46,70 @@ class SpawnProcessTaskSpec extends Specification { task.getPidFile().exists() } + void "should property start process when using log file"() { + given: + def command = './process.sh' + def ready = 'It is done...' + + and: + setExecutableProcess("process.sh") + + and: + task.command = command + task.ready = ready + task.directory = directory.toString() + task.logFileName = "log.txt" + + when: + task.spawn() + + then: + task.getPidFile().exists() + } + + void "should record all output in log file when using a log file"() { + given: + def command = './process.sh' + def ready = 'It is done...' + + and: + setExecutableProcess("process.sh") + + and: + task.command = command + task.ready = ready + task.directory = directory.toString() + task.logFileName = "log.txt" + + when: + task.spawn() + + then: + task.logFile.text.startsWith("Starting...\nIt is done...\n") + } + + void "should remove log file before starting process"() { + given: + def command = './process.sh' + def ready = 'It is done...' + + and: + setExecutableProcess("process.sh") + + and: + task.command = command + task.ready = ready + task.directory = directory.toString() + task.logFileName = "log.txt" + task.logFile.text = "Remove me" + + when: + task.spawn() + + then: + task.logFile.text.startsWith("Starting...\nIt is done...\n") + } + void "should allow the name of the pid lock file to be overriden"() { given: def command = './process.sh'