1. /*
  2. * Copyright 2000-2004 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. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Enumeration;
  21. import java.util.Vector;
  22. import org.apache.tools.ant.BuildException;
  23. import org.apache.tools.ant.Project;
  24. import org.apache.tools.ant.Task;
  25. import org.apache.tools.ant.types.Commandline;
  26. import org.apache.tools.ant.types.Environment;
  27. import org.apache.tools.ant.types.Path;
  28. import org.apache.tools.ant.types.RedirectorElement;
  29. import org.apache.tools.ant.util.FileUtils;
  30. /**
  31. * Executes a given command if the os platform is appropriate.
  32. *
  33. * @since Ant 1.2
  34. *
  35. * @ant.task category="control"
  36. */
  37. public class ExecTask extends Task {
  38. private String os;
  39. private File dir;
  40. protected boolean failOnError = false;
  41. protected boolean newEnvironment = false;
  42. private Long timeout = null;
  43. private Environment env = new Environment();
  44. protected Commandline cmdl = new Commandline();
  45. private String resultProperty;
  46. private boolean failIfExecFails = true;
  47. private String executable;
  48. private boolean resolveExecutable = false;
  49. private boolean spawn = false;
  50. private boolean incompatibleWithSpawn = false;
  51. //include locally for screening purposes
  52. private String inputString;
  53. private File input;
  54. private File output;
  55. private File error;
  56. protected Redirector redirector = new Redirector(this);
  57. protected RedirectorElement redirectorElement;
  58. /**
  59. * Controls whether the VM (1.3 and above) is used to execute the
  60. * command
  61. */
  62. private boolean vmLauncher = true;
  63. /**
  64. * set whether or not you want the process to be spawned
  65. * default is not spawned
  66. * @param spawn if true you do not want ant to wait for the end of the process
  67. * @since ant 1.6
  68. */
  69. public void setSpawn(boolean spawn) {
  70. this.spawn = spawn;
  71. }
  72. /**
  73. * Timeout in milliseconds after which the process will be killed.
  74. *
  75. * @param value timeout in milliseconds
  76. *
  77. * @since Ant 1.5
  78. */
  79. public void setTimeout(Long value) {
  80. timeout = value;
  81. incompatibleWithSpawn = true;
  82. }
  83. /**
  84. * Timeout in milliseconds after which the process will be killed.
  85. *
  86. * @param value timeout in milliseconds
  87. */
  88. public void setTimeout(Integer value) {
  89. if (value == null) {
  90. timeout = null;
  91. } else {
  92. setTimeout(new Long(value.intValue()));
  93. }
  94. incompatibleWithSpawn = true;
  95. }
  96. /**
  97. * Set the name of the executable program.
  98. * @param value the name of the executable program
  99. */
  100. public void setExecutable(String value) {
  101. this.executable = value;
  102. cmdl.setExecutable(value);
  103. }
  104. /**
  105. * Set the working directory of the process.
  106. * @param d the working directory of the process
  107. */
  108. public void setDir(File d) {
  109. this.dir = d;
  110. }
  111. /**
  112. * List of operating systems on which the command may be executed.
  113. * @param os list of operating systems on which the command may be executed
  114. */
  115. public void setOs(String os) {
  116. this.os = os;
  117. }
  118. /**
  119. * Sets a command line
  120. * @param cmdl command line
  121. * @ant.attribute ignore="true"
  122. */
  123. public void setCommand(Commandline cmdl) {
  124. log("The command attribute is deprecated. "
  125. + "Please use the executable attribute and nested arg elements.",
  126. Project.MSG_WARN);
  127. this.cmdl = cmdl;
  128. }
  129. /**
  130. * File the output of the process is redirected to. If error is not
  131. * redirected, it too will appear in the output
  132. *
  133. * @param out name of a file to which send output to
  134. */
  135. public void setOutput(File out) {
  136. this.output = out;
  137. incompatibleWithSpawn = true;
  138. }
  139. /**
  140. * Set the input to use for the task
  141. *
  142. * @param input name of a file to get input from
  143. */
  144. public void setInput(File input) {
  145. if (inputString != null) {
  146. throw new BuildException("The \"input\" and \"inputstring\" "
  147. + "attributes cannot both be specified");
  148. }
  149. this.input = input;
  150. incompatibleWithSpawn = true;
  151. }
  152. /**
  153. * Set the string to use as input
  154. *
  155. * @param inputString the string which is used as the input source
  156. */
  157. public void setInputString(String inputString) {
  158. if (input != null) {
  159. throw new BuildException("The \"input\" and \"inputstring\" "
  160. + "attributes cannot both be specified");
  161. }
  162. this.inputString = inputString;
  163. incompatibleWithSpawn = true;
  164. }
  165. /**
  166. * Controls whether error output of exec is logged. This is only useful
  167. * when output is being redirected and error output is desired in the
  168. * Ant log
  169. *
  170. * @param logError set to true to log error output in the normal ant log
  171. */
  172. public void setLogError(boolean logError) {
  173. redirector.setLogError(logError);
  174. incompatibleWithSpawn |= logError;
  175. }
  176. /**
  177. * File the error stream of the process is redirected to.
  178. *
  179. * @param error a file to which send stderr to
  180. *
  181. * @since ant 1.6
  182. */
  183. public void setError(File error) {
  184. this.error = error;
  185. incompatibleWithSpawn = true;
  186. }
  187. /**
  188. * Sets the property name whose value should be set to the output of
  189. * the process.
  190. *
  191. * @param outputProp name of property
  192. */
  193. public void setOutputproperty(String outputProp) {
  194. redirector.setOutputProperty(outputProp);
  195. incompatibleWithSpawn = true;
  196. }
  197. /**
  198. * Sets the name of the property whose value should be set to the error of
  199. * the process.
  200. *
  201. * @param errorProperty name of property
  202. *
  203. * @since ant 1.6
  204. */
  205. public void setErrorProperty(String errorProperty) {
  206. redirector.setErrorProperty(errorProperty);
  207. incompatibleWithSpawn = true;
  208. }
  209. /**
  210. * Fail if the command exits with a non-zero return code.
  211. *
  212. * @param fail if true fail the command on non-zero return code.
  213. */
  214. public void setFailonerror(boolean fail) {
  215. failOnError = fail;
  216. incompatibleWithSpawn |= fail;
  217. }
  218. /**
  219. * Do not propagate old environment when new environment variables are specified.
  220. *
  221. * @param newenv if true, do not propagate old environment
  222. * when new environment variables are specified.
  223. */
  224. public void setNewenvironment(boolean newenv) {
  225. newEnvironment = newenv;
  226. }
  227. /**
  228. * Sets a flag indicating whether to attempt to resolve the executable
  229. * to a file
  230. *
  231. * @param resolveExecutable if true, attempt to resolve the
  232. * path of the executable
  233. */
  234. public void setResolveExecutable(boolean resolveExecutable) {
  235. this.resolveExecutable = resolveExecutable;
  236. }
  237. /**
  238. * Indicates whether to attempt to resolve the executable to a
  239. * file
  240. *
  241. * @since Ant 1.6
  242. */
  243. public boolean getResolveExecutable() {
  244. return resolveExecutable;
  245. }
  246. /**
  247. * Add an environment variable to the launched process.
  248. *
  249. * @param var new environment variable
  250. */
  251. public void addEnv(Environment.Variable var) {
  252. env.addVariable(var);
  253. }
  254. /**
  255. * Adds a command-line argument.
  256. *
  257. * @return new command line argument created
  258. */
  259. public Commandline.Argument createArg() {
  260. return cmdl.createArgument();
  261. }
  262. /**
  263. * Sets the name of a property in which the return code of the
  264. * command should be stored. Only of interest if failonerror=false.
  265. *
  266. * @since Ant 1.5
  267. *
  268. * @param resultProperty name of property
  269. */
  270. public void setResultProperty(String resultProperty) {
  271. this.resultProperty = resultProperty;
  272. incompatibleWithSpawn = true;
  273. }
  274. /**
  275. * helper method to set result property to the
  276. * passed in value if appropriate
  277. *
  278. * @param result value desired for the result property value
  279. */
  280. protected void maybeSetResultPropertyValue(int result) {
  281. if (resultProperty != null) {
  282. String res = Integer.toString(result);
  283. getProject().setNewProperty(resultProperty, res);
  284. }
  285. }
  286. /**
  287. * Sets a flag to stop the build if program cannot be started.
  288. * Defaults to true.
  289. *
  290. * @param flag stop the build if program cannot be started
  291. *
  292. * @since Ant 1.5
  293. */
  294. public void setFailIfExecutionFails(boolean flag) {
  295. failIfExecFails = flag;
  296. incompatibleWithSpawn = true;
  297. }
  298. /**
  299. * Sets whether output should be appended to or overwrite an existing file.
  300. * Defaults to false.
  301. *
  302. * @param append if true append is desired
  303. *
  304. * @since 1.30, Ant 1.5
  305. */
  306. public void setAppend(boolean append) {
  307. redirector.setAppend(append);
  308. incompatibleWithSpawn = true;
  309. }
  310. /**
  311. * Add a <CODE>RedirectorElement</CODE> to this task.
  312. *
  313. * @param redirectorElement <CODE>RedirectorElement</CODE>.
  314. */
  315. public void addConfiguredRedirector(RedirectorElement redirectorElement) {
  316. if (this.redirectorElement != null) {
  317. throw new BuildException("cannot have > 1 nested <redirector>s");
  318. } else {
  319. this.redirectorElement = redirectorElement;
  320. incompatibleWithSpawn = true;
  321. }
  322. }
  323. /**
  324. * The method attempts to figure out where the executable is so that we can feed
  325. * the full path. We first try basedir, then the exec dir, and then
  326. * fallback to the straight executable name (i.e. on ther path).
  327. *
  328. * @param exec the name of the executable
  329. * @param searchPath if true, the excutable will be looked up in
  330. * the PATH environment and the absolute path is returned.
  331. *
  332. * @return the executable as a full path if it can be determined.
  333. *
  334. * @since Ant 1.6
  335. */
  336. protected String resolveExecutable(String exec, boolean searchPath) {
  337. if (!resolveExecutable) {
  338. return exec;
  339. }
  340. // try to find the executable
  341. File executableFile = getProject().resolveFile(exec);
  342. if (executableFile.exists()) {
  343. return executableFile.getAbsolutePath();
  344. }
  345. FileUtils fileUtils = FileUtils.newFileUtils();
  346. // now try to resolve against the dir if given
  347. if (dir != null) {
  348. executableFile = fileUtils.resolveFile(dir, exec);
  349. if (executableFile.exists()) {
  350. return executableFile.getAbsolutePath();
  351. }
  352. }
  353. // couldn't find it - must be on path
  354. if (searchPath) {
  355. Vector env = Execute.getProcEnvironment();
  356. Enumeration e = env.elements();
  357. Path p = null;
  358. while (e.hasMoreElements()) {
  359. String line = (String) e.nextElement();
  360. if (line.startsWith("PATH=") || line.startsWith("Path=")) {
  361. p = new Path(getProject(), line.substring(5));
  362. break;
  363. }
  364. }
  365. if (p != null) {
  366. String[] dirs = p.list();
  367. for (int i = 0; i < dirs.length; i++) {
  368. executableFile = fileUtils.resolveFile(new File(dirs[i]),
  369. exec);
  370. if (executableFile.exists()) {
  371. return executableFile.getAbsolutePath();
  372. }
  373. }
  374. }
  375. }
  376. // searchPath is false, or no PATH or not found - keep our
  377. // fingers crossed.
  378. return exec;
  379. }
  380. /**
  381. * Do the work.
  382. *
  383. * @throws BuildException in a number of circumstances :
  384. * <ul>
  385. * <li>if failIfExecFails is set to true and the process cannot be started</li>
  386. * <li>the java13command launcher can send build exceptions</li>
  387. * <li>this list is not exhaustive or limitative</li>
  388. * </ul>
  389. */
  390. public void execute() throws BuildException {
  391. File savedDir = dir; // possibly altered in prepareExec
  392. cmdl.setExecutable(resolveExecutable(executable, false));
  393. checkConfiguration();
  394. if (isValidOs()) {
  395. try {
  396. runExec(prepareExec());
  397. } finally {
  398. dir = savedDir;
  399. }
  400. }
  401. }
  402. /**
  403. * Has the user set all necessary attributes?
  404. * @throws BuildException if there are missing required parameters
  405. */
  406. protected void checkConfiguration() throws BuildException {
  407. if (cmdl.getExecutable() == null) {
  408. throw new BuildException("no executable specified", getLocation());
  409. }
  410. if (dir != null && !dir.exists()) {
  411. throw new BuildException("The directory you specified does not "
  412. + "exist");
  413. }
  414. if (dir != null && !dir.isDirectory()) {
  415. throw new BuildException("The directory you specified is not a "
  416. + "directory");
  417. }
  418. if (spawn && incompatibleWithSpawn) {
  419. getProject().log("spawn does not allow attributes related to input, "
  420. + "output, error, result", Project.MSG_ERR);
  421. getProject().log("spawn also does not allow timeout", Project.MSG_ERR);
  422. getProject().log( "finally, spawn is not compatible "
  423. + "with a nested I/O <redirector>", Project.MSG_ERR);
  424. throw new BuildException("You have used an attribute "
  425. + "or nested element which is not compatible with spawn");
  426. }
  427. setupRedirector();
  428. }
  429. /**
  430. * Set up properties on the redirector that we needed to store locally.
  431. */
  432. protected void setupRedirector() {
  433. redirector.setInput(input);
  434. redirector.setInputString(inputString);
  435. redirector.setOutput(output);
  436. redirector.setError(error);
  437. }
  438. /**
  439. * Is this the OS the user wanted?
  440. * @return boolean
  441. * <ul>
  442. * <li>
  443. * <code>true</code> if the os under which ant is running is
  444. * matches one os in the os attribute
  445. * or if the os attribute is null</li>
  446. * <li><code>false</code> otherwise.</li>
  447. * </ul>
  448. */
  449. protected boolean isValidOs() {
  450. // test if os match
  451. String myos = System.getProperty("os.name");
  452. log("Current OS is " + myos, Project.MSG_VERBOSE);
  453. if ((os != null) && (os.indexOf(myos) < 0)) {
  454. // this command will be executed only on the specified OS
  455. log("This OS, " + myos
  456. + " was not found in the specified list of valid OSes: " + os,
  457. Project.MSG_VERBOSE);
  458. return false;
  459. }
  460. return true;
  461. }
  462. /**
  463. * Sets a flag indicating if we want to launch new process with VM,
  464. * otherwise use the OS's shell.
  465. * Default value of the flag is true.
  466. * @param vmLauncher true if we want to launch new process with VM,
  467. * false if we want to use the OS's shell.
  468. */
  469. public void setVMLauncher(boolean vmLauncher) {
  470. this.vmLauncher = vmLauncher;
  471. }
  472. /**
  473. * Create an Execute instance with the correct working directory set.
  474. *
  475. * @return an instance of the Execute class
  476. *
  477. * @throws BuildException under unknown circumstances.
  478. */
  479. protected Execute prepareExec() throws BuildException {
  480. // default directory to the project's base directory
  481. if (dir == null) {
  482. dir = getProject().getBaseDir();
  483. }
  484. if (redirectorElement != null) {
  485. redirectorElement.configure(redirector);
  486. }
  487. Execute exe = new Execute(createHandler(), createWatchdog());
  488. exe.setAntRun(getProject());
  489. exe.setWorkingDirectory(dir);
  490. exe.setVMLauncher(vmLauncher);
  491. exe.setSpawn(spawn);
  492. String[] environment = env.getVariables();
  493. if (environment != null) {
  494. for (int i = 0; i < environment.length; i++) {
  495. log("Setting environment variable: " + environment[i],
  496. Project.MSG_VERBOSE);
  497. }
  498. }
  499. exe.setNewenvironment(newEnvironment);
  500. exe.setEnvironment(environment);
  501. return exe;
  502. }
  503. /**
  504. * A Utility method for this classes and subclasses to run an
  505. * Execute instance (an external command).
  506. *
  507. * @param exe instance of the execute class
  508. *
  509. * @throws IOException in case of problem to attach to the stdin/stdout/stderr
  510. * streams of the process
  511. */
  512. protected final void runExecute(Execute exe) throws IOException {
  513. int returnCode = -1; // assume the worst
  514. if (!spawn) {
  515. returnCode = exe.execute();
  516. //test for and handle a forced process death
  517. if (exe.killedProcess()) {
  518. String msg = "Timeout: killed the sub-process";
  519. if (failOnError) {
  520. throw new BuildException(msg);
  521. } else {
  522. log(msg, Project.MSG_WARN);
  523. }
  524. }
  525. maybeSetResultPropertyValue(returnCode);
  526. redirector.complete();
  527. if (Execute.isFailure(returnCode)) {
  528. if (failOnError) {
  529. throw new BuildException(getTaskType() + " returned: "
  530. + returnCode, getLocation());
  531. } else {
  532. log("Result: " + returnCode, Project.MSG_ERR);
  533. }
  534. }
  535. } else {
  536. exe.spawn();
  537. }
  538. }
  539. /**
  540. * Run the command using the given Execute instance. This may be
  541. * overridden by subclasses
  542. *
  543. * @param exe instance of Execute to run
  544. *
  545. * @throws BuildException if the new process could not be started
  546. * only if failIfExecFails is set to true (the default)
  547. */
  548. protected void runExec(Execute exe) throws BuildException {
  549. // show the command
  550. log(cmdl.describeCommand(), Project.MSG_VERBOSE);
  551. exe.setCommandline(cmdl.getCommandline());
  552. try {
  553. runExecute(exe);
  554. } catch (IOException e) {
  555. if (failIfExecFails) {
  556. throw new BuildException("Execute failed: " + e.toString(), e,
  557. getLocation());
  558. } else {
  559. log("Execute failed: " + e.toString(), Project.MSG_ERR);
  560. }
  561. } finally {
  562. // close the output file if required
  563. logFlush();
  564. }
  565. }
  566. /**
  567. * Create the StreamHandler to use with our Execute instance.
  568. *
  569. * @return instance of ExecuteStreamHandler
  570. *
  571. * @throws BuildException under unknown circumstances
  572. */
  573. protected ExecuteStreamHandler createHandler() throws BuildException {
  574. return redirector.createHandler();
  575. }
  576. /**
  577. * Create the Watchdog to kill a runaway process.
  578. *
  579. * @return instance of ExecuteWatchdog
  580. *
  581. * @throws BuildException under unknown circumstances
  582. */
  583. protected ExecuteWatchdog createWatchdog() throws BuildException {
  584. if (timeout == null) {
  585. return null;
  586. }
  587. return new ExecuteWatchdog(timeout.longValue());
  588. }
  589. /**
  590. * Flush the output stream - if there is one.
  591. */
  592. protected void logFlush() {
  593. }
  594. }