KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > ExecTask


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.File JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.util.Enumeration JavaDoc;
24 import java.util.Vector JavaDoc;
25 import java.util.Locale JavaDoc;
26
27 import org.apache.tools.ant.BuildException;
28 import org.apache.tools.ant.Project;
29 import org.apache.tools.ant.Task;
30 import org.apache.tools.ant.taskdefs.condition.Os;
31 import org.apache.tools.ant.types.Commandline;
32 import org.apache.tools.ant.types.Environment;
33 import org.apache.tools.ant.types.Path;
34 import org.apache.tools.ant.types.RedirectorElement;
35 import org.apache.tools.ant.util.FileUtils;
36
37 /**
38  * Executes a given command if the os platform is appropriate.
39  *
40  * @since Ant 1.2
41  *
42  * @ant.task category="control"
43  */

44 public class ExecTask extends Task {
45
46     // CheckStyle:VisibilityModifier OFF - bc
47
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
48
49     private String JavaDoc os;
50     private String JavaDoc osFamily;
51
52     private File JavaDoc dir;
53     protected boolean failOnError = false;
54     protected boolean newEnvironment = false;
55     private Long JavaDoc timeout = null;
56     private Environment env = new Environment();
57     protected Commandline cmdl = new Commandline();
58     private String JavaDoc resultProperty;
59     private boolean failIfExecFails = true;
60     private String JavaDoc executable;
61     private boolean resolveExecutable = false;
62     private boolean searchPath = false;
63     private boolean spawn = false;
64     private boolean incompatibleWithSpawn = false;
65
66     //include locally for screening purposes
67
private String JavaDoc inputString;
68     private File JavaDoc input;
69     private File JavaDoc output;
70     private File JavaDoc error;
71
72     protected Redirector redirector = new Redirector(this);
73     protected RedirectorElement redirectorElement;
74     // CheckStyle:VisibilityModifier ON
75

76     /**
77      * Controls whether the VM (1.3 and above) is used to execute the
78      * command
79      */

80     private boolean vmLauncher = true;
81
82
83     /**
84      * Create an instance.
85      * Needs to be configured by binding to a project.
86      */

87     public ExecTask() {
88     }
89
90     /**
91      * create an instance that is helping another task.
92      * Project, OwningTarget, TaskName and description are all
93      * pulled out
94      * @param owner task that we belong to
95      */

96     public ExecTask(Task owner) {
97         bindToOwner(owner);
98     }
99
100     /**
101      * Set whether or not you want the process to be spawned.
102      * Default is false.
103      * @param spawn if true you do not want Ant to wait for the end of the process.
104      * @since Ant 1.6
105      */

106     public void setSpawn(boolean spawn) {
107         this.spawn = spawn;
108     }
109
110     /**
111      * Set the timeout in milliseconds after which the process will be killed.
112      *
113      * @param value timeout in milliseconds.
114      *
115      * @since Ant 1.5
116      */

117     public void setTimeout(Long JavaDoc value) {
118         timeout = value;
119         incompatibleWithSpawn = true;
120     }
121
122     /**
123      * Set the timeout in milliseconds after which the process will be killed.
124      *
125      * @param value timeout in milliseconds.
126      */

127     public void setTimeout(Integer JavaDoc value) {
128         setTimeout(
129             (Long JavaDoc) ((value == null) ? null : new Long JavaDoc(value.intValue())));
130     }
131
132     /**
133      * Set the name of the executable program.
134      * @param value the name of the executable program.
135      */

136     public void setExecutable(String JavaDoc value) {
137         this.executable = value;
138         cmdl.setExecutable(value);
139     }
140
141     /**
142      * Set the working directory of the process.
143      * @param d the working directory of the process.
144      */

145     public void setDir(File JavaDoc d) {
146         this.dir = d;
147     }
148
149     /**
150      * List of operating systems on which the command may be executed.
151      * @param os list of operating systems on which the command may be executed.
152      */

153     public void setOs(String JavaDoc os) {
154         this.os = os;
155     }
156
157     /**
158      * Sets a command line.
159      * @param cmdl command line.
160      * @ant.attribute ignore="true"
161      */

162     public void setCommand(Commandline cmdl) {
163         log("The command attribute is deprecated.\n"
164             + "Please use the executable attribute and nested arg elements.",
165             Project.MSG_WARN);
166         this.cmdl = cmdl;
167     }
168
169     /**
170      * File the output of the process is redirected to. If error is not
171      * redirected, it too will appear in the output.
172      *
173      * @param out name of a file to which output should be sent.
174      */

175     public void setOutput(File JavaDoc out) {
176         this.output = out;
177         incompatibleWithSpawn = true;
178     }
179
180     /**
181      * Set the input file to use for the task.
182      *
183      * @param input name of a file from which to get input.
184      */

185     public void setInput(File JavaDoc input) {
186         if (inputString != null) {
187             throw new BuildException("The \"input\" and \"inputstring\" "
188                 + "attributes cannot both be specified");
189         }
190         this.input = input;
191         incompatibleWithSpawn = true;
192     }
193
194     /**
195      * Set the string to use as input.
196      *
197      * @param inputString the string which is used as the input source.
198      */

199     public void setInputString(String JavaDoc inputString) {
200         if (input != null) {
201             throw new BuildException("The \"input\" and \"inputstring\" "
202                 + "attributes cannot both be specified");
203         }
204         this.inputString = inputString;
205         incompatibleWithSpawn = true;
206     }
207
208     /**
209      * Controls whether error output of exec is logged. This is only useful when
210      * output is being redirected and error output is desired in the Ant log.
211      *
212      * @param logError set to true to log error output in the normal ant log.
213      */

214     public void setLogError(boolean logError) {
215         redirector.setLogError(logError);
216         incompatibleWithSpawn |= logError;
217     }
218
219     /**
220      * Set the File to which the error stream of the process should be redirected.
221      *
222      * @param error a file to which stderr should be sent.
223      *
224      * @since Ant 1.6
225      */

226     public void setError(File JavaDoc error) {
227         this.error = error;
228         incompatibleWithSpawn = true;
229     }
230
231     /**
232      * Sets the property name whose value should be set to the output of
233      * the process.
234      *
235      * @param outputProp name of property.
236      */

237     public void setOutputproperty(String JavaDoc outputProp) {
238         redirector.setOutputProperty(outputProp);
239         incompatibleWithSpawn = true;
240     }
241
242     /**
243      * Sets the name of the property whose value should be set to the error of
244      * the process.
245      *
246      * @param errorProperty name of property.
247      *
248      * @since Ant 1.6
249      */

250     public void setErrorProperty(String JavaDoc errorProperty) {
251         redirector.setErrorProperty(errorProperty);
252         incompatibleWithSpawn = true;
253     }
254
255     /**
256      * Fail if the command exits with a non-zero return code.
257      *
258      * @param fail if true fail the command on non-zero return code.
259      */

260     public void setFailonerror(boolean fail) {
261         failOnError = fail;
262         incompatibleWithSpawn |= fail;
263     }
264
265     /**
266      * Do not propagate old environment when new environment variables are specified.
267      *
268      * @param newenv if true, do not propagate old environment
269      * when new environment variables are specified.
270      */

271     public void setNewenvironment(boolean newenv) {
272         newEnvironment = newenv;
273     }
274
275     /**
276      * Set whether to attempt to resolve the executable to a file.
277      *
278      * @param resolveExecutable if true, attempt to resolve the
279      * path of the executable.
280      */

281     public void setResolveExecutable(boolean resolveExecutable) {
282         this.resolveExecutable = resolveExecutable;
283     }
284
285     /**
286      * Set whether to search nested, then
287      * system PATH environment variables for the executable.
288      *
289      * @param searchPath if true, search PATHs.
290      */

291     public void setSearchPath(boolean searchPath) {
292         this.searchPath = searchPath;
293     }
294
295     /**
296      * Indicates whether to attempt to resolve the executable to a
297      * file.
298      * @return the resolveExecutable flag
299      *
300      * @since Ant 1.6
301      */

302     public boolean getResolveExecutable() {
303         return resolveExecutable;
304     }
305
306     /**
307      * Add an environment variable to the launched process.
308      *
309      * @param var new environment variable.
310      */

311     public void addEnv(Environment.Variable var) {
312         env.addVariable(var);
313     }
314
315     /**
316      * Adds a command-line argument.
317      *
318      * @return new command line argument created.
319      */

320     public Commandline.Argument createArg() {
321         return cmdl.createArgument();
322     }
323
324     /**
325      * Sets the name of a property in which the return code of the
326      * command should be stored. Only of interest if failonerror=false.
327      *
328      * @since Ant 1.5
329      *
330      * @param resultProperty name of property.
331      */

332     public void setResultProperty(String JavaDoc resultProperty) {
333         this.resultProperty = resultProperty;
334         incompatibleWithSpawn = true;
335     }
336
337     /**
338      * Helper method to set result property to the
339      * passed in value if appropriate.
340      *
341      * @param result value desired for the result property value.
342      */

343     protected void maybeSetResultPropertyValue(int result) {
344         if (resultProperty != null) {
345             String JavaDoc res = Integer.toString(result);
346             getProject().setNewProperty(resultProperty, res);
347         }
348     }
349
350     /**
351      * Set whether to stop the build if program cannot be started.
352      * Defaults to true.
353      *
354      * @param flag stop the build if program cannot be started.
355      *
356      * @since Ant 1.5
357      */

358     public void setFailIfExecutionFails(boolean flag) {
359         failIfExecFails = flag;
360         incompatibleWithSpawn = true;
361     }
362
363     /**
364      * Set whether output should be appended to or overwrite an existing file.
365      * Defaults to false.
366      *
367      * @param append if true append is desired.
368      *
369      * @since 1.30, Ant 1.5
370      */

371     public void setAppend(boolean append) {
372         redirector.setAppend(append);
373         incompatibleWithSpawn = true;
374     }
375
376     /**
377      * Add a <code>RedirectorElement</code> to this task.
378      *
379      * @param redirectorElement <code>RedirectorElement</code>.
380      * @since Ant 1.6.2
381      */

382     public void addConfiguredRedirector(RedirectorElement redirectorElement) {
383         if (this.redirectorElement != null) {
384             throw new BuildException("cannot have > 1 nested <redirector>s");
385         }
386         this.redirectorElement = redirectorElement;
387         incompatibleWithSpawn = true;
388     }
389
390
391     /**
392      * Restrict this execution to a single OS Family
393      * @param osFamily the family to restrict to.
394      */

395     public void setOsFamily(String JavaDoc osFamily) {
396         this.osFamily = osFamily.toLowerCase(Locale.US);
397     }
398
399
400     /**
401      * The method attempts to figure out where the executable is so that we can feed
402      * the full path. We first try basedir, then the exec dir, and then
403      * fallback to the straight executable name (i.e. on the path).
404      *
405      * @param exec the name of the executable.
406      * @param mustSearchPath if true, the executable will be looked up in
407      * the PATH environment and the absolute path is returned.
408      *
409      * @return the executable as a full path if it can be determined.
410      *
411      * @since Ant 1.6
412      */

413     protected String JavaDoc resolveExecutable(String JavaDoc exec, boolean mustSearchPath) {
414         if (!resolveExecutable) {
415             return exec;
416         }
417         // try to find the executable
418
File JavaDoc executableFile = getProject().resolveFile(exec);
419         if (executableFile.exists()) {
420             return executableFile.getAbsolutePath();
421         }
422         // now try to resolve against the dir if given
423
if (dir != null) {
424             executableFile = FILE_UTILS.resolveFile(dir, exec);
425             if (executableFile.exists()) {
426                 return executableFile.getAbsolutePath();
427             }
428         }
429         // couldn't find it - must be on path
430
if (mustSearchPath) {
431             Path p = null;
432             String JavaDoc[] environment = env.getVariables();
433             if (environment != null) {
434                 for (int i = 0; i < environment.length; i++) {
435                     if (isPath(environment[i])) {
436                         p = new Path(getProject(), environment[i].substring(5));
437                         break;
438                     }
439                 }
440             }
441             if (p == null) {
442                 Vector JavaDoc envVars = Execute.getProcEnvironment();
443                 Enumeration JavaDoc e = envVars.elements();
444                 while (e.hasMoreElements()) {
445                     String JavaDoc line = (String JavaDoc) e.nextElement();
446                     if (isPath(line)) {
447                         p = new Path(getProject(), line.substring(5));
448                         break;
449                     }
450                 }
451             }
452             if (p != null) {
453                 String JavaDoc[] dirs = p.list();
454                 for (int i = 0; i < dirs.length; i++) {
455                     executableFile
456                         = FILE_UTILS.resolveFile(new File JavaDoc(dirs[i]), exec);
457                     if (executableFile.exists()) {
458                         return executableFile.getAbsolutePath();
459                     }
460                 }
461             }
462         }
463         // mustSearchPath is false, or no PATH or not found - keep our
464
// fingers crossed.
465
return exec;
466     }
467
468     /**
469      * Do the work.
470      *
471      * @throws BuildException in a number of circumstances:
472      * <ul>
473      * <li>if failIfExecFails is set to true and the process cannot be started</li>
474      * <li>the java13command launcher can send build exceptions</li>
475      * <li>this list is not exhaustive or limitative</li>
476      * </ul>
477      */

478     public void execute() throws BuildException {
479         // Quick fail if this is not a valid OS for the command
480
if (!isValidOs()) {
481             return;
482         }
483         File JavaDoc savedDir = dir; // possibly altered in prepareExec
484
cmdl.setExecutable(resolveExecutable(executable, searchPath));
485         checkConfiguration();
486         try {
487             runExec(prepareExec());
488         } finally {
489             dir = savedDir;
490         }
491     }
492
493     /**
494      * Has the user set all necessary attributes?
495      * @throws BuildException if there are missing required parameters.
496      */

497     protected void checkConfiguration() throws BuildException {
498         if (cmdl.getExecutable() == null) {
499             throw new BuildException("no executable specified", getLocation());
500         }
501         if (dir != null && !dir.exists()) {
502             throw new BuildException("The directory " + dir + " does not exist");
503         }
504         if (dir != null && !dir.isDirectory()) {
505             throw new BuildException(dir + " is not a directory");
506         }
507         if (spawn && incompatibleWithSpawn) {
508             getProject().log("spawn does not allow attributes related to input, "
509             + "output, error, result", Project.MSG_ERR);
510             getProject().log("spawn also does not allow timeout", Project.MSG_ERR);
511             getProject().log("finally, spawn is not compatible "
512                 + "with a nested I/O <redirector>", Project.MSG_ERR);
513             throw new BuildException("You have used an attribute "
514                 + "or nested element which is not compatible with spawn");
515         }
516         setupRedirector();
517     }
518
519     /**
520      * Set up properties on the redirector that we needed to store locally.
521      */

522     protected void setupRedirector() {
523         redirector.setInput(input);
524         redirector.setInputString(inputString);
525         redirector.setOutput(output);
526         redirector.setError(error);
527     }
528
529     /**
530      * Is this the OS the user wanted?
531      * @return boolean.
532      * <ul>
533      * <li>
534      * <li><code>true</code> if the os and osfamily attributes are null.</li>
535      * <li><code>true</code> if osfamily is set, and the os family and must match
536      * that of the current OS, according to the logic of
537      * {@link Os#isOs(String, String, String, String)}, and the result of the
538      * <code>os</code> attribute must also evaluate true.
539      * </li>
540      * <li>
541      * <code>true</code> if os is set, and the system.property os.name
542      * is found in the os attribute,</li>
543      * <li><code>false</code> otherwise.</li>
544      * </ul>
545      */

546     protected boolean isValidOs() {
547         //hand osfamily off to Os class, if set
548
if (osFamily != null && !Os.isOs(osFamily, null, null, null)) {
549             return false;
550         }
551         //the Exec OS check is different from Os.isOs(), which
552
//probes for a specific OS. Instead it searches the os field
553
//for the current os.name
554
String JavaDoc myos = System.getProperty("os.name");
555         log("Current OS is " + myos, Project.MSG_VERBOSE);
556         if ((os != null) && (os.indexOf(myos) < 0)) {
557             // this command will be executed only on the specified OS
558
log("This OS, " + myos
559                     + " was not found in the specified list of valid OSes: " + os,
560                     Project.MSG_VERBOSE);
561             return false;
562         }
563         return true;
564     }
565
566     /**
567      * Set whether to launch new process with VM, otherwise use the OS's shell.
568      * Default value is true.
569      * @param vmLauncher true if we want to launch new process with VM,
570      * false if we want to use the OS's shell.
571      */

572     public void setVMLauncher(boolean vmLauncher) {
573         this.vmLauncher = vmLauncher;
574     }
575
576     /**
577      * Create an Execute instance with the correct working directory set.
578      *
579      * @return an instance of the Execute class.
580      *
581      * @throws BuildException under unknown circumstances.
582      */

583     protected Execute prepareExec() throws BuildException {
584         // default directory to the project's base directory
585
if (dir == null) {
586             dir = getProject().getBaseDir();
587         }
588         if (redirectorElement != null) {
589             redirectorElement.configure(redirector);
590         }
591         Execute exe = new Execute(createHandler(), createWatchdog());
592         exe.setAntRun(getProject());
593         exe.setWorkingDirectory(dir);
594         exe.setVMLauncher(vmLauncher);
595         exe.setSpawn(spawn);
596         String JavaDoc[] environment = env.getVariables();
597         if (environment != null) {
598             for (int i = 0; i < environment.length; i++) {
599                 log("Setting environment variable: " + environment[i],
600                     Project.MSG_VERBOSE);
601             }
602         }
603         exe.setNewenvironment(newEnvironment);
604         exe.setEnvironment(environment);
605         return exe;
606     }
607
608     /**
609      * A Utility method for this classes and subclasses to run an
610      * Execute instance (an external command).
611      *
612      * @param exe instance of the execute class.
613      *
614      * @throws IOException in case of problem to attach to the stdin/stdout/stderr
615      * streams of the process.
616      */

617     protected final void runExecute(Execute exe) throws IOException JavaDoc {
618         int returnCode = -1; // assume the worst
619

620         if (!spawn) {
621             returnCode = exe.execute();
622
623             //test for and handle a forced process death
624
if (exe.killedProcess()) {
625                 String JavaDoc msg = "Timeout: killed the sub-process";
626                 if (failOnError) {
627                     throw new BuildException(msg);
628                 } else {
629                     log(msg, Project.MSG_WARN);
630                 }
631             }
632             maybeSetResultPropertyValue(returnCode);
633             redirector.complete();
634             if (Execute.isFailure(returnCode)) {
635                 if (failOnError) {
636                     throw new BuildException(getTaskType() + " returned: "
637                         + returnCode, getLocation());
638                 } else {
639                     log("Result: " + returnCode, Project.MSG_ERR);
640                 }
641             }
642         } else {
643             exe.spawn();
644         }
645     }
646
647     /**
648      * Run the command using the given Execute instance. This may be
649      * overridden by subclasses.
650      *
651      * @param exe instance of Execute to run.
652      *
653      * @throws BuildException if the new process could not be started
654      * only if failIfExecFails is set to true (the default).
655      */

656     protected void runExec(Execute exe) throws BuildException {
657         // show the command
658
log(cmdl.describeCommand(), Project.MSG_VERBOSE);
659
660         exe.setCommandline(cmdl.getCommandline());
661         try {
662             runExecute(exe);
663         } catch (IOException JavaDoc e) {
664             if (failIfExecFails) {
665                 throw new BuildException("Execute failed: " + e.toString(), e,
666                                          getLocation());
667             } else {
668                 log("Execute failed: " + e.toString(), Project.MSG_ERR);
669             }
670         } finally {
671             // close the output file if required
672
logFlush();
673         }
674     }
675
676     /**
677      * Create the StreamHandler to use with our Execute instance.
678      *
679      * @return instance of ExecuteStreamHandler.
680      *
681      * @throws BuildException under unknown circumstances.
682      */

683     protected ExecuteStreamHandler createHandler() throws BuildException {
684         return redirector.createHandler();
685     }
686
687     /**
688      * Create the Watchdog to kill a runaway process.
689      *
690      * @return instance of ExecuteWatchdog.
691      *
692      * @throws BuildException under unknown circumstances.
693      */

694     protected ExecuteWatchdog createWatchdog() throws BuildException {
695         return (timeout == null)
696             ? null : new ExecuteWatchdog(timeout.longValue());
697     }
698
699     /**
700      * Flush the output stream - if there is one.
701      */

702     protected void logFlush() {
703     }
704
705     private boolean isPath(String JavaDoc line) {
706         return line.startsWith("PATH=") || line.startsWith("Path=");
707     }
708
709 }
710
Popular Tags