| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ant.core; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.net.URL; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.ant.internal.core.AntClassLoader; |
| import org.eclipse.ant.internal.core.IAntCoreConstants; |
| import org.eclipse.ant.internal.core.InternalCoreAntMessages; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPlatformRunnable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| |
| /** |
| * Entry point for running Ant builds inside Eclipse. |
| */ |
| public class AntRunner implements IPlatformRunnable { |
| |
| private static boolean buildRunning= false; |
| protected String buildFileLocation = IAntCoreConstants.DEFAULT_BUILD_FILENAME; |
| protected List buildListeners; |
| protected String[] targets; |
| protected Map userProperties; |
| protected int messageOutputLevel = 2; // Project.MSG_INFO |
| protected String buildLoggerClassName; |
| protected String inputHandlerClassName; |
| protected String[] arguments; |
| protected String[] propertyFiles; |
| protected URL[] customClasspath; |
| protected String antHome; |
| |
| /** |
| * Sets the build file location on the file system. |
| * |
| * @param buildFileLocation the file system location of the build file |
| */ |
| public void setBuildFileLocation(String buildFileLocation) { |
| if (buildFileLocation == null) { |
| this.buildFileLocation = IAntCoreConstants.DEFAULT_BUILD_FILENAME; |
| } else { |
| this.buildFileLocation = buildFileLocation; |
| } |
| } |
| |
| /** |
| * Set the message output level. |
| * <p> |
| * Valid values are: |
| * <ul> |
| * <li><code>org.apache.tools.ant.Project.ERR</code>, |
| * <li><code>org.apache.tools.ant.Project.WARN</code>, |
| * <li><code>org.apache.tools.ant.Project.INFO</code>, |
| * <li><code>org.apache.tools.ant.Project.VERBOSE</code> or |
| * <li><code>org.apache.tools.ant.Project.DEBUG</code> |
| * </ul> |
| * |
| * @param level the message output level |
| */ |
| public void setMessageOutputLevel(int level) { |
| messageOutputLevel = level; |
| } |
| |
| /** |
| * Sets the arguments to be passed to the build (e.g. -Dos=win32 -Dws=win32 |
| * - verbose). |
| * |
| * @param arguments the arguments to be passed to the build |
| */ |
| public void setArguments(String arguments) { |
| this.arguments = getArray(arguments); |
| } |
| |
| /* |
| * Helper method to ensure an array is converted into an ArrayList. |
| */ |
| private String[] getArray(String args) { |
| StringBuffer sb = new StringBuffer(); |
| boolean waitingForQuote = false; |
| ArrayList result = new ArrayList(); |
| for (StringTokenizer tokens = new StringTokenizer(args, ", \"", true); tokens.hasMoreTokens();) { //$NON-NLS-1$ |
| String token = tokens.nextToken(); |
| if (waitingForQuote) { |
| if (token.equals("\"")) { //$NON-NLS-1$ |
| result.add(sb.toString()); |
| sb.setLength(0); |
| waitingForQuote = false; |
| } else { |
| sb.append(token); |
| } |
| } else { |
| if (token.equals("\"")) { //$NON-NLS-1$ |
| // test if we have something like -Dproperty="value" |
| if (result.size() > 0) { |
| int index = result.size() - 1; |
| String last = (String) result.get(index); |
| if (last.charAt(last.length() - 1) == '=') { |
| result.remove(index); |
| sb.append(last); |
| } |
| } |
| waitingForQuote = true; |
| } else { |
| if (!(token.equals(",") || token.equals(" "))) //$NON-NLS-1$ //$NON-NLS-2$ |
| result.add(token); |
| } |
| } |
| } |
| return (String[]) result.toArray(new String[result.size()]); |
| } |
| |
| /** |
| * Sets the arguments to be passed to the build (e.g. -Dos=win32 -Dws=win32 -verbose). |
| * |
| * @param arguments the arguments to be passed to the build |
| * @since 2.1 |
| */ |
| public void setArguments(String[] arguments) { |
| this.arguments = arguments; |
| } |
| |
| /** |
| * Sets the targets and execution order. |
| * |
| * @param executionTargets which targets should be run and in which order |
| */ |
| public void setExecutionTargets(String[] executionTargets) { |
| this.targets = executionTargets; |
| } |
| |
| /** |
| * Adds a build listener. The parameter <code>className</code> |
| * is the class name of a <code>org.apache.tools.ant.BuildListener</code> |
| * implementation. The class will be instantiated at runtime and the |
| * listener will be called on build events |
| * (<code>org.apache.tools.ant.BuildEvent</code>). |
| * |
| * @param className a build listener class name |
| */ |
| public void addBuildListener(String className) { |
| if (className == null) { |
| return; |
| } |
| if (buildListeners == null) { |
| buildListeners = new ArrayList(5); |
| } |
| buildListeners.add(className); |
| } |
| |
| /** |
| * Sets the build logger. The parameter <code>className</code> |
| * is the class name of a <code>org.apache.tools.ant.BuildLogger</code> |
| * implementation. The class will be instantiated at runtime and the |
| * logger will be called on build events |
| * (<code>org.apache.tools.ant.BuildEvent</code>). |
| * Only one build logger is permitted for any build. |
| * |
| * |
| * @param className a build logger class name |
| */ |
| public void addBuildLogger(String className) { |
| buildLoggerClassName = className; |
| } |
| |
| /** |
| * Adds user-defined properties. Keys and values must be String objects. |
| * |
| * @param properties a Map of user-defined properties |
| */ |
| public void addUserProperties(Map properties) { |
| if (userProperties == null) { |
| userProperties= new HashMap(properties); |
| } else { |
| userProperties.putAll(properties); |
| } |
| } |
| |
| /** |
| * Returns the buildfile target information. |
| * |
| * @return an array containing the target information |
| * |
| * @see TargetInfo |
| * @since 2.1 |
| * @throws CoreException Thrown if problem is encountered determining the targets |
| */ |
| public synchronized TargetInfo[] getAvailableTargets() throws CoreException { |
| Class classInternalAntRunner= null; |
| Object runner= null; |
| ClassLoader originalClassLoader= Thread.currentThread().getContextClassLoader(); |
| try { |
| classInternalAntRunner = getInternalAntRunner(); |
| runner = classInternalAntRunner.newInstance(); |
| |
| basicConfigure(classInternalAntRunner, runner); |
| |
| // get the info for each targets |
| Method getTargets = classInternalAntRunner.getMethod("getTargets", null); //$NON-NLS-1$ |
| Object results = getTargets.invoke(runner, null); |
| // get the default target |
| Method getDefault= classInternalAntRunner.getMethod("getDefaultTarget", null); //$NON-NLS-1$ |
| String defaultName= (String)getDefault.invoke(runner, null); |
| // collect the info into target objects |
| List infos = (List) results; |
| |
| ProjectInfo project= new ProjectInfo((String)infos.remove(0), (String)infos.remove(0)); |
| int i= 0; |
| Iterator iter= infos.iterator(); |
| TargetInfo[] targetInfo= new TargetInfo[infos.size()]; |
| List info; |
| while (iter.hasNext()) { |
| info= (List)iter.next(); |
| targetInfo[i++] = new TargetInfo(project, (String)info.get(0), (String)info.get(1), (String[])info.get(2), info.get(0).equals(defaultName)); |
| } |
| return targetInfo; |
| } catch (NoClassDefFoundError e) { |
| problemLoadingClass(e); |
| //not possible to reach this line |
| return new TargetInfo[0]; |
| } catch (ClassNotFoundException e) { |
| problemLoadingClass(e); |
| //not possible to reach this line |
| return new TargetInfo[0]; |
| } catch (InvocationTargetException e) { |
| handleInvocationTargetException(runner, classInternalAntRunner, e); |
| //not possible to reach this line |
| return new TargetInfo[0]; |
| } catch (Exception e) { |
| String message = (e.getMessage() == null) ? InternalCoreAntMessages.getString("AntRunner.Build_Failed._3") : e.getMessage(); //$NON-NLS-1$ |
| throw new CoreException(new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, message, e)); |
| } finally { |
| Thread.currentThread().setContextClassLoader(originalClassLoader); |
| } |
| } |
| |
| private void basicConfigure(Class classInternalAntRunner, Object runner) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { |
| Method setBuildFileLocation = classInternalAntRunner.getMethod("setBuildFileLocation", new Class[] { String.class }); //$NON-NLS-1$ |
| setBuildFileLocation.invoke(runner, new Object[] { buildFileLocation }); |
| |
| if (antHome != null) { |
| Method setAntHome = classInternalAntRunner.getMethod("setAntHome", new Class[] { String.class }); //$NON-NLS-1$ |
| setAntHome.invoke(runner, new Object[] { antHome }); |
| } |
| |
| setProperties(runner, classInternalAntRunner); |
| |
| if (arguments != null && arguments.length > 0) { |
| Method setArguments = classInternalAntRunner.getMethod("setArguments", new Class[] { String[].class }); //$NON-NLS-1$ |
| setArguments.invoke(runner, new Object[] { arguments }); |
| } |
| } |
| |
| /** |
| * Runs the build file. If a progress monitor is specified it will be |
| * available during the script execution as a reference in the Ant Project |
| * (<code>org.apache.tools.ant.Project.getReferences()</code>). A long- |
| * running task could, for example, get the monitor during its execution and |
| * check for cancellation. The key value to retrieve the progress monitor |
| * instance is <code>AntCorePlugin.ECLIPSE_PROGRESS_MONITOR</code>. |
| * |
| * Only one build can occur at any given time. |
| * |
| * Sets the current threads context class loader to the AntClassLoader |
| * for the duration of the build. |
| * |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| * @throws CoreException Thrown if a build is already occuring or if an exception occurs during the build |
| */ |
| public void run(IProgressMonitor monitor) throws CoreException { |
| if (buildRunning) { |
| IStatus status= new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, MessageFormat.format(InternalCoreAntMessages.getString("AntRunner.Already_in_progess"), new String[]{buildFileLocation}), null); //$NON-NLS-1$ |
| throw new CoreException(status); |
| } |
| buildRunning= true; |
| Object runner= null; |
| Class classInternalAntRunner= null; |
| ClassLoader originalClassLoader= Thread.currentThread().getContextClassLoader(); |
| try { |
| classInternalAntRunner = getInternalAntRunner(); |
| runner = classInternalAntRunner.newInstance(); |
| // set build file |
| Method setBuildFileLocation = classInternalAntRunner.getMethod("setBuildFileLocation", new Class[] { String.class }); //$NON-NLS-1$ |
| setBuildFileLocation.invoke(runner, new Object[] { buildFileLocation }); |
| |
| //set the custom classpath |
| if (customClasspath != null) { |
| Method setCustomClasspath = classInternalAntRunner.getMethod("setCustomClasspath", new Class[] { URL[].class }); //$NON-NLS-1$ |
| setCustomClasspath.invoke(runner, new Object[] { customClasspath }); |
| } |
| |
| // add listeners |
| if (buildListeners != null) { |
| Method addBuildListeners = classInternalAntRunner.getMethod("addBuildListeners", new Class[] { List.class }); //$NON-NLS-1$ |
| addBuildListeners.invoke(runner, new Object[] { buildListeners }); |
| } |
| |
| if (buildLoggerClassName == null) { |
| //indicate that the default logger is not to be used |
| buildLoggerClassName= ""; //$NON-NLS-1$ |
| } |
| // add build logger |
| Method addBuildLogger = classInternalAntRunner.getMethod("addBuildLogger", new Class[] { String.class }); //$NON-NLS-1$ |
| addBuildLogger.invoke(runner, new Object[] { buildLoggerClassName }); |
| |
| if (inputHandlerClassName != null) { |
| // add the input handler |
| Method setInputHandler = classInternalAntRunner.getMethod("setInputHandler", new Class[] { String.class }); //$NON-NLS-1$ |
| setInputHandler.invoke(runner, new Object[] { inputHandlerClassName }); |
| } |
| |
| basicConfigure(classInternalAntRunner, runner); |
| |
| // add progress monitor |
| if (monitor != null) { |
| Method setProgressMonitor = classInternalAntRunner.getMethod("setProgressMonitor", new Class[] { IProgressMonitor.class }); //$NON-NLS-1$ |
| setProgressMonitor.invoke(runner, new Object[] { monitor }); |
| } |
| |
| // set message output level |
| if (messageOutputLevel != 2) { //changed from the default Project.MSG_INFO |
| Method setMessageOutputLevel = classInternalAntRunner.getMethod("setMessageOutputLevel", new Class[] { int.class }); //$NON-NLS-1$ |
| setMessageOutputLevel.invoke(runner, new Object[] { new Integer(messageOutputLevel)}); |
| } |
| |
| // set execution targets |
| if (targets != null) { |
| Method setExecutionTargets = classInternalAntRunner.getMethod("setExecutionTargets", new Class[] { String[].class }); //$NON-NLS-1$ |
| setExecutionTargets.invoke(runner, new Object[] { targets }); |
| } |
| |
| // run |
| Method run = classInternalAntRunner.getMethod("run", null); //$NON-NLS-1$ |
| run.invoke(runner, null); |
| } catch (NoClassDefFoundError e) { |
| problemLoadingClass(e); |
| } catch (ClassNotFoundException e) { |
| problemLoadingClass(e); |
| } catch (InvocationTargetException e) { |
| handleInvocationTargetException(runner, classInternalAntRunner, e); |
| } catch (Exception e) { |
| String message = (e.getMessage() == null) ? InternalCoreAntMessages.getString("AntRunner.Build_Failed._3") : e.getMessage(); //$NON-NLS-1$ |
| IStatus status= new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, message, e); |
| throw new CoreException(status); |
| } finally { |
| buildRunning= false; |
| Thread.currentThread().setContextClassLoader(originalClassLoader); |
| } |
| } |
| |
| private Class getInternalAntRunner() throws ClassNotFoundException { |
| ClassLoader loader = getClassLoader(); |
| Thread.currentThread().setContextClassLoader(loader); |
| return loader.loadClass("org.eclipse.ant.internal.core.ant.InternalAntRunner"); //$NON-NLS-1$ |
| } |
| |
| private void setProperties(Object runner, Class classInternalAntRunner) |
| throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { |
| // add properties |
| if (userProperties != null) { |
| Method addUserProperties = classInternalAntRunner.getMethod("addUserProperties", new Class[] { Map.class }); //$NON-NLS-1$ |
| addUserProperties.invoke(runner, new Object[] { userProperties }); |
| } |
| |
| // add property files |
| if (propertyFiles != null) { |
| Method addPropertyFiles = classInternalAntRunner.getMethod("addPropertyFiles", new Class[] { String[].class }); //$NON-NLS-1$ |
| addPropertyFiles.invoke(runner, new Object[] { propertyFiles }); |
| } |
| } |
| |
| /* |
| * Handles exceptions that are loaded by the Ant Class Loader by |
| * asking the Internal Ant Runner class for the correct error message. |
| * |
| * Handles OperationCanceledExceptions, nested NoClassDefFoundError and |
| * nested ClassNotFoundException |
| */ |
| protected void handleInvocationTargetException(Object runner, Class classInternalAntRunner, InvocationTargetException e) throws CoreException { |
| Throwable realException = e.getTargetException(); |
| if (realException instanceof OperationCanceledException) { |
| return; |
| } |
| String message= null; |
| if (runner != null) { |
| try { |
| Method getBuildErrorMessage = classInternalAntRunner.getMethod("getBuildExceptionErrorMessage", new Class[] { Throwable.class }); //$NON-NLS-1$ |
| message= (String)getBuildErrorMessage.invoke(runner, new Object[] { realException }); |
| } catch (Exception ex) { |
| //do nothing as already in error state |
| } |
| } |
| // J9 throws NoClassDefFoundError nested in a InvocationTargetException |
| if (message == null && ((realException instanceof NoClassDefFoundError) || (realException instanceof ClassNotFoundException))) { |
| problemLoadingClass(realException); |
| return; |
| } |
| boolean internalError= false; |
| if (message == null) { |
| //error did not result from a BuildException |
| internalError= true; |
| message = (realException.getMessage() == null) ? InternalCoreAntMessages.getString("AntRunner.Build_Failed._3") : realException.getMessage(); //$NON-NLS-1$ |
| } |
| IStatus status= new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, message, realException); |
| if (internalError) { |
| AntCorePlugin.getPlugin().getLog().log(status); |
| } |
| throw new CoreException(status); |
| } |
| |
| protected void problemLoadingClass(Throwable e) throws CoreException { |
| String missingClassName= e.getMessage(); |
| String message; |
| if (missingClassName != null) { |
| missingClassName= missingClassName.replace('/', '.'); |
| message= InternalCoreAntMessages.getString("AntRunner.Could_not_find_one_or_more_classes._Please_check_the_Ant_classpath._2"); //$NON-NLS-1$ |
| message= MessageFormat.format(message, new String[]{missingClassName}); |
| } else { |
| message= InternalCoreAntMessages.getString("AntRunner.Could_not_find_one_or_more_classes._Please_check_the_Ant_classpath._1"); //$NON-NLS-1$ |
| } |
| IStatus status= new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, message, e); |
| AntCorePlugin.getPlugin().getLog().log(status); |
| throw new CoreException(status); |
| } |
| |
| /** |
| * Runs the build file. |
| * @throws CoreException Thrown if a build is already occuring or if an exception occurs during the build |
| */ |
| public void run() throws CoreException { |
| run(/*IProgressMonitor*/null); |
| } |
| |
| /** |
| * Invokes the building of a project object and executes a build using either a given |
| * target or the default target. This method is called when running Eclipse headless |
| * and specifying <code>org.eclipse.ant.core.antRunner</code> as the application. |
| * |
| * Sets the current threads context class loader to the AntClassLoader |
| * for the duration of the build. |
| * |
| * @param argArray the command line arguments |
| * @exception Exception if a problem occurred during the buildfile execution |
| * @return an exit object (<code>EXIT_OK</code>) indicating normal termination if no exception occurs |
| * @see org.eclipse.core.boot.IPlatformRunnable#run(java.lang.Object) |
| */ |
| public Object run(Object argArray) throws Exception { |
| ClassLoader originalClassLoader= Thread.currentThread().getContextClassLoader(); |
| try { |
| //set the preferences for headless mode |
| AntCorePlugin.getPlugin().setRunningHeadless(true); |
| |
| // Add debug information if necessary - fix for bug 5672. |
| // Since the platform parses the -debug command line arg |
| // and removes it from the args passed to the applications, |
| // we have to check if Eclipse is in debug mode in order to |
| // forward the -debug argument to Ant. |
| if (Platform.inDebugMode()) { |
| String[] args = (String[]) argArray; |
| String[] newArgs = new String[args.length + 1]; |
| for (int i = 0; i < args.length; i++) { |
| newArgs[i] = args[i]; |
| } |
| newArgs[args.length] = "-debug"; //$NON-NLS-1$ |
| argArray = newArgs; |
| } |
| ClassLoader loader = getClassLoader(); |
| Thread.currentThread().setContextClassLoader(loader); |
| Class classInternalAntRunner = loader.loadClass("org.eclipse.ant.internal.core.ant.InternalAntRunner"); //$NON-NLS-1$ |
| Object runner = classInternalAntRunner.newInstance(); |
| Method run = classInternalAntRunner.getMethod("run", new Class[] { Object.class }); //$NON-NLS-1$ |
| run.invoke(runner, new Object[] { argArray }); |
| } finally { |
| Thread.currentThread().setContextClassLoader(originalClassLoader); |
| } |
| |
| return EXIT_OK; |
| } |
| |
| private ClassLoader getClassLoader() { |
| if (customClasspath == null) { |
| return AntCorePlugin.getPlugin().getNewClassLoader(); |
| } |
| AntCorePreferences preferences = AntCorePlugin.getPlugin().getPreferences(); |
| List fullClasspath= new ArrayList(); |
| fullClasspath.addAll(Arrays.asList(customClasspath)); |
| fullClasspath.addAll(Arrays.asList(preferences.getExtraClasspathURLs())); |
| return new AntClassLoader((URL[])fullClasspath.toArray(new URL[fullClasspath.size()]), preferences.getPluginClassLoaders()); |
| } |
| |
| /** |
| * Sets the input handler. The parameter <code>className</code> |
| * is the class name of a <code>org.apache.tools.ant.input.InputHandler</code> |
| * implementation. The class will be instantiated at runtime and the |
| * input handler will be used to respond to <input> requests |
| * Only one input handler is permitted for any build. |
| * |
| * @param className an input handler class name |
| * @since 2.1 |
| */ |
| public void setInputHandler(String className) { |
| inputHandlerClassName= className; |
| } |
| |
| /** |
| * Sets the user specified property files. |
| * @param propertyFiles array of property file paths |
| * @since 2.1 |
| */ |
| public void setPropertyFiles(String[] propertyFiles) { |
| this.propertyFiles= propertyFiles; |
| } |
| |
| /** |
| * Sets the custom classpath to use for this build |
| * @param customClasspath array of URLs that define the custom classpath |
| */ |
| public void setCustomClasspath(URL[] customClasspath) { |
| this.customClasspath = customClasspath; |
| } |
| |
| /** |
| * Sets the Ant home to use for this build |
| * @param antHome String specifying the Ant home to use |
| * @since 2.1 |
| */ |
| public void setAntHome(String antHome) { |
| this.antHome= antHome; |
| } |
| /** |
| * Returns whether an Ant build is already in progress |
| * |
| * Only one Ant build can occur at any given time. |
| * |
| * @since 2.1 |
| * @return boolean |
| */ |
| public static boolean isBuildRunning() { |
| return buildRunning; |
| } |
| } |