You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Execute.java 43 kB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "Ant" and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant.taskdefs;
  55. import java.io.BufferedReader;
  56. import java.io.ByteArrayOutputStream;
  57. import java.io.File;
  58. import java.io.FileWriter;
  59. import java.io.IOException;
  60. import java.io.PrintWriter;
  61. import java.io.StringReader;
  62. import java.lang.reflect.InvocationTargetException;
  63. import java.lang.reflect.Method;
  64. import java.util.HashMap;
  65. import java.util.Iterator;
  66. import java.util.Vector;
  67. import org.apache.tools.ant.BuildException;
  68. import org.apache.tools.ant.Project;
  69. import org.apache.tools.ant.Task;
  70. import org.apache.tools.ant.taskdefs.condition.Os;
  71. import org.apache.tools.ant.types.Commandline;
  72. /**
  73. * Runs an external program.
  74. *
  75. * @author thomas.haas@softwired-inc.com
  76. * @author <a href="mailto:jtulley@novell.com">Jeff Tulley</a>
  77. * @author <a href="mailto:CHudak@arrowheadgrp.com">Charles Hudak</a>
  78. *
  79. * @since Ant 1.2
  80. *
  81. * @version $Revision$
  82. */
  83. public class Execute {
  84. /** Invalid exit code. **/
  85. public static final int INVALID = Integer.MAX_VALUE;
  86. private String[] cmdl = null;
  87. private String[] env = null;
  88. private int exitValue = INVALID;
  89. private ExecuteStreamHandler streamHandler;
  90. private ExecuteWatchdog watchdog;
  91. private File workingDirectory = null;
  92. private Project project = null;
  93. private boolean newEnvironment = false;
  94. /** Controls whether the VM is used to launch commands, where possible */
  95. private boolean useVMLauncher = true;
  96. private static String antWorkingDirectory = System.getProperty("user.dir");
  97. private static CommandLauncher vmLauncher = null;
  98. private static CommandLauncher shellLauncher = null;
  99. private static Vector procEnvironment = null;
  100. private boolean spawn = false;
  101. /** Used to destroy processes when the VM exits. */
  102. private static ProcessDestroyer processDestroyer = new ProcessDestroyer();
  103. /**
  104. * Builds a command launcher for the OS and JVM we are running under
  105. */
  106. static {
  107. // Try using a JDK 1.3 launcher
  108. try {
  109. if (Os.isFamily("openvms")) {
  110. vmLauncher = new VmsCommandLauncher();
  111. } else if (!Os.isFamily("os/2")) {
  112. vmLauncher = new Java13CommandLauncher();
  113. }
  114. } catch (NoSuchMethodException exc) {
  115. // Ignore and keep trying
  116. }
  117. if (Os.isFamily("mac")) {
  118. // Mac
  119. shellLauncher = new MacCommandLauncher(new CommandLauncher());
  120. } else if (Os.isFamily("os/2")) {
  121. // OS/2
  122. shellLauncher = new OS2CommandLauncher(new CommandLauncher());
  123. } else if (Os.isFamily("windows")) {
  124. // Windows. Need to determine which JDK we're running in
  125. CommandLauncher baseLauncher;
  126. if (System.getProperty("java.version").startsWith("1.1")) {
  127. // JDK 1.1
  128. baseLauncher = new Java11CommandLauncher();
  129. } else {
  130. // JDK 1.2
  131. baseLauncher = new CommandLauncher();
  132. }
  133. if (!Os.isFamily("win9x")) {
  134. // Windows XP/2000/NT
  135. shellLauncher = new WinNTCommandLauncher(baseLauncher);
  136. } else {
  137. // Windows 98/95 - need to use an auxiliary script
  138. shellLauncher
  139. = new ScriptCommandLauncher("bin/antRun.bat", baseLauncher);
  140. }
  141. } else if (Os.isFamily("netware")) {
  142. // NetWare. Need to determine which JDK we're running in
  143. CommandLauncher baseLauncher;
  144. if (System.getProperty("java.version").startsWith("1.1")) {
  145. // JDK 1.1
  146. baseLauncher = new Java11CommandLauncher();
  147. } else {
  148. // JDK 1.2
  149. baseLauncher = new CommandLauncher();
  150. }
  151. shellLauncher
  152. = new PerlScriptCommandLauncher("bin/antRun.pl", baseLauncher);
  153. } else if (Os.isFamily("openvms")) {
  154. // the vmLauncher already uses the shell
  155. shellLauncher = vmLauncher;
  156. } else {
  157. // Generic
  158. shellLauncher = new ScriptCommandLauncher("bin/antRun",
  159. new CommandLauncher());
  160. }
  161. }
  162. /**
  163. * set whether or not you want the process to be spawned
  164. * default is not spawned
  165. *
  166. * @param spawn if true you do not want ant to wait for the end of the process
  167. *
  168. * @since ant 1.6
  169. */
  170. public void setSpawn(boolean spawn) {
  171. this.spawn = spawn;
  172. }
  173. /**
  174. * Find the list of environment variables for this process.
  175. *
  176. * @return a vector containing the environment variables
  177. * the vector elements are strings formatted like variable = value
  178. */
  179. public static synchronized Vector getProcEnvironment() {
  180. if (procEnvironment != null) {
  181. return procEnvironment;
  182. }
  183. procEnvironment = new Vector();
  184. try {
  185. ByteArrayOutputStream out = new ByteArrayOutputStream();
  186. Execute exe = new Execute(new PumpStreamHandler(out));
  187. exe.setCommandline(getProcEnvCommand());
  188. // Make sure we do not recurse forever
  189. exe.setNewenvironment(true);
  190. int retval = exe.execute();
  191. if (retval != 0) {
  192. // Just try to use what we got
  193. }
  194. BufferedReader in =
  195. new BufferedReader(new StringReader(toString(out)));
  196. if (Os.isFamily("openvms")) {
  197. procEnvironment = addVMSLogicals(procEnvironment, in);
  198. return procEnvironment;
  199. }
  200. String var = null;
  201. String line, lineSep = System.getProperty("line.separator");
  202. while ((line = in.readLine()) != null) {
  203. if (line.indexOf('=') == -1) {
  204. // Chunk part of previous env var (UNIX env vars can
  205. // contain embedded new lines).
  206. if (var == null) {
  207. var = lineSep + line;
  208. } else {
  209. var += lineSep + line;
  210. }
  211. } else {
  212. // New env var...append the previous one if we have it.
  213. if (var != null) {
  214. procEnvironment.addElement(var);
  215. }
  216. var = line;
  217. }
  218. }
  219. // Since we "look ahead" before adding, there's one last env var.
  220. if (var != null) {
  221. procEnvironment.addElement(var);
  222. }
  223. } catch (java.io.IOException exc) {
  224. exc.printStackTrace();
  225. // Just try to see how much we got
  226. }
  227. return procEnvironment;
  228. }
  229. private static String[] getProcEnvCommand() {
  230. if (Os.isFamily("os/2")) {
  231. // OS/2 - use same mechanism as Windows 2000
  232. String[] cmd = {"cmd", "/c", "set" };
  233. return cmd;
  234. } else if (Os.isFamily("windows")) {
  235. // Determine if we're running under XP/2000/NT or 98/95
  236. if (!Os.isFamily("win9x")) {
  237. // Windows XP/2000/NT
  238. String[] cmd = {"cmd", "/c", "set" };
  239. return cmd;
  240. } else {
  241. // Windows 98/95
  242. String[] cmd = {"command.com", "/c", "set" };
  243. return cmd;
  244. }
  245. } else if (Os.isFamily("z/os") || Os.isFamily("unix")) {
  246. // On most systems one could use: /bin/sh -c env
  247. // Some systems have /bin/env, others /usr/bin/env, just try
  248. String[] cmd = new String[1];
  249. if (new File("/bin/env").canRead()) {
  250. cmd[0] = "/bin/env";
  251. } else if (new File("/usr/bin/env").canRead()) {
  252. cmd[0] = "/usr/bin/env";
  253. } else {
  254. // rely on PATH
  255. cmd[0] = "env";
  256. }
  257. return cmd;
  258. } else if (Os.isFamily("netware") || Os.isFamily("os/400")) {
  259. // rely on PATH
  260. String[] cmd = {"env"};
  261. return cmd;
  262. } else if (Os.isFamily("openvms")) {
  263. String[] cmd = {"show", "logical"};
  264. return cmd;
  265. } else {
  266. // MAC OS 9 and previous
  267. //TODO: I have no idea how to get it, someone must fix it
  268. String[] cmd = null;
  269. return cmd;
  270. }
  271. }
  272. /**
  273. * ByteArrayOutputStream#toString doesn't seem to work reliably on
  274. * OS/390, at least not the way we use it in the execution
  275. * context.
  276. *
  277. * @param bos the output stream that one wants to read
  278. * @return the output stream as a string, read with
  279. * special encodings in the case of z/os and os/400
  280. *
  281. * @since Ant 1.5
  282. */
  283. public static String toString(ByteArrayOutputStream bos) {
  284. if (Os.isFamily("z/os")) {
  285. try {
  286. return bos.toString("Cp1047");
  287. } catch (java.io.UnsupportedEncodingException e) {
  288. //noop default encoding used
  289. }
  290. } else if (Os.isFamily("os/400")) {
  291. try {
  292. return bos.toString("Cp500");
  293. } catch (java.io.UnsupportedEncodingException e) {
  294. //noop default encoding used
  295. }
  296. }
  297. return bos.toString();
  298. }
  299. /**
  300. * Creates a new execute object using <code>PumpStreamHandler</code> for
  301. * stream handling.
  302. */
  303. public Execute() {
  304. this(new PumpStreamHandler(), null);
  305. }
  306. /**
  307. * Creates a new execute object.
  308. *
  309. * @param streamHandler the stream handler used to handle the input and
  310. * output streams of the subprocess.
  311. */
  312. public Execute(ExecuteStreamHandler streamHandler) {
  313. this(streamHandler, null);
  314. }
  315. /**
  316. * Creates a new execute object.
  317. *
  318. * @param streamHandler the stream handler used to handle the input and
  319. * output streams of the subprocess.
  320. * @param watchdog a watchdog for the subprocess or <code>null</code> to
  321. * to disable a timeout for the subprocess.
  322. */
  323. public Execute(ExecuteStreamHandler streamHandler,
  324. ExecuteWatchdog watchdog) {
  325. this.streamHandler = streamHandler;
  326. this.watchdog = watchdog;
  327. }
  328. /**
  329. * Returns the commandline used to create a subprocess.
  330. *
  331. * @return the commandline used to create a subprocess
  332. */
  333. public String[] getCommandline() {
  334. return cmdl;
  335. }
  336. /**
  337. * Sets the commandline of the subprocess to launch.
  338. *
  339. * @param commandline the commandline of the subprocess to launch
  340. */
  341. public void setCommandline(String[] commandline) {
  342. cmdl = commandline;
  343. }
  344. /**
  345. * Set whether to propagate the default environment or not.
  346. *
  347. * @param newenv whether to propagate the process environment.
  348. */
  349. public void setNewenvironment(boolean newenv) {
  350. newEnvironment = newenv;
  351. }
  352. /**
  353. * Returns the environment used to create a subprocess.
  354. *
  355. * @return the environment used to create a subprocess
  356. */
  357. public String[] getEnvironment() {
  358. if (env == null || newEnvironment) {
  359. return env;
  360. }
  361. return patchEnvironment();
  362. }
  363. /**
  364. * Sets the environment variables for the subprocess to launch.
  365. *
  366. * @param env array of Strings, each element of which has
  367. * an environment variable settings in format <em>key=value</em>
  368. */
  369. public void setEnvironment(String[] env) {
  370. this.env = env;
  371. }
  372. /**
  373. * Sets the working directory of the process to execute.
  374. *
  375. * <p>This is emulated using the antRun scripts unless the OS is
  376. * Windows NT in which case a cmd.exe is spawned,
  377. * or MRJ and setting user.dir works, or JDK 1.3 and there is
  378. * official support in java.lang.Runtime.
  379. *
  380. * @param wd the working directory of the process.
  381. */
  382. public void setWorkingDirectory(File wd) {
  383. if (wd == null || wd.getAbsolutePath().equals(antWorkingDirectory)) {
  384. workingDirectory = null;
  385. } else {
  386. workingDirectory = wd;
  387. }
  388. }
  389. /**
  390. * Set the name of the antRun script using the project's value.
  391. *
  392. * @param project the current project.
  393. *
  394. * @throws BuildException not clear when it is going to throw an exception, but
  395. * it is the method's signature
  396. */
  397. public void setAntRun(Project project) throws BuildException {
  398. this.project = project;
  399. }
  400. /**
  401. * Launch this execution through the VM, where possible, rather than through
  402. * the OS's shell. In some cases and operating systems using the shell will
  403. * allow the shell to perform additional processing such as associating an
  404. * executable with a script, etc
  405. *
  406. * @param useVMLauncher true if exec should launch through thge VM,
  407. * false if the shell should be used to launch the
  408. * command.
  409. */
  410. public void setVMLauncher(boolean useVMLauncher) {
  411. this.useVMLauncher = useVMLauncher;
  412. }
  413. /**
  414. * Creates a process that runs a command.
  415. *
  416. * @param project the Project, only used for logging purposes, may be null.
  417. * @param command the command to run
  418. * @param env the environment for the command
  419. * @param dir the working directory for the command
  420. * @param useVM use the built-in exec command for JDK 1.3 if available.
  421. * @return the process started
  422. * @throws IOException forwarded from the particular launcher used
  423. *
  424. * @since Ant 1.5
  425. */
  426. public static Process launch(Project project, String[] command,
  427. String[] env, File dir, boolean useVM)
  428. throws IOException {
  429. CommandLauncher launcher
  430. = vmLauncher != null ? vmLauncher : shellLauncher;
  431. if (!useVM) {
  432. launcher = shellLauncher;
  433. }
  434. return launcher.exec(project, command, env, dir);
  435. }
  436. /**
  437. * Runs a process defined by the command line and returns its exit status.
  438. *
  439. * @return the exit status of the subprocess or <code>INVALID</code>
  440. * @exception java.io.IOException The exception is thrown, if launching
  441. * of the subprocess failed
  442. */
  443. public int execute() throws IOException {
  444. final Process process = launch(project, getCommandline(),
  445. getEnvironment(), workingDirectory,
  446. useVMLauncher);
  447. try {
  448. streamHandler.setProcessInputStream(process.getOutputStream());
  449. streamHandler.setProcessOutputStream(process.getInputStream());
  450. streamHandler.setProcessErrorStream(process.getErrorStream());
  451. } catch (IOException e) {
  452. process.destroy();
  453. throw e;
  454. }
  455. streamHandler.start();
  456. try {
  457. // add the process to the list of those to destroy if the VM exits
  458. //
  459. processDestroyer.add(process);
  460. if (watchdog != null) {
  461. watchdog.start(process);
  462. }
  463. waitFor(process);
  464. if (watchdog != null) {
  465. watchdog.stop();
  466. }
  467. streamHandler.stop();
  468. if (watchdog != null) {
  469. watchdog.checkException();
  470. }
  471. return getExitValue();
  472. } finally {
  473. // remove the process to the list of those to destroy if the VM exits
  474. //
  475. processDestroyer.remove(process);
  476. }
  477. }
  478. /**
  479. * Starts a process defined by the command line.
  480. * Ant will not wait for this process, nor log its output
  481. *
  482. * @throws java.io.IOException The exception is thrown, if launching
  483. * of the subprocess failed
  484. * @since ant 1.6
  485. */
  486. public void spawn() throws IOException {
  487. final Process process = launch(project, getCommandline(),
  488. getEnvironment(), workingDirectory,
  489. useVMLauncher);
  490. if (Os.isFamily("windows")) {
  491. try {
  492. Thread.sleep(1000);
  493. } catch (InterruptedException e) {
  494. project.log("interruption in the sleep after having spawned a process",
  495. Project.MSG_VERBOSE);
  496. }
  497. }
  498. project.log("spawned process " + process.toString(), Project.MSG_VERBOSE);
  499. }
  500. /**
  501. * wait for a given process
  502. *
  503. * @param process the process one wants to wait for
  504. */
  505. protected void waitFor(Process process) {
  506. try {
  507. process.waitFor();
  508. setExitValue(process.exitValue());
  509. } catch (InterruptedException e) {
  510. process.destroy();
  511. }
  512. }
  513. /**
  514. * set the exit value
  515. *
  516. * @param value exit value of the process
  517. */
  518. protected void setExitValue(int value) {
  519. exitValue = value;
  520. }
  521. /**
  522. * Query the exit value of the process.
  523. * @return the exit value, 1 if the process was killed,
  524. * or Execute.INVALID if no exit value has been received
  525. */
  526. public int getExitValue() {
  527. return exitValue;
  528. }
  529. /**
  530. * Checks whether <code>exitValue</code> signals a failure on the current
  531. * system (OS specific).
  532. *
  533. * <p><b>Note</b> that this method relies on the conventions of
  534. * the OS, it will return false results if the application you are
  535. * running doesn't follow these conventions. One notable
  536. * exception is the Java VM provided by HP for OpenVMS - it will
  537. * return 0 if successful (like on any other platform), but this
  538. * signals a failure on OpenVMS. So if you execute a new Java VM
  539. * on OpenVMS, you cannot trust this method.</p>
  540. *
  541. * @param exitValue the exit value (return code) to be checked
  542. * @return <code>true</code> if <code>exitValue</code> signals a failure
  543. */
  544. public static boolean isFailure(int exitValue) {
  545. if (Os.isFamily("openvms")) {
  546. // odd exit value signals failure
  547. return (exitValue % 2) == 0;
  548. } else {
  549. // non zero exit value signals failure
  550. return exitValue != 0;
  551. }
  552. }
  553. /**
  554. * test for an untimely death of the process
  555. * @return true iff a watchdog had to kill the process
  556. * @since Ant 1.5
  557. */
  558. public boolean killedProcess() {
  559. return watchdog != null && watchdog.killedProcess();
  560. }
  561. /**
  562. * Patch the current environment with the new values from the user.
  563. * @return the patched environment
  564. */
  565. private String[] patchEnvironment() {
  566. Vector osEnv = (Vector) getProcEnvironment().clone();
  567. for (int i = 0; i < env.length; i++) {
  568. int pos = env[i].indexOf('=');
  569. // Get key including "="
  570. String key = env[i].substring(0, pos + 1);
  571. int size = osEnv.size();
  572. for (int j = 0; j < size; j++) {
  573. if (((String) osEnv.elementAt(j)).startsWith(key)) {
  574. osEnv.removeElementAt(j);
  575. break;
  576. }
  577. }
  578. osEnv.addElement(env[i]);
  579. }
  580. String[] result = new String[osEnv.size()];
  581. osEnv.copyInto(result);
  582. return result;
  583. }
  584. /**
  585. * A utility method that runs an external command. Writes the output and
  586. * error streams of the command to the project log.
  587. *
  588. * @param task The task that the command is part of. Used for logging
  589. * @param cmdline The command to execute.
  590. *
  591. * @throws BuildException if the command does not return 0.
  592. */
  593. public static void runCommand(Task task, String[] cmdline)
  594. throws BuildException {
  595. try {
  596. task.log(Commandline.describeCommand(cmdline),
  597. Project.MSG_VERBOSE);
  598. Execute exe = new Execute(new LogStreamHandler(task,
  599. Project.MSG_INFO,
  600. Project.MSG_ERR));
  601. exe.setAntRun(task.getProject());
  602. exe.setCommandline(cmdline);
  603. int retval = exe.execute();
  604. if (isFailure(retval)) {
  605. throw new BuildException(cmdline[0]
  606. + " failed with return code " + retval, task.getLocation());
  607. }
  608. } catch (java.io.IOException exc) {
  609. throw new BuildException("Could not launch " + cmdline[0] + ": "
  610. + exc, task.getLocation());
  611. }
  612. }
  613. /**
  614. * This method is VMS specific and used by getProcEnvironment().
  615. *
  616. * Parses VMS logicals from <code>in</code> and adds them to
  617. * <code>environment</code>. <code>in</code> is expected to be the
  618. * output of "SHOW LOGICAL". The method takes care of parsing the output
  619. * correctly as well as making sure that a logical defined in multiple
  620. * tables only gets added from the highest order table. Logicals with
  621. * multiple equivalence names are mapped to a variable with multiple
  622. * values separated by a comma (,).
  623. */
  624. private static Vector addVMSLogicals(Vector environment, BufferedReader in)
  625. throws IOException {
  626. HashMap logicals = new HashMap();
  627. String logName = null, logValue = null, newLogName;
  628. String line = null;
  629. while ((line = in.readLine()) != null) {
  630. // parse the VMS logicals into required format ("VAR=VAL[,VAL2]")
  631. if (line.startsWith("\t=")) {
  632. // further equivalence name of previous logical
  633. if (logName != null) {
  634. logValue += "," + line.substring(4, line.length() - 1);
  635. }
  636. } else if (line.startsWith(" \"")) {
  637. // new logical?
  638. if (logName != null) {
  639. logicals.put(logName, logValue);
  640. }
  641. int eqIndex = line.indexOf('=');
  642. newLogName = line.substring(3, eqIndex - 2);
  643. if (logicals.containsKey(newLogName)) {
  644. // already got this logical from a higher order table
  645. logName = null;
  646. } else {
  647. logName = newLogName;
  648. logValue = line.substring(eqIndex + 3, line.length() - 1);
  649. }
  650. }
  651. }
  652. // Since we "look ahead" before adding, there's one last env var.
  653. if (logName != null) {
  654. logicals.put(logName, logValue);
  655. }
  656. for (Iterator i = logicals.keySet().iterator(); i.hasNext();) {
  657. String logical = (String) i.next();
  658. environment.add(logical + "=" + logicals.get(logical));
  659. }
  660. return environment;
  661. }
  662. /**
  663. * A command launcher for a particular JVM/OS platform. This class is
  664. * a general purpose command launcher which can only launch commands in
  665. * the current working directory.
  666. */
  667. private static class CommandLauncher {
  668. /**
  669. * Launches the given command in a new process.
  670. *
  671. * @param project The project that the command is part of
  672. * @param cmd The command to execute
  673. * @param env The environment for the new process. If null,
  674. * the environment of the current proccess is used.
  675. * @throws IOException if attempting to run a command in a specific directory
  676. */
  677. public Process exec(Project project, String[] cmd, String[] env)
  678. throws IOException {
  679. if (project != null) {
  680. project.log("Execute:CommandLauncher: "
  681. + Commandline.describeCommand(cmd), Project.MSG_DEBUG);
  682. }
  683. return Runtime.getRuntime().exec(cmd, env);
  684. }
  685. /**
  686. * Launches the given command in a new process, in the given working
  687. * directory.
  688. *
  689. * @param project The project that the command is part of
  690. * @param cmd The command to execute
  691. * @param env The environment for the new process. If null,
  692. * the environment of the current proccess is used.
  693. * @param workingDir The directory to start the command in. If null,
  694. * the current directory is used
  695. * @throws IOException if trying to change directory
  696. */
  697. public Process exec(Project project, String[] cmd, String[] env,
  698. File workingDir) throws IOException {
  699. if (workingDir == null) {
  700. return exec(project, cmd, env);
  701. }
  702. throw new IOException("Cannot execute a process in different "
  703. + "directory under this JVM");
  704. }
  705. }
  706. /**
  707. * A command launcher for JDK/JRE 1.1 under Windows. Fixes quoting problems
  708. * in Runtime.exec(). Can only launch commands in the current working
  709. * directory
  710. */
  711. private static class Java11CommandLauncher extends CommandLauncher {
  712. /**
  713. * Launches the given command in a new process. Needs to quote
  714. * arguments
  715. * @param project the ant project
  716. * @param cmd the command line to execute as an array of strings
  717. * @param env the environment to set as an array of strings
  718. * @throws IOException probably forwarded from Runtime#exec
  719. */
  720. public Process exec(Project project, String[] cmd, String[] env)
  721. throws IOException {
  722. // Need to quote arguments with spaces, and to escape
  723. // quote characters
  724. String[] newcmd = new String[cmd.length];
  725. for (int i = 0; i < cmd.length; i++) {
  726. newcmd[i] = Commandline.quoteArgument(cmd[i]);
  727. }
  728. if (project != null) {
  729. project.log("Execute:Java11CommandLauncher: "
  730. + Commandline.describeCommand(newcmd), Project.MSG_DEBUG);
  731. }
  732. return Runtime.getRuntime().exec(newcmd, env);
  733. }
  734. }
  735. /**
  736. * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in
  737. * Runtime.exec() command
  738. */
  739. private static class Java13CommandLauncher extends CommandLauncher {
  740. public Java13CommandLauncher() throws NoSuchMethodException {
  741. // Locate method Runtime.exec(String[] cmdarray,
  742. // String[] envp, File dir)
  743. myExecWithCWD = Runtime.class.getMethod("exec",
  744. new Class[] {String[].class, String[].class, File.class});
  745. }
  746. /**
  747. * Launches the given command in a new process, in the given working
  748. * directory
  749. * @param project the ant project
  750. * @param cmd the command line to execute as an array of strings
  751. * @param env the environment to set as an array of strings
  752. * @param workingDir the working directory where the command should run
  753. * @throws IOException probably forwarded from Runtime#exec
  754. */
  755. public Process exec(Project project, String[] cmd, String[] env,
  756. File workingDir) throws IOException {
  757. try {
  758. if (project != null) {
  759. project.log("Execute:Java13CommandLauncher: "
  760. + Commandline.describeCommand(cmd), Project.MSG_DEBUG);
  761. }
  762. Object[] arguments = {cmd, env, workingDir};
  763. return (Process) myExecWithCWD.invoke(Runtime.getRuntime(),
  764. arguments);
  765. } catch (InvocationTargetException exc) {
  766. Throwable realexc = exc.getTargetException();
  767. if (realexc instanceof ThreadDeath) {
  768. throw (ThreadDeath) realexc;
  769. } else if (realexc instanceof IOException) {
  770. throw (IOException) realexc;
  771. } else {
  772. throw new BuildException("Unable to execute command",
  773. realexc);
  774. }
  775. } catch (Exception exc) {
  776. // IllegalAccess, IllegalArgument, ClassCast
  777. throw new BuildException("Unable to execute command", exc);
  778. }
  779. }
  780. private Method myExecWithCWD;
  781. }
  782. /**
  783. * A command launcher that proxies another command launcher.
  784. *
  785. * Sub-classes override exec(args, env, workdir)
  786. */
  787. private static class CommandLauncherProxy extends CommandLauncher {
  788. CommandLauncherProxy(CommandLauncher launcher) {
  789. myLauncher = launcher;
  790. }
  791. /**
  792. * Launches the given command in a new process. Delegates this
  793. * method to the proxied launcher
  794. * @param project the ant project
  795. * @param cmd the command line to execute as an array of strings
  796. * @param env the environment to set as an array of strings
  797. * @throws IOException forwarded from the exec method of the command launcher
  798. */
  799. public Process exec(Project project, String[] cmd, String[] env)
  800. throws IOException {
  801. return myLauncher.exec(project, cmd, env);
  802. }
  803. private CommandLauncher myLauncher;
  804. }
  805. /**
  806. * A command launcher for OS/2 that uses 'cmd.exe' when launching
  807. * commands in directories other than the current working
  808. * directory.
  809. *
  810. * <p>Unlike Windows NT and friends, OS/2's cd doesn't support the
  811. * /d switch to change drives and directories in one go.</p>
  812. */
  813. private static class OS2CommandLauncher extends CommandLauncherProxy {
  814. OS2CommandLauncher(CommandLauncher launcher) {
  815. super(launcher);
  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 working directory where the command should run
  824. * @throws IOException forwarded from the exec method of the command launcher
  825. */
  826. public Process exec(Project project, String[] cmd, String[] env,
  827. File workingDir) throws IOException {
  828. File commandDir = workingDir;
  829. if (workingDir == null) {
  830. if (project != null) {
  831. commandDir = project.getBaseDir();
  832. } else {
  833. return exec(project, cmd, env);
  834. }
  835. }
  836. // Use cmd.exe to change to the specified drive and
  837. // directory before running the command
  838. final int preCmdLength = 7;
  839. final String cmdDir = commandDir.getAbsolutePath();
  840. String[] newcmd = new String[cmd.length + preCmdLength];
  841. newcmd[0] = "cmd";
  842. newcmd[1] = "/c";
  843. newcmd[2] = cmdDir.substring(0, 2);
  844. newcmd[3] = "&&";
  845. newcmd[4] = "cd";
  846. newcmd[5] = cmdDir.substring(2);
  847. newcmd[6] = "&&";
  848. System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);
  849. return exec(project, newcmd, env);
  850. }
  851. }
  852. /**
  853. * A command launcher for Windows XP/2000/NT that uses 'cmd.exe' when
  854. * launching commands in directories other than the current working
  855. * directory.
  856. */
  857. private static class WinNTCommandLauncher extends CommandLauncherProxy {
  858. WinNTCommandLauncher(CommandLauncher launcher) {
  859. super(launcher);
  860. }
  861. /**
  862. * Launches the given command in a new process, in the given working
  863. * directory.
  864. * @param project the ant project
  865. * @param cmd the command line to execute as an array of strings
  866. * @param env the environment to set as an array of strings
  867. * @param workingDir working directory where the command should run
  868. * @throws IOException forwarded from the exec method of the command launcher
  869. */
  870. public Process exec(Project project, String[] cmd, String[] env,
  871. File workingDir) throws IOException {
  872. File commandDir = workingDir;
  873. if (workingDir == null) {
  874. if (project != null) {
  875. commandDir = project.getBaseDir();
  876. } else {
  877. return exec(project, cmd, env);
  878. }
  879. }
  880. // Use cmd.exe to change to the specified directory before running
  881. // the command
  882. final int preCmdLength = 6;
  883. String[] newcmd = new String[cmd.length + preCmdLength];
  884. newcmd[0] = "cmd";
  885. newcmd[1] = "/c";
  886. newcmd[2] = "cd";
  887. newcmd[3] = "/d";
  888. newcmd[4] = commandDir.getAbsolutePath();
  889. newcmd[5] = "&&";
  890. System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);
  891. return exec(project, newcmd, env);
  892. }
  893. }
  894. /**
  895. * A command launcher for Mac that uses a dodgy mechanism to change
  896. * working directory before launching commands.
  897. */
  898. private static class MacCommandLauncher extends CommandLauncherProxy {
  899. MacCommandLauncher(CommandLauncher launcher) {
  900. super(launcher);
  901. }
  902. /**
  903. * Launches the given command in a new process, in the given working
  904. * directory
  905. * @param project the ant project
  906. * @param cmd the command line to execute as an array of strings
  907. * @param env the environment to set as an array of strings
  908. * @param workingDir working directory where the command should run
  909. * @throws IOException forwarded from the exec method of the command launcher
  910. */
  911. public Process exec(Project project, String[] cmd, String[] env,
  912. File workingDir) throws IOException {
  913. if (workingDir == null) {
  914. return exec(project, cmd, env);
  915. }
  916. System.getProperties().put("user.dir", workingDir.getAbsolutePath());
  917. try {
  918. return exec(project, cmd, env);
  919. } finally {
  920. System.getProperties().put("user.dir", antWorkingDirectory);
  921. }
  922. }
  923. }
  924. /**
  925. * A command launcher that uses an auxiliary script to launch commands
  926. * in directories other than the current working directory.
  927. */
  928. private static class ScriptCommandLauncher extends CommandLauncherProxy {
  929. ScriptCommandLauncher(String script, CommandLauncher launcher) {
  930. super(launcher);
  931. myScript = script;
  932. }
  933. /**
  934. * Launches the given command in a new process, in the given working
  935. * directory
  936. */
  937. public Process exec(Project project, String[] cmd, String[] env,
  938. File workingDir) throws IOException {
  939. if (project == null) {
  940. if (workingDir == null) {
  941. return exec(project, cmd, env);
  942. }
  943. throw new IOException("Cannot locate antRun script: "
  944. + "No project provided");
  945. }
  946. // Locate the auxiliary script
  947. String antHome = project.getProperty("ant.home");
  948. if (antHome == null) {
  949. throw new IOException("Cannot locate antRun script: "
  950. + "Property 'ant.home' not found");
  951. }
  952. String antRun = project.resolveFile(antHome + File.separator + myScript).toString();
  953. // Build the command
  954. File commandDir = workingDir;
  955. if (workingDir == null && project != null) {
  956. commandDir = project.getBaseDir();
  957. }
  958. String[] newcmd = new String[cmd.length + 2];
  959. newcmd[0] = antRun;
  960. newcmd[1] = commandDir.getAbsolutePath();
  961. System.arraycopy(cmd, 0, newcmd, 2, cmd.length);
  962. return exec(project, newcmd, env);
  963. }
  964. private String myScript;
  965. }
  966. /**
  967. * A command launcher that uses an auxiliary perl script to launch commands
  968. * in directories other than the current working directory.
  969. */
  970. private static class PerlScriptCommandLauncher
  971. extends CommandLauncherProxy {
  972. PerlScriptCommandLauncher(String script, CommandLauncher launcher) {
  973. super(launcher);
  974. myScript = script;
  975. }
  976. /**
  977. * Launches the given command in a new process, in the given working
  978. * directory
  979. */
  980. public Process exec(Project project, String[] cmd, String[] env,
  981. File workingDir) throws IOException {
  982. if (project == null) {
  983. if (workingDir == null) {
  984. return exec(project, cmd, env);
  985. }
  986. throw new IOException("Cannot locate antRun script: "
  987. + "No project provided");
  988. }
  989. // Locate the auxiliary script
  990. String antHome = project.getProperty("ant.home");
  991. if (antHome == null) {
  992. throw new IOException("Cannot locate antRun script: "
  993. + "Property 'ant.home' not found");
  994. }
  995. String antRun = project.resolveFile(antHome + File.separator + myScript).toString();
  996. // Build the command
  997. File commandDir = workingDir;
  998. if (workingDir == null && project != null) {
  999. commandDir = project.getBaseDir();
  1000. }
  1001. String[] newcmd = new String[cmd.length + 3];
  1002. newcmd[0] = "perl";
  1003. newcmd[1] = antRun;
  1004. newcmd[2] = commandDir.getAbsolutePath();
  1005. System.arraycopy(cmd, 0, newcmd, 3, cmd.length);
  1006. return exec(project, newcmd, env);
  1007. }
  1008. private String myScript;
  1009. }
  1010. /**
  1011. * A command launcher for VMS that writes the command to a temporary DCL
  1012. * script before launching commands. This is due to limitations of both
  1013. * the DCL interpreter and the Java VM implementation.
  1014. */
  1015. private static class VmsCommandLauncher extends Java13CommandLauncher {
  1016. public VmsCommandLauncher() throws NoSuchMethodException {
  1017. super();
  1018. }
  1019. /**
  1020. * Launches the given command in a new process.
  1021. */
  1022. public Process exec(Project project, String[] cmd, String[] env)
  1023. throws IOException {
  1024. String[] vmsCmd = {createCommandFile(cmd).getPath()};
  1025. return super.exec(project, vmsCmd, env);
  1026. }
  1027. /**
  1028. * Launches the given command in a new process, in the given working
  1029. * directory. Note that under Java 1.3.1, 1.4.0 and 1.4.1 on VMS this
  1030. * method only works if <code>workingDir</code> is null or the logical
  1031. * JAVA$FORK_SUPPORT_CHDIR needs to be set to TRUE.
  1032. */
  1033. public Process exec(Project project, String[] cmd, String[] env,
  1034. File workingDir) throws IOException {
  1035. String[] vmsCmd = {createCommandFile(cmd).getPath()};
  1036. return super.exec(project, vmsCmd, env, workingDir);
  1037. }
  1038. /*
  1039. * Writes the command into a temporary DCL script and returns the
  1040. * corresponding File object. The script will be deleted on exit.
  1041. */
  1042. private File createCommandFile(String[] cmd) throws IOException {
  1043. File script = File.createTempFile("ANT", ".COM");
  1044. script.deleteOnExit();
  1045. PrintWriter out = null;
  1046. try {
  1047. out = new PrintWriter(new FileWriter(script));
  1048. StringBuffer dclCmd = new StringBuffer("$");
  1049. for (int i = 0; i < cmd.length; i++) {
  1050. dclCmd.append(' ').append(cmd[i]);
  1051. }
  1052. out.println(dclCmd.toString());
  1053. } finally {
  1054. if (out != null) {
  1055. out.close();
  1056. }
  1057. }
  1058. return script;
  1059. }
  1060. }
  1061. }