// NAnt - A .NET build tool
// Copyright (C) 2001-2002 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Gerry Shaw (gerry_shaw@yahoo.com)
// Mike Krueger (mike@icsharpcode.net)
// Ian MacLean (imaclean@gmail.com)
// William E. Caputo (wecaputo@thoughtworks.com | logosity@yahoo.com)
// Gert Driesen (gert.driesen@ardatis.com)
using System;
using System.Globalization;
using System.Reflection;
using System.Xml;
using NAnt.Core.Attributes;
using NAnt.Core.Util;
namespace NAnt.Core {
///
/// Provides the abstract base class for tasks.
///
///
/// A task is a piece of code that can be executed.
///
[Serializable()]
public abstract class Task : Element {
#region Private Static Fields
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endregion Private Static Fields
#region Private Instance Fields
private bool _failOnError = true;
private bool _verbose = false;
private bool _ifDefined = true;
private bool _unlessDefined = false;
private Level _threshold = Level.Debug;
#endregion Private Instance Fields
#region Public Instance Properties
///
/// Determines if task failure stops the build, or is just reported.
/// The default is .
///
[TaskAttribute("failonerror")]
[BooleanValidator()]
public bool FailOnError {
get { return _failOnError; }
set { _failOnError = value; }
}
///
/// Determines whether the task should report detailed build log messages.
/// The default is .
///
[TaskAttribute("verbose")]
[BooleanValidator()]
public virtual bool Verbose {
get { return (_verbose || Project.Verbose); }
set { _verbose = value; }
}
///
/// If then the task will be executed; otherwise,
/// skipped. The default is .
///
[TaskAttribute("if")]
[BooleanValidator()]
public bool IfDefined {
get { return _ifDefined; }
set { _ifDefined = value; }
}
///
/// Opposite of . If
/// then the task will be executed; otherwise, skipped. The default is
/// .
///
[TaskAttribute("unless")]
[BooleanValidator()]
public bool UnlessDefined {
get { return _unlessDefined; }
set { _unlessDefined = value; }
}
///
/// The name of the task.
///
public override string Name {
get {
string name = null;
TaskNameAttribute taskName = (TaskNameAttribute) Attribute.GetCustomAttribute(GetType(), typeof(TaskNameAttribute));
if (taskName != null) {
name = taskName.Name;
}
return name;
}
}
///
/// The prefix used when sending messages to the log.
///
[Obsolete("Will be removed soon", false)]
public string LogPrefix {
get {
string prefix = "[" + Name + "] ";
return prefix.PadLeft(Project.IndentationSize);
}
}
///
/// Gets or sets the log threshold for this . By
/// default the threshold of a task is ,
/// causing no messages to be filtered in the task itself.
///
///
/// The log threshold level for this .
///
///
/// When the threshold of a is higher than the
/// threshold of the , then all messages will
/// still be delivered to the build listeners.
///
public Level Threshold {
get { return _threshold; }
set { _threshold = value; }
}
#endregion Public Instance Properties
#region Public Instance Methods
///
/// Executes the task unless it is skipped.
///
public void Execute() {
logger.Debug(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_TaskExecute"),
Name));
if (IfDefined && !UnlessDefined) {
try {
Project.OnTaskStarted(this, new BuildEventArgs(this));
ExecuteTask();
} catch (Exception ex) {
logger.Error(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1077"),
Name), ex);
if (FailOnError) {
throw;
} else {
if (this.Verbose) {
// output exception (with stacktrace) to build log
Log(Level.Error, ex.ToString());
} else {
string msg = ex.Message;
// get first nested exception
Exception nestedException = ex.InnerException;
// set initial indentation level for the nested exceptions
int exceptionIndentationLevel = 0;
// output message of nested exceptions
while (nestedException != null && !StringUtils.IsNullOrEmpty(nestedException.Message)) {
// indent exception message with 4 extra spaces
// (for each nesting level)
exceptionIndentationLevel += 4;
// start new line for each exception level
msg = (msg != null) ? msg + Environment.NewLine : string.Empty;
// output exception message
msg += new string(' ', exceptionIndentationLevel)
+ nestedException.Message;
// move on to next inner exception
nestedException = nestedException.InnerException;
}
// output message of exception(s) to build log
Log(Level.Error, msg);
}
}
} finally {
Project.OnTaskFinished(this, new BuildEventArgs(this));
}
}
}
///
/// Logs a message with the given priority.
///
/// The message priority at which the specified message is to be logged.
/// The message to be logged.
///
///
/// The actual logging is delegated to the project.
///
///
/// If the attribute is set on the task and a
/// message is logged with level , the
/// priority of the message will be increased to
/// when the threshold of the build log is .
///
///
/// This will allow individual tasks to run in verbose mode while
/// the build log itself is still configured with threshold
/// .
///
///
/// The threshold of the project is not taken into account to determine
/// whether a message should be passed to the logging infrastructure,
/// as build listeners might be interested in receiving all messages.
///
///
public override void Log(Level messageLevel, string message) {
if (!IsLogEnabledFor(messageLevel)) {
return;
}
if (_verbose && messageLevel == Level.Verbose && Project.Threshold == Level.Info) {
Project.Log(this, Level.Info, message);
} else {
Project.Log(this, messageLevel, message);
}
}
///
/// Logs a formatted message with the given priority.
///
/// The message priority at which the specified message is to be logged.
/// The message to log, containing zero or more format items.
/// An array containing zero or more objects to format.
///
///
/// The actual logging is delegated to the project.
///
///
/// If the attribute is set on the task and a
/// message is logged with level , the
/// priority of the message will be increased to .
/// when the threshold of the build log is .
///
///
/// This will allow individual tasks to run in verbose mode while
/// the build log itself is still configured with threshold
/// .
///
///
public override void Log(Level messageLevel, string message, params object[] args) {
string logMessage = string.Format(CultureInfo.InvariantCulture, message, args);
Log(messageLevel, logMessage);
}
///
/// Determines whether build output is enabled for the given
/// .
///
/// The to check.
///
/// if messages with the given
/// should be passed on to the logging infrastructure; otherwise,
/// .
///
///
/// The threshold of the project is not taken into account to determine
/// whether a message should be passed to the logging infrastructure,
/// as build listeners might be interested in receiving all messages.
///
public bool IsLogEnabledFor(Level messageLevel) {
if (_verbose && messageLevel == Level.Verbose) {
return Level.Info >= Threshold;
}
return (messageLevel >= Threshold);
}
///
/// Initializes the configuration of the task using configuration
/// settings retrieved from the NAnt configuration file.
///
///
/// TO-DO : Remove this temporary hack when a permanent solution is
/// available for loading the default values from the configuration
/// file if a build element is constructed from code.
///
public void InitializeTaskConfiguration() {
PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo propertyInfo in properties) {
XmlNode attributeNode = null;
string attributeValue = null;
FrameworkConfigurableAttribute frameworkAttribute = (FrameworkConfigurableAttribute)
Attribute.GetCustomAttribute(propertyInfo, typeof(FrameworkConfigurableAttribute));
if (frameworkAttribute != null) {
// locate XML configuration node for current attribute
attributeNode = GetAttributeConfigurationNode(
Project.TargetFramework, frameworkAttribute.Name);
if (attributeNode != null) {
// get the configured value
attributeValue = attributeNode.InnerText;
if (frameworkAttribute.ExpandProperties && Project.TargetFramework != null) {
try {
// expand attribute properites
attributeValue = Project.TargetFramework.Project.Properties.ExpandProperties(
attributeValue, Location);
} catch (Exception ex) {
// throw BuildException if required
if (frameworkAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1075"), frameworkAttribute.Name, Name), Location, ex);
}
// set value to null
attributeValue = null;
}
}
} else {
// check if its required
if (frameworkAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"'{0}' is a required framework configuration setting for the '{1}'"
+ " build element that should be set in the NAnt configuration file.",
frameworkAttribute.Name, Name), Location);
}
}
if (attributeValue != null) {
if (propertyInfo.CanWrite) {
Type propertyType = propertyInfo.PropertyType;
//validate attribute value with custom ValidatorAttribute(ors)
object[] validateAttributes = (ValidatorAttribute[])
Attribute.GetCustomAttributes(propertyInfo, typeof(ValidatorAttribute));
try {
foreach (ValidatorAttribute validator in validateAttributes) {
logger.Info(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1074"),
attributeValue, Name, validator.GetType().Name));
validator.Validate(attributeValue);
}
} catch (ValidationException ve) {
logger.Error("Validation Exception", ve);
throw new ValidationException("Validation failed on" + propertyInfo.DeclaringType.FullName, Location, ve);
}
// holds the attribute value converted to the property type
object propertyValue = null;
// If the object is an emum
if (propertyType.IsEnum) {
try {
propertyValue = Enum.Parse(propertyType, attributeValue);
} catch (Exception) {
// catch type conversion exceptions here
string message = "Invalid configuration value \"" + attributeValue + "\". Valid values for this attribute are: ";
foreach (object value in Enum.GetValues(propertyType)) {
message += value.ToString() + ", ";
}
// strip last ,
message = message.Substring(0, message.Length - 2);
throw new BuildException(message, Location);
}
} else {
propertyValue = Convert.ChangeType(attributeValue, propertyInfo.PropertyType, CultureInfo.InvariantCulture);
}
//set property value
propertyInfo.SetValue(this, propertyValue, BindingFlags.Public | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
}
}
}
}
}
#endregion Public Instance Methods
#region Protected Instance Methods
/// Deprecated (to be deleted).
[Obsolete("Deprecated- Use InitializeTask instead")]
protected override void InitializeElement(XmlNode elementNode) {
// Just defer for now so that everything just works
InitializeTask(elementNode);
}
/// Initializes the task.
protected virtual void InitializeTask(XmlNode taskNode) {
}
/// Executes the task.
protected abstract void ExecuteTask();
#endregion Protected Instance Methods
}
}