Home » apache-ant-1.7.1-src » org.apache.tools » ant » taskdefs » [javadoc | source]
    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    *
   17    */
   18   
   19   package org.apache.tools.ant.taskdefs;
   20   
   21   import java.io.BufferedReader;
   22   import java.io.ByteArrayOutputStream;
   23   import java.io.File;
   24   import java.io.FileWriter;
   25   import java.io.IOException;
   26   import java.io.OutputStream;
   27   import java.io.PrintWriter;
   28   import java.io.StringReader;
   29   import java.lang.reflect.InvocationTargetException;
   30   import java.lang.reflect.Method;
   31   import java.util.HashMap;
   32   import java.util.Iterator;
   33   import java.util.Vector;
   34   
   35   import org.apache.tools.ant.BuildException;
   36   import org.apache.tools.ant.MagicNames;
   37   import org.apache.tools.ant.Project;
   38   import org.apache.tools.ant.Task;
   39   import org.apache.tools.ant.taskdefs.condition.Os;
   40   import org.apache.tools.ant.types.Commandline;
   41   import org.apache.tools.ant.util.FileUtils;
   42   import org.apache.tools.ant.util.StringUtils;
   43   
   44   /**
   45    * Runs an external program.
   46    *
   47    * @since Ant 1.2
   48    *
   49    */
   50   public class Execute {
   51   
   52       private static final int ONE_SECOND = 1000;
   53   
   54       /** Invalid exit code.
   55        * set to {@link Integer#MAX_VALUE}
   56        */
   57       public static final int INVALID = Integer.MAX_VALUE;
   58   
   59       private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
   60   
   61       private String[] cmdl = null;
   62       private String[] env = null;
   63       private int exitValue = INVALID;
   64       private ExecuteStreamHandler streamHandler;
   65       private ExecuteWatchdog watchdog;
   66       private File workingDirectory = null;
   67       private Project project = null;
   68       private boolean newEnvironment = false;
   69       //TODO: nothing appears to read this but is set using a public setter.
   70       private boolean spawn = false;
   71   
   72   
   73       /** Controls whether the VM is used to launch commands, where possible. */
   74       private boolean useVMLauncher = true;
   75   
   76       private static String antWorkingDirectory = System.getProperty("user.dir");
   77       private static CommandLauncher vmLauncher = null;
   78       private static CommandLauncher shellLauncher = null;
   79       private static Vector procEnvironment = null;
   80   
   81       /** Used to destroy processes when the VM exits. */
   82       private static ProcessDestroyer processDestroyer = new ProcessDestroyer();
   83   
   84       /** Used for replacing env variables */
   85       private static boolean environmentCaseInSensitive = false;
   86   
   87       /*
   88        * Builds a command launcher for the OS and JVM we are running under.
   89        */
   90       static {
   91           // Try using a JDK 1.3 launcher
   92           try {
   93               if (!Os.isFamily("os/2")) {
   94                   vmLauncher = new Java13CommandLauncher();
   95               }
   96           } catch (NoSuchMethodException exc) {
   97               // Ignore and keep trying
   98           }
   99           if (Os.isFamily("mac") && !Os.isFamily("unix")) {
  100               // Mac
  101               shellLauncher = new MacCommandLauncher(new CommandLauncher());
  102           } else if (Os.isFamily("os/2")) {
  103               // OS/2
  104               shellLauncher = new OS2CommandLauncher(new CommandLauncher());
  105           } else if (Os.isFamily("windows")) {
  106               environmentCaseInSensitive = true;
  107               CommandLauncher baseLauncher = new CommandLauncher();
  108   
  109               if (!Os.isFamily("win9x")) {
  110                   // Windows XP/2000/NT
  111                   shellLauncher = new WinNTCommandLauncher(baseLauncher);
  112               } else {
  113                   // Windows 98/95 - need to use an auxiliary script
  114                   shellLauncher
  115                       = new ScriptCommandLauncher("bin/antRun.bat", baseLauncher);
  116               }
  117           } else if (Os.isFamily("netware")) {
  118   
  119               CommandLauncher baseLauncher = new CommandLauncher();
  120   
  121               shellLauncher
  122                   = new PerlScriptCommandLauncher("bin/antRun.pl", baseLauncher);
  123           } else if (Os.isFamily("openvms")) {
  124               // OpenVMS
  125               try {
  126                   shellLauncher = new VmsCommandLauncher();
  127               } catch (NoSuchMethodException exc) {
  128               // Ignore and keep trying
  129               }
  130           } else {
  131               // Generic
  132               shellLauncher = new ScriptCommandLauncher("bin/antRun",
  133                   new CommandLauncher());
  134           }
  135       }
  136   
  137       /**
  138        * Set whether or not you want the process to be spawned.
  139        * Default is not spawned.
  140        *
  141        * @param spawn if true you do not want Ant
  142        *              to wait for the end of the process.
  143        *
  144        * @since Ant 1.6
  145        */
  146       public void setSpawn(boolean spawn) {
  147           this.spawn = spawn;
  148       }
  149   
  150       /**
  151        * Find the list of environment variables for this process.
  152        *
  153        * @return a vector containing the environment variables.
  154        * The vector elements are strings formatted like variable = value.
  155        */
  156       public static synchronized Vector getProcEnvironment() {
  157           if (procEnvironment != null) {
  158               return procEnvironment;
  159           }
  160           procEnvironment = new Vector();
  161           try {
  162               ByteArrayOutputStream out = new ByteArrayOutputStream();
  163               Execute exe = new Execute(new PumpStreamHandler(out));
  164               exe.setCommandline(getProcEnvCommand());
  165               // Make sure we do not recurse forever
  166               exe.setNewenvironment(true);
  167               int retval = exe.execute();
  168               if (retval != 0) {
  169                   // Just try to use what we got
  170               }
  171               BufferedReader in =
  172                   new BufferedReader(new StringReader(toString(out)));
  173   
  174               if (Os.isFamily("openvms")) {
  175                   procEnvironment = addVMSLogicals(procEnvironment, in);
  176                   return procEnvironment;
  177               }
  178               String var = null;
  179               String line, lineSep = StringUtils.LINE_SEP;
  180               while ((line = in.readLine()) != null) {
  181                   if (line.indexOf('=') == -1) {
  182                       // Chunk part of previous env var (UNIX env vars can
  183                       // contain embedded new lines).
  184                       if (var == null) {
  185                           var = lineSep + line;
  186                       } else {
  187                           var += lineSep + line;
  188                       }
  189                   } else {
  190                       // New env var...append the previous one if we have it.
  191                       if (var != null) {
  192                           procEnvironment.addElement(var);
  193                       }
  194                       var = line;
  195                   }
  196               }
  197               // Since we "look ahead" before adding, there's one last env var.
  198               if (var != null) {
  199                   procEnvironment.addElement(var);
  200               }
  201           } catch (java.io.IOException exc) {
  202               exc.printStackTrace();
  203               // Just try to see how much we got
  204           }
  205           return procEnvironment;
  206       }
  207   
  208       /**
  209        * This is the operation to get our environment.
  210        * It is a notorious troublespot pre-Java1.5, and should be approached
  211        * with extreme caution.
  212        * @return
  213        */
  214       private static String[] getProcEnvCommand() {
  215           if (Os.isFamily("os/2")) {
  216               // OS/2 - use same mechanism as Windows 2000
  217               return new String[] {"cmd", "/c", "set" };
  218           } else if (Os.isFamily("windows")) {
  219               // Determine if we're running under XP/2000/NT or 98/95
  220               if (Os.isFamily("win9x")) {
  221                   // Windows 98/95
  222                   return new String[] {"command.com", "/c", "set" };
  223               } else {
  224                   // Windows XP/2000/NT/2003
  225                   return new String[] {"cmd", "/c", "set" };
  226               }
  227           } else if (Os.isFamily("z/os") || Os.isFamily("unix")) {
  228               // On most systems one could use: /bin/sh -c env
  229   
  230               // Some systems have /bin/env, others /usr/bin/env, just try
  231               String[] cmd = new String[1];
  232               if (new File("/bin/env").canRead()) {
  233                   cmd[0] = "/bin/env";
  234               } else if (new File("/usr/bin/env").canRead()) {
  235                   cmd[0] = "/usr/bin/env";
  236               } else {
  237                   // rely on PATH
  238                   cmd[0] = "env";
  239               }
  240               return cmd;
  241           } else if (Os.isFamily("netware") || Os.isFamily("os/400")) {
  242               // rely on PATH
  243               return new String[] {"env"};
  244           } else if (Os.isFamily("openvms")) {
  245               return new String[] {"show", "logical"};
  246           } else {
  247               // MAC OS 9 and previous
  248               //TODO: I have no idea how to get it, someone must fix it
  249               return null;
  250           }
  251       }
  252   
  253       /**
  254        * ByteArrayOutputStream#toString doesn't seem to work reliably on
  255        * OS/390, at least not the way we use it in the execution
  256        * context.
  257        *
  258        * @param bos the output stream that one wants to read.
  259        * @return the output stream as a string, read with
  260        * special encodings in the case of z/os and os/400.
  261        *
  262        * @since Ant 1.5
  263        */
  264       public static String toString(ByteArrayOutputStream bos) {
  265           if (Os.isFamily("z/os")) {
  266               try {
  267                   return bos.toString("Cp1047");
  268               } catch (java.io.UnsupportedEncodingException e) {
  269                   //noop default encoding used
  270               }
  271           } else if (Os.isFamily("os/400")) {
  272               try {
  273                   return bos.toString("Cp500");
  274               } catch (java.io.UnsupportedEncodingException e) {
  275                   //noop default encoding used
  276               }
  277           }
  278           return bos.toString();
  279       }
  280   
  281       /**
  282        * Creates a new execute object using <code>PumpStreamHandler</code> for
  283        * stream handling.
  284        */
  285       public Execute() {
  286           this(new PumpStreamHandler(), null);
  287       }
  288   
  289       /**
  290        * Creates a new execute object.
  291        *
  292        * @param streamHandler the stream handler used to handle the input and
  293        *        output streams of the subprocess.
  294        */
  295       public Execute(ExecuteStreamHandler streamHandler) {
  296           this(streamHandler, null);
  297       }
  298   
  299       /**
  300        * Creates a new execute object.
  301        *
  302        * @param streamHandler the stream handler used to handle the input and
  303        *        output streams of the subprocess.
  304        * @param watchdog a watchdog for the subprocess or <code>null</code> to
  305        *        to disable a timeout for the subprocess.
  306        */
  307       public Execute(ExecuteStreamHandler streamHandler,
  308                      ExecuteWatchdog watchdog) {
  309           setStreamHandler(streamHandler);
  310           this.watchdog = watchdog;
  311           //By default, use the shell launcher for VMS
  312           //
  313           if (Os.isFamily("openvms")) {
  314               useVMLauncher = false;
  315           }
  316       }
  317   
  318       /**
  319        * Set the stream handler to use.
  320        * @param streamHandler ExecuteStreamHandler.
  321        * @since Ant 1.6
  322        */
  323       public void setStreamHandler(ExecuteStreamHandler streamHandler) {
  324           this.streamHandler = streamHandler;
  325       }
  326   
  327       /**
  328        * Returns the commandline used to create a subprocess.
  329        *
  330        * @return the commandline used to create a subprocess.
  331        */
  332       public String[] getCommandline() {
  333           return cmdl;
  334       }
  335   
  336       /**
  337        * Sets the commandline of the subprocess to launch.
  338        *
  339        * @param commandline the commandline of the subprocess to launch.
  340        */
  341       public void setCommandline(String[] commandline) {
  342           cmdl = commandline;
  343       }
  344   
  345       /**
  346        * Set whether to propagate the default environment or not.
  347        *
  348        * @param newenv whether to propagate the process environment.
  349        */
  350       public void setNewenvironment(boolean newenv) {
  351           newEnvironment = newenv;
  352       }
  353   
  354       /**
  355        * Returns the environment used to create a subprocess.
  356        *
  357        * @return the environment used to create a subprocess.
  358        */
  359       public String[] getEnvironment() {
  360           return (env == null || newEnvironment)
  361               ? env : patchEnvironment();
  362       }
  363   
  364       /**
  365        * Sets the environment variables for the subprocess to launch.
  366        *
  367        * @param env array of Strings, each element of which has
  368        * an environment variable settings in format <em>key=value</em>.
  369        */
  370       public void setEnvironment(String[] env) {
  371           this.env = env;
  372       }
  373   
  374       /**
  375        * Sets the working directory of the process to execute.
  376        *
  377        * <p>This is emulated using the antRun scripts unless the OS is
  378        * Windows NT in which case a cmd.exe is spawned,
  379        * or MRJ and setting user.dir works, or JDK 1.3 and there is
  380        * official support in java.lang.Runtime.
  381        *
  382        * @param wd the working directory of the process.
  383        */
  384       public void setWorkingDirectory(File wd) {
  385           workingDirectory =
  386               (wd == null || wd.getAbsolutePath().equals(antWorkingDirectory))
  387               ? null : wd;
  388       }
  389   
  390       /**
  391        * Return the working directory.
  392        * @return the directory as a File.
  393        * @since Ant 1.7
  394        */
  395       public File getWorkingDirectory() {
  396           return workingDirectory == null ? new File(antWorkingDirectory)
  397                                           : workingDirectory;
  398       }
  399   
  400       /**
  401        * Set the name of the antRun script using the project's value.
  402        *
  403        * @param project the current project.
  404        *
  405        * @throws BuildException not clear when it is going to throw an exception, but
  406        * it is the method's signature.
  407        */
  408       public void setAntRun(Project project) throws BuildException {
  409           this.project = project;
  410       }
  411   
  412       /**
  413        * Launch this execution through the VM, where possible, rather than through
  414        * the OS's shell. In some cases and operating systems using the shell will
  415        * allow the shell to perform additional processing such as associating an
  416        * executable with a script, etc.
  417        *
  418        * @param useVMLauncher true if exec should launch through the VM,
  419        *                   false if the shell should be used to launch the
  420        *                   command.
  421        */
  422       public void setVMLauncher(boolean useVMLauncher) {
  423           this.useVMLauncher = useVMLauncher;
  424       }
  425   
  426       /**
  427        * Creates a process that runs a command.
  428        *
  429        * @param project the Project, only used for logging purposes, may be null.
  430        * @param command the command to run.
  431        * @param env the environment for the command.
  432        * @param dir the working directory for the command.
  433        * @param useVM use the built-in exec command for JDK 1.3 if available.
  434        * @return the process started.
  435        * @throws IOException forwarded from the particular launcher used.
  436        *
  437        * @since Ant 1.5
  438        */
  439       public static Process launch(Project project, String[] command,
  440                                    String[] env, File dir, boolean useVM)
  441           throws IOException {
  442           if (dir != null && !dir.exists()) {
  443               throw new BuildException(dir + " doesn't exist.");
  444           }
  445           CommandLauncher launcher
  446               = ((useVM && vmLauncher != null) ? vmLauncher : shellLauncher);
  447           return launcher.exec(project, command, env, dir);
  448       }
  449   
  450       /**
  451        * Runs a process defined by the command line and returns its exit status.
  452        *
  453        * @return the exit status of the subprocess or <code>INVALID</code>.
  454        * @exception java.io.IOException The exception is thrown, if launching
  455        *            of the subprocess failed.
  456        */
  457       public int execute() throws IOException {
  458           if (workingDirectory != null && !workingDirectory.exists()) {
  459               throw new BuildException(workingDirectory + " doesn't exist.");
  460           }
  461           final Process process = launch(project, getCommandline(),
  462                                          getEnvironment(), workingDirectory,
  463                                          useVMLauncher);
  464           try {
  465               streamHandler.setProcessInputStream(process.getOutputStream());
  466               streamHandler.setProcessOutputStream(process.getInputStream());
  467               streamHandler.setProcessErrorStream(process.getErrorStream());
  468           } catch (IOException e) {
  469               process.destroy();
  470               throw e;
  471           }
  472           streamHandler.start();
  473   
  474           try {
  475               // add the process to the list of those to destroy if the VM exits
  476               //
  477               processDestroyer.add(process);
  478   
  479               if (watchdog != null) {
  480                   watchdog.start(process);
  481               }
  482               waitFor(process);
  483   
  484               if (watchdog != null) {
  485                   watchdog.stop();
  486               }
  487               streamHandler.stop();
  488               closeStreams(process);
  489   
  490               if (watchdog != null) {
  491                   watchdog.checkException();
  492               }
  493               return getExitValue();
  494           } catch (ThreadDeath t) {
  495               // #31928: forcibly kill it before continuing.
  496               process.destroy();
  497               throw t;
  498           } finally {
  499               // remove the process to the list of those to destroy if
  500               // the VM exits
  501               //
  502               processDestroyer.remove(process);
  503           }
  504       }
  505   
  506       /**
  507        * Starts a process defined by the command line.
  508        * Ant will not wait for this process, nor log its output.
  509        *
  510        * @throws java.io.IOException The exception is thrown, if launching
  511        *            of the subprocess failed.
  512        * @since Ant 1.6
  513        */
  514       public void spawn() throws IOException {
  515           if (workingDirectory != null && !workingDirectory.exists()) {
  516               throw new BuildException(workingDirectory + " doesn't exist.");
  517           }
  518           final Process process = launch(project, getCommandline(),
  519                                          getEnvironment(), workingDirectory,
  520                                          useVMLauncher);
  521           if (Os.isFamily("windows")) {
  522               try {
  523                   Thread.sleep(ONE_SECOND);
  524               } catch (InterruptedException e) {
  525                   project.log("interruption in the sleep after having spawned a"
  526                               + " process", Project.MSG_VERBOSE);
  527               }
  528           }
  529           OutputStream dummyOut = new OutputStream() {
  530               public void write(int b) throws IOException {
  531               }
  532           };
  533   
  534           ExecuteStreamHandler handler = new PumpStreamHandler(dummyOut);
  535           handler.setProcessErrorStream(process.getErrorStream());
  536           handler.setProcessOutputStream(process.getInputStream());
  537           handler.start();
  538           process.getOutputStream().close();
  539   
  540           project.log("spawned process " + process.toString(),
  541                       Project.MSG_VERBOSE);
  542       }
  543   
  544       /**
  545        * Wait for a given process.
  546        *
  547        * @param process the process one wants to wait for.
  548        */
  549       protected void waitFor(Process process) {
  550           try {
  551               process.waitFor();
  552               setExitValue(process.exitValue());
  553           } catch (InterruptedException e) {
  554               process.destroy();
  555           }
  556       }
  557   
  558       /**
  559        * Set the exit value.
  560        *
  561        * @param value exit value of the process.
  562        */
  563       protected void setExitValue(int value) {
  564           exitValue = value;
  565       }
  566   
  567       /**
  568        * Query the exit value of the process.
  569        * @return the exit value or Execute.INVALID if no exit value has
  570        * been received.
  571        */
  572       public int getExitValue() {
  573           return exitValue;
  574       }
  575   
  576       /**
  577        * Checks whether <code>exitValue</code> signals a failure on the current
  578        * system (OS specific).
  579        *
  580        * <p><b>Note</b> that this method relies on the conventions of
  581        * the OS, it will return false results if the application you are
  582        * running doesn't follow these conventions.  One notable
  583        * exception is the Java VM provided by HP for OpenVMS - it will
  584        * return 0 if successful (like on any other platform), but this
  585        * signals a failure on OpenVMS.  So if you execute a new Java VM
  586        * on OpenVMS, you cannot trust this method.</p>
  587        *
  588        * @param exitValue the exit value (return code) to be checked.
  589        * @return <code>true</code> if <code>exitValue</code> signals a failure.
  590        */
  591       public static boolean isFailure(int exitValue) {
  592           //on openvms even exit value signals failure;
  593           // for other platforms nonzero exit value signals failure
  594           return Os.isFamily("openvms")
  595               ? (exitValue % 2 == 0) : (exitValue != 0);
  596       }
  597   
  598       /**
  599        * Did this execute return in a failure.
  600        * @see #isFailure(int)
  601        * @return true if and only if the exit code is interpreted as a failure
  602        * @since Ant1.7
  603        */
  604       public boolean isFailure() {
  605           return isFailure(getExitValue());
  606       }
  607   
  608       /**
  609        * Test for an untimely death of the process.
  610        * @return true if a watchdog had to kill the process.
  611        * @since Ant 1.5
  612        */
  613       public boolean killedProcess() {
  614           return watchdog != null && watchdog.killedProcess();
  615       }
  616   
  617       /**
  618        * Patch the current environment with the new values from the user.
  619        * @return the patched environment.
  620        */
  621       private String[] patchEnvironment() {
  622           // On OpenVMS Runtime#exec() doesn't support the environment array,
  623           // so we only return the new values which then will be set in
  624           // the generated DCL script, inheriting the parent process environment
  625           if (Os.isFamily("openvms")) {
  626               return env;
  627           }
  628           Vector osEnv = (Vector) getProcEnvironment().clone();
  629           for (int i = 0; i < env.length; i++) {
  630               String keyValue = env[i];
  631               // Get key including "="
  632               String key = keyValue.substring(0, keyValue.indexOf('=') + 1);
  633               if (environmentCaseInSensitive) {
  634                   // Nb: using default locale as key is a env name
  635                   key = key.toLowerCase();
  636               }
  637               int size = osEnv.size();
  638               // Find the key in the current enviroment copy
  639               // and remove it.
  640               for (int j = 0; j < size; j++) {
  641                   String osEnvItem = (String) osEnv.elementAt(j);
  642                   String convertedItem = environmentCaseInSensitive
  643                       ? osEnvItem.toLowerCase() : osEnvItem;
  644                   if (convertedItem.startsWith(key)) {
  645                       osEnv.removeElementAt(j);
  646                       if (environmentCaseInSensitive) {
  647                           // Use the original casiness of the key
  648                           keyValue = osEnvItem.substring(0, key.length())
  649                               + keyValue.substring(key.length());
  650                       }
  651                       break;
  652                   }
  653               }
  654               // Add the key to the enviromnent copy
  655               osEnv.addElement(keyValue);
  656           }
  657           return (String[]) (osEnv.toArray(new String[osEnv.size()]));
  658       }
  659   
  660       /**
  661        * A utility method that runs an external command.  Writes the output and
  662        * error streams of the command to the project log.
  663        *
  664        * @param task      The task that the command is part of.  Used for logging
  665        * @param cmdline   The command to execute.
  666        *
  667        * @throws BuildException if the command does not exit successfully.
  668        */
  669       public static void runCommand(Task task, String[] cmdline)
  670           throws BuildException {
  671           try {
  672               task.log(Commandline.describeCommand(cmdline),
  673                        Project.MSG_VERBOSE);
  674               Execute exe = new Execute(
  675                   new LogStreamHandler(task, Project.MSG_INFO, Project.MSG_ERR));
  676               exe.setAntRun(task.getProject());
  677               exe.setCommandline(cmdline);
  678               int retval = exe.execute();
  679               if (isFailure(retval)) {
  680                   throw new BuildException(cmdline[0]
  681                       + " failed with return code " + retval, task.getLocation());
  682               }
  683           } catch (java.io.IOException exc) {
  684               throw new BuildException("Could not launch " + cmdline[0] + ": "
  685                   + exc, task.getLocation());
  686           }
  687       }
  688   
  689       /**
  690        * Close the streams belonging to the given Process.
  691        * @param process   the <code>Process</code>.
  692        */
  693       public static void closeStreams(Process process) {
  694           FileUtils.close(process.getInputStream());
  695           FileUtils.close(process.getOutputStream());
  696           FileUtils.close(process.getErrorStream());
  697       }
  698   
  699       /**
  700        * This method is VMS specific and used by getProcEnvironment().
  701        *
  702        * Parses VMS logicals from <code>in</code> and adds them to
  703        * <code>environment</code>.  <code>in</code> is expected to be the
  704        * output of "SHOW LOGICAL".  The method takes care of parsing the output
  705        * correctly as well as making sure that a logical defined in multiple
  706        * tables only gets added from the highest order table.  Logicals with
  707        * multiple equivalence names are mapped to a variable with multiple
  708        * values separated by a comma (,).
  709        */
  710       private static Vector addVMSLogicals(Vector environment, BufferedReader in)
  711           throws IOException {
  712           HashMap logicals = new HashMap();
  713           String logName = null, logValue = null, newLogName;
  714           String line = null;
  715           // CheckStyle:MagicNumber OFF
  716           while ((line = in.readLine()) != null) {
  717               // parse the VMS logicals into required format ("VAR=VAL[,VAL2]")
  718               if (line.startsWith("\t=")) {
  719                   // further equivalence name of previous logical
  720                   if (logName != null) {
  721                       logValue += "," + line.substring(4, line.length() - 1);
  722                   }
  723               } else if (line.startsWith("  \"")) {
  724                   // new logical?
  725                   if (logName != null) {
  726                       logicals.put(logName, logValue);
  727                   }
  728                   int eqIndex = line.indexOf('=');
  729                   newLogName = line.substring(3, eqIndex - 2);
  730                   if (logicals.containsKey(newLogName)) {
  731                       // already got this logical from a higher order table
  732                       logName = null;
  733                   } else {
  734                       logName = newLogName;
  735                       logValue = line.substring(eqIndex + 3, line.length() - 1);
  736                   }
  737               }
  738           }
  739           // CheckStyle:MagicNumber ON
  740           // Since we "look ahead" before adding, there's one last env var.
  741           if (logName != null) {
  742               logicals.put(logName, logValue);
  743           }
  744           for (Iterator i = logicals.keySet().iterator(); i.hasNext();) {
  745               String logical = (String) i.next();
  746               environment.add(logical + "=" + logicals.get(logical));
  747           }
  748           return environment;
  749       }
  750   
  751       /**
  752        * A command launcher for a particular JVM/OS platform.  This class is
  753        * a general purpose command launcher which can only launch commands in
  754        * the current working directory.
  755        */
  756       private static class CommandLauncher {
  757           /**
  758            * Launches the given command in a new process.
  759            *
  760            * @param project       The project that the command is part of.
  761            * @param cmd           The command to execute.
  762            * @param env           The environment for the new process.  If null,
  763            *                      the environment of the current process is used.
  764            * @return the created Process.
  765            * @throws IOException if attempting to run a command in a
  766            * specific directory.
  767            */
  768           public Process exec(Project project, String[] cmd, String[] env)
  769                throws IOException {
  770               if (project != null) {
  771                   project.log("Execute:CommandLauncher: "
  772                       + Commandline.describeCommand(cmd), Project.MSG_DEBUG);
  773               }
  774               return Runtime.getRuntime().exec(cmd, env);
  775           }
  776   
  777           /**
  778            * Launches the given command in a new process, in the given working
  779            * directory.
  780            *
  781            * @param project       The project that the command is part of.
  782            * @param cmd           The command to execute.
  783            * @param env           The environment for the new process.  If null,
  784            *                      the environment of the current process is used.
  785            * @param workingDir    The directory to start the command in.  If null,
  786            *                      the current directory is used.
  787            * @return the created Process.
  788            * @throws IOException  if trying to change directory.
  789            */
  790           public Process exec(Project project, String[] cmd, String[] env,
  791                               File workingDir) throws IOException {
  792               if (workingDir == null) {
  793                   return exec(project, cmd, env);
  794               }
  795               throw new IOException("Cannot execute a process in different "
  796                   + "directory under this JVM");
  797           }
  798       }
  799   
  800       /**
  801        * A command launcher for JDK/JRE 1.3 (and higher).  Uses the built-in
  802        * Runtime.exec() command.
  803        */
  804       private static class Java13CommandLauncher extends CommandLauncher {
  805           private Method myExecWithCWD;
  806   
  807           public Java13CommandLauncher() throws NoSuchMethodException {
  808               // Locate method Runtime.exec(String[] cmdarray,
  809               //                            String[] envp, File dir)
  810               myExecWithCWD = Runtime.class.getMethod("exec",
  811                   new Class[] {String[].class, String[].class, File.class});
  812           }
  813   
  814           /**
  815            * Launches the given command in a new process, in the given working
  816            * directory.
  817            * @param project the Ant project.
  818            * @param cmd the command line to execute as an array of strings.
  819            * @param env the environment to set as an array of strings.
  820            * @param workingDir the working directory where the command
  821            * should run.
  822            * @return the created Process.
  823            * @throws IOException probably forwarded from Runtime#exec.
  824            */
  825           public Process exec(Project project, String[] cmd, String[] env,
  826                               File workingDir) throws IOException {
  827               try {
  828                   if (project != null) {
  829                       project.log("Execute:Java13CommandLauncher: "
  830                           + Commandline.describeCommand(cmd), Project.MSG_DEBUG);
  831                   }
  832                   return (Process) myExecWithCWD.invoke(Runtime.getRuntime(),
  833                      /* the arguments: */ new Object[] {cmd, env, workingDir});
  834               } catch (InvocationTargetException exc) {
  835                   Throwable realexc = exc.getTargetException();
  836                   if (realexc instanceof ThreadDeath) {
  837                       throw (ThreadDeath) realexc;
  838                   } else if (realexc instanceof IOException) {
  839                       throw (IOException) realexc;
  840                   } else {
  841                       throw new BuildException("Unable to execute command",
  842                                                realexc);
  843                   }
  844               } catch (Exception exc) {
  845                   // IllegalAccess, IllegalArgument, ClassCast
  846                   throw new BuildException("Unable to execute command", exc);
  847               }
  848           }
  849       }
  850   
  851       /**
  852        * A command launcher that proxies another command launcher.
  853        *
  854        * Sub-classes override exec(args, env, workdir).
  855        */
  856       private static class CommandLauncherProxy extends CommandLauncher {
  857           private CommandLauncher myLauncher;
  858   
  859           CommandLauncherProxy(CommandLauncher launcher) {
  860               myLauncher = launcher;
  861           }
  862   
  863           /**
  864            * Launches the given command in a new process.  Delegates this
  865            * method to the proxied launcher.
  866            * @param project the Ant project.
  867            * @param cmd the command line to execute as an array of strings.
  868            * @param env the environment to set as an array of strings.
  869            * @return the created Process.
  870            * @throws IOException forwarded from the exec method of the
  871            * command launcher.
  872            */
  873           public Process exec(Project project, String[] cmd, String[] env)
  874               throws IOException {
  875               return myLauncher.exec(project, cmd, env);
  876           }
  877       }
  878   
  879       /**
  880        * A command launcher for OS/2 that uses 'cmd.exe' when launching
  881        * commands in directories other than the current working
  882        * directory.
  883        *
  884        * <p>Unlike Windows NT and friends, OS/2's cd doesn't support the
  885        * /d switch to change drives and directories in one go.</p>
  886        */
  887       private static class OS2CommandLauncher extends CommandLauncherProxy {
  888           OS2CommandLauncher(CommandLauncher launcher) {
  889               super(launcher);
  890           }
  891   
  892           /**
  893            * Launches the given command in a new process, in the given working
  894            * directory.
  895            * @param project the Ant project.
  896            * @param cmd the command line to execute as an array of strings.
  897            * @param env the environment to set as an array of strings.
  898            * @param workingDir working directory where the command should run.
  899            * @return the created Process.
  900            * @throws IOException forwarded from the exec method of the
  901            * command launcher.
  902            */
  903           public Process exec(Project project, String[] cmd, String[] env,
  904                               File workingDir) throws IOException {
  905               File commandDir = workingDir;
  906               if (workingDir == null) {
  907                   if (project != null) {
  908                       commandDir = project.getBaseDir();
  909                   } else {
  910                       return exec(project, cmd, env);
  911                   }
  912               }
  913               // Use cmd.exe to change to the specified drive and
  914               // directory before running the command
  915               final int preCmdLength = 7;
  916               final String cmdDir = commandDir.getAbsolutePath();
  917               String[] newcmd = new String[cmd.length + preCmdLength];
  918               // CheckStyle:MagicNumber OFF - do not bother
  919               newcmd[0] = "cmd";
  920               newcmd[1] = "/c";
  921               newcmd[2] = cmdDir.substring(0, 2);
  922               newcmd[3] = "&&";
  923               newcmd[4] = "cd";
  924               newcmd[5] = cmdDir.substring(2);
  925               newcmd[6] = "&&";
  926               // CheckStyle:MagicNumber ON
  927               System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);
  928   
  929               return exec(project, newcmd, env);
  930           }
  931       }
  932   
  933       /**
  934        * A command launcher for Windows XP/2000/NT that uses 'cmd.exe' when
  935        * launching commands in directories other than the current working
  936        * directory.
  937        */
  938       private static class WinNTCommandLauncher extends CommandLauncherProxy {
  939           WinNTCommandLauncher(CommandLauncher launcher) {
  940               super(launcher);
  941           }
  942   
  943           /**
  944            * Launches the given command in a new process, in the given working
  945            * directory.
  946            * @param project the Ant project.
  947            * @param cmd the command line to execute as an array of strings.
  948            * @param env the environment to set as an array of strings.
  949            * @param workingDir working directory where the command should run.
  950            * @return the created Process.
  951            * @throws IOException forwarded from the exec method of the
  952            * command launcher.
  953            */
  954           public Process exec(Project project, String[] cmd, String[] env,
  955                               File workingDir) throws IOException {
  956               File commandDir = workingDir;
  957               if (workingDir == null) {
  958                   if (project != null) {
  959                       commandDir = project.getBaseDir();
  960                   } else {
  961                       return exec(project, cmd, env);
  962                   }
  963               }
  964               // Use cmd.exe to change to the specified directory before running
  965               // the command
  966               final int preCmdLength = 6;
  967               String[] newcmd = new String[cmd.length + preCmdLength];
  968               // CheckStyle:MagicNumber OFF - do not bother
  969               newcmd[0] = "cmd";
  970               newcmd[1] = "/c";
  971               newcmd[2] = "cd";
  972               newcmd[3] = "/d";
  973               newcmd[4] = commandDir.getAbsolutePath();
  974               newcmd[5] = "&&";
  975               // CheckStyle:MagicNumber ON
  976               System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);
  977   
  978               return exec(project, newcmd, env);
  979           }
  980       }
  981   
  982       /**
  983        * A command launcher for Mac that uses a dodgy mechanism to change
  984        * working directory before launching commands.
  985        */
  986       private static class MacCommandLauncher extends CommandLauncherProxy {
  987           MacCommandLauncher(CommandLauncher launcher) {
  988               super(launcher);
  989           }
  990   
  991           /**
  992            * Launches the given command in a new process, in the given working
  993            * directory.
  994            * @param project the Ant project.
  995            * @param cmd the command line to execute as an array of strings.
  996            * @param env the environment to set as an array of strings.
  997            * @param workingDir working directory where the command should run.
  998            * @return the created Process.
  999            * @throws IOException forwarded from the exec method of the
 1000            * command launcher.
 1001            */
 1002           public Process exec(Project project, String[] cmd, String[] env,
 1003                               File workingDir) throws IOException {
 1004               if (workingDir == null) {
 1005                   return exec(project, cmd, env);
 1006               }
 1007               System.getProperties().put("user.dir", workingDir.getAbsolutePath());
 1008               try {
 1009                   return exec(project, cmd, env);
 1010               } finally {
 1011                   System.getProperties().put("user.dir", antWorkingDirectory);
 1012               }
 1013           }
 1014       }
 1015   
 1016       /**
 1017        * A command launcher that uses an auxiliary script to launch commands
 1018        * in directories other than the current working directory.
 1019        */
 1020       private static class ScriptCommandLauncher extends CommandLauncherProxy {
 1021           ScriptCommandLauncher(String script, CommandLauncher launcher) {
 1022               super(launcher);
 1023               myScript = script;
 1024           }
 1025   
 1026           /**
 1027            * Launches the given command in a new process, in the given working
 1028            * directory.
 1029            * @param project the Ant project.
 1030            * @param cmd the command line to execute as an array of strings.
 1031            * @param env the environment to set as an array of strings.
 1032            * @param workingDir working directory where the command should run.
 1033            * @return the created Process.
 1034            * @throws IOException forwarded from the exec method of the
 1035            * command launcher.
 1036            */
 1037           public Process exec(Project project, String[] cmd, String[] env,
 1038                               File workingDir) throws IOException {
 1039               if (project == null) {
 1040                   if (workingDir == null) {
 1041                       return exec(project, cmd, env);
 1042                   }
 1043                   throw new IOException("Cannot locate antRun script: "
 1044                       + "No project provided");
 1045               }
 1046               // Locate the auxiliary script
 1047               String antHome = project.getProperty(MagicNames.ANT_HOME);
 1048               if (antHome == null) {
 1049                   throw new IOException("Cannot locate antRun script: "
 1050                       + "Property '" + MagicNames.ANT_HOME + "' not found");
 1051               }
 1052               String antRun =
 1053                   FILE_UTILS.resolveFile(project.getBaseDir(),
 1054                           antHome + File.separator + myScript).toString();
 1055   
 1056               // Build the command
 1057               File commandDir = workingDir;
 1058               if (workingDir == null) {
 1059                   commandDir = project.getBaseDir();
 1060               }
 1061               String[] newcmd = new String[cmd.length + 2];
 1062               newcmd[0] = antRun;
 1063               newcmd[1] = commandDir.getAbsolutePath();
 1064               System.arraycopy(cmd, 0, newcmd, 2, cmd.length);
 1065   
 1066               return exec(project, newcmd, env);
 1067           }
 1068   
 1069           private String myScript;
 1070       }
 1071   
 1072       /**
 1073        * A command launcher that uses an auxiliary perl script to launch commands
 1074        * in directories other than the current working directory.
 1075        */
 1076       private static class PerlScriptCommandLauncher
 1077           extends CommandLauncherProxy {
 1078           private String myScript;
 1079   
 1080           PerlScriptCommandLauncher(String script, CommandLauncher launcher) {
 1081               super(launcher);
 1082               myScript = script;
 1083           }
 1084   
 1085           /**
 1086            * Launches the given command in a new process, in the given working
 1087            * directory.
 1088            * @param project the Ant project.
 1089            * @param cmd the command line to execute as an array of strings.
 1090            * @param env the environment to set as an array of strings.
 1091            * @param workingDir working directory where the command should run.
 1092            * @return the created Process.
 1093            * @throws IOException forwarded from the exec method of the
 1094            * command launcher.
 1095            */
 1096           public Process exec(Project project, String[] cmd, String[] env,
 1097                               File workingDir) throws IOException {
 1098               if (project == null) {
 1099                   if (workingDir == null) {
 1100                       return exec(project, cmd, env);
 1101                   }
 1102                   throw new IOException("Cannot locate antRun script: "
 1103                       + "No project provided");
 1104               }
 1105               // Locate the auxiliary script
 1106               String antHome = project.getProperty(MagicNames.ANT_HOME);
 1107               if (antHome == null) {
 1108                   throw new IOException("Cannot locate antRun script: "
 1109                       + "Property '" + MagicNames.ANT_HOME + "' not found");
 1110               }
 1111               String antRun =
 1112                   FILE_UTILS.resolveFile(project.getBaseDir(),
 1113                           antHome + File.separator + myScript).toString();
 1114   
 1115               // Build the command
 1116               File commandDir = workingDir;
 1117               if (workingDir == null) {
 1118                   commandDir = project.getBaseDir();
 1119               }
 1120               // CheckStyle:MagicNumber OFF
 1121               String[] newcmd = new String[cmd.length + 3];
 1122               newcmd[0] = "perl";
 1123               newcmd[1] = antRun;
 1124               newcmd[2] = commandDir.getAbsolutePath();
 1125               System.arraycopy(cmd, 0, newcmd, 3, cmd.length);
 1126               // CheckStyle:MagicNumber ON
 1127   
 1128               return exec(project, newcmd, env);
 1129           }
 1130       }
 1131   
 1132       /**
 1133        * A command launcher for VMS that writes the command to a temporary DCL
 1134        * script before launching commands.  This is due to limitations of both
 1135        * the DCL interpreter and the Java VM implementation.
 1136        */
 1137       private static class VmsCommandLauncher extends Java13CommandLauncher {
 1138   
 1139           public VmsCommandLauncher() throws NoSuchMethodException {
 1140               super();
 1141           }
 1142   
 1143           /**
 1144            * Launches the given command in a new process.
 1145            * @param project the Ant project.
 1146            * @param cmd the command line to execute as an array of strings.
 1147            * @param env the environment to set as an array of strings.
 1148            * @return the created Process.
 1149            * @throws IOException forwarded from the exec method of the
 1150            * command launcher.
 1151            */
 1152           public Process exec(Project project, String[] cmd, String[] env)
 1153               throws IOException {
 1154               File cmdFile = createCommandFile(cmd, env);
 1155               Process p
 1156                   = super.exec(project, new String[] {cmdFile.getPath()}, env);
 1157               deleteAfter(cmdFile, p);
 1158               return p;
 1159           }
 1160   
 1161           /**
 1162            * Launches the given command in a new process, in the given working
 1163            * directory.  Note that under Java 1.3.1, 1.4.0 and 1.4.1 on VMS this
 1164            * method only works if <code>workingDir</code> is null or the logical
 1165            * JAVA$FORK_SUPPORT_CHDIR needs to be set to TRUE.
 1166            * @param project the Ant project.
 1167            * @param cmd the command line to execute as an array of strings.
 1168            * @param env the environment to set as an array of strings.
 1169            * @param workingDir working directory where the command should run.
 1170            * @return the created Process.
 1171            * @throws IOException forwarded from the exec method of the
 1172            * command launcher.
 1173            */
 1174           public Process exec(Project project, String[] cmd, String[] env,
 1175                               File workingDir) throws IOException {
 1176               File cmdFile = createCommandFile(cmd, env);
 1177               Process p = super.exec(project, new String[] {cmdFile.getPath()},
 1178                                      env, workingDir);
 1179               deleteAfter(cmdFile, p);
 1180               return p;
 1181           }
 1182   
 1183           /*
 1184            * Writes the command into a temporary DCL script and returns the
 1185            * corresponding File object.  The script will be deleted on exit.
 1186            * @param cmd the command line to execute as an array of strings.
 1187            * @param env the environment to set as an array of strings.
 1188            * @return the command File.
 1189            * @throws IOException if errors are encountered creating the file.
 1190            */
 1191           private File createCommandFile(String[] cmd, String[] env)
 1192               throws IOException {
 1193               File script = FILE_UTILS.createTempFile("ANT", ".COM", null, true, true);
 1194               PrintWriter out = null;
 1195               try {
 1196                   out = new PrintWriter(new FileWriter(script));
 1197   
 1198                   // add the environment as logicals to the DCL script
 1199                   if (env != null) {
 1200                       int eqIndex;
 1201                       for (int i = 0; i < env.length; i++) {
 1202                           eqIndex = env[i].indexOf('=');
 1203                           if (eqIndex != -1) {
 1204                               out.print("$ DEFINE/NOLOG ");
 1205                               out.print(env[i].substring(0, eqIndex));
 1206                               out.print(" \"");
 1207                               out.print(env[i].substring(eqIndex + 1));
 1208                               out.println('\"');
 1209                           }
 1210                       }
 1211                   }
 1212                   out.print("$ " + cmd[0]);
 1213                   for (int i = 1; i < cmd.length; i++) {
 1214                       out.println(" -");
 1215                       out.print(cmd[i]);
 1216                   }
 1217               } finally {
 1218                   if (out != null) {
 1219                       out.close();
 1220                   }
 1221               }
 1222               return script;
 1223           }
 1224   
 1225           private void deleteAfter(final File f, final Process p) {
 1226               new Thread() {
 1227                   public void run() {
 1228                       try {
 1229                           p.waitFor();
 1230                       } catch (InterruptedException e) {
 1231                           //ignore
 1232                       }
 1233                       FileUtils.delete(f);
 1234                   }
 1235               }
 1236               .start();
 1237           }
 1238       }
 1239   }

Save This Page
Home » apache-ant-1.7.1-src » org.apache.tools » ant » taskdefs » [javadoc | source]