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