/* * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2002 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.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 org.apache.tools.ant.taskdefs.condition.Os; import java.io.File; import java.io.IOException; 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 * @author Jeff Tulley * * @since Ant 1.2 * * @version $Revision$ */ 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; /** 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; /** Used to destroy processes when the VM exits. */ private static ProcessDestroyer processDestroyer = new ProcessDestroyer(); /** * 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 trying } if (Os.isFamily("mac")) { // Mac shellLauncher = new MacCommandLauncher(new CommandLauncher()); } else if (Os.isFamily("os/2")) { // OS/2 - use same mechanism as Windows 2000 shellLauncher = new WinNTCommandLauncher(new CommandLauncher()); } else if (Os.isFamily("windows")) { // 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(); } if (!Os.isFamily("win9x")) { // Windows XP/2000/NT shellLauncher = new WinNTCommandLauncher(baseLauncher); } else { // Windows 98/95 - need to use an auxiliary script shellLauncher = new ScriptCommandLauncher("bin/antRun.bat", baseLauncher); } } else if (Os.isFamily("netware")) { // NetWare. 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(); } shellLauncher = new PerlScriptCommandLauncher("bin/antRun.pl", 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. if (var != null) { procEnvironment.addElement(var); } } catch (java.io.IOException exc) { exc.printStackTrace(); // Just try to see how much we got } return procEnvironment; } private static String[] getProcEnvCommand() { if (Os.isFamily("os/2")) { // OS/2 - use same mechanism as Windows 2000 // Not sure String[] cmd = {"cmd", "/c", "set" }; return cmd; } else if (Os.isFamily("windows")) { // Determine if we're running under XP/2000/NT or 98/95 if (!Os.isFamily("win9x")) { // Windows XP/2000/NT String[] cmd = {"cmd", "/c", "set" }; return cmd; } else { // Windows 98/95 String[] cmd = {"command.com", "/c", "set" }; return cmd; } } else if (Os.isFamily("unix")) { // Generic UNIX // Alternatively one could use: /bin/sh -c env String[] cmd = {"/usr/bin/env"}; return cmd; } else if (Os.isFamily("netware")) { String[] cmd = {"env"}; return cmd; } else { // MAC OS 9 and previous // TODO: I have no idea how to get it, someone must fix it String[] cmd = null; return cmd; } } /** * Creates a new execute object using PumpStreamHandler 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 null 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 key=value */ public void setEnvironment(String[] env) { this.env = env; } /** * Sets the working directory of the process to execute. * *

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; } /** * Creates a process that runs a command. * * @param project the Project, only used for logging purposes, may be null. * @param command the command to run * @param env the environment for the command * @param the working directory for the command * @param useVM use the built-in exec command for JDK 1.3 if available. * * @since Ant 1.5 */ public static Process launch(Project project, String[] command, String[] env, File dir, boolean useVM) throws IOException { CommandLauncher launcher = vmLauncher != null ? vmLauncher : shellLauncher; if (!useVM) { launcher = shellLauncher; } return launcher.exec(project, command, env, dir); } /** * Runs a process defined by the command line and returns its exit status. * * @return the exit status of the subprocess or INVALID * @exception java.io.IOExcpetion The exception is thrown, if launching * of the subprocess failed */ public int execute() throws IOException { final Process process = launch(project, getCommandline(), getEnvironment(), workingDirectory, useVMLauncher); try { streamHandler.setProcessInputStream(process.getOutputStream()); streamHandler.setProcessOutputStream(process.getInputStream()); streamHandler.setProcessErrorStream(process.getErrorStream()); } catch (IOException e) { process.destroy(); throw e; } streamHandler.start(); // add the process to the list of those to destroy if the VM exits // processDestroyer.add(process); if (watchdog != null) { watchdog.start(process); } waitFor(process); // remove the process to the list of those to destroy if the VM exits // processDestroyer.remove(process); if (watchdog != null) { watchdog.stop(); } streamHandler.stop(); if (watchdog != null) { watchdog.checkException(); } return getExitValue(); } protected void waitFor(Process process) { try { process.waitFor(); setExitValue(process.exitValue()); } catch (InterruptedException e) {} } protected void setExitValue(int value) { exitValue = value; } /** * query the exit value of the process. * @return the exit value, 1 if the process was killed, * or Project.INVALID if no exit value has been received */ public int getExitValue() { return exitValue; } /** * test for an untimely death of the process * @return true iff a watchdog had to kill the process * @since Ant 1.5 */ public boolean killedProcess() { return watchdog != null && watchdog.killedProcess(); } /** * 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 XP/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; } /** * A command launcher that uses an auxiliary perl script to launch commands * in directories other than the current working directory. */ private static class PerlScriptCommandLauncher extends CommandLauncherProxy { PerlScriptCommandLauncher(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 + 3]; newcmd[0] = "perl"; newcmd[1] = antRun; newcmd[2] = commandDir.getAbsolutePath(); System.arraycopy(cmd, 0, newcmd, 3, cmd.length); return exec(project, newcmd, env); } private String _script; } }