Bug 5907 - ExecTask waits regardless of what you are executing
Summary: ExecTask waits regardless of what you are executing
Status: RESOLVED FIXED
Alias: None
Product: Ant
Classification: Unclassified
Component: Core tasks (show other bugs)
Version: 1.4.1
Hardware: All All
: P3 enhancement with 9 votes (vote)
Target Milestone: 1.6
Assignee: Ant Notifications List
URL:
Keywords: PatchAvailable
: 4092 18572 22553 22710 (view as bug list)
Depends on:
Blocks:
 
Reported: 2002-01-17 10:52 UTC by Kevin Ross
Modified: 2004-11-16 19:05 UTC (History)
5 users (show)



Attachments
ExecTask.java (11.36 KB, text/plain)
2002-01-17 10:53 UTC, Kevin Ross
Details
Execute.java (26.59 KB, text/plain)
2002-01-17 10:53 UTC, Kevin Ross
Details
PumpStreamHandler.java (4.97 KB, text/plain)
2002-01-17 10:54 UTC, Kevin Ross
Details
patch (4.36 KB, patch)
2003-06-12 19:21 UTC, Peter Nimmervoll
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Kevin Ross 2002-01-17 10:52:06 UTC
Code modified and works below on windows.  Non-OS specific changes.

Modified:
---------
1. 
org.apache.tools.ant.taskdefs.ExecTask - added setWaitfor()

2. 
org.apache.tools.ant.taskdefs.Execute - added setWaitfor(), enhanced waitFor()

3. 
org.apache.tools.ant.taskdefs.PumpStreamHandler - bug - stop() now stops 
instead of waiting indefinitely

look for tag @modification in code.  Submitted on behalf of Charles Hudak 
<CHudak@arrowheadgrp.com> by Kevin Ross <Kevin.Ross@Bredex.com>

==========================
Execute.java
==========================
package org.apache.tools.ant.taskdefs;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Commandline;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Vector;

/**
 * Runs an external program.
 *
 * @author thomas.haas@softwired-inc.com
 * @modification Charles Hudak <CHudak@arrowheadgrp.com> waitFor()
 */
public class Execute {

    /** Invalid exit code. **/
    public final static int INVALID = Integer.MAX_VALUE;

    private String[] cmdl = null;
    private String[] env = null;
    private int exitValue = INVALID;
    private ExecuteStreamHandler streamHandler;
    private ExecuteWatchdog watchdog;
    private File workingDirectory = null;
    private Project project = null;
    private boolean newEnvironment = false;
    private boolean waitFor = true;
    

    /** Controls whether the VM is used to launch commands, where possible */
    private boolean useVMLauncher = true;    
    
    private static String antWorkingDirectory = System.getProperty("user.dir");
    private static CommandLauncher vmLauncher = null;
    private static CommandLauncher shellLauncher = null;
    private static Vector procEnvironment = null;

    /** 
     * Builds a command launcher for the OS and JVM we are running under
     */
    static {
        // Try using a JDK 1.3 launcher
        try {
            vmLauncher = new Java13CommandLauncher();
        }
        catch ( NoSuchMethodException exc ) {
            // Ignore and keep try
        }

        String osname = System.getProperty("os.name").toLowerCase();
        if ( osname.indexOf("mac os") >= 0 ) {
            // Mac
            shellLauncher = new MacCommandLauncher(new CommandLauncher());
        }
        else if ( osname.indexOf("os/2") >= 0 ) {
            // OS/2 - use same mechanism as Windows 2000
            shellLauncher = new WinNTCommandLauncher(new CommandLauncher());
        }
        else if ( osname.indexOf("windows") >= 0 ) {
            // Windows.  Need to determine which JDK we're running in
            CommandLauncher baseLauncher;
            if ( System.getProperty("java.version").startsWith("1.1") ) {
                // JDK 1.1
                baseLauncher = new Java11CommandLauncher();
            }
            else {
                // JDK 1.2
                baseLauncher = new CommandLauncher();
            }

            // Determine if we're running under 2000/NT or 98/95
            if ( osname.indexOf("nt") >= 0 || osname.indexOf("2000") >= 0 ) {
                // Windows 2000/NT
                shellLauncher = new WinNTCommandLauncher(baseLauncher);
            }
            else {
                // Windows 98/95 - need to use an auxiliary script
                shellLauncher = new ScriptCommandLauncher("bin/antRun.bat", 
baseLauncher);
            }
        }
        else {
            // Generic
            shellLauncher = new ScriptCommandLauncher("bin/antRun", new 
CommandLauncher());
        }
    }

    /**
     * Find the list of environment variables for this process.
     */
    public static synchronized Vector getProcEnvironment() {
        if (procEnvironment != null) return procEnvironment;

        procEnvironment = new Vector();
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Execute exe = new Execute(new PumpStreamHandler(out));
            exe.setCommandline(getProcEnvCommand());
            // Make sure we do not recurse forever
            exe.setNewenvironment(true);
            int retval = exe.execute();
            if ( retval != 0 ) {
                // Just try to use what we got
            }

            BufferedReader in = 
                new BufferedReader(new StringReader(out.toString()));
            String var = null;
            String line, lineSep = System.getProperty("line.separator");
            while ((line = in.readLine()) != null) {
                if (line.indexOf('=') == -1) {
                    // Chunk part of previous env var (UNIX env vars can
                    // contain embedded new lines).
                    if (var == null) {
                        var = lineSep + line;
                    }
                    else {
                        var += lineSep + line;
                    }
                }
                else {
                    // New env var...append the previous one if we have it.
                    if (var != null) {
                        procEnvironment.addElement(var);
                    }
                    var = line;
                }
            }
            // Since we "look ahead" before adding, there's one last env var.
            procEnvironment.addElement(var);
        } 
        catch (java.io.IOException exc) {
            exc.printStackTrace();
            // Just try to see how much we got
        }
        return procEnvironment;
    }

    private static String[] getProcEnvCommand() {
        String osname = System.getProperty("os.name").toLowerCase();
        if ( osname.indexOf("mac os") >= 0 ) {
            // Mac
            // Determine if we are running under OS X
            try {
                String version = System.getProperty("os.version");
                int majorVersion = 
                    Integer.parseInt(version.substring(0, version.indexOf
('.')));

                if (majorVersion >= 10) {
                    // OS X - just line UNIX
                    String[] cmd = {"/usr/bin/env"};
                    return cmd;
                }
            } catch (NumberFormatException e) {
                // fall through to OS 9
            }
            // OS 9 and previous
            // TODO: I have no idea how to get it, someone must fix it
            String[] cmd = null;
            return cmd;
        }
        else if ( osname.indexOf("os/2") >= 0 ) {
            // OS/2 - use same mechanism as Windows 2000
            // Not sure
            String[] cmd = {"cmd", "/c", "set" };
            return cmd;
        }
        else if ( osname.indexOf("indows") >= 0 ) {
            // Determine if we're running under 2000/NT or 98/95
            if ( osname.indexOf("nt") >= 0 || osname.indexOf("2000") >= 0 ) {
                // Windows 2000/NT
                String[] cmd = {"cmd", "/c", "set" };
                return cmd;
            }
            else {
                // Windows 98/95 - need to use an auxiliary script
                String[] cmd = {"command.com", "/c", "set" };
                return cmd;
            }
        }
        else {
            // Generic UNIX
            // Alternatively one could use: /bin/sh -c env
            String[] cmd = {"/usr/bin/env"};
            return cmd;
        }
    }

    /**
     * Creates a new execute object using <code>PumpStreamHandler</code> for
     * stream handling.
     */
    public Execute() {
        this(new PumpStreamHandler(), null);
    }


    /**
     * Creates a new execute object.
     *
     * @param streamHandler the stream handler used to handle the input and
     *        output streams of the subprocess.
     */
    public Execute(ExecuteStreamHandler streamHandler) {
        this(streamHandler, null);
    }

    /**
     * Creates a new execute object.
     *
     * @param streamHandler the stream handler used to handle the input and
     *        output streams of the subprocess.
     * @param watchdog a watchdog for the subprocess or <code>null</code> to
     *        to disable a timeout for the subprocess.
     */
    public Execute(ExecuteStreamHandler streamHandler, ExecuteWatchdog 
watchdog) {
        this.streamHandler = streamHandler;
        this.watchdog = watchdog;
    }


    /**
     * Returns the commandline used to create a subprocess.
     *
     * @return the commandline used to create a subprocess
     */
    public String[] getCommandline() {
        return cmdl;
    }


    /**
     * Sets the commandline of the subprocess to launch.
     *
     * @param commandline the commandline of the subprocess to launch
     */
    public void setCommandline(String[] commandline) {
        cmdl = commandline;
    }

    /**
     * Set whether to propagate the default environment or not.
     *
     * @param newenv whether to propagate the process environment.
     */
    public void setNewenvironment(boolean newenv) {
        newEnvironment = newenv;
    }

    /**
     * Returns the environment used to create a subprocess.
     *
     * @return the environment used to create a subprocess
     */
    public String[] getEnvironment() {
        if (env == null || newEnvironment) return env;
        return patchEnvironment();
    }


    /**
     * Sets the environment variables for the subprocess to launch.
     *
     * @param commandline array of Strings, each element of which has
     * an environment variable settings in format <em>key=value</em> 
     */
    public void setEnvironment(String[] env) {
        this.env = env;
    }

    /**
     * Sets the working directory of the process to execute.
     *
     * <p>This is emulated using the antRun scripts unless the OS is
     * Windows NT in which case a cmd.exe is spawned,
     * or MRJ and setting user.dir works, or JDK 1.3 and there is
     * official support in java.lang.Runtime.
     *
     * @param wd the working directory of the process.
     */
    public void setWorkingDirectory(File wd) {
        if (wd == null || wd.getAbsolutePath().equals(antWorkingDirectory))
            workingDirectory = null;
        else
            workingDirectory = wd;
    }

    /**
     * Set the name of the antRun script using the project's value.
     *
     * @param project the current project.
     */
    public void setAntRun(Project project) throws BuildException {
        this.project = project;
    }

    /**
     * Launch this execution through the VM, where possible, rather than through
     * the OS's shell. In some cases and operating systems using the shell will 
     * allow the shell to perform additional processing such as associating an 
     * executable with a script, etc
     *
     * @param vmLauncher true if exec should launch through thge VM, 
     *                   false if the shell should be used to launch the 
command.
     */
    public void setVMLauncher(boolean useVMLauncher) {
        this.useVMLauncher = useVMLauncher;
    }
    
    /**
     * Runs a process defined by the command line and returns its exit status.
     *
     * @return the exit status of the subprocess or <code>INVALID</code>
     * @exception java.io.IOExcpetion The exception is thrown, if launching
     *            of the subprocess failed
     */
    public int execute() throws IOException {
        CommandLauncher launcher = vmLauncher != null ? vmLauncher : 
shellLauncher;
        if (!useVMLauncher) {
            launcher = shellLauncher;
        }
        
        final Process process = launcher.exec(project, getCommandline(), 
getEnvironment(), workingDirectory);
        try {
            streamHandler.setProcessInputStream(process.getOutputStream());
            streamHandler.setProcessOutputStream(process.getInputStream());
            streamHandler.setProcessErrorStream(process.getErrorStream());
        } catch (IOException e) {
            process.destroy();
            throw e;
        }
        streamHandler.start();
        
        if (watchdog != null) 
            watchdog.start(process);
        
        waitFor(process);
        
        if (watchdog != null) 
            watchdog.stop();
        
        streamHandler.stop();
        
        if (watchdog != null) 
            watchdog.checkException();
        
        return getExitValue();
    }

    /**
     * @modification Charles Hudak <CHudak@arrowheadgrp.com> 
     */
    protected void waitFor(Process process) {

        if (waitFor)
        {
            try {
                
                if(project != null) project.log("Execute.waitFor()...", 
Project.MSG_DEBUG);
                process.waitFor();
                if(project != null) project.log("Execute.waitFor()...done.", 
Project.MSG_DEBUG);
                setExitValue(process.exitValue());
            } 
            catch (InterruptedException e) {}
        } 
        else {

            setExitValue(0);
        }
    }
    
    public void setWaitfor(boolean wait)
    {
        this.waitFor = wait;
    }    

    protected void setExitValue(int value) {
        exitValue = value;
    }

    public int getExitValue() {
        return exitValue;
    }

    /**
     * Patch the current environment with the new values from the user.
     * @return the patched environment
     */
    private String[] patchEnvironment() {
        Vector osEnv = (Vector) getProcEnvironment().clone();
        for (int i = 0; i < env.length; i++) {
            int pos = env[i].indexOf('=');
            // Get key including "="
            String key = env[i].substring(0, pos+1);
            int size = osEnv.size();
            for (int j = 0; j < size; j++) {
                if (((String)osEnv.elementAt(j)).startsWith(key)) {
                    osEnv.removeElementAt(j);
                    break;
                }
            }
            osEnv.addElement(env[i]);
        }
        String[] result = new String[osEnv.size()];
        osEnv.copyInto(result);
        return result;
    }

    /**
     * A utility method that runs an external command.  Writes the output and
     * error streams of the command to the project log.
     *
     * @param task      The task that the command is part of.  Used for logging
     * @param cmdline   The command to execute.
     *
     * @throws BuildException if the command does not return 0.
     */
    public static void runCommand(Task task, String[] cmdline) throws 
BuildException
    {
        try {
            task.log(Commandline.toString(cmdline), Project.MSG_VERBOSE);
            Execute exe = new Execute(new LogStreamHandler(task, 
                                                           Project.MSG_INFO,
                                                           Project.MSG_ERR));
            exe.setAntRun(task.getProject());
            exe.setCommandline(cmdline);
            int retval = exe.execute();
            if ( retval != 0 ) {
                throw new BuildException(cmdline[0] + " failed with return 
code " + retval, task.getLocation());
            }
        } 
        catch (java.io.IOException exc) {
            throw new BuildException("Could not launch " + cmdline[0] + ": " + 
exc, task.getLocation());
        }
    }

    /**
     * A command launcher for a particular JVM/OS platform.  This class is
     * a general purpose command launcher which can only launch commands in
     * the current working directory.
     */
    private static class CommandLauncher
    {
        /** 
         * Launches the given command in a new process.
         *
         * @param project       The project that the command is part of
         * @param cmd           The command to execute
         * @param env           The environment for the new process.  If null,
         *                      the environment of the current proccess is used.
         */
        public Process exec(Project project, String[] cmd, String[] env) throws 
IOException
        {
            if (project != null) {
                project.log("Execute:CommandLauncher: " +
                            Commandline.toString(cmd), Project.MSG_DEBUG);
            }                            
            return Runtime.getRuntime().exec(cmd, env);
        }

        /** 
         * Launches the given command in a new process, in the given working
         * directory.
         *
         * @param project       The project that the command is part of
         * @param cmd           The command to execute
         * @param env           The environment for the new process.  If null,
         *                      the environment of the current proccess is used.
         * @param workingDir    The directory to start the command in.  If null,
         *                      the current directory is used
         */
        public Process exec(Project project, String[] cmd, String[] env, File 
workingDir) throws IOException
        {
            if ( workingDir == null ) {
                return exec(project, cmd, env);
            }
            throw new IOException("Cannot execute a process in different 
directory under this JVM");
        }
    }

    /**
     * A command launcher for JDK/JRE 1.1 under Windows.  Fixes quoting problems
     * in Runtime.exec().  Can only launch commands in the current working
     * directory
     */
    private static class Java11CommandLauncher extends CommandLauncher
    {
        /**
         * Launches the given command in a new process.  Needs to quote
         * arguments
         */
        public Process exec(Project project, String[] cmd, String[] env) throws 
IOException 
        {
            // Need to quote arguments with spaces, and to escape quote 
characters
            String[] newcmd = new String[cmd.length];
            for ( int i = 0; i < cmd.length; i++ ) {
                newcmd[i] = Commandline.quoteArgument(cmd[i]);
            }
            if (project != null) {
                project.log("Execute:Java11CommandLauncher: " +
                            Commandline.toString(newcmd), Project.MSG_DEBUG);
            }                            
            return Runtime.getRuntime().exec(newcmd, env);
        }
    }

    /**
     * A command launcher for JDK/JRE 1.3 (and higher).  Uses the built-in
     * Runtime.exec() command
     */
    private static class Java13CommandLauncher extends CommandLauncher
    {
        public Java13CommandLauncher() throws NoSuchMethodException
        {
            // Locate method Runtime.exec(String[] cmdarray, String[] envp, 
File dir)
            _execWithCWD = Runtime.class.getMethod("exec", new Class[] {String
[].class, String[].class, File.class});
        }

        /** 
         * Launches the given command in a new process, in the given working
         * directory
         */
        public Process exec(Project project, String[] cmd, String[] env, File 
workingDir) 
            throws IOException
        {
            try {
                if (project != null) {
                    project.log("Execute:Java13CommandLauncher: " +
                                Commandline.toString(cmd), Project.MSG_DEBUG);
                }                                
                Object[] arguments = { cmd, env, workingDir };
                return (Process)_execWithCWD.invoke(Runtime.getRuntime(), 
arguments);
            } 
            catch (InvocationTargetException exc) {
                Throwable realexc = exc.getTargetException();
                if ( realexc instanceof ThreadDeath ) {
                    throw (ThreadDeath)realexc;
                } 
                else if ( realexc instanceof IOException ) {
                    throw (IOException)realexc;
                } 
                else {
                    throw new BuildException("Unable to execute command", 
realexc);
                }
            } 
            catch (Exception exc) {
                // IllegalAccess, IllegalArgument, ClassCast
                throw new BuildException("Unable to execute command", exc);
            }
        }
        
        private Method _execWithCWD;
    }
    
    /**
     * A command launcher that proxies another command launcher.  
     *
     * Sub-classes override exec(args, env, workdir)
     */
    private static class CommandLauncherProxy extends CommandLauncher
    {
        CommandLauncherProxy(CommandLauncher launcher)
        {
            _launcher = launcher;
        }

        /** 
         * Launches the given command in a new process.  Delegates this
         * method to the proxied launcher
         */
        public Process exec(Project project, String[] cmd, String[] env) throws 
IOException
        {
            return _launcher.exec(project, cmd, env);
        }

        private CommandLauncher _launcher;
    }

    /**
     * A command launcher for Windows 2000/NT that uses 'cmd.exe' when
     * launching commands in directories other than the current working
     * directory.
     */
    private static class WinNTCommandLauncher extends CommandLauncherProxy
    {
        WinNTCommandLauncher(CommandLauncher launcher)
        {
            super(launcher);
        }

        /** 
         * Launches the given command in a new process, in the given working
         * directory.
         */
        public Process exec(Project project, String[] cmd, String[] env, File 
workingDir) throws IOException
        {
            File commandDir = workingDir;
            if ( workingDir == null ) {
                if ( project != null ) {
                    commandDir = project.getBaseDir();
                } else {
                    return exec(project, cmd, env);
                }
            }

            // Use cmd.exe to change to the specified directory before running
            // the command
            final int preCmdLength = 6;
            String[] newcmd = new String[cmd.length + preCmdLength];
            newcmd[0] = "cmd";
            newcmd[1] = "/c";
            newcmd[2] = "cd";
            newcmd[3] = "/d";
            newcmd[4] = commandDir.getAbsolutePath();
            newcmd[5] = "&&";
            System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);

            return exec(project, newcmd, env);
        }
    }

    /**
     * A command launcher for Mac that uses a dodgy mechanism to change
     * working directory before launching commands.
     */
    private static class MacCommandLauncher extends CommandLauncherProxy
    {
        MacCommandLauncher(CommandLauncher launcher)
        {
            super(launcher);
        }

        /** 
         * Launches the given command in a new process, in the given working
         * directory
         */
        public Process exec(Project project, String[] cmd, String[] env, File 
workingDir) throws IOException
        {
            if ( workingDir == null ) {
                return exec(project, cmd, env);
            }

            System.getProperties().put("user.dir", workingDir.getAbsolutePath
());
            try {
                return exec(project, cmd, env);
            } 
            finally {
                System.getProperties().put("user.dir", antWorkingDirectory);
            }
        }
    }

    /**
     * A command launcher that uses an auxiliary script to launch commands
     * in directories other than the current working directory.
     */
    private static class ScriptCommandLauncher extends CommandLauncherProxy
    {
        ScriptCommandLauncher(String script, CommandLauncher launcher)
        {
            super(launcher);
            _script = script;
        }

        /** 
         * Launches the given command in a new process, in the given working
         * directory
         */
        public Process exec(Project project, String[] cmd, String[] env, File 
workingDir) throws IOException
        {
            if ( project == null ) {
                if ( workingDir == null ) {
                    return exec(project, cmd, env);
                }
                throw new IOException("Cannot locate antRun script: No project 
provided");
            }
            
            // Locate the auxiliary script
            String antHome = project.getProperty("ant.home");
            if ( antHome == null ) {
                throw new IOException("Cannot locate antRun script: 
Property 'ant.home' not found");
            }
            String antRun = project.resolveFile(antHome + File.separator + 
_script).toString();

            // Build the command
            File commandDir = workingDir;
            if ( workingDir == null && project != null ) {
                commandDir = project.getBaseDir();
            }

            String[] newcmd = new String[cmd.length + 2];
            newcmd[0] = antRun;
            newcmd[1] = commandDir.getAbsolutePath();
            System.arraycopy(cmd, 0, newcmd, 2, cmd.length);
            
            return exec(project, newcmd, env);
        }

        private String _script;
    }
}


=================================================
ExecTask.java
=================================================

package org.apache.tools.ant.taskdefs;

import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;

import java.io.*;

/**
 * Executes a given command if the os platform is appropriate.
 *
 * @author duncan@x180.com
 * @author rubys@us.ibm.com
 * @author thomas.haas@softwired-inc.com
 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
 * @author <a href="mailto:mariusz@rakiura.org">Mariusz Nowostawski</a> 
 * @modification Charles Hudak <CHudak@arrowheadgrp.com> setWaitFor()
 */
public class ExecTask extends Task {

    private static String lSep = System.getProperty("line.separator");

    private String os;
    private File out;
    private File dir;
    protected boolean failOnError = false;
    protected boolean newEnvironment = false;
    private Integer timeout = null;
    private Environment env = new Environment();
    protected Commandline cmdl = new Commandline();
    private FileOutputStream fos = null;
    private ByteArrayOutputStream baos = null;
    private String outputprop;
    private boolean waitFor = true;    

    /** Controls whether the VM (1.3 and above) is used to execute the command 
*/
    private boolean vmLauncher = true;
    
    /**
     * Controls whether or not to wait for the process to finish.
     *
     * @Author Charles Hudak <CHudak@arrowheadgrp.com>
     */
    public void setWaitfor(boolean wait)
    {
        this.waitFor = wait;
    }    
    
    /**
     * Timeout in milliseconds after which the process will be killed.
     */
    public void setTimeout(Integer value) {
        timeout = value;
    }

    /**
     * The command to execute.
     */
    public void setExecutable(String value) {
        cmdl.setExecutable(value);
    }

    /**
     * The working directory of the process
     */
    public void setDir(File d) {
        this.dir = d;
    }

    /**
     * Only execute the process if <code>os.name</code> is included in this 
string.
     */
    public void setOs(String os) {
        this.os = os;
    }

    /**
     * The full commandline to execute, executable + arguments.
     */
    public void setCommand(Commandline cmdl) {
        log("The command attribute is deprecated. " +
            "Please use the executable attribute and nested arg elements.",
            Project.MSG_WARN);
        this.cmdl = cmdl;
    }

    /**
     * File the output of the process is redirected to.
     */
    public void setOutput(File out) {
        this.out = out;
    }

    /**
     * Property name whose value should be set to the output of
     * the process
     */
    public void setOutputproperty(String outputprop) {
	this.outputprop = outputprop;
    }

    /**
     * Throw a BuildException if process returns non 0.
     */
    public void setFailonerror(boolean fail) {
        failOnError = fail;
    }

    /**
     * Use a completely new environment
     */
    public void setNewenvironment(boolean newenv) {
        newEnvironment = newenv;
    }

    /**
     * Add a nested env element - an environment variable.
     */
    public void addEnv(Environment.Variable var) {
        env.addVariable(var);
    }

    /**
     * Add a nested arg element - a command line argument.
     */
    public Commandline.Argument createArg() {
        return cmdl.createArgument();
    }

    /**
     * Do the work.
     */
    public void execute() throws BuildException {
        checkConfiguration();
        if (isValidOs()) {
            runExec(prepareExec());
        }
    }

    /**
     * Has the user set all necessary attributes?
     */
    protected void checkConfiguration() throws BuildException {
        if (cmdl.getExecutable() == null) {
            throw new BuildException("no executable specified", location);
        }
        if (dir != null && !dir.exists()) {
        	throw new BuildException("The directory you specified does not 
exist");
        }
        if (dir != null && !dir.isDirectory()) {
        	throw new BuildException("The directory you specified is not a 
directory");
        }
    }

    /**
     * Is this the OS the user wanted?
     */
    protected boolean isValidOs() {
        // test if os match
        String myos = System.getProperty("os.name");
        log("Current OS is " + myos, Project.MSG_VERBOSE);
        if ((os != null) && (os.indexOf(myos) < 0)){
            // this command will be executed only on the specified OS
            log("This OS, " + myos + " was not found in the specified list of 
valid OSes: " + os, Project.MSG_VERBOSE);
            return false;
        }
        return true;
    }

    /**
     * Control whether the VM is used to launch the new process or
     * whether the OS's shell is used.
     */
    public void setVMLauncher(boolean vmLauncher) {
        this.vmLauncher = vmLauncher;
    }
    
    /**
     * Create an Execute instance with the correct working directory set.
     * @modification Charles Hudak <CHudak@arrowheadgrp.com> setWaitFor()
     */
    protected Execute prepareExec() throws BuildException {
        // default directory to the project's base directory
        if (dir == null) dir = project.getBaseDir();
        // show the command
        log(cmdl.toString(), Project.MSG_VERBOSE);
        
        Execute exe = new Execute(createHandler(), createWatchdog());
        exe.setAntRun(project);
        exe.setWorkingDirectory(dir);
        exe.setVMLauncher(vmLauncher);
        exe.setWaitfor(this.waitFor);
        if(this.waitFor){
         
            log("Waiting for process to complete...", Project.MSG_INFO);
        }
        else{
            
            log("Spawning process without waiting to complete.", 
Project.MSG_INFO); 
        }
        
        String[] environment = env.getVariables();
        if (environment != null) {
            for (int i=0; i<environment.length; i++) {
                log("Setting environment variable: "+environment[i],
                    Project.MSG_VERBOSE);
            }
        }
        exe.setNewenvironment(newEnvironment);
        exe.setEnvironment(environment);
        return exe;
    }

    /**
     * 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

        err = exe.execute();
        if (err != 0) {
            if (failOnError) {
                throw new BuildException(taskType + " returned: "+err, 
location);
            } else {
                log("Result: " + err, Project.MSG_ERR);
            }
        }
        if (baos != null) {
            BufferedReader in = 
                new BufferedReader(new StringReader(baos.toString()));
            String line = null;
            StringBuffer val = new StringBuffer();
            while ((line = in.readLine()) != null) {
                if (val.length() != 0) {
                    val.append(lSep);
                }
                val.append(line);
            }
            project.setProperty(outputprop, val.toString());
        }
    }
    
    /**
     * Run the command using the given Execute instance. This may be overidden 
by subclasses
     */
    protected void runExec(Execute exe) throws BuildException {
        exe.setCommandline(cmdl.getCommandline());
        try {
            runExecute(exe);
        } catch (IOException e) {
            throw new BuildException("Execute failed: " + e, e, location);
        } finally {
            // close the output file if required
            logFlush();
        }
    }

    /**
     * Create the StreamHandler to use with our Execute instance.
     */
    protected ExecuteStreamHandler createHandler() throws BuildException {
        if(out!=null)  {
            try {
                fos = new FileOutputStream(out);
                log("Output redirected to " + out, Project.MSG_VERBOSE);
                return new PumpStreamHandler(fos);
            } catch (FileNotFoundException fne) {
                throw new BuildException("Cannot write to "+out, fne, location);
            } catch (IOException ioe) {
                throw new BuildException("Cannot write to "+out, ioe, location);
            }
        } else if (outputprop != null) {
	    //	    try {
	    baos = new ByteArrayOutputStream();
	    log("Output redirected to ByteArray", Project.MSG_VERBOSE);
	    return new PumpStreamHandler(baos);
        } else {
            return new LogStreamHandler(this,
                                        Project.MSG_INFO, Project.MSG_WARN);
        }
    }

    /**
     * Create the Watchdog to kill a runaway process.
     */
    protected ExecuteWatchdog createWatchdog() throws BuildException {
        if (timeout == null) return null;
        return new ExecuteWatchdog(timeout.intValue());
    }

    /**
     * Flush the output stream - if there is one.
     */
    protected void logFlush() {
        try {
            if (fos != null) fos.close();
            if (baos != null) baos.close();
        } catch (IOException io) {}
    }

}


=================================================
PumpStreamHandler.java
=================================================

package org.apache.tools.ant.taskdefs;

import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Copies standard output and error of subprocesses to standard output and
 * error of the parent process.
 *
 * TODO: standard input of the subprocess is not implemented.
 *
 * @author thomas.haas@softwired-inc.com
 * @modification Charles Hudak <CHudak@arrowheadgrp.com> stop()
 */
public class PumpStreamHandler implements ExecuteStreamHandler {

    private Thread inputThread;
    private Thread errorThread;

    private OutputStream out, err;

    public PumpStreamHandler(OutputStream out, OutputStream err) {
        this.out = out;
        this.err = err;
    }

    public PumpStreamHandler(OutputStream outAndErr) {
        this(outAndErr, outAndErr);
    }

    public PumpStreamHandler() {
        this(System.out, System.err);
    }

    public void setProcessOutputStream(InputStream is) {
        createProcessOutputPump(is, out);
    }


    public void setProcessErrorStream(InputStream is) {
        createProcessErrorPump(is, err);
    }


    public void setProcessInputStream(OutputStream os) {
    }


    public void start() {
        inputThread.start();
        errorThread.start();
    }


    /**
     * When stop() is called, stop listening and move on.  Thread.join() will 
wait,
     *  therefore, put a short timeout on it.
     *
     *  @modification Charles Hudak <CHudak@arrowheadgrp.com>
     */
    public void stop() {
        try {
            inputThread.join(500);
        } catch(InterruptedException e) {}
        try {
            errorThread.join(500);
        } catch(InterruptedException e) {}
        try {
            err.flush();
        } catch (IOException e) {}
        try {
            out.flush();
        } catch (IOException e) {}
    }
    
    protected OutputStream getErr() {
        return err;
    }

    protected OutputStream getOut() {
        return out;
    }

    protected void createProcessOutputPump(InputStream is, OutputStream os) {
        inputThread = createPump(is, os);
    }

    protected void createProcessErrorPump(InputStream is, OutputStream os) {
        errorThread = createPump(is, os);
    }


    /**
     * Creates a stream pumper to copy the given input stream to the given 
output stream.
     */
    protected Thread createPump(InputStream is, OutputStream os) {
        final Thread result = new Thread(new StreamPumper(is, os));
        result.setDaemon(true);
        return result;
    }

}
Comment 1 Kevin Ross 2002-01-17 10:53:20 UTC
Created attachment 1027 [details]
ExecTask.java
Comment 2 Kevin Ross 2002-01-17 10:53:41 UTC
Created attachment 1028 [details]
Execute.java
Comment 3 Kevin Ross 2002-01-17 10:54:01 UTC
Created attachment 1029 [details]
PumpStreamHandler.java
Comment 4 Conor MacNeill 2003-01-18 14:43:07 UTC
*** Bug 4092 has been marked as a duplicate of this bug. ***
Comment 5 briank 2003-05-23 23:36:21 UTC
Has any validation of this patch been made yet?  Why is the status still "NEW"?
Comment 6 Ron Blum 2003-06-11 23:23:15 UTC
Has this functionality been incorporated into the mainline code?  It fixes a 
series of duplicates, which means there are quite a few people who depend on 
this functionality; and there is already a new PR (18572) which is potentially 
a duplicate, as well.

We've been using this patch for a year; however, we would prefer to move on to 
1.5.x without it because we'd rather not muck around with the core code.
Comment 7 Peter Nimmervoll 2003-06-12 19:19:17 UTC
IMO this patch didn´t get in because no one work with modified Sources. 
Attaching diff against current CVS HEAD, please commit. 
Comment 8 Peter Nimmervoll 2003-06-12 19:21:27 UTC
Created attachment 6783 [details]
patch
Comment 9 Peter Nimmervoll 2003-06-26 17:57:49 UTC
*** Bug 18572 has been marked as a duplicate of this bug. ***
Comment 10 Antoine Levy-Lambert 2003-07-26 17:40:28 UTC
the patch in this bug report introduces a new attribute waitfor for <exec/>
the default is false; if it is set to true ant will not wait for the process to 
finish.
Comments ?
Comment 11 Steve Loughran 2003-07-27 04:27:06 UTC
hmmm. A lot of people have wanted true spawning, running programs after Ant
exits. Does this give us that functionality?
Comment 12 Antoine Levy-Lambert 2003-07-27 16:11:29 UTC
I have prepared something similar to the patch included in this report.
I have tested on a shell script.
It works fine if I add in the script :
    trap "" SIGHUP
I am calling the new attribute spawn, rather than waitfor. So spawn has the 
default false (current behavior); when set to true ant does not wait for the 
process to finish, and does not log what the process is doing either.

Comment 13 Antoine Levy-Lambert 2003-07-28 10:49:46 UTC
I have fixed this in CVS.
The new attribute is called spawn. When set to true, it means that you want the 
process to run independently of ant.
To make things simple, I have disconnected completely the spawned process from 
ant's stream handlers and logging system.
I have tested my change on Solaris and on Windows 2000 with a simple script 
which does :

rm test.log
sleep 10
touch test.log

the script is still running after ant is finished and the test.log gets written.

here is my xml :

<project name="exec" default="default">
<target name="default">
<exec executable="C:/programme/cygwin/bin/sh.exe" spawn="true" os="Windows 
2000">
   <arg value="C:/dev/testant/titi.sh"/>
</exec>
<exec executable="/usr/bin/sh" spawn="true" os="SunOS">
   <arg value="titi.sh"/>
</exec>
</target>
</project>
Comment 14 Dominique Devienne 2003-07-28 14:10:29 UTC
Could you please test with a program (like java.exe/java) instead of a script 
please? Also, adding the 'spawn' attribute to <java> would be extremely useful.

Thanks, --DD
Comment 15 Antoine Levy-Lambert 2003-07-28 14:43:13 UTC
I will test with java.
Also concerning adding spawn attribute to the java task, I think someone emailed 
some code (was it you DD ?). It will help me obviously on this front if there is 
a good contribution.
Comment 16 Antoine Levy-Lambert 2003-07-28 16:04:24 UTC
The spawn attribute is working with Java on Win 2000 and Solaris.
Comment 17 Antoine Levy-Lambert 2003-07-30 14:25:21 UTC
The spawn attribute has been added to the Java task and there is one testcase 
too, similar (in java) to the test for <exec spawn="true"> (sleep 4 seconds and 
write a temporary file).
Comment 18 Antoine Levy-Lambert 2003-08-19 19:07:56 UTC
*** Bug 22553 has been marked as a duplicate of this bug. ***
Comment 19 Stefan Bodewig 2003-08-26 07:36:59 UTC
*** Bug 22710 has been marked as a duplicate of this bug. ***