/******************************************************************************* * Copyright (c) 2000, 2007 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 * Michael Giroux (michael.giroux@objectweb.org) - bug 149739 *******************************************************************************/ package org.eclipse.buckminster.ant; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.ant.core.AntCorePlugin; import org.eclipse.ant.core.Property; import org.eclipse.ant.internal.core.IAntCoreConstants; import org.eclipse.ant.internal.core.InternalCoreAntMessages; import org.eclipse.buckminster.runtime.BuckminsterPreferences; import org.eclipse.buckminster.runtime.Logger; import org.eclipse.buckminster.runtime.Trivial; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; /** * Entry point for running Ant builds inside Eclipse (within the same JRE). * Clients may instantiate this class; it is not intended to be subclassed. * <p/> * <div class="TableSubHeadingColor"> <b>Usage note:</b><br/> * As opposed to {@link org.eclipse.ant.core.AntRunner} this class will not set * up a new classloader for each call and consequently, it does not support the * customClassPath. * <p> * Refer to the "Platform Ant Support" chapter of the Programmer's Guide section * in the Platform Plug-in Developer Guide for complete details. * </p> * </div> */ @SuppressWarnings("restriction") public class AntRunner { /** Message priority of "error". */ public static final int MSG_ERR = 0; /** Message priority of "warning". */ public static final int MSG_WARN = 1; /** Message priority of "information". */ public static final int MSG_INFO = 2; /** Message priority of "verbose". */ public static final int MSG_VERBOSE = 3; /** Message priority of "debug". */ public static final int MSG_DEBUG = 4; private static boolean buildRunning = false; private static final Class<?> internalAntRunnerClass; private static final Method addBuildLogger; private static final Method getBuildErrorMessage; private static final Method setBuildFileLocation; private static final Method setAntHome; private static final Method addUserProperties; private static final Method addPropertyFiles; private static final Method setArguments; private static final Method setProgressMonitor; private static final Method setMessageOutputLevel; private static final Method setExecutionTargets; private static final Method run; static { try { Class<?>[] string = new Class<?>[] { String.class }; Class<?>[] strings = new Class<?>[] { String[].class }; internalAntRunnerClass = getInternalAntRunnerClass(); addBuildLogger = internalAntRunnerClass.getMethod("addBuildLogger", string); //$NON-NLS-1$ getBuildErrorMessage = internalAntRunnerClass.getMethod("getBuildExceptionErrorMessage", //$NON-NLS-1$ new Class[] { Throwable.class }); run = internalAntRunnerClass.getMethod("run", Trivial.EMPTY_CLASS_ARRAY); //$NON-NLS-1$ setBuildFileLocation = internalAntRunnerClass.getMethod("setBuildFileLocation", string); //$NON-NLS-1$ setAntHome = internalAntRunnerClass.getMethod("setAntHome", string); //$NON-NLS-1$ addUserProperties = internalAntRunnerClass.getMethod("addUserProperties", new Class[] { Map.class }); //$NON-NLS-1$ addPropertyFiles = internalAntRunnerClass.getMethod("addPropertyFiles", strings); //$NON-NLS-1$ setArguments = internalAntRunnerClass.getMethod("setArguments", strings); //$NON-NLS-1$ setProgressMonitor = internalAntRunnerClass.getMethod("setProgressMonitor", //$NON-NLS-1$ new Class[] { IProgressMonitor.class }); setMessageOutputLevel = internalAntRunnerClass.getMethod("setMessageOutputLevel", //$NON-NLS-1$ new Class[] { int.class }); setExecutionTargets = internalAntRunnerClass.getMethod("setExecutionTargets", strings); //$NON-NLS-1$ } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(problemLoadingClass(e)); } catch (NoSuchMethodException e) { throw new ExceptionInInitializerError(e); } } private static ClassLoader getClassLoader() { return AntCorePlugin.getPlugin().getNewClassLoader(); } private static List<Property> getGlobalAntProperties() { return AntCorePlugin.getPlugin().getPreferences().getProperties(); } private static Class<?> getInternalAntRunnerClass() throws ClassNotFoundException { ClassLoader loader = getClassLoader(); Thread.currentThread().setContextClassLoader(loader); return loader.loadClass("org.eclipse.ant.internal.core.ant.InternalAntRunner"); //$NON-NLS-1$ } /* * 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 */ static CoreException handleInvocationTargetException(Object runner, Class<?> classInternalAntRunner, InvocationTargetException e) { Throwable realException = e.getTargetException(); if (realException instanceof OperationCanceledException) return new CoreException(Status.CANCEL_STATUS); String message = null; if (runner != null) { try { 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))) return problemLoadingClass(realException); boolean internalError = false; if (message == null) { // error did not result from a BuildException // internalError = true; message = (realException.getMessage() == null) ? InternalCoreAntMessages.AntRunner_Build_Failed__3 : realException.getMessage(); } IStatus status = new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, message, realException); if (internalError) AntCorePlugin.getPlugin().getLog().log(status); return new CoreException(status); } /** * 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; } static CoreException problemLoadingClass(Throwable e) { String missingClassName = e.getMessage(); String message; if (missingClassName != null) { missingClassName = missingClassName.replace('/', '.'); message = InternalCoreAntMessages.AntRunner_Could_not_find_one_or_more_classes__Please_check_the_Ant_classpath__2; message = NLS.bind(message, new String[] { missingClassName }); } else { message = InternalCoreAntMessages.AntRunner_Could_not_find_one_or_more_classes__Please_check_the_Ant_classpath__1; } IStatus status = new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, message, e); AntCorePlugin.getPlugin().getLog().log(status); return new CoreException(status); } private String buildFileLocation = IAntCoreConstants.DEFAULT_BUILD_FILENAME; private String[] targets; private Map<String, String> userProperties; private String[] arguments; private String[] propertyFiles; private String antHome; private String buildLoggerClassName; /** * Adds user-defined properties. Keys and values must be String objects. * * @param properties * a Map of user-defined properties */ public void addUserProperties(Map<String, String> properties) { if (userProperties == null) userProperties = new HashMap<String, String>(properties); else userProperties.putAll(properties); } /** * 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 occurring or if an exception * occurs during the build */ public void run(IProgressMonitor monitor) throws CoreException { synchronized (internalAntRunnerClass) { Object runner = null; ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { runner = internalAntRunnerClass.newInstance(); setBuildFileLocation.invoke(runner, new Object[] { buildFileLocation }); if (antHome != null) setAntHome.invoke(runner, new Object[] { antHome }); if (buildLoggerClassName == null) // // indicate that the default logger is not to be used // buildLoggerClassName = ""; //$NON-NLS-1$ addBuildLogger.invoke(runner, new Object[] { buildLoggerClassName }); if (userProperties != null) { Map<String, String> allProps = userProperties; // The eclipse ant runner will not include the global // properties // if we add user properties so we need to include them here // List<Property> properties = getGlobalAntProperties(); if (properties != null) { allProps = new HashMap<String, String>(userProperties); for (Property property : properties) { // We must do early expansion since the expansion is // based // on Eclipse variables and not on other properties. // String value = property.getValue(true); if (value != null) allProps.put(property.getName(), value); } } addUserProperties.invoke(runner, new Object[] { allProps }); } if (propertyFiles != null && propertyFiles.length > 0) addPropertyFiles.invoke(runner, new Object[] { propertyFiles }); if (arguments != null && arguments.length > 0) setArguments.invoke(runner, new Object[] { arguments }); if (monitor != null) setProgressMonitor.invoke(runner, new Object[] { monitor }); int messageOutputLevel; switch (BuckminsterPreferences.getLogLevelAntLogger()) { case Logger.DEBUG: messageOutputLevel = MSG_DEBUG; break; case Logger.WARNING: messageOutputLevel = MSG_WARN; break; case Logger.ERROR: messageOutputLevel = MSG_ERR; break; default: messageOutputLevel = MSG_INFO; } if (messageOutputLevel != MSG_INFO) setMessageOutputLevel.invoke(runner, new Object[] { new Integer(messageOutputLevel) }); if (targets != null) setExecutionTargets.invoke(runner, new Object[] { targets }); run.invoke(runner, Trivial.EMPTY_OBJECT_ARRAY); } catch (NoClassDefFoundError e) { throw problemLoadingClass(e); } catch (InvocationTargetException e) { throw handleInvocationTargetException(runner, internalAntRunnerClass, e); } catch (Exception e) { String message = (e.getMessage() == null) ? InternalCoreAntMessages.AntRunner_Build_Failed__3 : e.getMessage(); 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); } } } /** * 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; } /** * Sets the arguments to be passed to the build (e.g. -verbose). * * @param arguments * the arguments to be passed to the build */ public void setArguments(String[] arguments) { this.arguments = arguments; } /** * Sets the build file location on the file system. * * @param buildFileLocation * the file system location of the build file */ public void setBuildFileLocation(IPath buildFileLocation) { if (buildFileLocation == null) this.buildFileLocation = IAntCoreConstants.DEFAULT_BUILD_FILENAME; else this.buildFileLocation = buildFileLocation.toOSString(); } /** * Sets the build logger. The parameter <code>className</code> is the class * name of an <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. * * <p> * Refer to {@link AntRunner Usage Note} for implementation details. * * @param className * a build logger class name */ public void setBuildLogger(String className) { buildLoggerClassName = className; } /** * Sets the targets and execution order. * * @param executionTargets * which targets should be run and in which order */ public void setExecutionTargets(String[] executionTargets) { targets = executionTargets; } /** * Sets the user specified property files. * * @param propertyFiles * array of property file paths * @since 2.1 */ public void setPropertyFiles(String[] propertyFiles) { this.propertyFiles = propertyFiles; } }