// 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 } }