source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/Execute.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

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