blob: 7b6eb8bcfe94608fa04474bd2333a74bd5f3114f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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>&lt;class&gt;</em> - the class which will handle input requests</li>
* <li><b>-logger</b> <em>&lt;classname&gt;</em> - the class which is to perform logging</li>
* <li><b>-listener</b> <em>&lt;classname&gt;</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>&lt;file&gt;</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>&lt;file&gt;</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>&lt;path&gt;</em> - specifies a path to search for jars and classes</li>
* <li><b>-logfile</b>, <b>-l</b> <em>&lt;file&gt;</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>&lt;number&gt;</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>&lt;class&gt;</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>&lt;property&gt;=&lt;value&gt;</em> - use value for given property</li>
* <li><b>-propertyfile</b> <em>&lt;name&gt;</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;
}
}