Browse Source

Allow <exec>'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
master
Conor MacNeill 22 years ago
parent
commit
9606283711
5 changed files with 246 additions and 36 deletions
  1. +3
    -0
      WHATSNEW
  2. +17
    -3
      docs/manual/CoreTasks/exec.html
  3. +109
    -31
      src/main/org/apache/tools/ant/taskdefs/ExecTask.java
  4. +18
    -2
      src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java
  5. +99
    -0
      src/main/org/apache/tools/ant/util/TeeOutputStream.java

+ 3
- 0
WHATSNEW View File

@@ -122,6 +122,9 @@ Fixed bugs:

* splash screen wouldn't disppear when build was finished.

* <exec> output and error streams can now be redirected independently to either
a property or a file (or both)

Other changes:
--------------
* The filesetmanifest attribute of <jar> has been reenabled.


+ 17
- 3
docs/manual/CoreTasks/exec.html View File

@@ -54,18 +54,32 @@ Windows executable and is not aware of Cygwin conventions.
<tr>
<td valign="top">output</td>
<td valign="top">the file to which the output of the command should be
redirected.</td>
redirected. If the error stream is not also redirected to a file
or poerperty, it will appear in the output</td>
<td align="center" valign="top">No</td>
</tr>
<tr>
<td valign="top">error</td>
<td valign="top">the file to which the standard error of the command should be
redirected. </td>
<td align="center" valign="top">No</td>
</tr>
<tr>
<td valign="top">append</td>
<td valign="top">whether output should be appended to or overwrite
an existing file. Defaults to false.</td>
<td valign="top">whether output and error files should be appended to or overwritten.
Defaults to false.</td>
<td align="center" valign="top">No</td>
</tr>
<tr>
<td valign="top">outputproperty</td>
<td valign="top">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.</td>
<td align="center" valign="top">No</td>
</tr>
<tr>
<td valign="top">errorproperty</td>
<td valign="top">the name of a property in which the standard error of the
command should be stored.</td>
<td align="center" valign="top">No</td>
</tr>


+ 109
- 31
src/main/org/apache/tools/ant/taskdefs/ExecTask.java View File

@@ -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);
}

/**


+ 18
- 2
src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java View File

@@ -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) {}
}



+ 99
- 0
src/main/org/apache/tools/ant/util/TeeOutputStream.java View File

@@ -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
* <http://www.apache.org/>.
*/

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);
}
}


Loading…
Cancel
Save