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 44 kB

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