001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.Enumeration;
024: import java.util.Vector;
025: import java.util.Locale;
026:
027: import org.apache.tools.ant.BuildException;
028: import org.apache.tools.ant.Project;
029: import org.apache.tools.ant.Task;
030: import org.apache.tools.ant.taskdefs.condition.Os;
031: import org.apache.tools.ant.types.Commandline;
032: import org.apache.tools.ant.types.Environment;
033: import org.apache.tools.ant.types.Path;
034: import org.apache.tools.ant.types.RedirectorElement;
035: import org.apache.tools.ant.util.FileUtils;
036:
037: /**
038: * Executes a given command if the os platform is appropriate.
039: *
040: * @since Ant 1.2
041: *
042: * @ant.task category="control"
043: */
044: public class ExecTask extends Task {
045:
046: // CheckStyle:VisibilityModifier OFF - bc
047: private static final FileUtils FILE_UTILS = FileUtils
048: .getFileUtils();
049:
050: private String os;
051: private String osFamily;
052:
053: private File dir;
054: protected boolean failOnError = false;
055: protected boolean newEnvironment = false;
056: private Long timeout = null;
057: private Environment env = new Environment();
058: protected Commandline cmdl = new Commandline();
059: private String resultProperty;
060: private boolean failIfExecFails = true;
061: private String executable;
062: private boolean resolveExecutable = false;
063: private boolean searchPath = false;
064: private boolean spawn = false;
065: private boolean incompatibleWithSpawn = false;
066:
067: //include locally for screening purposes
068: private String inputString;
069: private File input;
070: private File output;
071: private File error;
072:
073: protected Redirector redirector = new Redirector(this );
074: protected RedirectorElement redirectorElement;
075: // CheckStyle:VisibilityModifier ON
076:
077: /**
078: * Controls whether the VM (1.3 and above) is used to execute the
079: * command
080: */
081: private boolean vmLauncher = true;
082:
083: /**
084: * Create an instance.
085: * Needs to be configured by binding to a project.
086: */
087: public ExecTask() {
088: }
089:
090: /**
091: * create an instance that is helping another task.
092: * Project, OwningTarget, TaskName and description are all
093: * pulled out
094: * @param owner task that we belong to
095: */
096: public ExecTask(Task owner) {
097: bindToOwner(owner);
098: }
099:
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 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 value) {
128: setTimeout((Long) ((value == null) ? null : new Long(value
129: .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 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 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 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(
164: "The command attribute is deprecated.\n"
165: + "Please use the executable attribute and nested arg elements.",
166: Project.MSG_WARN);
167: this .cmdl = cmdl;
168: }
169:
170: /**
171: * File the output of the process is redirected to. If error is not
172: * redirected, it too will appear in the output.
173: *
174: * @param out name of a file to which output should be sent.
175: */
176: public void setOutput(File out) {
177: this .output = out;
178: incompatibleWithSpawn = true;
179: }
180:
181: /**
182: * Set the input file to use for the task.
183: *
184: * @param input name of a file from which to get input.
185: */
186: public void setInput(File input) {
187: if (inputString != null) {
188: throw new BuildException(
189: "The \"input\" and \"inputstring\" "
190: + "attributes cannot both be specified");
191: }
192: this .input = input;
193: incompatibleWithSpawn = true;
194: }
195:
196: /**
197: * Set the string to use as input.
198: *
199: * @param inputString the string which is used as the input source.
200: */
201: public void setInputString(String inputString) {
202: if (input != null) {
203: throw new BuildException(
204: "The \"input\" and \"inputstring\" "
205: + "attributes cannot both be specified");
206: }
207: this .inputString = inputString;
208: incompatibleWithSpawn = true;
209: }
210:
211: /**
212: * Controls whether error output of exec is logged. This is only useful when
213: * output is being redirected and error output is desired in the Ant log.
214: *
215: * @param logError set to true to log error output in the normal ant log.
216: */
217: public void setLogError(boolean logError) {
218: redirector.setLogError(logError);
219: incompatibleWithSpawn |= logError;
220: }
221:
222: /**
223: * Set the File to which the error stream of the process should be redirected.
224: *
225: * @param error a file to which stderr should be sent.
226: *
227: * @since Ant 1.6
228: */
229: public void setError(File error) {
230: this .error = error;
231: incompatibleWithSpawn = true;
232: }
233:
234: /**
235: * Sets the property name whose value should be set to the output of
236: * the process.
237: *
238: * @param outputProp name of property.
239: */
240: public void setOutputproperty(String outputProp) {
241: redirector.setOutputProperty(outputProp);
242: incompatibleWithSpawn = true;
243: }
244:
245: /**
246: * Sets the name of the property whose value should be set to the error of
247: * the process.
248: *
249: * @param errorProperty name of property.
250: *
251: * @since Ant 1.6
252: */
253: public void setErrorProperty(String errorProperty) {
254: redirector.setErrorProperty(errorProperty);
255: incompatibleWithSpawn = true;
256: }
257:
258: /**
259: * Fail if the command exits with a non-zero return code.
260: *
261: * @param fail if true fail the command on non-zero return code.
262: */
263: public void setFailonerror(boolean fail) {
264: failOnError = fail;
265: incompatibleWithSpawn |= fail;
266: }
267:
268: /**
269: * Do not propagate old environment when new environment variables are specified.
270: *
271: * @param newenv if true, do not propagate old environment
272: * when new environment variables are specified.
273: */
274: public void setNewenvironment(boolean newenv) {
275: newEnvironment = newenv;
276: }
277:
278: /**
279: * Set whether to attempt to resolve the executable to a file.
280: *
281: * @param resolveExecutable if true, attempt to resolve the
282: * path of the executable.
283: */
284: public void setResolveExecutable(boolean resolveExecutable) {
285: this .resolveExecutable = resolveExecutable;
286: }
287:
288: /**
289: * Set whether to search nested, then
290: * system PATH environment variables for the executable.
291: *
292: * @param searchPath if true, search PATHs.
293: */
294: public void setSearchPath(boolean searchPath) {
295: this .searchPath = searchPath;
296: }
297:
298: /**
299: * Indicates whether to attempt to resolve the executable to a
300: * file.
301: * @return the resolveExecutable flag
302: *
303: * @since Ant 1.6
304: */
305: public boolean getResolveExecutable() {
306: return resolveExecutable;
307: }
308:
309: /**
310: * Add an environment variable to the launched process.
311: *
312: * @param var new environment variable.
313: */
314: public void addEnv(Environment.Variable var) {
315: env.addVariable(var);
316: }
317:
318: /**
319: * Adds a command-line argument.
320: *
321: * @return new command line argument created.
322: */
323: public Commandline.Argument createArg() {
324: return cmdl.createArgument();
325: }
326:
327: /**
328: * Sets the name of a property in which the return code of the
329: * command should be stored. Only of interest if failonerror=false.
330: *
331: * @since Ant 1.5
332: *
333: * @param resultProperty name of property.
334: */
335: public void setResultProperty(String resultProperty) {
336: this .resultProperty = resultProperty;
337: incompatibleWithSpawn = true;
338: }
339:
340: /**
341: * Helper method to set result property to the
342: * passed in value if appropriate.
343: *
344: * @param result value desired for the result property value.
345: */
346: protected void maybeSetResultPropertyValue(int result) {
347: if (resultProperty != null) {
348: String res = Integer.toString(result);
349: getProject().setNewProperty(resultProperty, res);
350: }
351: }
352:
353: /**
354: * Set whether to stop the build if program cannot be started.
355: * Defaults to true.
356: *
357: * @param flag stop the build if program cannot be started.
358: *
359: * @since Ant 1.5
360: */
361: public void setFailIfExecutionFails(boolean flag) {
362: failIfExecFails = flag;
363: incompatibleWithSpawn = true;
364: }
365:
366: /**
367: * Set whether output should be appended to or overwrite an existing file.
368: * Defaults to false.
369: *
370: * @param append if true append is desired.
371: *
372: * @since 1.30, Ant 1.5
373: */
374: public void setAppend(boolean append) {
375: redirector.setAppend(append);
376: incompatibleWithSpawn = true;
377: }
378:
379: /**
380: * Add a <code>RedirectorElement</code> to this task.
381: *
382: * @param redirectorElement <code>RedirectorElement</code>.
383: * @since Ant 1.6.2
384: */
385: public void addConfiguredRedirector(
386: RedirectorElement redirectorElement) {
387: if (this .redirectorElement != null) {
388: throw new BuildException(
389: "cannot have > 1 nested <redirector>s");
390: }
391: this .redirectorElement = redirectorElement;
392: incompatibleWithSpawn = true;
393: }
394:
395: /**
396: * Restrict this execution to a single OS Family
397: * @param osFamily the family to restrict to.
398: */
399: public void setOsFamily(String osFamily) {
400: this .osFamily = osFamily.toLowerCase(Locale.US);
401: }
402:
403: /**
404: * The method attempts to figure out where the executable is so that we can feed
405: * the full path. We first try basedir, then the exec dir, and then
406: * fallback to the straight executable name (i.e. on the path).
407: *
408: * @param exec the name of the executable.
409: * @param mustSearchPath if true, the executable will be looked up in
410: * the PATH environment and the absolute path is returned.
411: *
412: * @return the executable as a full path if it can be determined.
413: *
414: * @since Ant 1.6
415: */
416: protected String resolveExecutable(String exec,
417: boolean mustSearchPath) {
418: if (!resolveExecutable) {
419: return exec;
420: }
421: // try to find the executable
422: File executableFile = getProject().resolveFile(exec);
423: if (executableFile.exists()) {
424: return executableFile.getAbsolutePath();
425: }
426: // now try to resolve against the dir if given
427: if (dir != null) {
428: executableFile = FILE_UTILS.resolveFile(dir, exec);
429: if (executableFile.exists()) {
430: return executableFile.getAbsolutePath();
431: }
432: }
433: // couldn't find it - must be on path
434: if (mustSearchPath) {
435: Path p = null;
436: String[] environment = env.getVariables();
437: if (environment != null) {
438: for (int i = 0; i < environment.length; i++) {
439: if (isPath(environment[i])) {
440: p = new Path(getProject(), environment[i]
441: .substring(5));
442: break;
443: }
444: }
445: }
446: if (p == null) {
447: Vector envVars = Execute.getProcEnvironment();
448: Enumeration e = envVars.elements();
449: while (e.hasMoreElements()) {
450: String line = (String) e.nextElement();
451: if (isPath(line)) {
452: p = new Path(getProject(), line.substring(5));
453: break;
454: }
455: }
456: }
457: if (p != null) {
458: String[] dirs = p.list();
459: for (int i = 0; i < dirs.length; i++) {
460: executableFile = FILE_UTILS.resolveFile(new File(
461: dirs[i]), exec);
462: if (executableFile.exists()) {
463: return executableFile.getAbsolutePath();
464: }
465: }
466: }
467: }
468: // mustSearchPath is false, or no PATH or not found - keep our
469: // fingers crossed.
470: return exec;
471: }
472:
473: /**
474: * Do the work.
475: *
476: * @throws BuildException in a number of circumstances:
477: * <ul>
478: * <li>if failIfExecFails is set to true and the process cannot be started</li>
479: * <li>the java13command launcher can send build exceptions</li>
480: * <li>this list is not exhaustive or limitative</li>
481: * </ul>
482: */
483: public void execute() throws BuildException {
484: // Quick fail if this is not a valid OS for the command
485: if (!isValidOs()) {
486: return;
487: }
488: File savedDir = dir; // possibly altered in prepareExec
489: cmdl.setExecutable(resolveExecutable(executable, searchPath));
490: checkConfiguration();
491: try {
492: runExec(prepareExec());
493: } finally {
494: dir = savedDir;
495: }
496: }
497:
498: /**
499: * Has the user set all necessary attributes?
500: * @throws BuildException if there are missing required parameters.
501: */
502: protected void checkConfiguration() throws BuildException {
503: if (cmdl.getExecutable() == null) {
504: throw new BuildException("no executable specified",
505: getLocation());
506: }
507: if (dir != null && !dir.exists()) {
508: throw new BuildException("The directory " + dir
509: + " does not exist");
510: }
511: if (dir != null && !dir.isDirectory()) {
512: throw new BuildException(dir + " is not a directory");
513: }
514: if (spawn && incompatibleWithSpawn) {
515: getProject().log(
516: "spawn does not allow attributes related to input, "
517: + "output, error, result", Project.MSG_ERR);
518: getProject().log("spawn also does not allow timeout",
519: Project.MSG_ERR);
520: getProject().log(
521: "finally, spawn is not compatible "
522: + "with a nested I/O <redirector>",
523: Project.MSG_ERR);
524: throw new BuildException(
525: "You have used an attribute "
526: + "or nested element which is not compatible with spawn");
527: }
528: setupRedirector();
529: }
530:
531: /**
532: * Set up properties on the redirector that we needed to store locally.
533: */
534: protected void setupRedirector() {
535: redirector.setInput(input);
536: redirector.setInputString(inputString);
537: redirector.setOutput(output);
538: redirector.setError(error);
539: }
540:
541: /**
542: * Is this the OS the user wanted?
543: * @return boolean.
544: * <ul>
545: * <li>
546: * <li><code>true</code> if the os and osfamily attributes are null.</li>
547: * <li><code>true</code> if osfamily is set, and the os family and must match
548: * that of the current OS, according to the logic of
549: * {@link Os#isOs(String, String, String, String)}, and the result of the
550: * <code>os</code> attribute must also evaluate true.
551: * </li>
552: * <li>
553: * <code>true</code> if os is set, and the system.property os.name
554: * is found in the os attribute,</li>
555: * <li><code>false</code> otherwise.</li>
556: * </ul>
557: */
558: protected boolean isValidOs() {
559: //hand osfamily off to Os class, if set
560: if (osFamily != null && !Os.isOs(osFamily, null, null, null)) {
561: return false;
562: }
563: //the Exec OS check is different from Os.isOs(), which
564: //probes for a specific OS. Instead it searches the os field
565: //for the current os.name
566: String myos = System.getProperty("os.name");
567: log("Current OS is " + myos, Project.MSG_VERBOSE);
568: if ((os != null) && (os.indexOf(myos) < 0)) {
569: // this command will be executed only on the specified OS
570: log(
571: "This OS, "
572: + myos
573: + " was not found in the specified list of valid OSes: "
574: + os, Project.MSG_VERBOSE);
575: return false;
576: }
577: return true;
578: }
579:
580: /**
581: * Set whether to launch new process with VM, otherwise use the OS's shell.
582: * Default value is true.
583: * @param vmLauncher true if we want to launch new process with VM,
584: * false if we want to use the OS's shell.
585: */
586: public void setVMLauncher(boolean vmLauncher) {
587: this .vmLauncher = vmLauncher;
588: }
589:
590: /**
591: * Create an Execute instance with the correct working directory set.
592: *
593: * @return an instance of the Execute class.
594: *
595: * @throws BuildException under unknown circumstances.
596: */
597: protected Execute prepareExec() throws BuildException {
598: // default directory to the project's base directory
599: if (dir == null) {
600: dir = getProject().getBaseDir();
601: }
602: if (redirectorElement != null) {
603: redirectorElement.configure(redirector);
604: }
605: Execute exe = new Execute(createHandler(), createWatchdog());
606: exe.setAntRun(getProject());
607: exe.setWorkingDirectory(dir);
608: exe.setVMLauncher(vmLauncher);
609: exe.setSpawn(spawn);
610: String[] environment = env.getVariables();
611: if (environment != null) {
612: for (int i = 0; i < environment.length; i++) {
613: log("Setting environment variable: " + environment[i],
614: Project.MSG_VERBOSE);
615: }
616: }
617: exe.setNewenvironment(newEnvironment);
618: exe.setEnvironment(environment);
619: return exe;
620: }
621:
622: /**
623: * A Utility method for this classes and subclasses to run an
624: * Execute instance (an external command).
625: *
626: * @param exe instance of the execute class.
627: *
628: * @throws IOException in case of problem to attach to the stdin/stdout/stderr
629: * streams of the process.
630: */
631: protected final void runExecute(Execute exe) throws IOException {
632: int returnCode = -1; // assume the worst
633:
634: if (!spawn) {
635: returnCode = exe.execute();
636:
637: //test for and handle a forced process death
638: if (exe.killedProcess()) {
639: String msg = "Timeout: killed the sub-process";
640: if (failOnError) {
641: throw new BuildException(msg);
642: } else {
643: log(msg, Project.MSG_WARN);
644: }
645: }
646: maybeSetResultPropertyValue(returnCode);
647: redirector.complete();
648: if (Execute.isFailure(returnCode)) {
649: if (failOnError) {
650: throw new BuildException(getTaskType()
651: + " returned: " + returnCode, getLocation());
652: } else {
653: log("Result: " + returnCode, Project.MSG_ERR);
654: }
655: }
656: } else {
657: exe.spawn();
658: }
659: }
660:
661: /**
662: * Run the command using the given Execute instance. This may be
663: * overridden by subclasses.
664: *
665: * @param exe instance of Execute to run.
666: *
667: * @throws BuildException if the new process could not be started
668: * only if failIfExecFails is set to true (the default).
669: */
670: protected void runExec(Execute exe) throws BuildException {
671: // show the command
672: log(cmdl.describeCommand(), Project.MSG_VERBOSE);
673:
674: exe.setCommandline(cmdl.getCommandline());
675: try {
676: runExecute(exe);
677: } catch (IOException e) {
678: if (failIfExecFails) {
679: throw new BuildException("Execute failed: "
680: + e.toString(), e, getLocation());
681: } else {
682: log("Execute failed: " + e.toString(), Project.MSG_ERR);
683: }
684: } finally {
685: // close the output file if required
686: logFlush();
687: }
688: }
689:
690: /**
691: * Create the StreamHandler to use with our Execute instance.
692: *
693: * @return instance of ExecuteStreamHandler.
694: *
695: * @throws BuildException under unknown circumstances.
696: */
697: protected ExecuteStreamHandler createHandler()
698: throws BuildException {
699: return redirector.createHandler();
700: }
701:
702: /**
703: * Create the Watchdog to kill a runaway process.
704: *
705: * @return instance of ExecuteWatchdog.
706: *
707: * @throws BuildException under unknown circumstances.
708: */
709: protected ExecuteWatchdog createWatchdog() throws BuildException {
710: return (timeout == null) ? null : new ExecuteWatchdog(timeout
711: .longValue());
712: }
713:
714: /**
715: * Flush the output stream - if there is one.
716: */
717: protected void logFlush() {
718: }
719:
720: private boolean isPath(String line) {
721: return line.startsWith("PATH=") || line.startsWith("Path=");
722: }
723:
724: }
|