From 96062837118b7dffe0a2a11f60008ac2645b281f Mon Sep 17 00:00:00 2001 From: Conor MacNeill Date: Thu, 6 Feb 2003 12:53:49 +0000 Subject: [PATCH] Allow 's error and output streams to be redirected independently This is primarily to address bug 7330 but it includes a few other changes. You can now use both output and outputproperty. While it was not documented, previously, these used to be mutually exclusive. A simple TeeOutptuStream is used to write the value to both a property and a file, if required. The LogStreamHandler is no longer used by Exec. The main thing it did over the PumpStreamHandler was to close the output streams when the stream handler's stop method was called. This needed to be done in the PumpStreamHandler in any case or the output file would be left open. The PumpStreamHandler can now close its streams on stop() although the default is still to leave them open There are new attributes "error" and "errorProperty" which if present separate output and error streams. If not present the current behaviour is unchanged. PR: 7330 git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@274003 13f79535-47bb-0310-9956-ffa450edef68 --- WHATSNEW | 3 + docs/manual/CoreTasks/exec.html | 20 ++- .../apache/tools/ant/taskdefs/ExecTask.java | 140 ++++++++++++++---- .../tools/ant/taskdefs/PumpStreamHandler.java | 20 ++- .../tools/ant/util/TeeOutputStream.java | 99 +++++++++++++ 5 files changed, 246 insertions(+), 36 deletions(-) create mode 100644 src/main/org/apache/tools/ant/util/TeeOutputStream.java diff --git a/WHATSNEW b/WHATSNEW index a40d074ea..9d504d941 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -122,6 +122,9 @@ Fixed bugs: * splash screen wouldn't disppear when build was finished. +* output and error streams can now be redirected independently to either + a property or a file (or both) + Other changes: -------------- * The filesetmanifest attribute of has been reenabled. diff --git a/docs/manual/CoreTasks/exec.html b/docs/manual/CoreTasks/exec.html index 88bd86670..9028c7b4a 100644 --- a/docs/manual/CoreTasks/exec.html +++ b/docs/manual/CoreTasks/exec.html @@ -54,18 +54,32 @@ Windows executable and is not aware of Cygwin conventions. output the file to which the output of the command should be - redirected. + redirected. If the error stream is not also redirected to a file + or poerperty, it will appear in the output + No + + + error + the file to which the standard error of the command should be + redirected. No append - whether output should be appended to or overwrite - an existing file. Defaults to false. + whether output and error files should be appended to or overwritten. + Defaults to false. No outputproperty the name of a property in which the output of the + command should be stored. Unless the error stream is redirected to a separate + file or stream, this property will include the error output. + No + + + errorproperty + the name of a property in which the standard error of the command should be stored. No diff --git a/src/main/org/apache/tools/ant/taskdefs/ExecTask.java b/src/main/org/apache/tools/ant/taskdefs/ExecTask.java index 6eec69e11..88aa7fd78 100644 --- a/src/main/org/apache/tools/ant/taskdefs/ExecTask.java +++ b/src/main/org/apache/tools/ant/taskdefs/ExecTask.java @@ -61,6 +61,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringReader; +import java.io.OutputStream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; @@ -68,6 +69,7 @@ import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.Environment; import org.apache.tools.ant.util.StringUtils; import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.TeeOutputStream; /** * Executes a given command if the os platform is appropriate. @@ -86,6 +88,8 @@ public class ExecTask extends Task { private String os; private File out; + private File error; + private File dir; protected boolean failOnError = false; protected boolean newEnvironment = false; @@ -94,7 +98,9 @@ public class ExecTask extends Task { protected Commandline cmdl = new Commandline(); private FileOutputStream fos = null; private ByteArrayOutputStream baos = null; + private ByteArrayOutputStream errorBaos = null; private String outputprop; + private String errorProperty; private String resultProperty; private boolean failIfExecFails = true; private boolean append = false; @@ -160,12 +166,22 @@ public class ExecTask extends Task { } /** - * File the output of the process is redirected to. + * File the output of the process is redirected to. If error is not + * redirected, it too will appear in the output */ public void setOutput(File out) { this.out = out; } + /** + * File the error stream of the process is redirected to. + * + * @since ant 1.6 + */ + public void setError(File error) { + this.error = error; + } + /** * Property name whose value should be set to the output of * the process. @@ -174,6 +190,16 @@ public class ExecTask extends Task { this.outputprop = outputprop; } + /** + * Property name whose value should be set to the error of + * the process. + * + * @since ant 1.6 + */ + public void setErrorProperty(String errorProperty) { + this.errorProperty = errorProperty; + } + /** * Fail if the command exits with a non-zero return code. */ @@ -363,39 +389,48 @@ public class ExecTask extends Task { return exe; } + private void setPropertyFromBAOS(ByteArrayOutputStream baos, + String propertyName) throws IOException { + + BufferedReader in = + new BufferedReader(new StringReader(Execute.toString(baos))); + String line = null; + StringBuffer val = new StringBuffer(); + while ((line = in.readLine()) != null) { + if (val.length() != 0) { + val.append(StringUtils.LINE_SEP); + } + val.append(line); + } + getProject().setNewProperty(propertyName, val.toString()); + } + /** * A Utility method for this classes and subclasses to run an * Execute instance (an external command). */ protected final void runExecute(Execute exe) throws IOException { - int err = -1; // assume the worst + int returnCode = -1; // assume the worst - err = exe.execute(); + returnCode = exe.execute(); //test for and handle a forced process death if (exe.killedProcess()) { log("Timeout: killed the sub-process", Project.MSG_WARN); } - maybeSetResultPropertyValue(err); - if (err != 0) { + maybeSetResultPropertyValue(returnCode); + if (returnCode != 0) { if (failOnError) { - throw new BuildException(getTaskType() + " returned: " + err, - getLocation()); + throw new BuildException(getTaskType() + " returned: " + + returnCode, getLocation()); } else { - log("Result: " + err, Project.MSG_ERR); + log("Result: " + returnCode, Project.MSG_ERR); } } if (baos != null) { - BufferedReader in = - new BufferedReader(new StringReader(Execute.toString(baos))); - String line = null; - StringBuffer val = new StringBuffer(); - while ((line = in.readLine()) != null) { - if (val.length() != 0) { - val.append(StringUtils.LINE_SEP); - } - val.append(line); - } - getProject().setNewProperty(outputprop, val.toString()); + setPropertyFromBAOS(baos, outputprop); + } + if (errorBaos != null) { + setPropertyFromBAOS(errorBaos, errorProperty); } } @@ -427,26 +462,69 @@ public class ExecTask extends Task { * Create the StreamHandler to use with our Execute instance. */ protected ExecuteStreamHandler createHandler() throws BuildException { - if (out != null) { + OutputStream outputStream = null; + OutputStream errorStream = null; + + if (out == null && outputprop == null) { + outputStream = new LogOutputStream(this, Project.MSG_INFO); + errorStream = new LogOutputStream(this, Project.MSG_WARN); + } else { + if (out != null) { + try { + outputStream + = new FileOutputStream(out.getAbsolutePath(), append); + log("Output redirected to " + out, Project.MSG_VERBOSE); + } catch (FileNotFoundException fne) { + throw new BuildException("Cannot write to " + out, fne, + getLocation()); + } catch (IOException ioe) { + throw new BuildException("Cannot write to " + out, ioe, + getLocation()); + } + } + + if (outputprop != null) { + baos = new ByteArrayOutputStream(); + log("Output redirected to ByteArray", Project.MSG_VERBOSE); + if (out == null) { + outputStream = baos; + } else { + outputStream = new TeeOutputStream(outputStream, baos); + } + } else { + baos = null; + } + + errorStream = outputStream; + } + + if (error != null) { try { - fos = new FileOutputStream(out.getAbsolutePath(), append); - log("Output redirected to " + out, Project.MSG_VERBOSE); - return new PumpStreamHandler(fos); + errorStream + = new FileOutputStream(error.getAbsolutePath(), append); + log("Error redirected to " + out, Project.MSG_VERBOSE); } catch (FileNotFoundException fne) { - throw new BuildException("Cannot write to " + out, fne, + throw new BuildException("Cannot write to " + error, fne, getLocation()); } catch (IOException ioe) { - throw new BuildException("Cannot write to " + out, ioe, + throw new BuildException("Cannot write to " + error, ioe, getLocation()); } - } else if (outputprop != null) { - baos = new ByteArrayOutputStream(); - log("Output redirected to ByteArray", Project.MSG_VERBOSE); - return new PumpStreamHandler(baos); + } + + if (errorProperty != null) { + errorBaos = new ByteArrayOutputStream(); + log("Error redirected to ByteArray", Project.MSG_VERBOSE); + if (error == null) { + errorStream = errorBaos; + } else { + errorStream = new TeeOutputStream(errorStream, errorBaos); + } } else { - return new LogStreamHandler(this, - Project.MSG_INFO, Project.MSG_WARN); + errorBaos = null; } + + return new PumpStreamHandler(outputStream, errorStream, true, true); } /** diff --git a/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java b/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java index 9a6652277..d68152cdc 100644 --- a/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java +++ b/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java @@ -72,11 +72,21 @@ public class PumpStreamHandler implements ExecuteStreamHandler { private Thread inputThread; private Thread errorThread; - private OutputStream out, err; + private OutputStream out; + private OutputStream err; + private boolean closeOutOnStop = false; + private boolean closeErrOnStop = false; - public PumpStreamHandler(OutputStream out, OutputStream err) { + public PumpStreamHandler(OutputStream out, OutputStream err, + boolean closeOutOnStop, boolean closeErrOnStop) { this.out = out; this.err = err; + this.closeOutOnStop = closeOutOnStop; + this.closeErrOnStop = closeErrOnStop; + } + + public PumpStreamHandler(OutputStream out, OutputStream err) { + this(out, err, false, false); } public PumpStreamHandler(OutputStream outAndErr) { @@ -116,9 +126,15 @@ public class PumpStreamHandler implements ExecuteStreamHandler { } catch (InterruptedException e) {} try { err.flush(); + if (closeErrOnStop) { + err.close(); + } } catch (IOException e) {} try { out.flush(); + if (closeOutOnStop) { + out.close(); + } } catch (IOException e) {} } diff --git a/src/main/org/apache/tools/ant/util/TeeOutputStream.java b/src/main/org/apache/tools/ant/util/TeeOutputStream.java new file mode 100644 index 000000000..d9bbecf57 --- /dev/null +++ b/src/main/org/apache/tools/ant/util/TeeOutputStream.java @@ -0,0 +1,99 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR + * ITS 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.util; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * A simple T-piece to replicate an output stream into two separate streams + * + * @author Conor MacNeill + */ +public class TeeOutputStream extends OutputStream { + private OutputStream left; + private OutputStream right; + + public TeeOutputStream(OutputStream left, OutputStream right) { + this.left = left; + this.right = right; + } + + public void close() throws IOException { + left.close(); + right.close(); + } + + public void flush() throws IOException { + left.flush(); + right.flush(); + } + + public void write(byte[] b) throws IOException { + left.write(b); + right.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + left.write(b, off, len); + right.write(b, off, len); + } + + public void write(int b) throws IOException { + left.write(b); + right.write(b); + } +} +