/[Apache-SVN]/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Execute.java
ViewVC logotype

Contents of /ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Execute.java

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

apache@apache.org
ViewVC Help
Powered by ViewVC 1.1.2