/******************************************************************************* * Copyright (c) 2000, 2017 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 - derived implementation * Blake Meike (blakem@world.std.com)- patch for bug 31691 and bug 34488 * Rob Stryker - Bug 459188 - InternalAntRunner tries to set null user property *******************************************************************************/ package org.eclipse.ant.internal.core.ant; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Vector; import org.apache.tools.ant.AntTypeDefinition; import org.apache.tools.ant.BuildEvent; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildListener; import org.apache.tools.ant.BuildLogger; import org.apache.tools.ant.ComponentHelper; import org.apache.tools.ant.DefaultLogger; import org.apache.tools.ant.DemuxOutputStream; import org.apache.tools.ant.Diagnostics; import org.apache.tools.ant.Main; import org.apache.tools.ant.Project; import org.apache.tools.ant.ProjectHelper; import org.apache.tools.ant.Target; import org.apache.tools.ant.TaskAdapter; import org.apache.tools.ant.XmlLogger; import org.eclipse.ant.core.AntCorePlugin; import org.eclipse.ant.core.AntCorePreferences; import org.eclipse.ant.core.AntSecurityException; import org.eclipse.ant.core.ProjectInfo; import org.eclipse.ant.core.Property; import org.eclipse.ant.core.TargetInfo; import org.eclipse.ant.core.Task; import org.eclipse.ant.core.Type; import org.eclipse.ant.internal.core.AbstractEclipseBuildLogger; import org.eclipse.ant.internal.core.AntCoreUtil; import org.eclipse.ant.internal.core.AntSecurityManager; import org.eclipse.ant.internal.core.IAntCoreConstants; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; 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.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.variables.VariablesPlugin; import org.osgi.framework.Bundle; import org.osgi.framework.Version; /** * Eclipse application entry point into Ant. Derived from the original Ant Main class to ensure that the functionality is equivalent when running in * the platform. */ public class InternalAntRunner { private IProgressMonitor monitor; private ArrayList<String> buildListeners; private String buildFileLocation; /** * Targets we want to run. */ private Vector<String> targets; private Map<String, String> userProperties; private boolean noExplicitUserProperties = true; private Project currentProject; private String defaultTarget; private BuildLogger buildLogger = null; /** * Cache of the Ant version number when it has been loaded */ private String antVersionNumber = null; /** Current message output status. Follows Project.MSG_XXX */ private int messageOutputLevel = Project.MSG_INFO; /** Indicates whether output to the log is to be unadorned. */ private boolean emacsMode = false; /** Indicates we should only parse and display the project help information */ private boolean projectHelp = false; /** Stream that we are using for logging */ private PrintStream out = System.out; /** Stream that we are using for logging error messages */ private PrintStream err = System.err; /** * The Ant logger class. There may be only one logger. It will have the right to use the 'out' PrintStream. The class must implement the * BuildLogger interface. An empty String indicates that no logger is to be used. A <code>null</code> name indicates that the * <code>org.apache.tools.ant.DefaultLogger</code> will be used. */ private String loggerClassname = null; /** Extra arguments to be parsed as command line arguments. */ private String[] extraArguments = null; private boolean executed = false; private ArrayList<String> propertyFiles = new ArrayList<>(); private URL[] customClasspath = null; /** * The Ant InputHandler class. There may be only one input handler. */ private String inputHandlerClassname = null; private String buildAntHome = null; /** * Indicates whether to execute all targets that do not depend on failed targets * * @since Ant 1.6.0 */ private boolean keepGoing = false; /** * Indicates whether this build is to support interactive input * * @since Ant 1.6.0 */ private boolean allowInput = true; private String fEarlyErrorMessage = null; private boolean unknownTargetsFound = false; /** * Adds a build listener. * * @param classNames * the fully qualified names of the build listeners to be added */ public void addBuildListeners(List<String> classNames) { if (buildListeners == null) { buildListeners = new ArrayList<>(classNames.size()); } buildListeners.addAll(classNames); } protected void addBuildListener(String clazz) { if (buildListeners == null) { buildListeners = new ArrayList<>(); } buildListeners.add(clazz); } /** * Adds a build logger. There can be only one build logger. * * @param className * The fully qualified name of the build logger to add */ public void addBuildLogger(String className) { loggerClassname = className; } /** * Adds user properties to the current collection of user properties. * * @param properties * The user properties to be added */ public void addUserProperties(Map<String, String> properties) { if (userProperties == null) { userProperties = new HashMap<>(properties); } else { userProperties.putAll(properties); } noExplicitUserProperties = false; } /** * Adds user property files. * * @param additionalPropertyFiles * The property files to add * @since 2.1 */ public void addPropertyFiles(String[] additionalPropertyFiles) { propertyFiles.addAll(Arrays.asList(additionalPropertyFiles)); } /** * Tries to add the build listeners to the backing {@link Project} * * @param project * the project to add the listeners to * @param log * if we should be logging {@link ClassCastException}s here or not. */ protected void addBuildListeners(Project project, boolean log) { String className = null; try { BuildLogger logger = createLogger(); if (logger != null) { project.addBuildListener(logger); } if (buildListeners != null) { for (Iterator<String> iterator = buildListeners.iterator(); iterator.hasNext();) { className = iterator.next(); Class<?> listener = Class.forName(className); project.addBuildListener((BuildListener) listener.newInstance()); } } } catch (ClassCastException e) { String message = MessageFormat.format(InternalAntMessages.InternalAntRunner_not_an_instance_of_apache_ant_BuildListener, new Object[] { className }); if (log) { logMessage(null, message, Project.MSG_ERR); } throw new BuildException(message, e); } catch (BuildException e) { throw e; } catch (Exception e) { throw new BuildException(e); } } private void setProperties(Project project, boolean substituteVariables) { setBuiltInProperties(project); if (userProperties != null) { for (Iterator<Map.Entry<String, String>> iterator = userProperties.entrySet().iterator(); iterator.hasNext();) { Entry<String, String> entry = iterator.next(); String value = entry.getValue(); if (substituteVariables && value != null) { try { value = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(value); } catch (CoreException e) { // do nothing } } if (value != null) project.setUserProperty(entry.getKey(), value); } // may have properties set (always have the Ant process ID) // using the Arguments and not the Properties page // if set using the arguments, still include the global properties if (noExplicitUserProperties) { setGlobalProperties(project, substituteVariables); } } else { setGlobalProperties(project, substituteVariables); } } /** * Sets the default <code>ant.file</code> and <code>ant.version</code> properties in the given {@link Project} * * @param project */ protected void setBuiltInProperties(Project project) { // note also see processAntHome for system properties that are set project.setUserProperty("ant.file", getBuildFileLocation()); //$NON-NLS-1$ project.setUserProperty("ant.version", Main.getAntVersion()); //$NON-NLS-1$ } private void setGlobalProperties(Project project, boolean substituteVariables) { AntCorePreferences prefs = AntCorePlugin.getPlugin().getPreferences(); List<Property> properties = prefs.getProperties(); if (properties != null) { for (Property property : properties) { String value = property.getValue(substituteVariables); if (value != null) { project.setUserProperty(property.getName(), value); } } } } private void setTasks(Project project) { List<Task> tasks = AntCorePlugin.getPlugin().getPreferences().getTasks(); for (Task task : tasks) { if (isVersionCompatible("1.6")) { //$NON-NLS-1$ AntTypeDefinition def = new AntTypeDefinition(); String name = ProjectHelper.genComponentName(task.getURI(), task.getTaskName()); def.setName(name); def.setClassName(task.getClassName()); def.setClassLoader(this.getClass().getClassLoader()); def.setAdaptToClass(org.apache.tools.ant.Task.class); def.setAdapterClass(TaskAdapter.class); ComponentHelper.getComponentHelper(project).addDataTypeDefinition(def); } else { try { Class<?> taskClass = Class.forName(task.getClassName()); if (isVersionCompatible("1.5")) { //$NON-NLS-1$ try { project.checkTaskClass(taskClass); } catch (BuildException e) { IStatus status = new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, MessageFormat.format(InternalAntMessages.InternalAntRunner_Error_setting_Ant_task, new Object[] { task.getTaskName() }), e); AntCorePlugin.getPlugin().getLog().log(status); continue; } } project.addTaskDefinition(task.getTaskName(), taskClass); } catch (ClassNotFoundException e) { IStatus status = new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, MessageFormat.format(InternalAntMessages.InternalAntRunner_Class_not_found_for_task, new Object[] { task.getClassName(), task.getTaskName() }), e); AntCorePlugin.getPlugin().getLog().log(status); } } } } private void setTypes(Project project) { List<Type> types = AntCorePlugin.getPlugin().getPreferences().getTypes(); for (Iterator<Type> iterator = types.iterator(); iterator.hasNext();) { Type type = iterator.next(); if (isVersionCompatible("1.6")) { //$NON-NLS-1$ AntTypeDefinition def = new AntTypeDefinition(); String name = ProjectHelper.genComponentName(type.getURI(), type.getTypeName()); def.setName(name); def.setClassName(type.getClassName()); def.setClassLoader(this.getClass().getClassLoader()); ComponentHelper.getComponentHelper(project).addDataTypeDefinition(def); } else { try { Class<?> typeClass = Class.forName(type.getClassName()); project.addDataTypeDefinition(type.getTypeName(), typeClass); } catch (ClassNotFoundException e) { IStatus status = new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.ERROR_RUNNING_BUILD, MessageFormat.format(InternalAntMessages.InternalAntRunner_Class_not_found_for_type, new Object[] { type.getClassName(), type.getTypeName() }), e); AntCorePlugin.getPlugin().getLog().log(status); } } } } /** * Parses the build file and adds necessary information into the given project. * * @param project * The project to configure */ protected void parseBuildFile(Project project) { File buildFile = new File(getBuildFileLocation()); if (!buildFile.exists()) { throw new BuildException(MessageFormat.format(InternalAntMessages.InternalAntRunner_Buildfile_does_not_exist, new Object[] { buildFile.getAbsolutePath() })); } if (!buildFile.isFile()) { throw new BuildException(MessageFormat.format(InternalAntMessages.InternalAntRunner_Buildfile_is_not_a_file, new Object[] { buildFile.getAbsolutePath() })); } if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ parseBuildFile(project, buildFile); } else { ProjectHelper helper = ProjectHelper.getProjectHelper(); project.addReference("ant.projectHelper", helper); //$NON-NLS-1$ helper.parse(project, buildFile); } } /** * @deprecated support for Ant older than 1.5 */ @Deprecated private void parseBuildFile(Project project, File buildFile) { ProjectHelper.configureProject(project, buildFile); } /** * Gets all the target information from the build script. Returns a list of lists. Each item in the enclosing list represents a target, where the * first element is the name, the second element is the description, the third element is the project name, and the last elements is an array of * dependencies. * * @return a list of {@link TargetInfo} objects */ public List<TargetInfo> getTargets() { try { setJavaClassPath(); Project antProject = getProject(); processAntHome(false); antProject.init(); setTypes(antProject); boolean exceptionState = processProperties(AntCoreUtil.getArrayList(extraArguments)); if (fEarlyErrorMessage != null) { if (exceptionState) { throw new BuildException(fEarlyErrorMessage); } } setProperties(antProject, false); if (isVersionCompatible("1.5")) { //$NON-NLS-1$ new InputHandlerSetter().setInputHandler(antProject, "org.eclipse.ant.internal.core.ant.NullInputHandler"); //$NON-NLS-1$ } parseBuildFile(antProject); defaultTarget = antProject.getDefaultTarget(); Hashtable<String, Target> projectTargets = antProject.getTargets(); ArrayList<TargetInfo> infos = new ArrayList<>(); ProjectInfo pinfo = new ProjectInfo(antProject.getName(), antProject.getDescription()); boolean defaultFound = false; for (Target target : projectTargets.values()) { String name = target.getName(); if (name.length() == 0) { // "no name" implicit target of Ant 1.6 continue; } if (target.getName().equals(defaultTarget)) { defaultFound = true; } ArrayList<String> dependencies = new ArrayList<>(); Enumeration<String> enumeration = target.getDependencies(); while (enumeration.hasMoreElements()) { dependencies.add(enumeration.nextElement()); } String[] dependencyArray = new String[dependencies.size()]; dependencies.toArray(dependencyArray); TargetInfo info = new TargetInfo(pinfo, name, target.getDescription(), dependencyArray, defaultFound); infos.add(info); } if (!defaultFound) { // default target must exist throw new BuildException(MessageFormat.format(InternalAntMessages.InternalAntRunner_Default_target_does_not_exist, new Object[] { "'", //$NON-NLS-1$ defaultTarget, "'" })); //$NON-NLS-1$ } return infos; } finally { processAntHome(true); } } /** * Returns a list of target names in the build script. * * @return a list of target names */ private List<String> getTargetNames() { try { setJavaClassPath(); Project antProject; antProject = getProject(); processAntHome(false); antProject.init(); setTypes(antProject); processProperties(AntCoreUtil.getArrayList(extraArguments)); setProperties(antProject, false); if (isVersionCompatible("1.5")) { //$NON-NLS-1$ new InputHandlerSetter().setInputHandler(antProject, "org.eclipse.ant.internal.core.ant.NullInputHandler"); //$NON-NLS-1$ } parseBuildFile(antProject); Hashtable<String, Target> projectTargets = antProject.getTargets(); ArrayList<String> names = new ArrayList<>(); for (Target target : projectTargets.values()) { String name = target.getName(); if (name.length() == 0) { // "no name" implicit target of Ant 1.6 continue; } names.add(name); } return names; } finally { processAntHome(true); } } private Project getProject() { Project antProject; if (isVersionCompatible("1.6")) { //$NON-NLS-1$ // in Ant version 1.6 or greater all tasks can exist outside the scope of a target if (isVersionCompatible("1.6.3")) { //$NON-NLS-1$ antProject = new InternalProject2(); } else { antProject = new Project(); } } else { antProject = new InternalProject(); } return antProject; } /** * Returns the default target name that was last computed or <code>null</code> if no default target has been computed. * * @return the default target name */ public String getDefaultTarget() { return defaultTarget; } /** * Runs the build script. */ public void run() { run(AntCoreUtil.getArrayList(extraArguments)); } private void printArguments(Project project) { if ((messageOutputLevel != Project.MSG_DEBUG) && (messageOutputLevel != Project.MSG_VERBOSE)) { return; } StringBuffer sb = new StringBuffer(); for (int i = 0; i < extraArguments.length; i++) { sb.append(extraArguments[i]); sb.append(' '); } project.log(MessageFormat.format(InternalAntMessages.InternalAntRunner_Arguments, new Object[] { sb.toString().trim() })); } private void createMonitorBuildListener(Project project) { if (monitor == null) { return; } Vector<String> chosenTargets = targets; if (chosenTargets == null || chosenTargets.isEmpty()) { chosenTargets = new Vector<>(1); String defltTarget = project.getDefaultTarget(); if (defltTarget != null) { chosenTargets.add(defltTarget); } } project.addBuildListener(new ProgressBuildListener(project, chosenTargets, monitor)); } /** * Invokes the building of a project object and executes a build using either a given target or the default target. This method is called if * running in headless mode. * * @see org.eclipse.ant.core.AntRunner#run(Object) * @param argArray * the command line arguments * @exception Exception * execution exceptions */ public void run(Object argArray) throws Exception { run(AntCoreUtil.getArrayList((String[]) argArray)); } /** * Attempts to run the given list of command line arguments. Note that the list passed to this method must support {@link List#remove(Object)}. * <br> * <br> * This method directly processes the following arguments: * <ul> * <li><b>-projecthelp</b>, <b>-p</b> - print project help information</li> * </ul> * * @param argList * the raw list of command line arguments */ private void run(List<String> argList) { setCurrentProject(new Project()); if (isVersionCompatible("1.6.3")) { //$NON-NLS-1$ new ExecutorSetter().setExecutor(currentProject); } Throwable error = null; PrintStream originalErr = System.err; PrintStream originalOut = System.out; InputStream originalIn = System.in; SecurityManager originalSM = System.getSecurityManager(); setJavaClassPath(); executed = true; processAntHome(false); try { if (argList != null && (argList.remove("-projecthelp") || argList.remove("-p"))) { //$NON-NLS-1$ //$NON-NLS-2$ projectHelp = true; } getCurrentProject().init(); if (argList != null) { executed = preprocessCommandLine(argList); if (!executed) { return; } } boolean exceptionState = processProperties(argList); addBuildListeners(getCurrentProject(), true); addInputHandler(getCurrentProject()); remapSystemIn(); System.setOut(new PrintStream(new DemuxOutputStream(getCurrentProject(), false))); System.setErr(new PrintStream(new DemuxOutputStream(getCurrentProject(), true))); if (!projectHelp) { fireBuildStarted(getCurrentProject()); } if (fEarlyErrorMessage != null) { // an error occurred processing the properties // build started has fired and we have // listeners/loggers to report the error logMessage(getCurrentProject(), fEarlyErrorMessage, Project.MSG_ERR); if (exceptionState) { throw new BuildException(fEarlyErrorMessage); } } // properties can only be set after buildStarted as some listeners/loggers // depend on this (e.g. XMLLogger) setProperties(getCurrentProject(), true); if (argList != null && !argList.isEmpty()) { try { executed = processCommandLine(argList); } catch (BuildException e) { executed = false; throw e; } } if (!executed) { return; } // needs to occur after processCommandLine(List) if (allowInput && (inputHandlerClassname != null && inputHandlerClassname.length() > 0)) { if (isVersionCompatible("1.6")) { //$NON-NLS-1$ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=182577 // getCurrentProject().setDefaultInputStream(originalIn); System.getProperties().remove("eclipse.ant.noInput"); //$NON-NLS-1$ } } else { // set the system property that any input handler // can check to see if handling input is allowed System.setProperty("eclipse.ant.noInput", "true"); //$NON-NLS-1$//$NON-NLS-2$ if (isVersionCompatible("1.5") && (inputHandlerClassname == null || inputHandlerClassname.length() == 0)) { //$NON-NLS-1$ InputHandlerSetter setter = new InputHandlerSetter(); setter.setInputHandler(getCurrentProject(), "org.eclipse.ant.internal.core.ant.FailInputHandler"); //$NON-NLS-1$ } } if (!projectHelp) { logMessage(currentProject, MessageFormat.format(InternalAntMessages.InternalAntRunner_Build_file, new Object[] { getBuildFileLocation() }), Project.MSG_INFO); setTasks(getCurrentProject()); setTypes(getCurrentProject()); if (isVersionCompatible("1.6")) { //$NON-NLS-1$ getCurrentProject().setKeepGoingMode(keepGoing); } parseBuildFile(getCurrentProject()); } createMonitorBuildListener(getCurrentProject()); if (projectHelp) { if (isVersionCompatible("1.7")) { //$NON-NLS-1$ new EclipseMainHelper().runProjectHelp(getBuildFileLocation(), getCurrentProject()); return; } logMessage(currentProject, InternalAntMessages.InternalAntRunner_ant_1_7_needed_for_help_info, Project.MSG_ERR); executed = false; return; } if (extraArguments != null) { printArguments(getCurrentProject()); } System.setSecurityManager(new AntSecurityManager(originalSM, Thread.currentThread())); if (targets == null) { targets = new Vector<>(1); } String dtarget = currentProject.getDefaultTarget(); if (targets.isEmpty() && dtarget != null) { targets.add(dtarget); } if (!isVersionCompatible("1.6.3")) { //$NON-NLS-1$ getCurrentProject().addReference(IAntCoreConstants.TARGET_VECTOR_NAME, targets); } getCurrentProject().executeTargets(targets); } catch (OperationCanceledException e) { executed = false; logMessage(currentProject, e.getMessage(), Project.MSG_INFO); throw e; } catch (AntSecurityException e) { // expected } catch (RuntimeException e) { error = e; throw e; } catch (Error e) { error = e; throw e; } finally { System.setErr(originalErr); System.setOut(originalOut); System.setIn(originalIn); if (System.getSecurityManager() instanceof AntSecurityManager) { System.setSecurityManager(originalSM); } if (!projectHelp) { if (AntCorePlugin.getPlugin().getBundle().getState() != Bundle.ACTIVE) { return; } fireBuildFinished(getCurrentProject(), error); } // close any user specified build log if (err != originalErr) { err.close(); } if (out != originalOut) { out.close(); } processAntHome(true); if (!allowInput) { System.getProperties().remove("eclipse.ant.noInput"); //$NON-NLS-1$ } } } /** * Re-maps {@link System.in} to the Ant input stream setter */ protected void remapSystemIn() { if (!isVersionCompatible("1.6")) { //$NON-NLS-1$ return; } DemuxInputStreamSetter setter = new DemuxInputStreamSetter(); setter.remapSystemIn(currentProject); } private void processAntHome(boolean finished) { AntCorePreferences prefs = AntCorePlugin.getPlugin().getPreferences(); String antHome = prefs.getAntHome(); if (buildAntHome != null && !finished) { antHome = buildAntHome; } if (antHome == null || antHome.length() == 0) { System.getProperties().remove("ant.home"); //$NON-NLS-1$ System.getProperties().remove("ant.library.dir"); //$NON-NLS-1$ } else { System.setProperty("ant.home", antHome); //$NON-NLS-1$ File antLibDir = new File(antHome, "lib"); //$NON-NLS-1$ System.setProperty("ant.library.dir", antLibDir.getAbsolutePath()); //$NON-NLS-1$ } } public void setAntHome(String antHome) { this.buildAntHome = antHome; } /** * Creates and returns the default build logger for logging build events to the ant log. * * @return the default build logger for logging build events to the ant log can return <code>null</code> if no logging is to occur */ protected BuildLogger createLogger() { if (loggerClassname == null) { buildLogger = new DefaultLogger(); } else if (!IAntCoreConstants.EMPTY_STRING.equals(loggerClassname)) { try { buildLogger = (BuildLogger) (Class.forName(loggerClassname).newInstance()); } catch (ClassCastException e) { String message = MessageFormat.format(InternalAntMessages.InternalAntRunner_not_an_instance_of_apache_ant_BuildLogger, new Object[] { loggerClassname }); logMessage(null, message, Project.MSG_ERR); throw new BuildException(message, e); } catch (Exception e) { String message = MessageFormat.format(InternalAntMessages.InternalAntRunner_Unable_to_instantiate_logger, new Object[] { loggerClassname }); logMessage(null, message, Project.MSG_ERR); throw new BuildException(message, e); } } if (buildLogger != null) { buildLogger.setMessageOutputLevel(messageOutputLevel); buildLogger.setOutputPrintStream(out); buildLogger.setErrorPrintStream(err); buildLogger.setEmacsMode(emacsMode); if (buildLogger instanceof AbstractEclipseBuildLogger) { ((AbstractEclipseBuildLogger) buildLogger).configure(userProperties); } } return buildLogger; } /** * Project.fireBuildStarted is protected in Ant earlier than 1.5.*. Provides backwards compatibility with old Ant installs. */ protected void fireBuildStarted(Project project) { if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ BuildEvent event = new BuildEvent(project); for (BuildListener listener : project.getBuildListeners()) { listener.buildStarted(event); } } else { project.fireBuildStarted(); } } /** * Sends the the event to the backing project that the build has completed * * @param project * @param error */ protected void fireBuildFinished(Project project, Throwable error) { if (usingXmlLogger()) { // generate the log file in the correct location String fileName = project.getProperty("XmlLogger.file"); //$NON-NLS-1$ if (fileName == null) { fileName = "log.xml"; //$NON-NLS-1$ } String realPath = new Path(getBuildFileLocation()).toFile().getAbsolutePath(); IPath path = new Path(realPath); path = path.removeLastSegments(1); path = path.addTrailingSeparator(); path = path.append(fileName); project.setProperty("XmlLogger.file", path.toOSString()); //$NON-NLS-1$ } if (error == null && executed) { logMessage(project, InternalAntMessages.InternalAntRunner_BUILD_SUCCESSFUL_1, messageOutputLevel); } if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ BuildEvent event = new BuildEvent(project); event.setException(error); for (BuildListener listener : project.getBuildListeners()) { listener.buildFinished(event); } } else { project.fireBuildFinished(error); } } private boolean usingXmlLogger() { if (buildLogger instanceof XmlLogger) { return true; } if (buildListeners != null) { for (BuildListener listener : currentProject.getBuildListeners()) { if (listener instanceof XmlLogger) { return true; } } } return false; } protected void logMessage(Project project, String message, int priority) { if (project != null) { project.log(message, priority); } else { if (buildListeners != null) { Project p = new Project(); addBuildListeners(p, false); p.log(message, priority); } else { IStatus s = new Status(IStatus.ERROR, AntCorePlugin.PI_ANTCORE, AntCorePlugin.INTERNAL_ERROR, message, null); AntCorePlugin.getPlugin().getLog().log(s); } } } /** * Sets the buildFileLocation. * * @param buildFileLocation * the file system location of the build file */ public void setBuildFileLocation(String buildFileLocation) { this.buildFileLocation = buildFileLocation; if (currentProject != null) { currentProject.setUserProperty("ant.file", buildFileLocation); //$NON-NLS-1$ } } /** * Sets the input handler class name. * * @param inputHandlerClassname * the name of the class to use for the input handler */ public void setInputHandler(String inputHandlerClassname) { this.inputHandlerClassname = inputHandlerClassname; } /** * Returns the class name of the input handler to use * * @return the input handler class name */ protected String getInputHandler() { return this.inputHandlerClassname; } protected String getBuildLogger() { return this.loggerClassname; } /** * Returns the location of the buildfile. If one has not been supplied the default location of <code>build.xml</code> will be returned * * @return the absolute location of the build file */ protected String getBuildFileLocation() { if (buildFileLocation == null) { buildFileLocation = new File("build.xml").getAbsolutePath(); //$NON-NLS-1$ } return buildFileLocation; } /** * Sets the message output level. Use -1 for none. * * @param level * The message output level */ public void setMessageOutputLevel(int level) { messageOutputLevel = level; if (buildLogger != null) { buildLogger.setMessageOutputLevel(level); } } /** * Sets the extra user arguments * * @param args * The extra user arguments */ public void setArguments(String[] args) { extraArguments = args; } /** * Sets the execution targets. * * @param executionTargets * The targets to execute for the build */ public void setExecutionTargets(String[] executionTargets) { targets = new Vector<>(executionTargets.length); for (int i = 0; i < executionTargets.length; i++) { targets.add(executionTargets[i]); } } /* * Returns a String representation of the Ant version number as specified in the version.txt file. */ protected String getAntVersionNumber() throws BuildException { if (antVersionNumber == null) { try (InputStream in = Main.class.getResourceAsStream("/org/apache/tools/ant/version.txt")) { //$NON-NLS-1$ Properties props = new Properties(); props.load(in); String versionNumber = props.getProperty("VERSION"); //$NON-NLS-1$ antVersionNumber = versionNumber; } catch (IOException ioe) { throw new BuildException(MessageFormat.format(InternalAntMessages.InternalAntRunner_Could_not_load_the_version_information, new Object[] { ioe.getMessage() }), ioe); } catch (NullPointerException npe) { throw new BuildException(MessageFormat.format(InternalAntMessages.InternalAntRunner_Could_not_load_the_version_information, new Object[] { npe.getMessage() }), npe); } } return antVersionNumber; } /* * Returns whether the given version is compatible with the current Ant version. A version is compatible if it is less than or equal to the * current version. */ protected boolean isVersionCompatible(String comparison) { String version = getAntVersionNumber(); Version osgiVersion = new Version(version); Version osgiComparison = new Version(comparison); return osgiVersion.compareTo(osgiComparison) >= 0; } /** * Pre-processes the raw command line to set up input handling and logging. <br> * <br> * This method checks for the following command line arguments: * <ul> * <li><b>-inputhandler</b> <em><class></em> - the class which will handle input requests</li> * <li><b>-logger</b> <em><classname></em> - the class which is to perform logging</li> * <li><b>-listener</b> <em><classname></em> - add an instance of class as a project listener</li> * </ul> * * @param commands * the raw command line arguments passed in from the application * @return <code>true</code> if it is OK to run with the given list of arguments <code>false</code> otherwise */ protected boolean preprocessCommandLine(List<String> commands) { String arg = AntCoreUtil.getArgument(commands, "-listener"); //$NON-NLS-1$ while (arg != null) { if (arg.length() == 0) { throw new BuildException(InternalAntMessages.InternalAntRunner_specify_a_classname_using_the_listener_argument); } if (buildListeners == null) { buildListeners = new ArrayList<>(1); } buildListeners.add(arg); arg = AntCoreUtil.getArgument(commands, "-listener"); //$NON-NLS-1$ } arg = AntCoreUtil.getArgument(commands, "-logger"); //$NON-NLS-1$ if (arg != null) { if (arg.length() == 0) { throw new BuildException(InternalAntMessages.InternalAntRunner_specify_a_classname_using_the_logger_argument); } loggerClassname = arg; } arg = AntCoreUtil.getArgument(commands, "-logger"); //$NON-NLS-1$ if (arg != null) { throw new BuildException(InternalAntMessages.InternalAntRunner_Only_one_logger_class_may_be_specified); } arg = AntCoreUtil.getArgument(commands, "-inputhandler"); //$NON-NLS-1$ if (arg != null) { if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ throw new BuildException(InternalAntMessages.InternalAntRunner_Specifying_an_InputHandler_is_an_Ant_1_5_feature); } if (arg.length() == 0) { throw new BuildException(InternalAntMessages.InternalAntRunner_specify_a_classname_the_inputhandler_argument); } inputHandlerClassname = arg; } arg = AntCoreUtil.getArgument(commands, "-inputhandler"); //$NON-NLS-1$ if (arg != null) { throw new BuildException(InternalAntMessages.InternalAntRunner_Only_one_input_handler_class_may_be_specified); } return true; } /** * Process the command line arguments. <br> * <br> * The listing of supported Ant command line arguments is: * <ul> * <li><b>-buildfile</b>, <b>-file</b>, <b>-f</b> <em><file></em> - use given buildfile</li> * <li><b>-debug</b>, <b>-d</b> - print debugging information</li> * <li><b>-diagnostics</b> - print information that might be helpful diagnose or report problems</li> * <li><b>-emacs</b>, <b>-e</b> - produce logging information without adornments</li> * <li><b>-find</b>, <b>-s</b> <em><file></em> - search for buildfile towards the root of the filesystem and use it</li> * <li><b>-help</b>, <b>-h</b> - print this message</li> * <li><b>-keep-going</b>, <b>-k</b> - execute all targets that do not depend on failed target(s)</li> * <li><b>-lib</b> <em><path></em> - specifies a path to search for jars and classes</li> * <li><b>-logfile</b>, <b>-l</b> <em><file></em> - use given file for logging</li> * <li><b>-noinput</b> - do not allow interactive input</li> * <li><b>-quiet</b>, <b>-q</b> - be extra quiet</li> * <li><b>-verbose</b>, <b>-v</b> - be extra verbose</li> * <li><b>-version</b> - print the version information and exit</li> * </ul> * The list of other Ant command line arguments that we currently do not support: * <ul> * <li><b>-nice</b> <em><number></em> - A niceness value for the main thread - 1 (lowest) to 10 (highest); 5 is the default</li> * <li><b>-nouserlib</b> - Run ant without using the jar files from ${user.home}/.ant/lib</li> * <li><b>-noclasspath</b> - Run ant without using CLASSPATH</li> * <li><b>-autoproxy</b> - Java 1.5+ : use the OS proxies</li> * <li><b>-main</b> <em><class></em> - override Ant's normal entry point</li> * </ul> * * @param list * the raw command line arguments passed in from the application * @return <code>true</code> if it is OK to run with the given list of arguments <code>false</code> otherwise */ private boolean processCommandLine(List<String> commands) { if (commands.remove("-help") || commands.remove("-h")) { //$NON-NLS-1$ //$NON-NLS-2$ if (isVersionCompatible("1.7")) { //$NON-NLS-1$ new EclipseMainHelper().runUsage(getBuildFileLocation(), currentProject); } else { logMessage(currentProject, InternalAntMessages.InternalAntRunner_ant_1_7_needed_for_help_message, Project.MSG_WARN); } return false; } if (commands.remove("-version")) { //$NON-NLS-1$ printVersion(); return false; } if (commands.remove("-verbose") || commands.remove("-v")) { //$NON-NLS-1$ //$NON-NLS-2$ printVersion(); setMessageOutputLevel(Project.MSG_VERBOSE); } if (commands.remove("-debug") || commands.remove("-d")) { //$NON-NLS-1$ //$NON-NLS-2$ printVersion(); setMessageOutputLevel(Project.MSG_DEBUG); } if (commands.remove("-quiet") || commands.remove("-q")) { //$NON-NLS-1$ //$NON-NLS-2$ setMessageOutputLevel(Project.MSG_WARN); } if (commands.remove("-emacs") || commands.remove("-e")) { //$NON-NLS-1$ //$NON-NLS-2$ emacsMode = true; if (buildLogger != null) { buildLogger.setEmacsMode(true); } } if (commands.remove("-diagnostics")) { //$NON-NLS-1$ if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ throw new BuildException(InternalAntMessages.InternalAntRunner_The_diagnositics_options_is_an_Ant_1_5_feature); } try { Diagnostics.doReport(System.out); } catch (NullPointerException e) { logMessage(currentProject, InternalAntMessages.InternalAntRunner_anthome_must_be_set_to_use_ant_diagnostics, Project.MSG_ERR); } return false; } String arg = AntCoreUtil.getArgument(commands, "-logfile"); //$NON-NLS-1$ if (arg == null) { arg = AntCoreUtil.getArgument(commands, "-l"); //$NON-NLS-1$ } if (arg != null) { if (arg.length() == 0) { String message = InternalAntMessages.InternalAntRunner_specify_a_log_file_using_the_log_argument; logMessage(currentProject, message, Project.MSG_ERR); throw new BuildException(message); } try { createLogFile(arg); } catch (IOException e) { // just log message and ignore exception logMessage(currentProject, MessageFormat.format(InternalAntMessages.InternalAntRunner_Could_not_write_to_log_file, new Object[] { arg }), Project.MSG_ERR); return false; } } arg = AntCoreUtil.getArgument(commands, "-buildfile"); //$NON-NLS-1$ if (arg == null) { arg = AntCoreUtil.getArgument(commands, "-file"); //$NON-NLS-1$ if (arg == null) { arg = AntCoreUtil.getArgument(commands, "-f"); //$NON-NLS-1$ } } if (arg != null) { if (arg.length() == 0) { String message = InternalAntMessages.InternalAntRunner_specify_a_buildfile_using_the_buildfile_argument; logMessage(currentProject, message, Project.MSG_ERR); throw new BuildException(message); } setBuildFileLocation(arg); } if (isVersionCompatible("1.6")) { //$NON-NLS-1$ if (commands.remove("-k") || commands.remove("-keep-going")) { //$NON-NLS-1$ //$NON-NLS-2$ keepGoing = true; } if (commands.remove("-noinput")) { //$NON-NLS-1$ allowInput = false; } arg = AntCoreUtil.getArgument(commands, "-lib"); //$NON-NLS-1$ if (arg != null) { logMessage(currentProject, InternalAntMessages.InternalAntRunner_157, Project.MSG_ERR); return false; } } arg = AntCoreUtil.getArgument(commands, "-find"); //$NON-NLS-1$ if (arg == null) { arg = AntCoreUtil.getArgument(commands, "-s"); //$NON-NLS-1$ } if (arg != null) { logMessage(currentProject, InternalAntMessages.InternalAntRunner_find_not_supported, Project.MSG_ERR); return false; } if (!commands.isEmpty()) { processUnrecognizedCommands(commands); } if (!commands.isEmpty()) { processUnrecognizedTargets(commands); } if (!commands.isEmpty()) { processTargets(commands); } if (unknownTargetsFound && (targets == null || targets.isEmpty())) { // Some targets are specified but none of them are good. Hence quit // https://bugs.eclipse.org/bugs/show_bug.cgi?id=352536 logMessage(currentProject, InternalAntMessages.InternalAntRunner_no_known_target, Project.MSG_ERR); return false; } return true; } /** * Checks for unrecognized targets on the command line and removes them. * * @since 3.6 */ private void processUnrecognizedTargets(List<String> commands) { List<String> names = getTargetNames(); ListIterator<String> iterator = commands.listIterator(); while (iterator.hasNext()) { String target = iterator.next(); if (!names.contains(target)) { iterator.remove(); String message = MessageFormat.format(InternalAntMessages.InternalAntRunner_unknown_target, new Object[] { target }); logMessage(currentProject, message, Project.MSG_WARN); unknownTargetsFound = true; } } } /** * Checks for unrecognized arguments on the command line. Since there is no syntactic way to distinguish between ant -foo target1 target2 ant -foo * fooarg target we remove everything up to the last argument that begins with a '-'. In the latter case, above, that means that there will be an * extra target, 'fooarg', left lying around. */ protected void processUnrecognizedCommands(List<String> commands) { int p = -1; // find the last arg that begins with '-' for (int i = commands.size() - 1; i >= 0; i--) { if (commands.get(i).startsWith("-")) { //$NON-NLS-1$ p = i; break; } } if (p < 0) { return; } // remove everything preceding that last '-arg' String s = IAntCoreConstants.EMPTY_STRING; for (int i = 0; i <= p; i++) { s += " " + (commands.get(0)); //$NON-NLS-1$ commands.remove(0); } // warn of ignored commands String message = MessageFormat.format(InternalAntMessages.InternalAntRunner_Unknown_argument, new Object[] { s.substring(1) }); logMessage(currentProject, message, Project.MSG_WARN); } /** * Checks for targets specified at the command line. */ protected void processTargets(List<String> commands) { if (targets == null) { targets = new Vector<>(commands.size()); } for (Iterator<String> iter = commands.iterator(); iter.hasNext();) { targets.add(iter.next()); } } /** * Creates the log file with the name specified by the user. If the fileName is not absolute, the file will be created in the working directory if * specified or in the same directory as the location of the build file. */ protected void createLogFile(String fileName) throws FileNotFoundException, IOException { File logFile = AntCoreUtil.getFileRelativeToBaseDir(fileName, currentProject.getUserProperty("basedir"), getBuildFileLocation()); //$NON-NLS-1$ // this stream is closed in the finally block of run(list) out = new PrintStream(new FileOutputStream(logFile)); err = out; logMessage(currentProject, MessageFormat.format(InternalAntMessages.InternalAntRunner_Using_file_as_build_log, new Object[] { logFile.getCanonicalPath() }), Project.MSG_INFO); if (buildLogger != null) { buildLogger.setErrorPrintStream(err); buildLogger.setOutputPrintStream(out); } } /** * Processes the command line properties and adds the user properties. <br> * <br> * Any user properties that have been explicitly set are set as well. Ensures that -D properties take precedence. <br> * <br> * This command lie arguments used are: * <ul> * <li><b>-D</b> <em><property>=<value></em> - use value for given property</li> * <li><b>-propertyfile</b> <em><name></em> - load all properties from file with -D properties taking precedence</li> * </ul> */ private boolean processProperties(List<String> commands) { boolean exceptionToBeThrown = false; // MULTIPLE property files are allowed String arg = AntCoreUtil.getArgument(commands, "-propertyfile"); //$NON-NLS-1$ while (arg != null) { if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ fEarlyErrorMessage = InternalAntMessages.InternalAntRunner_Specifying_property_files_is_a_Ant_1_5_feature; break; } if (arg.length() == 0) { fEarlyErrorMessage = InternalAntMessages.InternalAntRunner_specify_a_property_filename_when_using_propertyfile_argument; exceptionToBeThrown = true; break; } propertyFiles.add(arg); arg = AntCoreUtil.getArgument(commands, "-propertyfile"); //$NON-NLS-1$ } String[] globalPropertyFiles = AntCorePlugin.getPlugin().getPreferences().getCustomPropertyFiles(); if (globalPropertyFiles.length > 0) { if (!isVersionCompatible("1.5")) { //$NON-NLS-1$ fEarlyErrorMessage = InternalAntMessages.InternalAntRunner_Specifying_property_files_is_a_Ant_1_5_feature; } else { if (propertyFiles == null) { propertyFiles = new ArrayList<>(globalPropertyFiles.length); } propertyFiles.addAll(Arrays.asList(globalPropertyFiles)); } } if (propertyFiles != null && !propertyFiles.isEmpty()) { loadPropertyFiles(); } if (commands != null) { processMinusDProperties(commands); } return exceptionToBeThrown; } /** * Process properties specified using <code>-D</code> * * @param commands */ protected void processMinusDProperties(List<String> commands) { if (!commands.isEmpty() && userProperties == null) { userProperties = new HashMap<>(); } AntCoreUtil.processMinusDProperties(commands, userProperties); } /** * Logs a message with the client indicating the version of <b>Ant</b> that this class fronts. */ protected void printVersion() { logMessage(currentProject, Main.getAntVersion(), Project.MSG_INFO); } /** * Sets the build progress monitor. * * @param monitor * The progress monitor to use */ public void setProgressMonitor(IProgressMonitor monitor) { this.monitor = monitor; } /** * Returns the current {@link Project} context * * @return the current {@link Project} */ protected Project getCurrentProject() { return currentProject; } /** * Sets the current {@link Project} context * * @param currentProject * the new {@link Project} */ protected void setCurrentProject(Project currentProject) { this.currentProject = currentProject; } public String getBuildExceptionErrorMessage(Throwable t) { if (t instanceof BuildException) { return t.toString(); } return null; } /** * Load all properties from the files specified by -propertyfile. */ protected void loadPropertyFiles() { if (userProperties == null) { userProperties = new HashMap<>(); } try { List<Properties> allProperties = AntCoreUtil.loadPropertyFiles(propertyFiles, currentProject.getUserProperty("basedir"), getBuildFileLocation()); //$NON-NLS-1$ Iterator<Properties> iter = allProperties.iterator(); while (iter.hasNext()) { Properties props = iter.next(); Enumeration<?> propertyNames = props.propertyNames(); while (propertyNames.hasMoreElements()) { String name = (String) propertyNames.nextElement(); // most specific to global // do not overwrite specific with a global property if (userProperties.get(name) == null) { userProperties.put(name, props.getProperty(name)); } } } } catch (IOException e) { fEarlyErrorMessage = MessageFormat.format(InternalAntMessages.InternalAntRunner_could_not_load_property_file, new Object[] { e.getMessage() }); } } /** * Creates the InputHandler and adds it to the project. * * @exception BuildException * if a specified InputHandler implementation could not be loaded. */ protected void addInputHandler(Project project) { if (!isVersionCompatible("1.5") || (inputHandlerClassname != null && inputHandlerClassname.length() == 0)) { //$NON-NLS-1$ return; } InputHandlerSetter setter = new InputHandlerSetter(); setter.setInputHandler(project, inputHandlerClassname); } /* * Sets the Java class path in org.apache.tools.ant.types.Path */ private void setJavaClassPath() { URL[] antClasspath = null; AntCorePreferences prefs = AntCorePlugin.getPlugin().getPreferences(); if (customClasspath == null) { antClasspath = prefs.getURLs(); } else { URL[] extraClasspath = prefs.getExtraClasspathURLs(); antClasspath = new URL[customClasspath.length + extraClasspath.length]; System.arraycopy(customClasspath, 0, antClasspath, 0, customClasspath.length); System.arraycopy(extraClasspath, 0, antClasspath, customClasspath.length, extraClasspath.length); } StringBuffer buff = new StringBuffer(); File file = null; for (int i = 0; i < antClasspath.length; i++) { try { file = new File(FileLocator.toFileURL(antClasspath[i]).getPath()); } catch (IOException e) { continue; } buff.append(file.getAbsolutePath()); buff.append("; "); //$NON-NLS-1$ } org.apache.tools.ant.types.Path systemClasspath = new org.apache.tools.ant.types.Path(null, buff.substring(0, buff.length() - 2)); org.apache.tools.ant.types.Path.systemClasspath = systemClasspath; } /** * Sets the custom classpath to be included when setting the Java classpath for this build. * * @param classpath * The custom classpath for this build. */ public void setCustomClasspath(URL[] classpath) { customClasspath = classpath; } }