/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2001, ThoughtWorks, Inc.
* 200 E. Randolph, 25th Floor
* Chicago, IL 60601 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************/
package net.sourceforge.cruisecontrol.builders;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.util.BuildOutputLogger;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.CompositeConsumer;
import net.sourceforge.cruisecontrol.util.IO;
import net.sourceforge.cruisecontrol.util.StreamConsumer;
import net.sourceforge.cruisecontrol.util.StreamPumper;
import net.sourceforge.cruisecontrol.util.StreamLogger;
import org.apache.log4j.Logger;
/**
* Takes a script and runs it. Monitors how long the script takes to run, and
* returns whether the script completed or not.
*/
public class ScriptRunner {
static final Logger LOG = Logger.getLogger(ScriptRunner.class);
public static final long NO_TIMEOUT = -1;
private static class AsyncKiller implements Runnable {
private final Process p;
private final long timeout;
private boolean killed;
AsyncKiller(final Process p, final long timeout) {
this.p = p;
this.timeout = timeout;
}
public void run() {
try {
Thread.sleep(timeout * 1000L);
synchronized (this) {
p.destroy();
killed = true;
}
} catch (InterruptedException expected) {
// ignore, this is expected if the script was killed
}
}
public synchronized boolean processKilled() {
return killed;
}
}
/**
* build and return the results via xml. debug status can be determined from
* log4j category once we get all the logging in place.
*
* @param workingDir The directory to run the script from.
* @param script The details on the script to be run.
* @param timeout Time in seconds after which the script should be killed.
* @return true if the script was killed due to timeout expiring
* @throws CruiseControlException if it breaks
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger)
*/
public boolean runScript(final File workingDir, final Script script, final long timeout)
throws CruiseControlException {
return runScript(workingDir, script, timeout, null, null);
}
/**
* build and return the results via xml. debug status can be determined from
* log4j category once we get all the logging in place.
*
* @param workingDir The directory to run the script from.
* @param script The details on the script to be run.
* @param timeout Time in seconds after which the script should be killed.
* @param buildOutputConsumer Optional script output consumer.
* @return true if the script was killed due to timeout expiring
* @throws CruiseControlException if it breaks
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger)
*/
public boolean runScript(final File workingDir, final Script script, final long timeout,
final BuildOutputLogger buildOutputConsumer)
throws CruiseControlException {
return runScript(workingDir, script, timeout, null, buildOutputConsumer);
}
/**
* Run the given script, let it run on maximum the given timeout, (optionally) feed its STDIN by data from the given
* stream, and (optionally) feed its STDOUT/STDERR to the given consumer.
*
* NOTE: everything printed to STDOUT and STDERR by the script is automatically passed to the CruiseControl log file
* through consumers returned by {@link #getDirectOutLogger()} and {@link #getDirectErrLogger()}.
* NOTE: everything printed to STDOUT and STDERR is passed to {@link Script} (if it is the instance of
* {@link StreamConsumer}), if allowed by the value returned by {@link #letConsumeOut()} and
* {@link #letConsumeErr()} methods.
* NOTE: everything printed to STDOUT and STDERR is passed to {@link BuildOutputLogger}, if allowed by the value
* returned by {@link #letConsumeOut()} and {@link #letConsumeErr()} methods.
*
* @param workingDir The directory to run the script from.
* @param script The details on the script to be run.
* @param timeout Time in seconds after which the script is killed, if still running.
* @param scriptInputProvider Optional script input provider. If set, data read from it are passed into the STDIN
* of the script.
* @param buildOutputConsumer Optional script output consumer. Everything printed on STDOUT by the script is passed
* into the consumer. Can be <code>null</code>, if you don't care about it.
* @return true if the script was killed due to timeout expiring
* @throws CruiseControlException if it breaks
*/
public boolean runScript(final File workingDir, final Script script, final long timeout,
final InputStream scriptInputProvider, final BuildOutputLogger buildOutputConsumer)
throws CruiseControlException {
final Commandline commandline = script.buildCommandline();
if (workingDir != null) {
// TODO: workingDir should be set already by the script
// Remove workingDir parameter from the interface
commandline.setWorkingDir(workingDir);
}
if (buildOutputConsumer != null) {
buildOutputConsumer.clear();
}
final Process p;
try {
// Do not close STDOUT of the process. It will be closed by Pumper, once read (see Pumper#run())
commandline.setCloseStdIn(false);
p = commandline.execute();
} catch (IOException e) {
throw new CruiseControlException("Encountered an IO exception while attempting to execute '"
+ script.toString() + "'. CruiseControl cannot continue.", e);
}
final CompositeConsumer consumerForError = new CompositeConsumer(getDirectErrLogger());
final CompositeConsumer consumerForOut = new CompositeConsumer(getDirectOutLogger());
if (buildOutputConsumer != null) {
if (letConsumeErr()) {
consumerForError.add(buildOutputConsumer);
}
if (letConsumeOut()) {
consumerForOut.add(buildOutputConsumer);
}
}
// Pass the output through the script if required (required by default), since it searches for error
// string in the messages, and includes the messages in build report.
if (script instanceof StreamConsumer) {
if (letConsumeErr()) {
consumerForError.add((StreamConsumer) script);
}
if (letConsumeOut()) {
consumerForOut.add((StreamConsumer) script);
}
}
final StreamPumper errorPumper = getErrPumper(p, consumerForError);
final StreamPumper outPumper = getOutPumper(p, consumerForOut);
final StreamPumper inPumper = getInPumper(p, scriptInputProvider);
final Thread stdin = new Thread(inPumper);
stdin.start();
final Thread stderr = new Thread(errorPumper);
stderr.start();
final Thread stdout = new Thread(outPumper);
stdout.start();
final AsyncKiller killer = new AsyncKiller(p, timeout);
final Thread asyncKillerThread;
if (timeout > 0) {
asyncKillerThread = new Thread(killer);
asyncKillerThread.start();
} else {
asyncKillerThread = null;
}
int exitCode = -1;
try {
exitCode = p.waitFor();
if (asyncKillerThread != null) {
asyncKillerThread.interrupt();
}
stderr.join();
stdout.join();
stdin.join();
} catch (InterruptedException e) {
LOG.info("Was interrupted while waiting for script to finish."
+ " CruiseControl will continue, assuming that it completed");
} finally {
IO.close(p);
}
script.setExitCode(exitCode);
if (buildOutputConsumer != null) {
buildOutputConsumer.clear();
}
return !killer.processKilled();
}
public boolean runScript(Script script, long timeout, BuildOutputLogger logger) throws CruiseControlException {
return runScript(null, script, timeout, null, logger);
}
/**
* Returns the instance of StreamPumper which reads data from STDOUT of the process. This default
* implementation returns new instance of StreamPumper class filled by <code>p.getInputStream()</code>
* and <code>consumer</code>.
*
* @param p the process to read STDOUT from. Note that the p.getInputStream() is called here!
* @param consumer the consumer to which the STDOUT is pushed by the pumper
* @return the instance of stream pumper.
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected StreamPumper getOutPumper(final Process p, final StreamConsumer consumer) {
return new StreamPumper(p.getInputStream(), consumer);
} // getOutPumper
/**
* Returns the instance of StreamPumper which reads data from STDERR of the process. This default
* implementation returns new instance of StreamPumper class filled by <code>p.getErrorStream()</code>
* and <code>consumer</code>.
*
* @param p the process to read STDERR from. Note that the p.getErrorStream() is called here!
* @param consumer the consumer to which the STDERR is pushed by the pumper
* @return the instance of stream pumper.
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected StreamPumper getErrPumper(final Process p, final StreamConsumer consumer) {
return new StreamPumper(p.getErrorStream(), consumer);
} // getErrPumper
/**
* Returns the instance of StreamPumper which writes data to STDIN of the process. This default
* implementation returns new instance of StreamPumper class filling <code>p.getOutputStream()</code>
* from <code>source</code> stream.
*
* @param p the process to write STDIN to. Note that the p.getOutputStream() is called here!
* @param source the stream from which to read data fill to the process
* @return the instance of stream pumper.
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected StreamPumper getInPumper(final Process p, final InputStream source) {
return new StreamPumper(source, true, null, p.getOutputStream());
} // getInPumper
/**
* Returns the consumer through which everything printed to STDOUT of the script is stored
* directly into the log (through {@link #LOG} instance). This default implementation stores
* the STDOUT on {@link org.apache.log4j.Level#INFO} level.
*
* @return the instance of stream consumer.
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected StreamConsumer getDirectOutLogger() {
return StreamLogger.getInfoLogger(LOG);
} // getDirectOutLogger
/**
* Returns the consumer through which everything printed to STDERR of the script is stored
* directly into the log (through {@link #LOG} instance). This default implementation stores
* the STDOUT on {@link org.apache.log4j.Level#WARN} level.
*
* @return the instance of stream consumer.
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected StreamConsumer getDirectErrLogger() {
return StreamLogger.getWarnLogger(LOG);
} // getDirectErrLogger
/**
* The value returned controls if everything printed to STDOUT of the script is passed to
* other consumers in {@link #runScript(File, Script, long, InputStream, BuildOutputLogger)}.
* Mind that the output may be quite large! This default implementation returns <code>true</code>.
*
* @return let the STDOUT of the script be consumed by Script and BuildOutputLogger
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected boolean letConsumeOut() {
return true;
} // letConsumeOut
/**
* The value returned controls if everything printed to STDERR of the script is passed to
* other consumers in {@link #runScript(File, Script, long, InputStream, BuildOutputLogger)}.
* Mind that the output may be quite large! This default implementation returns <code>true</code>.
*
* @return let the STDERR of the script be consumed by Script and BuildOutputLogger
* @see #runScript(File, Script, long, InputStream, BuildOutputLogger) where the method
* is called.
*/
protected boolean letConsumeErr() {
return true;
} // letConsumeErr
}