0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant.taskdefs;
0020:
0021: import java.io.BufferedReader;
0022: import java.io.ByteArrayOutputStream;
0023: import java.io.File;
0024: import java.io.FileWriter;
0025: import java.io.IOException;
0026: import java.io.OutputStream;
0027: import java.io.PrintWriter;
0028: import java.io.StringReader;
0029: import java.lang.reflect.InvocationTargetException;
0030: import java.lang.reflect.Method;
0031: import java.util.HashMap;
0032: import java.util.Iterator;
0033: import java.util.Vector;
0034:
0035: import org.apache.tools.ant.BuildException;
0036: import org.apache.tools.ant.MagicNames;
0037: import org.apache.tools.ant.Project;
0038: import org.apache.tools.ant.Task;
0039: import org.apache.tools.ant.taskdefs.condition.Os;
0040: import org.apache.tools.ant.types.Commandline;
0041: import org.apache.tools.ant.util.FileUtils;
0042: import org.apache.tools.ant.util.StringUtils;
0043:
0044: /**
0045: * Runs an external program.
0046: *
0047: * @since Ant 1.2
0048: *
0049: */
0050: public class Execute {
0051:
0052: /** Invalid exit code.
0053: * set to {@link Integer#MAX_VALUE}
0054: */
0055: public static final int INVALID = Integer.MAX_VALUE;
0056:
0057: private static final FileUtils FILE_UTILS = FileUtils
0058: .getFileUtils();
0059:
0060: private String[] cmdl = null;
0061: private String[] env = null;
0062: private int exitValue = INVALID;
0063: private ExecuteStreamHandler streamHandler;
0064: private ExecuteWatchdog watchdog;
0065: private File workingDirectory = null;
0066: private Project project = null;
0067: private boolean newEnvironment = false;
0068: //TODO: nothing appears to read this but is set using a public setter.
0069: private boolean spawn = false;
0070:
0071: /** Controls whether the VM is used to launch commands, where possible. */
0072: private boolean useVMLauncher = true;
0073:
0074: private static String antWorkingDirectory = System
0075: .getProperty("user.dir");
0076: private static CommandLauncher vmLauncher = null;
0077: private static CommandLauncher shellLauncher = null;
0078: private static Vector procEnvironment = null;
0079:
0080: /** Used to destroy processes when the VM exits. */
0081: private static ProcessDestroyer processDestroyer = new ProcessDestroyer();
0082:
0083: /** Used for replacing env variables */
0084: private static boolean environmentCaseInSensitive = false;
0085:
0086: /*
0087: * Builds a command launcher for the OS and JVM we are running under.
0088: */
0089: static {
0090: // Try using a JDK 1.3 launcher
0091: try {
0092: if (!Os.isFamily("os/2")) {
0093: vmLauncher = new Java13CommandLauncher();
0094: }
0095: } catch (NoSuchMethodException exc) {
0096: // Ignore and keep trying
0097: }
0098: if (Os.isFamily("mac") && !Os.isFamily("unix")) {
0099: // Mac
0100: shellLauncher = new MacCommandLauncher(
0101: new CommandLauncher());
0102: } else if (Os.isFamily("os/2")) {
0103: // OS/2
0104: shellLauncher = new OS2CommandLauncher(
0105: new CommandLauncher());
0106: } else if (Os.isFamily("windows")) {
0107: environmentCaseInSensitive = true;
0108: CommandLauncher baseLauncher = new CommandLauncher();
0109:
0110: if (!Os.isFamily("win9x")) {
0111: // Windows XP/2000/NT
0112: shellLauncher = new WinNTCommandLauncher(baseLauncher);
0113: } else {
0114: // Windows 98/95 - need to use an auxiliary script
0115: shellLauncher = new ScriptCommandLauncher(
0116: "bin/antRun.bat", baseLauncher);
0117: }
0118: } else if (Os.isFamily("netware")) {
0119:
0120: CommandLauncher baseLauncher = new CommandLauncher();
0121:
0122: shellLauncher = new PerlScriptCommandLauncher(
0123: "bin/antRun.pl", baseLauncher);
0124: } else if (Os.isFamily("openvms")) {
0125: // OpenVMS
0126: try {
0127: shellLauncher = new VmsCommandLauncher();
0128: } catch (NoSuchMethodException exc) {
0129: // Ignore and keep trying
0130: }
0131: } else {
0132: // Generic
0133: shellLauncher = new ScriptCommandLauncher("bin/antRun",
0134: new CommandLauncher());
0135: }
0136: }
0137:
0138: /**
0139: * Set whether or not you want the process to be spawned.
0140: * Default is not spawned.
0141: *
0142: * @param spawn if true you do not want Ant
0143: * to wait for the end of the process.
0144: *
0145: * @since Ant 1.6
0146: */
0147: public void setSpawn(boolean spawn) {
0148: this .spawn = spawn;
0149: }
0150:
0151: /**
0152: * Find the list of environment variables for this process.
0153: *
0154: * @return a vector containing the environment variables.
0155: * The vector elements are strings formatted like variable = value.
0156: */
0157: public static synchronized Vector getProcEnvironment() {
0158: if (procEnvironment != null) {
0159: return procEnvironment;
0160: }
0161: procEnvironment = new Vector();
0162: try {
0163: ByteArrayOutputStream out = new ByteArrayOutputStream();
0164: Execute exe = new Execute(new PumpStreamHandler(out));
0165: exe.setCommandline(getProcEnvCommand());
0166: // Make sure we do not recurse forever
0167: exe.setNewenvironment(true);
0168: int retval = exe.execute();
0169: if (retval != 0) {
0170: // Just try to use what we got
0171: }
0172: BufferedReader in = new BufferedReader(new StringReader(
0173: toString(out)));
0174:
0175: if (Os.isFamily("openvms")) {
0176: procEnvironment = addVMSLogicals(procEnvironment, in);
0177: return procEnvironment;
0178: }
0179: String var = null;
0180: String line, lineSep = StringUtils.LINE_SEP;
0181: while ((line = in.readLine()) != null) {
0182: if (line.indexOf('=') == -1) {
0183: // Chunk part of previous env var (UNIX env vars can
0184: // contain embedded new lines).
0185: if (var == null) {
0186: var = lineSep + line;
0187: } else {
0188: var += lineSep + line;
0189: }
0190: } else {
0191: // New env var...append the previous one if we have it.
0192: if (var != null) {
0193: procEnvironment.addElement(var);
0194: }
0195: var = line;
0196: }
0197: }
0198: // Since we "look ahead" before adding, there's one last env var.
0199: if (var != null) {
0200: procEnvironment.addElement(var);
0201: }
0202: } catch (java.io.IOException exc) {
0203: exc.printStackTrace();
0204: // Just try to see how much we got
0205: }
0206: return procEnvironment;
0207: }
0208:
0209: /**
0210: * This is the operation to get our environment.
0211: * It is a notorious troublespot pre-Java1.5, and should be approached
0212: * with extreme caution.
0213: * @return
0214: */
0215: private static String[] getProcEnvCommand() {
0216: if (Os.isFamily("os/2")) {
0217: // OS/2 - use same mechanism as Windows 2000
0218: return new String[] { "cmd", "/c", "set" };
0219: } else if (Os.isFamily("windows")) {
0220: // Determine if we're running under XP/2000/NT or 98/95
0221: if (Os.isFamily("win9x")) {
0222: // Windows 98/95
0223: return new String[] { "command.com", "/c", "set" };
0224: } else {
0225: // Windows XP/2000/NT/2003
0226: return new String[] { "cmd", "/c", "set" };
0227: }
0228: } else if (Os.isFamily("z/os") || Os.isFamily("unix")) {
0229: // On most systems one could use: /bin/sh -c env
0230:
0231: // Some systems have /bin/env, others /usr/bin/env, just try
0232: String[] cmd = new String[1];
0233: if (new File("/bin/env").canRead()) {
0234: cmd[0] = "/bin/env";
0235: } else if (new File("/usr/bin/env").canRead()) {
0236: cmd[0] = "/usr/bin/env";
0237: } else {
0238: // rely on PATH
0239: cmd[0] = "env";
0240: }
0241: return cmd;
0242: } else if (Os.isFamily("netware") || Os.isFamily("os/400")) {
0243: // rely on PATH
0244: return new String[] { "env" };
0245: } else if (Os.isFamily("openvms")) {
0246: return new String[] { "show", "logical" };
0247: } else {
0248: // MAC OS 9 and previous
0249: //TODO: I have no idea how to get it, someone must fix it
0250: return null;
0251: }
0252: }
0253:
0254: /**
0255: * ByteArrayOutputStream#toString doesn't seem to work reliably on
0256: * OS/390, at least not the way we use it in the execution
0257: * context.
0258: *
0259: * @param bos the output stream that one wants to read.
0260: * @return the output stream as a string, read with
0261: * special encodings in the case of z/os and os/400.
0262: *
0263: * @since Ant 1.5
0264: */
0265: public static String toString(ByteArrayOutputStream bos) {
0266: if (Os.isFamily("z/os")) {
0267: try {
0268: return bos.toString("Cp1047");
0269: } catch (java.io.UnsupportedEncodingException e) {
0270: //noop default encoding used
0271: }
0272: } else if (Os.isFamily("os/400")) {
0273: try {
0274: return bos.toString("Cp500");
0275: } catch (java.io.UnsupportedEncodingException e) {
0276: //noop default encoding used
0277: }
0278: }
0279: return bos.toString();
0280: }
0281:
0282: /**
0283: * Creates a new execute object using <code>PumpStreamHandler</code> for
0284: * stream handling.
0285: */
0286: public Execute() {
0287: this (new PumpStreamHandler(), null);
0288: }
0289:
0290: /**
0291: * Creates a new execute object.
0292: *
0293: * @param streamHandler the stream handler used to handle the input and
0294: * output streams of the subprocess.
0295: */
0296: public Execute(ExecuteStreamHandler streamHandler) {
0297: this (streamHandler, null);
0298: }
0299:
0300: /**
0301: * Creates a new execute object.
0302: *
0303: * @param streamHandler the stream handler used to handle the input and
0304: * output streams of the subprocess.
0305: * @param watchdog a watchdog for the subprocess or <code>null</code> to
0306: * to disable a timeout for the subprocess.
0307: */
0308: public Execute(ExecuteStreamHandler streamHandler,
0309: ExecuteWatchdog watchdog) {
0310: setStreamHandler(streamHandler);
0311: this .watchdog = watchdog;
0312: //By default, use the shell launcher for VMS
0313: //
0314: if (Os.isFamily("openvms")) {
0315: useVMLauncher = false;
0316: }
0317: }
0318:
0319: /**
0320: * Set the stream handler to use.
0321: * @param streamHandler ExecuteStreamHandler.
0322: * @since Ant 1.6
0323: */
0324: public void setStreamHandler(ExecuteStreamHandler streamHandler) {
0325: this .streamHandler = streamHandler;
0326: }
0327:
0328: /**
0329: * Returns the commandline used to create a subprocess.
0330: *
0331: * @return the commandline used to create a subprocess.
0332: */
0333: public String[] getCommandline() {
0334: return cmdl;
0335: }
0336:
0337: /**
0338: * Sets the commandline of the subprocess to launch.
0339: *
0340: * @param commandline the commandline of the subprocess to launch.
0341: */
0342: public void setCommandline(String[] commandline) {
0343: cmdl = commandline;
0344: }
0345:
0346: /**
0347: * Set whether to propagate the default environment or not.
0348: *
0349: * @param newenv whether to propagate the process environment.
0350: */
0351: public void setNewenvironment(boolean newenv) {
0352: newEnvironment = newenv;
0353: }
0354:
0355: /**
0356: * Returns the environment used to create a subprocess.
0357: *
0358: * @return the environment used to create a subprocess.
0359: */
0360: public String[] getEnvironment() {
0361: return (env == null || newEnvironment) ? env
0362: : patchEnvironment();
0363: }
0364:
0365: /**
0366: * Sets the environment variables for the subprocess to launch.
0367: *
0368: * @param env array of Strings, each element of which has
0369: * an environment variable settings in format <em>key=value</em>.
0370: */
0371: public void setEnvironment(String[] env) {
0372: this .env = env;
0373: }
0374:
0375: /**
0376: * Sets the working directory of the process to execute.
0377: *
0378: * <p>This is emulated using the antRun scripts unless the OS is
0379: * Windows NT in which case a cmd.exe is spawned,
0380: * or MRJ and setting user.dir works, or JDK 1.3 and there is
0381: * official support in java.lang.Runtime.
0382: *
0383: * @param wd the working directory of the process.
0384: */
0385: public void setWorkingDirectory(File wd) {
0386: workingDirectory = (wd == null || wd.getAbsolutePath().equals(
0387: antWorkingDirectory)) ? null : wd;
0388: }
0389:
0390: /**
0391: * Return the working directory.
0392: * @return the directory as a File.
0393: * @since Ant 1.7
0394: */
0395: public File getWorkingDirectory() {
0396: return workingDirectory == null ? new File(antWorkingDirectory)
0397: : workingDirectory;
0398: }
0399:
0400: /**
0401: * Set the name of the antRun script using the project's value.
0402: *
0403: * @param project the current project.
0404: *
0405: * @throws BuildException not clear when it is going to throw an exception, but
0406: * it is the method's signature.
0407: */
0408: public void setAntRun(Project project) throws BuildException {
0409: this .project = project;
0410: }
0411:
0412: /**
0413: * Launch this execution through the VM, where possible, rather than through
0414: * the OS's shell. In some cases and operating systems using the shell will
0415: * allow the shell to perform additional processing such as associating an
0416: * executable with a script, etc.
0417: *
0418: * @param useVMLauncher true if exec should launch through the VM,
0419: * false if the shell should be used to launch the
0420: * command.
0421: */
0422: public void setVMLauncher(boolean useVMLauncher) {
0423: this .useVMLauncher = useVMLauncher;
0424: }
0425:
0426: /**
0427: * Creates a process that runs a command.
0428: *
0429: * @param project the Project, only used for logging purposes, may be null.
0430: * @param command the command to run.
0431: * @param env the environment for the command.
0432: * @param dir the working directory for the command.
0433: * @param useVM use the built-in exec command for JDK 1.3 if available.
0434: * @return the process started.
0435: * @throws IOException forwarded from the particular launcher used.
0436: *
0437: * @since Ant 1.5
0438: */
0439: public static Process launch(Project project, String[] command,
0440: String[] env, File dir, boolean useVM) throws IOException {
0441: if (dir != null && !dir.exists()) {
0442: throw new BuildException(dir + " doesn't exist.");
0443: }
0444: CommandLauncher launcher = ((useVM && vmLauncher != null) ? vmLauncher
0445: : shellLauncher);
0446: return launcher.exec(project, command, env, dir);
0447: }
0448:
0449: /**
0450: * Runs a process defined by the command line and returns its exit status.
0451: *
0452: * @return the exit status of the subprocess or <code>INVALID</code>.
0453: * @exception java.io.IOException The exception is thrown, if launching
0454: * of the subprocess failed.
0455: */
0456: public int execute() throws IOException {
0457: if (workingDirectory != null && !workingDirectory.exists()) {
0458: throw new BuildException(workingDirectory
0459: + " doesn't exist.");
0460: }
0461: final Process process = launch(project, getCommandline(),
0462: getEnvironment(), workingDirectory, useVMLauncher);
0463: try {
0464: streamHandler.setProcessInputStream(process
0465: .getOutputStream());
0466: streamHandler.setProcessOutputStream(process
0467: .getInputStream());
0468: streamHandler.setProcessErrorStream(process
0469: .getErrorStream());
0470: } catch (IOException e) {
0471: process.destroy();
0472: throw e;
0473: }
0474: streamHandler.start();
0475:
0476: try {
0477: // add the process to the list of those to destroy if the VM exits
0478: //
0479: processDestroyer.add(process);
0480:
0481: if (watchdog != null) {
0482: watchdog.start(process);
0483: }
0484: waitFor(process);
0485:
0486: if (watchdog != null) {
0487: watchdog.stop();
0488: }
0489: streamHandler.stop();
0490: closeStreams(process);
0491:
0492: if (watchdog != null) {
0493: watchdog.checkException();
0494: }
0495: return getExitValue();
0496: } catch (ThreadDeath t) {
0497: // #31928: forcibly kill it before continuing.
0498: process.destroy();
0499: throw t;
0500: } finally {
0501: // remove the process to the list of those to destroy if
0502: // the VM exits
0503: //
0504: processDestroyer.remove(process);
0505: }
0506: }
0507:
0508: /**
0509: * Starts a process defined by the command line.
0510: * Ant will not wait for this process, nor log its output.
0511: *
0512: * @throws java.io.IOException The exception is thrown, if launching
0513: * of the subprocess failed.
0514: * @since Ant 1.6
0515: */
0516: public void spawn() throws IOException {
0517: if (workingDirectory != null && !workingDirectory.exists()) {
0518: throw new BuildException(workingDirectory
0519: + " doesn't exist.");
0520: }
0521: final Process process = launch(project, getCommandline(),
0522: getEnvironment(), workingDirectory, useVMLauncher);
0523: if (Os.isFamily("windows")) {
0524: try {
0525: Thread.sleep(1000);
0526: } catch (InterruptedException e) {
0527: project.log(
0528: "interruption in the sleep after having spawned a"
0529: + " process", Project.MSG_VERBOSE);
0530: }
0531: }
0532: OutputStream dummyOut = new OutputStream() {
0533: public void write(int b) throws IOException {
0534: }
0535: };
0536:
0537: ExecuteStreamHandler handler = new PumpStreamHandler(dummyOut);
0538: handler.setProcessErrorStream(process.getErrorStream());
0539: handler.setProcessOutputStream(process.getInputStream());
0540: handler.start();
0541: process.getOutputStream().close();
0542:
0543: project.log("spawned process " + process.toString(),
0544: Project.MSG_VERBOSE);
0545: }
0546:
0547: /**
0548: * Wait for a given process.
0549: *
0550: * @param process the process one wants to wait for.
0551: */
0552: protected void waitFor(Process process) {
0553: try {
0554: process.waitFor();
0555: setExitValue(process.exitValue());
0556: } catch (InterruptedException e) {
0557: process.destroy();
0558: }
0559: }
0560:
0561: /**
0562: * Set the exit value.
0563: *
0564: * @param value exit value of the process.
0565: */
0566: protected void setExitValue(int value) {
0567: exitValue = value;
0568: }
0569:
0570: /**
0571: * Query the exit value of the process.
0572: * @return the exit value or Execute.INVALID if no exit value has
0573: * been received.
0574: */
0575: public int getExitValue() {
0576: return exitValue;
0577: }
0578:
0579: /**
0580: * Checks whether <code>exitValue</code> signals a failure on the current
0581: * system (OS specific).
0582: *
0583: * <p><b>Note</b> that this method relies on the conventions of
0584: * the OS, it will return false results if the application you are
0585: * running doesn't follow these conventions. One notable
0586: * exception is the Java VM provided by HP for OpenVMS - it will
0587: * return 0 if successful (like on any other platform), but this
0588: * signals a failure on OpenVMS. So if you execute a new Java VM
0589: * on OpenVMS, you cannot trust this method.</p>
0590: *
0591: * @param exitValue the exit value (return code) to be checked.
0592: * @return <code>true</code> if <code>exitValue</code> signals a failure.
0593: */
0594: public static boolean isFailure(int exitValue) {
0595: //on openvms even exit value signals failure;
0596: // for other platforms nonzero exit value signals failure
0597: return Os.isFamily("openvms") ? (exitValue % 2 == 0)
0598: : (exitValue != 0);
0599: }
0600:
0601: /**
0602: * Did this execute return in a failure.
0603: * @see #isFailure(int)
0604: * @return true if and only if the exit code is interpreted as a failure
0605: * @since Ant1.7
0606: */
0607: public boolean isFailure() {
0608: return isFailure(getExitValue());
0609: }
0610:
0611: /**
0612: * Test for an untimely death of the process.
0613: * @return true if a watchdog had to kill the process.
0614: * @since Ant 1.5
0615: */
0616: public boolean killedProcess() {
0617: return watchdog != null && watchdog.killedProcess();
0618: }
0619:
0620: /**
0621: * Patch the current environment with the new values from the user.
0622: * @return the patched environment.
0623: */
0624: private String[] patchEnvironment() {
0625: // On OpenVMS Runtime#exec() doesn't support the environment array,
0626: // so we only return the new values which then will be set in
0627: // the generated DCL script, inheriting the parent process environment
0628: if (Os.isFamily("openvms")) {
0629: return env;
0630: }
0631: Vector osEnv = (Vector) getProcEnvironment().clone();
0632: for (int i = 0; i < env.length; i++) {
0633: String keyValue = env[i];
0634: // Get key including "="
0635: String key = keyValue.substring(0,
0636: keyValue.indexOf('=') + 1);
0637: if (environmentCaseInSensitive) {
0638: // Nb: using default locale as key is a env name
0639: key = key.toLowerCase();
0640: }
0641: int size = osEnv.size();
0642: // Find the key in the current enviroment copy
0643: // and remove it.
0644: for (int j = 0; j < size; j++) {
0645: String osEnvItem = (String) osEnv.elementAt(j);
0646: String convertedItem = environmentCaseInSensitive ? osEnvItem
0647: .toLowerCase()
0648: : osEnvItem;
0649: if (convertedItem.startsWith(key)) {
0650: osEnv.removeElementAt(j);
0651: if (environmentCaseInSensitive) {
0652: // Use the original casiness of the key
0653: keyValue = osEnvItem.substring(0, key.length())
0654: + keyValue.substring(key.length());
0655: }
0656: break;
0657: }
0658: }
0659: // Add the key to the enviromnent copy
0660: osEnv.addElement(keyValue);
0661: }
0662: return (String[]) (osEnv.toArray(new String[osEnv.size()]));
0663: }
0664:
0665: /**
0666: * A utility method that runs an external command. Writes the output and
0667: * error streams of the command to the project log.
0668: *
0669: * @param task The task that the command is part of. Used for logging
0670: * @param cmdline The command to execute.
0671: *
0672: * @throws BuildException if the command does not exit successfully.
0673: */
0674: public static void runCommand(Task task, String[] cmdline)
0675: throws BuildException {
0676: try {
0677: task.log(Commandline.describeCommand(cmdline),
0678: Project.MSG_VERBOSE);
0679: Execute exe = new Execute(new LogStreamHandler(task,
0680: Project.MSG_INFO, Project.MSG_ERR));
0681: exe.setAntRun(task.getProject());
0682: exe.setCommandline(cmdline);
0683: int retval = exe.execute();
0684: if (isFailure(retval)) {
0685: throw new BuildException(cmdline[0]
0686: + " failed with return code " + retval, task
0687: .getLocation());
0688: }
0689: } catch (java.io.IOException exc) {
0690: throw new BuildException("Could not launch " + cmdline[0]
0691: + ": " + exc, task.getLocation());
0692: }
0693: }
0694:
0695: /**
0696: * Close the streams belonging to the given Process.
0697: * @param process the <code>Process</code>.
0698: */
0699: public static void closeStreams(Process process) {
0700: FileUtils.close(process.getInputStream());
0701: FileUtils.close(process.getOutputStream());
0702: FileUtils.close(process.getErrorStream());
0703: }
0704:
0705: /**
0706: * This method is VMS specific and used by getProcEnvironment().
0707: *
0708: * Parses VMS logicals from <code>in</code> and adds them to
0709: * <code>environment</code>. <code>in</code> is expected to be the
0710: * output of "SHOW LOGICAL". The method takes care of parsing the output
0711: * correctly as well as making sure that a logical defined in multiple
0712: * tables only gets added from the highest order table. Logicals with
0713: * multiple equivalence names are mapped to a variable with multiple
0714: * values separated by a comma (,).
0715: */
0716: private static Vector addVMSLogicals(Vector environment,
0717: BufferedReader in) throws IOException {
0718: HashMap logicals = new HashMap();
0719: String logName = null, logValue = null, newLogName;
0720: String line = null;
0721: while ((line = in.readLine()) != null) {
0722: // parse the VMS logicals into required format ("VAR=VAL[,VAL2]")
0723: if (line.startsWith("\t=")) {
0724: // further equivalence name of previous logical
0725: if (logName != null) {
0726: logValue += ","
0727: + line.substring(4, line.length() - 1);
0728: }
0729: } else if (line.startsWith(" \"")) {
0730: // new logical?
0731: if (logName != null) {
0732: logicals.put(logName, logValue);
0733: }
0734: int eqIndex = line.indexOf('=');
0735: newLogName = line.substring(3, eqIndex - 2);
0736: if (logicals.containsKey(newLogName)) {
0737: // already got this logical from a higher order table
0738: logName = null;
0739: } else {
0740: logName = newLogName;
0741: logValue = line.substring(eqIndex + 3, line
0742: .length() - 1);
0743: }
0744: }
0745: }
0746: // Since we "look ahead" before adding, there's one last env var.
0747: if (logName != null) {
0748: logicals.put(logName, logValue);
0749: }
0750: for (Iterator i = logicals.keySet().iterator(); i.hasNext();) {
0751: String logical = (String) i.next();
0752: environment.add(logical + "=" + logicals.get(logical));
0753: }
0754: return environment;
0755: }
0756:
0757: /**
0758: * A command launcher for a particular JVM/OS platform. This class is
0759: * a general purpose command launcher which can only launch commands in
0760: * the current working directory.
0761: */
0762: private static class CommandLauncher {
0763: /**
0764: * Launches the given command in a new process.
0765: *
0766: * @param project The project that the command is part of.
0767: * @param cmd The command to execute.
0768: * @param env The environment for the new process. If null,
0769: * the environment of the current process is used.
0770: * @return the created Process.
0771: * @throws IOException if attempting to run a command in a
0772: * specific directory.
0773: */
0774: public Process exec(Project project, String[] cmd, String[] env)
0775: throws IOException {
0776: if (project != null) {
0777: project.log("Execute:CommandLauncher: "
0778: + Commandline.describeCommand(cmd),
0779: Project.MSG_DEBUG);
0780: }
0781: return Runtime.getRuntime().exec(cmd, env);
0782: }
0783:
0784: /**
0785: * Launches the given command in a new process, in the given working
0786: * directory.
0787: *
0788: * @param project The project that the command is part of.
0789: * @param cmd The command to execute.
0790: * @param env The environment for the new process. If null,
0791: * the environment of the current process is used.
0792: * @param workingDir The directory to start the command in. If null,
0793: * the current directory is used.
0794: * @return the created Process.
0795: * @throws IOException if trying to change directory.
0796: */
0797: public Process exec(Project project, String[] cmd,
0798: String[] env, File workingDir) throws IOException {
0799: if (workingDir == null) {
0800: return exec(project, cmd, env);
0801: }
0802: throw new IOException(
0803: "Cannot execute a process in different "
0804: + "directory under this JVM");
0805: }
0806: }
0807:
0808: /**
0809: * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in
0810: * Runtime.exec() command.
0811: */
0812: private static class Java13CommandLauncher extends CommandLauncher {
0813: private Method myExecWithCWD;
0814:
0815: public Java13CommandLauncher() throws NoSuchMethodException {
0816: // Locate method Runtime.exec(String[] cmdarray,
0817: // String[] envp, File dir)
0818: myExecWithCWD = Runtime.class.getMethod("exec",
0819: new Class[] { String[].class, String[].class,
0820: File.class });
0821: }
0822:
0823: /**
0824: * Launches the given command in a new process, in the given working
0825: * directory.
0826: * @param project the Ant project.
0827: * @param cmd the command line to execute as an array of strings.
0828: * @param env the environment to set as an array of strings.
0829: * @param workingDir the working directory where the command
0830: * should run.
0831: * @return the created Process.
0832: * @throws IOException probably forwarded from Runtime#exec.
0833: */
0834: public Process exec(Project project, String[] cmd,
0835: String[] env, File workingDir) throws IOException {
0836: try {
0837: if (project != null) {
0838: project.log("Execute:Java13CommandLauncher: "
0839: + Commandline.describeCommand(cmd),
0840: Project.MSG_DEBUG);
0841: }
0842: return (Process) myExecWithCWD.invoke(Runtime
0843: .getRuntime(),
0844: /* the arguments: */new Object[] { cmd, env,
0845: workingDir });
0846: } catch (InvocationTargetException exc) {
0847: Throwable realexc = exc.getTargetException();
0848: if (realexc instanceof ThreadDeath) {
0849: throw (ThreadDeath) realexc;
0850: } else if (realexc instanceof IOException) {
0851: throw (IOException) realexc;
0852: } else {
0853: throw new BuildException(
0854: "Unable to execute command", realexc);
0855: }
0856: } catch (Exception exc) {
0857: // IllegalAccess, IllegalArgument, ClassCast
0858: throw new BuildException("Unable to execute command",
0859: exc);
0860: }
0861: }
0862: }
0863:
0864: /**
0865: * A command launcher that proxies another command launcher.
0866: *
0867: * Sub-classes override exec(args, env, workdir).
0868: */
0869: private static class CommandLauncherProxy extends CommandLauncher {
0870: private CommandLauncher myLauncher;
0871:
0872: CommandLauncherProxy(CommandLauncher launcher) {
0873: myLauncher = launcher;
0874: }
0875:
0876: /**
0877: * Launches the given command in a new process. Delegates this
0878: * method to the proxied launcher.
0879: * @param project the Ant project.
0880: * @param cmd the command line to execute as an array of strings.
0881: * @param env the environment to set as an array of strings.
0882: * @return the created Process.
0883: * @throws IOException forwarded from the exec method of the
0884: * command launcher.
0885: */
0886: public Process exec(Project project, String[] cmd, String[] env)
0887: throws IOException {
0888: return myLauncher.exec(project, cmd, env);
0889: }
0890: }
0891:
0892: /**
0893: * A command launcher for OS/2 that uses 'cmd.exe' when launching
0894: * commands in directories other than the current working
0895: * directory.
0896: *
0897: * <p>Unlike Windows NT and friends, OS/2's cd doesn't support the
0898: * /d switch to change drives and directories in one go.</p>
0899: */
0900: private static class OS2CommandLauncher extends
0901: CommandLauncherProxy {
0902: OS2CommandLauncher(CommandLauncher launcher) {
0903: super (launcher);
0904: }
0905:
0906: /**
0907: * Launches the given command in a new process, in the given working
0908: * directory.
0909: * @param project the Ant project.
0910: * @param cmd the command line to execute as an array of strings.
0911: * @param env the environment to set as an array of strings.
0912: * @param workingDir working directory where the command should run.
0913: * @return the created Process.
0914: * @throws IOException forwarded from the exec method of the
0915: * command launcher.
0916: */
0917: public Process exec(Project project, String[] cmd,
0918: String[] env, File workingDir) throws IOException {
0919: File commandDir = workingDir;
0920: if (workingDir == null) {
0921: if (project != null) {
0922: commandDir = project.getBaseDir();
0923: } else {
0924: return exec(project, cmd, env);
0925: }
0926: }
0927: // Use cmd.exe to change to the specified drive and
0928: // directory before running the command
0929: final int preCmdLength = 7;
0930: final String cmdDir = commandDir.getAbsolutePath();
0931: String[] newcmd = new String[cmd.length + preCmdLength];
0932: newcmd[0] = "cmd";
0933: newcmd[1] = "/c";
0934: newcmd[2] = cmdDir.substring(0, 2);
0935: newcmd[3] = "&&";
0936: newcmd[4] = "cd";
0937: newcmd[5] = cmdDir.substring(2);
0938: newcmd[6] = "&&";
0939: System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);
0940:
0941: return exec(project, newcmd, env);
0942: }
0943: }
0944:
0945: /**
0946: * A command launcher for Windows XP/2000/NT that uses 'cmd.exe' when
0947: * launching commands in directories other than the current working
0948: * directory.
0949: */
0950: private static class WinNTCommandLauncher extends
0951: CommandLauncherProxy {
0952: WinNTCommandLauncher(CommandLauncher launcher) {
0953: super (launcher);
0954: }
0955:
0956: /**
0957: * Launches the given command in a new process, in the given working
0958: * directory.
0959: * @param project the Ant project.
0960: * @param cmd the command line to execute as an array of strings.
0961: * @param env the environment to set as an array of strings.
0962: * @param workingDir working directory where the command should run.
0963: * @return the created Process.
0964: * @throws IOException forwarded from the exec method of the
0965: * command launcher.
0966: */
0967: public Process exec(Project project, String[] cmd,
0968: String[] env, File workingDir) throws IOException {
0969: File commandDir = workingDir;
0970: if (workingDir == null) {
0971: if (project != null) {
0972: commandDir = project.getBaseDir();
0973: } else {
0974: return exec(project, cmd, env);
0975: }
0976: }
0977: // Use cmd.exe to change to the specified directory before running
0978: // the command
0979: final int preCmdLength = 6;
0980: String[] newcmd = new String[cmd.length + preCmdLength];
0981: newcmd[0] = "cmd";
0982: newcmd[1] = "/c";
0983: newcmd[2] = "cd";
0984: newcmd[3] = "/d";
0985: newcmd[4] = commandDir.getAbsolutePath();
0986: newcmd[5] = "&&";
0987: System.arraycopy(cmd, 0, newcmd, preCmdLength, cmd.length);
0988:
0989: return exec(project, newcmd, env);
0990: }
0991: }
0992:
0993: /**
0994: * A command launcher for Mac that uses a dodgy mechanism to change
0995: * working directory before launching commands.
0996: */
0997: private static class MacCommandLauncher extends
0998: CommandLauncherProxy {
0999: MacCommandLauncher(CommandLauncher launcher) {
1000: super (launcher);
1001: }
1002:
1003: /**
1004: * Launches the given command in a new process, in the given working
1005: * directory.
1006: * @param project the Ant project.
1007: * @param cmd the command line to execute as an array of strings.
1008: * @param env the environment to set as an array of strings.
1009: * @param workingDir working directory where the command should run.
1010: * @return the created Process.
1011: * @throws IOException forwarded from the exec method of the
1012: * command launcher.
1013: */
1014: public Process exec(Project project, String[] cmd,
1015: String[] env, File workingDir) throws IOException {
1016: if (workingDir == null) {
1017: return exec(project, cmd, env);
1018: }
1019: System.getProperties().put("user.dir",
1020: workingDir.getAbsolutePath());
1021: try {
1022: return exec(project, cmd, env);
1023: } finally {
1024: System.getProperties().put("user.dir",
1025: antWorkingDirectory);
1026: }
1027: }
1028: }
1029:
1030: /**
1031: * A command launcher that uses an auxiliary script to launch commands
1032: * in directories other than the current working directory.
1033: */
1034: private static class ScriptCommandLauncher extends
1035: CommandLauncherProxy {
1036: ScriptCommandLauncher(String script, CommandLauncher launcher) {
1037: super (launcher);
1038: myScript = script;
1039: }
1040:
1041: /**
1042: * Launches the given command in a new process, in the given working
1043: * directory.
1044: * @param project the Ant project.
1045: * @param cmd the command line to execute as an array of strings.
1046: * @param env the environment to set as an array of strings.
1047: * @param workingDir working directory where the command should run.
1048: * @return the created Process.
1049: * @throws IOException forwarded from the exec method of the
1050: * command launcher.
1051: */
1052: public Process exec(Project project, String[] cmd,
1053: String[] env, File workingDir) throws IOException {
1054: if (project == null) {
1055: if (workingDir == null) {
1056: return exec(project, cmd, env);
1057: }
1058: throw new IOException("Cannot locate antRun script: "
1059: + "No project provided");
1060: }
1061: // Locate the auxiliary script
1062: String antHome = project.getProperty(MagicNames.ANT_HOME);
1063: if (antHome == null) {
1064: throw new IOException("Cannot locate antRun script: "
1065: + "Property '" + MagicNames.ANT_HOME
1066: + "' not found");
1067: }
1068: String antRun = FILE_UTILS.resolveFile(
1069: project.getBaseDir(),
1070: antHome + File.separator + myScript).toString();
1071:
1072: // Build the command
1073: File commandDir = workingDir;
1074: if (workingDir == null && project != null) {
1075: commandDir = project.getBaseDir();
1076: }
1077: String[] newcmd = new String[cmd.length + 2];
1078: newcmd[0] = antRun;
1079: newcmd[1] = commandDir.getAbsolutePath();
1080: System.arraycopy(cmd, 0, newcmd, 2, cmd.length);
1081:
1082: return exec(project, newcmd, env);
1083: }
1084:
1085: private String myScript;
1086: }
1087:
1088: /**
1089: * A command launcher that uses an auxiliary perl script to launch commands
1090: * in directories other than the current working directory.
1091: */
1092: private static class PerlScriptCommandLauncher extends
1093: CommandLauncherProxy {
1094: private String myScript;
1095:
1096: PerlScriptCommandLauncher(String script,
1097: CommandLauncher launcher) {
1098: super (launcher);
1099: myScript = script;
1100: }
1101:
1102: /**
1103: * Launches the given command in a new process, in the given working
1104: * directory.
1105: * @param project the Ant project.
1106: * @param cmd the command line to execute as an array of strings.
1107: * @param env the environment to set as an array of strings.
1108: * @param workingDir working directory where the command should run.
1109: * @return the created Process.
1110: * @throws IOException forwarded from the exec method of the
1111: * command launcher.
1112: */
1113: public Process exec(Project project, String[] cmd,
1114: String[] env, File workingDir) throws IOException {
1115: if (project == null) {
1116: if (workingDir == null) {
1117: return exec(project, cmd, env);
1118: }
1119: throw new IOException("Cannot locate antRun script: "
1120: + "No project provided");
1121: }
1122: // Locate the auxiliary script
1123: String antHome = project.getProperty(MagicNames.ANT_HOME);
1124: if (antHome == null) {
1125: throw new IOException("Cannot locate antRun script: "
1126: + "Property '" + MagicNames.ANT_HOME
1127: + "' not found");
1128: }
1129: String antRun = FILE_UTILS.resolveFile(
1130: project.getBaseDir(),
1131: antHome + File.separator + myScript).toString();
1132:
1133: // Build the command
1134: File commandDir = workingDir;
1135: if (workingDir == null && project != null) {
1136: commandDir = project.getBaseDir();
1137: }
1138: String[] newcmd = new String[cmd.length + 3];
1139: newcmd[0] = "perl";
1140: newcmd[1] = antRun;
1141: newcmd[2] = commandDir.getAbsolutePath();
1142: System.arraycopy(cmd, 0, newcmd, 3, cmd.length);
1143:
1144: return exec(project, newcmd, env);
1145: }
1146: }
1147:
1148: /**
1149: * A command launcher for VMS that writes the command to a temporary DCL
1150: * script before launching commands. This is due to limitations of both
1151: * the DCL interpreter and the Java VM implementation.
1152: */
1153: private static class VmsCommandLauncher extends
1154: Java13CommandLauncher {
1155:
1156: public VmsCommandLauncher() throws NoSuchMethodException {
1157: super ();
1158: }
1159:
1160: /**
1161: * Launches the given command in a new process.
1162: * @param project the Ant project.
1163: * @param cmd the command line to execute as an array of strings.
1164: * @param env the environment to set as an array of strings.
1165: * @return the created Process.
1166: * @throws IOException forwarded from the exec method of the
1167: * command launcher.
1168: */
1169: public Process exec(Project project, String[] cmd, String[] env)
1170: throws IOException {
1171: File cmdFile = createCommandFile(cmd, env);
1172: Process p = super .exec(project, new String[] { cmdFile
1173: .getPath() }, env);
1174: deleteAfter(cmdFile, p);
1175: return p;
1176: }
1177:
1178: /**
1179: * Launches the given command in a new process, in the given working
1180: * directory. Note that under Java 1.3.1, 1.4.0 and 1.4.1 on VMS this
1181: * method only works if <code>workingDir</code> is null or the logical
1182: * JAVA$FORK_SUPPORT_CHDIR needs to be set to TRUE.
1183: * @param project the Ant project.
1184: * @param cmd the command line to execute as an array of strings.
1185: * @param env the environment to set as an array of strings.
1186: * @param workingDir working directory where the command should run.
1187: * @return the created Process.
1188: * @throws IOException forwarded from the exec method of the
1189: * command launcher.
1190: */
1191: public Process exec(Project project, String[] cmd,
1192: String[] env, File workingDir) throws IOException {
1193: File cmdFile = createCommandFile(cmd, env);
1194: Process p = super .exec(project, new String[] { cmdFile
1195: .getPath() }, env, workingDir);
1196: deleteAfter(cmdFile, p);
1197: return p;
1198: }
1199:
1200: /*
1201: * Writes the command into a temporary DCL script and returns the
1202: * corresponding File object. The script will be deleted on exit.
1203: * @param cmd the command line to execute as an array of strings.
1204: * @param env the environment to set as an array of strings.
1205: * @return the command File.
1206: * @throws IOException if errors are encountered creating the file.
1207: */
1208: private File createCommandFile(String[] cmd, String[] env)
1209: throws IOException {
1210: File script = FILE_UTILS
1211: .createTempFile("ANT", ".COM", null);
1212: script.deleteOnExit();
1213: PrintWriter out = null;
1214: try {
1215: out = new PrintWriter(new FileWriter(script));
1216:
1217: // add the environment as logicals to the DCL script
1218: if (env != null) {
1219: int eqIndex;
1220: for (int i = 0; i < env.length; i++) {
1221: eqIndex = env[i].indexOf('=');
1222: if (eqIndex != -1) {
1223: out.print("$ DEFINE/NOLOG ");
1224: out.print(env[i].substring(0, eqIndex));
1225: out.print(" \"");
1226: out.print(env[i].substring(eqIndex + 1));
1227: out.println('\"');
1228: }
1229: }
1230: }
1231: out.print("$ " + cmd[0]);
1232: for (int i = 1; i < cmd.length; i++) {
1233: out.println(" -");
1234: out.print(cmd[i]);
1235: }
1236: } finally {
1237: if (out != null) {
1238: out.close();
1239: }
1240: }
1241: return script;
1242: }
1243:
1244: private void deleteAfter(final File f, final Process p) {
1245: new Thread() {
1246: public void run() {
1247: try {
1248: p.waitFor();
1249: } catch (InterruptedException e) {
1250: //ignore
1251: }
1252: FileUtils.delete(f);
1253: }
1254: }.start();
1255: }
1256: }
1257: }
|