/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.ant.internal.build; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import org.eclipse.buckminster.ant.AntBuilderConstants; import org.eclipse.buckminster.ant.AntRunner; import org.eclipse.buckminster.ant.Messages; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.build.AbstractBuckminsterBuilder; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.osgi.util.NLS; /** * <p> * This builder will execute Ant scripts fast and efficiently. Contrary to the * normal Eclipse builder, it not instantiate a new ClassLoader each time it is * called. The <code>ClassLoader</code> is in fact only instantiated once for * all bulder instances and the classpath can therefore not vary between those * instances. * </p> * <p> * The drawback with this design is that it is less flexible. The class path * cannot be modified on a per-builder basis (only for Ant as a whole)</code> * can vary. * </p> * <p> * A strong motivation for the selected design was that an approach where a new * chain of ClassLoaders where set up each time around was extremely memory * consuming. Caches used by the <code>sun.misc.URL.Classpath</code> (probably * timed caches) caused <code>OutOfMemoryException</code> when many builds where * executed within a short timeframe. * </p> * * @author Thomas Hallgren */ public class AntBuilder extends AbstractBuckminsterBuilder implements AntBuilderConstants { private IFile scriptFile; @Override protected IProject[] doBuild(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { PrintStream origOut = System.out; PrintStream origErr = System.err; try { String target = getTarget(args, kind); String[] targets = target == null ? null : new String[] { target }; // only set the 'kind' property if a propname is given // String kindPropName = AbstractBuckminsterBuilder.getValue(args, ARG_BUILD_KIND_PROPERTY_KEY); Map<String, String> props = getFixedProperties(args); if (kindPropName != null) props.put(kindPropName, AbstractBuckminsterBuilder.kindToString(kind)); IPath baseDir = getBaseDir(args); if (baseDir != null) props.put("basedir", baseDir.toOSString()); //$NON-NLS-1$ System.setOut(getOutStream()); System.setErr(getErrStream()); AntRunner runner = new AntRunner(); runner.setBuildFileLocation(getScriptFile(args).getLocation()); runner.setExecutionTargets(targets); runner.setBuildLogger("org.eclipse.buckminster.ant.support.AntBuildLogger"); //$NON-NLS-1$ runner.addUserProperties(props); runner.run(monitor); } catch (CoreException e) { CorePlugin.getLogger().error(e, e.getMessage()); throw e; } finally { System.setOut(origOut); System.setErr(origErr); } return null; } /** * The path to use as <code>basedir</code> in the ant build. * * @param args * The map of arguments that where passed to the build. * @return The basedir of the build or <code>null</code> if not set. */ protected IPath getBaseDir(Map<String, String> args) { String baseDir = getValue(args, ARG_OVERRIDE_BASEDIR_KEY); IPath baseDirPath = null; if (baseDir != null) { baseDirPath = new Path(baseDir); if (!baseDirPath.isAbsolute()) baseDirPath = getProject().getLocation().append(baseDirPath); } return baseDirPath; } /** * Returns a new map with fixed properties that are guaranteed not to change * between each build. The method will return a new map for each call and it * is ok if the caller wishes to add more entries to that map. * * @param args * The map of arguments that where passed to the build. * @return A map of fixed properties. */ protected Map<String, String> getFixedProperties(Map<String, String> args) { Map<String, String> props = new HashMap<String, String>(); // only set the 'component name' property if a propname is given // String componentPropName = getValue(args, ARG_COMPONENT_NAME_PROPERTY_KEY); if (componentPropName != null) props.put(componentPropName, getProject().getName()); return props; } /** * Returns the script file * * @param args * The map of arguments that where passed to the build. * @return The script file. */ protected IFile getScriptFile(Map<String, String> args) throws CoreException { if (scriptFile == null) { // script name must always be relative to project root // String sf = getValue(args, ARG_SCRIPT_FILE_KEY); if (sf == null) sf = DEFAULT_SCRIPT_FILE; IPath relativeScriptFilePath = new Path(sf); if (relativeScriptFilePath.isAbsolute()) throw BuckminsterException.fromMessage(NLS.bind(Messages.AntBuilder_the_script_file_name_must_be_relative_to_the_project_root_0, sf)); scriptFile = getProject().getFile(relativeScriptFilePath); notifyOnChangedResources(new IResource[] { scriptFile }); } return scriptFile; } /** * Returns the target for a specific <code>kind</code> of build or * <code>null</code> if no target has been specificed for the * <code>kind</code>. * * @param args * The map of arguments that where passed to the build. * @param kind * The * {@link org.eclipse.core.resources.IncrementalProjectBuilder * IncrementalProjectBuilder} build kind. * @return The name of the target. */ protected String getTarget(Map<String, String> args, int kind) { String target = null; if (kind == AUTO_BUILD) target = getValue(args, ARG_AUTO_KIND_TARGET_KEY); else if (kind == CLEAN_BUILD) { target = getValue(args, ARG_CLEAN_KIND_TARGET_KEY); if (target == null) target = DEFAULT_CLEAN_KIND_TARGET; } else if (kind == FULL_BUILD) target = getValue(args, ARG_FULL_KIND_TARGET_KEY); else if (kind == INCREMENTAL_BUILD) target = getValue(args, ARG_INCREMENTAL_KIND_TARGET_KEY); return target; } @Override protected void resourcesChangeNotification(IResource[] changedResources) { // should only be the scriptfile at this time... // if someone did something to the build script, force a full build // for (IResource r : changedResources) if (r.equals(scriptFile)) { forgetLastBuiltState(); scriptFile = null; break; } } }