diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Execute.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Execute.java | 734 |
1 files changed, 734 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Execute.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Execute.java new file mode 100644 index 00000000..9523f453 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Execute.java @@ -0,0 +1,734 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Vector; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.condition.Os; +import org.apache.tools.ant.taskdefs.launcher.CommandLauncher; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; + +/** + * Runs an external program. + * + * @since Ant 1.2 + */ +public class Execute { + + private static final int ONE_SECOND = 1000; + + /** + * Invalid exit code. set to {@link Integer#MAX_VALUE} + */ + public static final int INVALID = Integer.MAX_VALUE; + + private String[] cmdl = null; + private String[] env = null; + private int exitValue = INVALID; + private ExecuteStreamHandler streamHandler; + private final 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 Map<String, String> procEnvironment = null; + + /** Used to destroy processes when the VM exits. */ + private static ProcessDestroyer processDestroyer = new ProcessDestroyer(); + + /** Used for replacing env variables */ + private static boolean environmentCaseInSensitive = false; + + static { + if (Os.isFamily("windows")) { + environmentCaseInSensitive = true; + } + } + + /** + * Set whether or not you want the process to be spawned. + * Default is not spawned. + * + * @param spawn if true you do not want Ant + * to wait for the end of the process. + * Has no influence in here, the calling task contains + * and acts accordingly + * + * @since Ant 1.6 + * @deprecated + */ + @Deprecated + public void setSpawn(boolean spawn) { + // Method did not do anything to begin with + } + + /** + * Find the list of environment variables for this process. + * + * @return a map containing the environment variables. + * @since Ant 1.8.2 + */ + public static synchronized Map<String,String> getEnvironmentVariables() { + if (procEnvironment != null) { + return procEnvironment; + } + if (!Os.isFamily("openvms")) { + try { + procEnvironment = System.getenv(); + return procEnvironment; + } catch (Exception x) { + x.printStackTrace(); + } + } + + procEnvironment = new LinkedHashMap<String, String>(); + 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(toString(out))); + + if (Os.isFamily("openvms")) { + procEnvironment = getVMSLogicals(in); + return procEnvironment; + } + String var = null; + String line, lineSep = StringUtils.LINE_SEP; + 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) { + int eq = var.indexOf("="); + procEnvironment.put(var.substring(0, eq), + var.substring(eq + 1)); + } + var = line; + } + } + // Since we "look ahead" before adding, there's one last env var. + if (var != null) { + int eq = var.indexOf("="); + procEnvironment.put(var.substring(0, eq), var.substring(eq + 1)); + } + } catch (java.io.IOException exc) { + exc.printStackTrace(); + // Just try to see how much we got + } + return procEnvironment; + } + + /** + * Find the list of environment variables for this process. + * + * @return a vector containing the environment variables. + * The vector elements are strings formatted like variable = value. + * @deprecated use #getEnvironmentVariables instead + */ + @Deprecated + public static synchronized Vector<String> getProcEnvironment() { + Vector<String> v = new Vector<String>(); + for (Entry<String, String> entry : getEnvironmentVariables().entrySet()) { + v.add(entry.getKey() + "=" + entry.getValue()); + } + return v; + } + + /** + * This is the operation to get our environment. + * It is a notorious troublespot pre-Java1.5, and should be approached + * with extreme caution. + * + * @return command and arguments to get our environment + */ + private static String[] getProcEnvCommand() { + if (Os.isFamily("os/2")) { + // OS/2 - use same mechanism as Windows 2000 + return new String[] {"cmd", "/c", "set"}; + } else if (Os.isFamily("windows")) { + // Determine if we're running under XP/2000/NT or 98/95 + if (Os.isFamily("win9x")) { + // Windows 98/95 + return new String[] {"command.com", "/c", "set"}; + } else { + // Windows XP/2000/NT/2003 + return new String[] {"cmd", "/c", "set"}; + } + } else if (Os.isFamily("z/os") || Os.isFamily("unix")) { + // On most systems one could use: /bin/sh -c env + + // Some systems have /bin/env, others /usr/bin/env, just try + String[] cmd = new String[1]; + if (new File("/bin/env").canRead()) { + cmd[0] = "/bin/env"; + } else if (new File("/usr/bin/env").canRead()) { + cmd[0] = "/usr/bin/env"; + } else { + // rely on PATH + cmd[0] = "env"; + } + return cmd; + } else if (Os.isFamily("netware") || Os.isFamily("os/400")) { + // rely on PATH + return new String[] {"env"}; + } else if (Os.isFamily("openvms")) { + return new String[] {"show", "logical"}; + } else { + // MAC OS 9 and previous + // TODO: I have no idea how to get it, someone must fix it + return null; + } + } + + /** + * ByteArrayOutputStream#toString doesn't seem to work reliably on + * OS/390, at least not the way we use it in the execution + * context. + * + * @param bos the output stream that one wants to read. + * @return the output stream as a string, read with + * special encodings in the case of z/os and os/400. + * @since Ant 1.5 + */ + public static String toString(ByteArrayOutputStream bos) { + if (Os.isFamily("z/os")) { + try { + return bos.toString("Cp1047"); + } catch (java.io.UnsupportedEncodingException e) { + // noop default encoding used + } + } else if (Os.isFamily("os/400")) { + try { + return bos.toString("Cp500"); + } catch (java.io.UnsupportedEncodingException e) { + // noop default encoding used + } + } + return bos.toString(); + } + + /** + * 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 disable a timeout for the subprocess. + */ + public Execute(ExecuteStreamHandler streamHandler, + ExecuteWatchdog watchdog) { + setStreamHandler(streamHandler); + this.watchdog = watchdog; + // By default, use the shell launcher for VMS + // + if (Os.isFamily("openvms")) { + useVMLauncher = false; + } + } + + /** + * Set the stream handler to use. + * + * @param streamHandler ExecuteStreamHandler. + * @since Ant 1.6 + */ + public void setStreamHandler(ExecuteStreamHandler streamHandler) { + this.streamHandler = streamHandler; + } + + /** + * 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() { + return (env == null || newEnvironment) + ? env : patchEnvironment(); + } + + /** + * Sets the environment variables for the subprocess to launch. + * + * @param env 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) { + workingDirectory = + (wd == null || wd.getAbsolutePath().equals(antWorkingDirectory)) + ? null : wd; + } + + /** + * Return the working directory. + * + * @return the directory as a File. + * @since Ant 1.7 + */ + public File getWorkingDirectory() { + return workingDirectory == null ? new File(antWorkingDirectory) + : workingDirectory; + } + + /** + * Set the name of the antRun script using the project's value. + * + * @param project the current project. + * @throws BuildException not clear when it is going to throw an exception, but + * it is the method's signature. + */ + 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 useVMLauncher true if exec should launch through the 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 dir the working directory for the command. + * @param useVM use the built-in exec command for JDK 1.3 if available. + * @return the process started. + * @throws IOException forwarded from the particular launcher used. + * @since Ant 1.5 + */ + public static Process launch(Project project, String[] command, + String[] env, File dir, boolean useVM) + throws IOException { + if (dir != null && !dir.exists()) { + throw new BuildException(dir + " doesn't exist."); + } + + CommandLauncher vmLauncher = CommandLauncher.getVMLauncher(project); + CommandLauncher launcher = (useVM && vmLauncher != null) + ? vmLauncher : CommandLauncher.getShellLauncher(project); + 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 <code>INVALID</code>. + * @exception java.io.IOException The exception is thrown, if launching + * of the subprocess failed. + */ + public int execute() throws IOException { + if (workingDirectory != null && !workingDirectory.exists()) { + throw new BuildException(workingDirectory + " doesn't exist."); + } + 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(); + + try { + // 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); + + if (watchdog != null) { + watchdog.stop(); + } + streamHandler.stop(); + closeStreams(process); + + if (watchdog != null) { + watchdog.checkException(); + } + return getExitValue(); + } catch (ThreadDeath t) { + // #31928: forcibly kill it before continuing. + process.destroy(); + throw t; + } finally { + // remove the process to the list of those to destroy if + // the VM exits + // + processDestroyer.remove(process); + } + } + + /** + * Starts a process defined by the command line. + * Ant will not wait for this process, nor log its output. + * + * @throws java.io.IOException The exception is thrown, if launching + * of the subprocess failed. + * @since Ant 1.6 + */ + public void spawn() throws IOException { + if (workingDirectory != null && !workingDirectory.exists()) { + throw new BuildException(workingDirectory + " doesn't exist."); + } + final Process process = launch(project, getCommandline(), + getEnvironment(), workingDirectory, + useVMLauncher); + if (Os.isFamily("windows")) { + try { + Thread.sleep(ONE_SECOND); + } catch (InterruptedException e) { + project.log("interruption in the sleep after having spawned a" + + " process", Project.MSG_VERBOSE); + } + } + OutputStream dummyOut = new OutputStream() { + @Override + public void write(int b) throws IOException { + // Method intended to swallow whatever comes at it + } + }; + + ExecuteStreamHandler handler = new PumpStreamHandler(dummyOut); + handler.setProcessErrorStream(process.getErrorStream()); + handler.setProcessOutputStream(process.getInputStream()); + handler.start(); + process.getOutputStream().close(); + + project.log("spawned process " + process.toString(), + Project.MSG_VERBOSE); + } + + /** + * Wait for a given process. + * + * @param process the process one wants to wait for. + */ + protected void waitFor(Process process) { + try { + process.waitFor(); + setExitValue(process.exitValue()); + } catch (InterruptedException e) { + process.destroy(); + } + } + + /** + * Set the exit value. + * + * @param value exit value of the process. + */ + protected void setExitValue(int value) { + exitValue = value; + } + + /** + * Query the exit value of the process. + * + * @return the exit value or Execute.INVALID if no exit value has + * been received. + */ + public int getExitValue() { + return exitValue; + } + + /** + * Checks whether <code>exitValue</code> signals a failure on the current + * system (OS specific). + * + * <p><b>Note</b> that this method relies on the conventions of + * the OS, it will return false results if the application you are + * running doesn't follow these conventions. One notable + * exception is the Java VM provided by HP for OpenVMS - it will + * return 0 if successful (like on any other platform), but this + * signals a failure on OpenVMS. So if you execute a new Java VM + * on OpenVMS, you cannot trust this method.</p> + * + * @param exitValue the exit value (return code) to be checked. + * @return <code>true</code> if <code>exitValue</code> signals a failure. + */ + public static boolean isFailure(int exitValue) { + // on openvms even exit value signals failure; + // for other platforms nonzero exit value signals failure + return Os.isFamily("openvms") + ? (exitValue % 2 == 0) : (exitValue != 0); + } + + /** + * Did this execute return in a failure. + * + * @see #isFailure(int) + * @return true if and only if the exit code is interpreted as a failure + * @since Ant1.7 + */ + public boolean isFailure() { + return isFailure(getExitValue()); + } + + /** + * Test for an untimely death of the process. + * + * @return true if 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() { + // On OpenVMS Runtime#exec() doesn't support the environment array, + // so we only return the new values which then will be set in + // the generated DCL script, inheriting the parent process environment + if (Os.isFamily("openvms")) { + return env; + } + Map<String, String> osEnv = + new LinkedHashMap<String, String>(getEnvironmentVariables()); + for (int i = 0; i < env.length; i++) { + String keyValue = env[i]; + String key = keyValue.substring(0, keyValue.indexOf('=')); + // Find the key in the current environment copy + // and remove it. + + // Try without changing case first + if (osEnv.remove(key) == null && environmentCaseInSensitive) { + // not found, maybe perform a case insensitive search + + for (String osEnvItem : osEnv.keySet()) { + // Nb: using default locale as key is a env name + if (osEnvItem.toLowerCase().equals(key.toLowerCase())) { + // Use the original casiness of the key + key = osEnvItem; + break; + } + } + } + + // Add the key to the enviromnent copy + osEnv.put(key, keyValue.substring(key.length() + 1)); + } + + ArrayList<String> l = new ArrayList<String>(); + for (Entry<String, String> entry : osEnv.entrySet()) { + l.add(entry.getKey() + "=" + entry.getValue()); + } + return l.toArray(new String[osEnv.size()]); + } + + /** + * 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 exit successfully. + */ + public static void runCommand(Task task, String[] cmdline) + throws BuildException { + try { + task.log(Commandline.describeCommand(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 (isFailure(retval)) { + 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()); + } + } + + /** + * Close the streams belonging to the given Process. + * + * @param process the <code>Process</code>. + */ + public static void closeStreams(Process process) { + FileUtils.close(process.getInputStream()); + FileUtils.close(process.getOutputStream()); + FileUtils.close(process.getErrorStream()); + } + + /** + * This method is VMS specific and used by getEnvironmentVariables(). + * + * Parses VMS logicals from <code>in</code> and returns them as a Map. + * <code>in</code> is expected to be the + * output of "SHOW LOGICAL". The method takes care of parsing the output + * correctly as well as making sure that a logical defined in multiple + * tables only gets added from the highest order table. Logicals with + * multiple equivalence names are mapped to a variable with multiple + * values separated by a comma (,). + */ + private static Map<String, String> getVMSLogicals(BufferedReader in) + throws IOException { + HashMap<String, String> logicals = new HashMap<String, String>(); + String logName = null, logValue = null, newLogName; + String line = null; + // CheckStyle:MagicNumber OFF + while ((line = in.readLine()) != null) { + // parse the VMS logicals into required format ("VAR=VAL[,VAL2]") + if (line.startsWith("\t=")) { + // further equivalence name of previous logical + if (logName != null) { + logValue += "," + line.substring(4, line.length() - 1); + } + } else if (line.startsWith(" \"")) { + // new logical? + if (logName != null) { + logicals.put(logName, logValue); + } + int eqIndex = line.indexOf('='); + newLogName = line.substring(3, eqIndex - 2); + if (logicals.containsKey(newLogName)) { + // already got this logical from a higher order table + logName = null; + } else { + logName = newLogName; + logValue = line.substring(eqIndex + 3, line.length() - 1); + } + } + } + // CheckStyle:MagicNumber ON + // Since we "look ahead" before adding, there's one last env var. + if (logName != null) { + logicals.put(logName, logValue); + } + return logicals; + } +} |