// NAnt - A .NET build tool // Copyright (C) 2001-2003 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) // Ian MacLean (imaclean@gmail.com) // Scott Hernandez (ScottHernandez@hotmail.com) // William E. Caputo (wecaputo@thoughtworks.com | logosity@yahoo.com) using System; using System.Collections; using System.Collections.Specialized; using System.Configuration; using System.Globalization; using System.IO; using System.Reflection; using System.Text; using System.Xml; using Microsoft.Win32; using NAnt.Core.Tasks; using NAnt.Core.Util; namespace NAnt.Core { /// /// Central representation of a NAnt project. /// /// /// /// The method will initialize the project with the build /// file specified in the constructor and execute the default target. /// /// /// /// /// /// /// /// If no target is given, the default target will be executed if specified /// in the project. /// /// /// /// /// [Serializable()] public class Project { #region Private Static Fields /// /// Holds the logger for this class. /// private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); // XML element and attribute names that are not defined in metadata private const string RootXml = "project"; private const string ProjectNameAttribute = "name"; private const string ProjectDefaultAttribte = "default"; private const string ProjectBaseDirAttribute = "basedir"; private const string TargetXml = "target"; private const string WildTarget = "*"; /// /// Constant for the "visiting" state, used when traversing a DFS of /// target dependencies. /// private const string Visiting = "VISITING"; /// /// Constant for the "visited" state, used when traversing a DFS of /// target dependencies. /// private const string Visited = "VISITED"; #endregion Private Static Fields #region Internal Static Fields // named properties internal const string NAntPlatform = "nant.platform"; internal const string NAntPlatformName = NAntPlatform + ".name"; internal const string NAntPropertyFileName = "nant.filename"; internal const string NAntPropertyVersion = "nant.version"; internal const string NAntPropertyLocation = "nant.location"; internal const string NAntPropertyProjectName = "nant.project.name"; internal const string NAntPropertyProjectBuildFile = "nant.project.buildfile"; internal const string NAntPropertyProjectBaseDir = "nant.project.basedir"; internal const string NAntPropertyProjectDefault = "nant.project.default"; internal const string NAntPropertyOnSuccess = "nant.onsuccess"; internal const string NAntPropertyOnFailure = "nant.onfailure"; #endregion Internal Static Fields #region Public Instance Events public event BuildEventHandler BuildStarted; public event BuildEventHandler BuildFinished; public event BuildEventHandler TargetStarted; public event BuildEventHandler TargetFinished; public event BuildEventHandler TaskStarted; public event BuildEventHandler TaskFinished; public event BuildEventHandler MessageLogged; #endregion Public Instance Events #region Private Instance Fields private string _baseDirectory; private string _projectName = ""; private string _defaultTargetName; private int _indentationSize = 4; private int _indentationLevel = 0; private BuildListenerCollection _buildListeners = new BuildListenerCollection(); private StringCollection _buildTargets = new StringCollection(); private TargetCollection _targets = new TargetCollection(); private LocationMap _locationMap = new LocationMap(); private PropertyDictionary _properties; private PropertyDictionary _frameworkNeutralProperties; private Target _currentTarget; // info about frameworks private FrameworkInfoDictionary _frameworks = new FrameworkInfoDictionary(); private FrameworkInfo _runtimeFramework; private FrameworkInfo _targetFramework; [NonSerialized()] private XmlNode _configurationNode; [NonSerialized()] private XmlDocument _doc; // set in ctorHelper [NonSerialized()] private XmlNamespaceManager _nsMgr = new XmlNamespaceManager(new NameTable()); //used to map "nant" to default namespace. [NonSerialized()] private DataTypeBaseDictionary _dataTypeReferences = new DataTypeBaseDictionary(); /// /// Holds the default threshold for build loggers. /// private Level _threshold = Level.Info; #endregion Private Instance Fields #region Public Instance Constructors /// /// Initializes a new class with the given /// document, message threshold and indentation level. /// /// Any valid build format will do. /// The message threshold. /// The project indentation level. public Project(XmlDocument doc, Level threshold, int indentLevel) { // use NAnt settings from application configuration file for loading // internal configuration settings _configurationNode = GetConfigurationNode(); // initialize project CtorHelper(doc, threshold, indentLevel, Optimizations.None); } /// /// Initializes a new class with the given /// document, message threshold and indentation level, and using /// the specified to load internal configuration /// settings. /// /// Any valid build format will do. /// The message threshold. /// The project indentation level. /// The NAnt should use to initialize configuration settings. /// /// This constructor is useful for developers using NAnt as a class /// library. /// public Project(XmlDocument doc, Level threshold, int indentLevel, XmlNode configurationNode) { // set configuration node to use for loading internal configuration // settings _configurationNode = configurationNode; // initialize project CtorHelper(doc, threshold, indentLevel, Optimizations.None); } /// /// Initializes a new class with the given /// source, message threshold and indentation level. /// /// /// The full path to the build file. /// This can be of any form that accepts. /// /// The message threshold. /// The project indentation level. /// /// If the source is a uri of form 'file:///path' then use the path part. /// public Project(string uriOrFilePath, Level threshold, int indentLevel) { // use NAnt settings from application configuration file for loading // internal configuration settings _configurationNode = GetConfigurationNode(); // initialize project CtorHelper(LoadBuildFile(uriOrFilePath), threshold, indentLevel, Optimizations.None); } /// /// Initializes a new class with the given /// source, message threshold and indentation level, and using /// the specified to load internal configuration /// settings. /// /// /// The full path to the build file. /// This can be of any form that accepts. /// /// The message threshold. /// The project indentation level. /// The NAnt should use to initialize configuration settings. /// is . /// /// If the source is a uri of form 'file:///path' then use the path part. /// public Project(string uriOrFilePath, Level threshold, int indentLevel, XmlNode configurationNode) { // set configuration node to use for loading internal configuration // settings _configurationNode = configurationNode; // initialize project CtorHelper(LoadBuildFile(uriOrFilePath), threshold, indentLevel, Optimizations.None); } #endregion Public Instance Constructors #region Internal Instance Constructors /// /// Initializes a as subproject of the specified /// . /// /// /// The full path to the build file. /// This can be of any form that accepts. /// /// The parent . /// /// Optimized for framework initialization projects, by skipping automatic /// discovery of extension assemblies and framework configuration. /// internal Project(string uriOrFilePath, Project parent) { // set configuration node to use for loading internal configuration // settings _configurationNode = parent.ConfigurationNode; // initialize project CtorHelper(LoadBuildFile(uriOrFilePath), parent.Threshold, parent.IndentationLevel + 1, Optimizations.SkipFrameworkConfiguration); // add listeners of current project to new project AttachBuildListeners(parent.BuildListeners); // inherit discovered frameworks from current project foreach (FrameworkInfo framework in parent.Frameworks) { Frameworks.Add(framework.Name, framework); } // have the new project inherit the runtime framework from the // current project RuntimeFramework = parent.RuntimeFramework; // have the new project inherit the current framework from the // current project TargetFramework = parent.TargetFramework; } /// /// Initializes a with /// set to , and /// set to 0. /// /// An containing the build script. /// /// Optimized for framework initialization projects, by skipping automatic /// discovery of extension assemblies and framework configuration. /// internal Project(XmlDocument doc) { // initialize project CtorHelper(doc, Level.None, 0, Optimizations.SkipAutomaticDiscovery | Optimizations.SkipFrameworkConfiguration); } #endregion Internal Instance Constructors #region Public Instance Properties /// /// Gets or sets the indendation level of the build output. /// /// /// The indentation level of the build output. /// /// /// To change the , the /// and methods should be used. /// public int IndentationLevel { get { return _indentationLevel; } } /// /// Gets or sets the indentation size of the build output. /// /// /// The indendation size of the build output. /// public int IndentationSize { get { return _indentationSize; } } /// /// Gets or sets the default threshold level for build loggers. /// /// /// The default threshold level for build loggers. /// public Level Threshold { get { return _threshold; } set { _threshold = value; } } /// /// Gets the name of the . /// /// /// The name of the or an empty /// if no name is specified. /// public string ProjectName { get { return _projectName; } } /// /// Gets or sets the base directory used for relative references. /// /// /// The base directory used for relative references. /// /// The directory is not rooted. /// /// /// The gets and sets the built-in property /// named "nant.project.basedir". /// /// public string BaseDirectory { get { if (_baseDirectory == null) { return null; } if (!Path.IsPathRooted(_baseDirectory)) { throw new BuildException(string.Format(CultureInfo.InstalledUICulture, "Invalid base directory '{0}'. The project base directory" + "must be rooted.", _baseDirectory), Location.UnknownLocation); } return _baseDirectory; } set { if (!Path.IsPathRooted(value)) { throw new BuildException(string.Format(CultureInfo.InstalledUICulture, "Invalid base directory '{0}'. The project base directory" + "must be rooted.", value), Location.UnknownLocation); } Properties[NAntPropertyProjectBaseDir] = _baseDirectory = value; } } /// /// Gets the . /// /// /// The . /// /// /// The defines the current namespace /// scope and provides methods for looking up namespace information. /// public XmlNamespaceManager NamespaceManager { get { return _nsMgr; } } /// /// Gets the form of the current project definition. /// /// /// The form of the current project definition. /// public Uri BuildFileUri { get { //TODO: Need to remove this. if (Document == null || StringUtils.IsNullOrEmpty(Document.BaseURI)) { return null; //new Uri("http://localhost"); } else { // manually escape '#' in URI (why doesn't .NET do this?) to allow // projects in paths containing a '#' character string escapedUri = Document.BaseURI.Replace("#", Uri.HexEscape('#')); // return escaped URI return new Uri(escapedUri); } } } /// /// Gets a collection of available .NET frameworks. /// /// /// A collection of available .NET frameworks. /// public FrameworkInfoDictionary Frameworks { get { return _frameworks; } } /// /// Gets the framework in which NAnt is currently running. /// /// /// The framework in which NAnt is currently running. /// public FrameworkInfo RuntimeFramework { get { return _runtimeFramework; } set { _runtimeFramework = value; } } /// /// Gets or sets the framework to use for compilation. /// /// /// The framework to use for compilation. /// /// /// We will use compiler tools and system assemblies for this framework /// in framework-related tasks. /// public FrameworkInfo TargetFramework { get { return _targetFramework; } set { _targetFramework = value; UpdateTargetFrameworkProperties(); } } /// /// Gets the name of the platform on which NAnt is currently running. /// /// /// The name of the platform on which NAnt is currently running. /// /// /// /// Possible values are: /// /// /// /// win32 /// /// /// unix /// /// /// /// NAnt does not support the current platform. public string PlatformName { get { if (PlatformHelper.IsWin32) { return "win32"; } else if (PlatformHelper.IsUnix) { return "unix"; } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA1060"), Environment.OSVersion.Platform.ToString(CultureInfo.InvariantCulture), (int) Environment.OSVersion.Platform)); } } } /// /// Gets the current target. /// /// /// The current target, or if no target is /// executing. /// public Target CurrentTarget { get { return _currentTarget; } } /// /// Gets the path to the build file. /// /// /// The path to the build file, or if the build /// document is not file backed. /// public string BuildFileLocalName { get { if (BuildFileUri != null && BuildFileUri.IsFile) { return BuildFileUri.LocalPath; } else { return null; } } } /// /// Gets the active definition. /// /// /// The active definition. /// public XmlDocument Document { get { return _doc; } } /// /// Gets the NAnt should use to initialize /// configuration settings. /// /// /// The NAnt should use to initialize /// configuration settings. /// public XmlNode ConfigurationNode { get { return _configurationNode; } } /// /// Gets the name of the target that will be executed when no other /// build targets are specified. /// /// /// The name of the target that will be executed when no other /// build targets are specified, or if no /// default target is specified in the build file. /// public string DefaultTargetName { get { return _defaultTargetName; } } /// /// Gets a value indicating whether tasks should output more build log /// messages. /// /// /// if tasks should output more build log message; /// otherwise, . /// public bool Verbose { get { return Level.Verbose >= Threshold; } } /// /// The list of targets to build. /// /// /// Targets are built in the order they appear in the collection. If /// the collection is empty the default target will be built. /// public StringCollection BuildTargets { get { return _buildTargets; } } /// /// Gets the properties defined in this project. /// /// The properties defined in this project. /// /// /// This is the collection of properties that are defined by the system /// and property task statements. /// /// /// These properties can be used in expansion. /// /// public PropertyDictionary Properties { get { return _properties; } } /// /// Gets the framework-neutral properties defined in the NAnt /// configuration file. /// /// /// The framework-neutral properties defined in the NAnt configuration /// file. /// /// /// /// This is the collection of read-only properties that are defined in /// the NAnt configuration file. /// /// /// These properties can only be used for expansion in framework-specific /// and framework-neutral configuration settings. These properties are /// not available for expansion in the build file. /// /// public PropertyDictionary FrameworkNeutralProperties { get { return _frameworkNeutralProperties; } } /// /// Gets the instances defined in this project. /// /// /// The instances defined in this project. /// /// /// /// This is the collection of instances that /// are defined by (eg fileset) declarations. /// /// public DataTypeBaseDictionary DataTypeReferences { get { return _dataTypeReferences; } } /// /// Gets the targets defined in this project. /// /// /// The targets defined in this project. /// public TargetCollection Targets { get { return _targets; } } /// /// Gets the build listeners for this project. /// /// /// The build listeners for this project. /// public BuildListenerCollection BuildListeners { get { return _buildListeners; } } #endregion Public Instance Properties #region Internal Instance Properties internal LocationMap LocationMap { get { return _locationMap; } } #endregion Internal Instance Properties #region Public Instance Methods /// /// Dispatches a event to the build listeners /// for this . /// /// The source of the event. /// A that contains the event data. public void OnBuildStarted(object sender, BuildEventArgs e) { if (BuildStarted != null) { BuildStarted(sender, e); } } /// /// Dispatches a event to the build listeners /// for this . /// /// The source of the event. /// A that contains the event data. public void OnBuildFinished(object sender, BuildEventArgs e) { if (BuildFinished != null) { BuildFinished(sender, e); } } /// /// Dispatches a event to the build listeners /// for this . /// /// The source of the event. /// A that contains the event data. public void OnTargetStarted(object sender, BuildEventArgs e) { if (TargetStarted != null) { TargetStarted(sender, e); } } /// /// Dispatches a event to the build listeners /// for this . /// /// The source of the event. /// A that contains the event data. public void OnTargetFinished(object sender, BuildEventArgs e) { if (TargetFinished != null) { TargetFinished(sender, e); } } /// /// Dispatches a event to the build listeners /// for this . /// /// The source of the event. /// A that contains the event data. public void OnTaskStarted(object sender, BuildEventArgs e) { if (TaskStarted != null) { TaskStarted(sender, e); } } /// /// Dispatches the event to the build listeners /// for this . /// /// The source of the event. /// A that contains the event data. public void OnTaskFinished(object sender, BuildEventArgs e) { if (TaskFinished != null) { TaskFinished(sender, e); } } /// /// Dispatches a event to the build listeners /// for this . /// /// A that contains the event data. public void OnMessageLogged(BuildEventArgs e) { if (MessageLogged != null) { MessageLogged(this, e); } } /// /// Writes a level message to the build log with /// the given . /// /// The to log at. /// The message to log. public void Log(Level messageLevel, string message) { BuildEventArgs eventArgs = new BuildEventArgs(this); eventArgs.Message = message; eventArgs.MessageLevel = messageLevel; OnMessageLogged(eventArgs); } /// /// Writes a level formatted message to the build /// log with the given . /// /// The to log at. /// The message to log, containing zero or more format items. /// An array containing zero or more objects to format. public void Log(Level messageLevel, string message, params object[] args) { BuildEventArgs eventArgs = new BuildEventArgs(this); eventArgs.Message = string.Format(CultureInfo.InvariantCulture, message, args); eventArgs.MessageLevel = messageLevel; OnMessageLogged(eventArgs); } /// /// Writes a task level message to the build log /// with the given . /// /// The from which the message originated. /// The to log at. /// The message to log. public void Log(Task task, Level messageLevel, string message) { BuildEventArgs eventArgs = new BuildEventArgs(task); eventArgs.Message = message; eventArgs.MessageLevel = messageLevel; OnMessageLogged(eventArgs); } /// /// Writes a level message to the build log with /// the given . /// /// The from which the message orignated. /// The level to log at. /// The message to log. public void Log(Target target, Level messageLevel, string message) { BuildEventArgs eventArgs = new BuildEventArgs(target); eventArgs.Message = message; eventArgs.MessageLevel = messageLevel; OnMessageLogged(eventArgs); } /// /// Executes the default target. /// /// /// No top level error handling is done. Any /// will be passed onto the caller. /// public virtual void Execute() { if (BuildTargets.Count == 0 && !StringUtils.IsNullOrEmpty(DefaultTargetName)) { BuildTargets.Add(DefaultTargetName); } //log the targets specified, or the default target if specified. StringBuilder sb = new StringBuilder(); if (BuildTargets != null) { foreach(string target in BuildTargets) { sb.Append(target); sb.Append(" "); } } if(sb.Length > 0) { Log(Level.Info, "Target(s) specified: " + sb.ToString()); Log(Level.Info, string.Empty); } else { Log(Level.Info, string.Empty); } // initialize the list of Targets, and execute any global tasks. InitializeProjectDocument(Document); if (BuildTargets.Count == 0) { //It is okay if there are no targets defined in a build file. //It just means we have all global tasks. -- skot //throw new BuildException("No Target Specified"); } else { foreach (string targetName in BuildTargets) { //do not force dependencies of build targets. Execute(targetName, false); } } } /// /// Executes a specific target, and its dependencies. /// /// The name of the target to execute. /// /// Global tasks are not executed. /// public void Execute(string targetName) { Execute(targetName, true); } /// /// Executes a specific target. /// /// The name of the target to execute. /// Whether dependencies should be forced to execute /// /// Global tasks are not executed. /// public void Execute(string targetName, bool forceDependencies) { // sort the dependency tree, and run everything from the // beginning until we hit our targetName. // // sorting checks if all the targets (and dependencies) // exist, and if there is any cycle in the dependency // graph. TargetCollection sortedTargets = TopologicalTargetSort(targetName, Targets); int currentIndex = 0; Target currentTarget; // store calling target Target callingTarget = _currentTarget; do { // determine target that should be executed currentTarget = (Target) sortedTargets[currentIndex++]; // store target that will be executed _currentTarget = currentTarget; // only execute targets that have not been executed already, if // we are not forcing. if (forceDependencies || !currentTarget.Executed) { currentTarget.Execute(); } } while (!currentTarget.Name.Equals(targetName)); // restore calling target, as a task might have caused the // current target to be executed and when finished executing this // target, the target that contained the task should be // considered the current target again _currentTarget = callingTarget; } /// /// Executes the default target and wraps in error handling and time /// stamping. /// /// /// if the build was successful; otherwise, /// . /// public bool Run() { Exception error = null; try { OnBuildStarted(this, new BuildEventArgs(this)); // output build file that we're running Log(Level.Info, "Buildfile: {0}", BuildFileUri); // output current target framework in build log Log(Level.Info, "Target framework: {0}", TargetFramework != null ? TargetFramework.Description : "None"); // write verbose project information after Initialize to make // sure properties are correctly initialized Log(Level.Verbose, "Base Directory: {0}.", BaseDirectory); // execute the project Execute(); // signal build success return true; } catch (BuildException e) { // store exception in error variable in order to include it // in the BuildFinished event. error = e; // log exception details to log4net logger.Error("Build failed.", e); // signal build failure return false; } catch (Exception e) { // store exception in error variable in order to include it // in the BuildFinished event. error = e; // log exception details to log4net logger.Fatal("Build failed.", e); // signal build failure return false; } finally { string endTarget; if (error == null) { endTarget = Properties[NAntPropertyOnSuccess]; } else { endTarget = Properties[NAntPropertyOnFailure]; } // TO-DO : remove this after release of NAnt 0.8.4 or so string deprecatedFailureTarget = Properties["nant.failure"]; if (!StringUtils.IsNullOrEmpty(deprecatedFailureTarget)) { Log(Level.Warning, "The 'nant.failure' property has been deprecated." + " You should use '{0}' to designate the target that should be" + " executed when the build fails." + Environment.NewLine, Project.NAntPropertyOnFailure); if (error != null) { Execute(deprecatedFailureTarget); } } if (!StringUtils.IsNullOrEmpty(endTarget)) { // executing the target identified by the 'nant.onsuccess' // or 'nant.onfailure' properties should not affect the // build outcome CallTask callTask = new CallTask(); callTask.Parent = this; callTask.Project = this; callTask.NamespaceManager = NamespaceManager; callTask.Verbose = Verbose; callTask.FailOnError = false; callTask.TargetName = endTarget; callTask.Execute(); } // fire BuildFinished event with details of build outcome BuildEventArgs buildFinishedArgs = new BuildEventArgs(this); buildFinishedArgs.Exception = error; OnBuildFinished(this, buildFinishedArgs); } } public DataTypeBase CreateDataTypeBase(XmlNode elementNode) { DataTypeBase type = TypeFactory.CreateDataType(elementNode, this); type.Project = this; type.Parent = this; type.NamespaceManager = NamespaceManager; type.Initialize(elementNode); return type; } /// /// Creates a new from the given . /// /// The definition. /// The new instance. public Task CreateTask(XmlNode taskNode) { return CreateTask(taskNode, null); } /// /// Creates a new from the given /// within a . /// /// The definition. /// The owner . /// The new instance. public Task CreateTask(XmlNode taskNode, Target target) { Task task = TypeFactory.CreateTask(taskNode, this); task.Project = this; task.Parent = target; task.NamespaceManager = NamespaceManager; task.Initialize(taskNode); return task; } /// /// Expands a from known properties. /// /// The with replacement tokens. /// The location in the build file. Used to throw more accurate exceptions. /// The expanded and replaced . public string ExpandProperties(string input, Location location) { return Properties.ExpandProperties(input, location); } /// /// Combines the specified path with the of /// the to form a full path to file or directory. /// /// The relative or absolute path. /// /// A rooted path, or the of the /// if the parameter is a null reference. /// public string GetFullPath(string path) { if (StringUtils.IsNullOrEmpty(path)) { return BaseDirectory; } // check whether path is a file URI try { Uri uri = new Uri(path); if (uri.IsFile) { path = uri.LocalPath; } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA1061"), path, uri.Scheme), Location.UnknownLocation); } } catch { // ignore exception and treat path as normal path } if (!Path.IsPathRooted(path)) { path = Path.GetFullPath(Path.Combine(BaseDirectory, path)); } return path; } /// /// Creates the default and attaches it to /// the . /// public void CreateDefaultLogger() { IBuildLogger buildLogger = new DefaultLogger(); // hook up to build events BuildStarted += new BuildEventHandler(buildLogger.BuildStarted); BuildFinished += new BuildEventHandler(buildLogger.BuildFinished); TargetStarted += new BuildEventHandler(buildLogger.TargetStarted); TargetFinished += new BuildEventHandler(buildLogger.TargetFinished); TaskStarted += new BuildEventHandler(buildLogger.TaskStarted); TaskFinished += new BuildEventHandler(buildLogger.TaskFinished); MessageLogged += new BuildEventHandler(buildLogger.MessageLogged); // set threshold of logger equal to threshold of the project buildLogger.Threshold = Threshold; // add default logger to list of build listeners BuildListeners.Add(buildLogger); } /// /// Increases the of the . /// public void Indent() { _indentationLevel++; } /// /// Decreases the of the . /// public void Unindent() { _indentationLevel--; } /// /// Detaches the currently attached instances /// from the . /// public void DetachBuildListeners() { foreach (IBuildListener listener in BuildListeners) { BuildStarted -= new BuildEventHandler(listener.BuildStarted); BuildFinished -= new BuildEventHandler(listener.BuildFinished); TargetStarted -= new BuildEventHandler(listener.TargetStarted); TargetFinished -= new BuildEventHandler(listener.TargetFinished); TaskStarted -= new BuildEventHandler(listener.TaskStarted); TaskFinished -= new BuildEventHandler(listener.TaskFinished); MessageLogged -= new BuildEventHandler(listener.MessageLogged); if (typeof(IBuildLogger).IsAssignableFrom(listener.GetType())) { ((IBuildLogger)listener).Flush(); } } BuildListeners.Clear(); } /// /// Attaches the specified build listeners to the . /// /// The instances to attach to the . /// /// The currently attached instances will /// be detached before the new instances /// are attached. /// public void AttachBuildListeners(BuildListenerCollection listeners) { // detach currently attached build listeners DetachBuildListeners(); foreach (IBuildListener listener in listeners) { // hook up listener to project build events BuildStarted += new BuildEventHandler(listener.BuildStarted); BuildFinished += new BuildEventHandler(listener.BuildFinished); TargetStarted += new BuildEventHandler(listener.TargetStarted); TargetFinished += new BuildEventHandler(listener.TargetFinished); TaskStarted += new BuildEventHandler(listener.TaskStarted); TaskFinished += new BuildEventHandler(listener.TaskFinished); MessageLogged += new BuildEventHandler(listener.MessageLogged); // add listener to project listener list BuildListeners.Add(listener); } } #endregion Public Instance Methods #region Protected Instance Methods /// /// Inits stuff: /// TypeFactory: Calls Initialize and AddProject /// Log.IndentSize set to 12 /// Project properties are initialized ("nant.* stuff set") /// /// NAnt Props: /// nant.filename /// nant.version /// nant.location /// nant.project.name /// nant.project.buildfile (if doc has baseuri) /// nant.project.basedir /// nant.project.default = defaultTarget /// nant.tasks.[name] = true /// nant.tasks.[name].location = AssemblyFileName /// /// /// An representing the project definition. /// The project message threshold. /// The project indentation level. /// Optimization flags. /// is . private void CtorHelper(XmlDocument doc, Level threshold, int indentLevel, Optimizations optimization) { if (doc == null) { throw new ArgumentNullException("doc"); } string newBaseDir = null; _properties = new PropertyDictionary(this); _frameworkNeutralProperties = new PropertyDictionary(this); // set the project definition _doc = doc; // set the indentation size of the build output _indentationSize = 12; // set the indentation level of the build output _indentationLevel = indentLevel; // set the project message threshold Threshold = threshold; // add default logger CreateDefaultLogger(); // configure platform properties ConfigurePlatformProperties(); // fill the namespace manager up, so we can make qualified xpath // expressions if (StringUtils.IsNullOrEmpty(doc.DocumentElement.NamespaceURI)) { string defURI; if (doc.DocumentElement.Attributes["xmlns", "nant"] == null) { defURI = @"http://none"; } else { defURI = doc.DocumentElement.Attributes["xmlns", "nant"].Value; } XmlAttribute attr = doc.CreateAttribute("xmlns"); attr.Value = defURI; doc.DocumentElement.Attributes.Append(attr); } NamespaceManager.AddNamespace("nant", doc.DocumentElement.NamespaceURI); // check to make sure that the root element in named correctly if (!doc.DocumentElement.LocalName.Equals(RootXml)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA1059"), doc.BaseURI, RootXml)); } // get project attributes if (doc.DocumentElement.HasAttribute(ProjectNameAttribute)) { _projectName = doc.DocumentElement.GetAttribute(ProjectNameAttribute); } if (doc.DocumentElement.HasAttribute(ProjectBaseDirAttribute)) { newBaseDir = doc.DocumentElement.GetAttribute(ProjectBaseDirAttribute); } if (doc.DocumentElement.HasAttribute(ProjectDefaultAttribte)) { _defaultTargetName = doc.DocumentElement.GetAttribute(ProjectDefaultAttribte); } // give the project a meaningful base directory if (StringUtils.IsNullOrEmpty(newBaseDir)) { if (!StringUtils.IsNullOrEmpty(BuildFileLocalName)) { newBaseDir = Path.GetDirectoryName(BuildFileLocalName); } else { newBaseDir = Environment.CurrentDirectory; } } else { // if basedir attribute is set to a relative path, then resolve // it relative to the build file path if (!StringUtils.IsNullOrEmpty(BuildFileLocalName) && !Path.IsPathRooted(newBaseDir)) { newBaseDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(BuildFileLocalName), newBaseDir)); } } newBaseDir = Path.GetFullPath(newBaseDir); // base directory must be rooted. BaseDirectory = newBaseDir; // load project-level extensions assemblies bool scan = ((optimization & Optimizations.SkipAutomaticDiscovery) == 0); TypeFactory.AddProject(this, scan); if ((optimization & Optimizations.SkipFrameworkConfiguration) == 0) { // load settings out of settings file ProjectSettingsLoader psl = new ProjectSettingsLoader(this); psl.ProcessSettings(); } // set here and in nant:Main Assembly ass = Assembly.GetExecutingAssembly(); // TO-DO: remove these built-in properties after NAnt 0.87 (?) // as these have been deprecated since NAnt 0.85 Properties.AddReadOnly(NAntPropertyFileName, ass.CodeBase); Properties.AddReadOnly(NAntPropertyVersion, ass.GetName().Version.ToString()); Properties.AddReadOnly(NAntPropertyLocation, AppDomain.CurrentDomain.BaseDirectory); Properties.AddReadOnly(NAntPropertyProjectName, ProjectName); if (BuildFileUri != null) { Properties.AddReadOnly(NAntPropertyProjectBuildFile, BuildFileUri.ToString()); } Properties.AddReadOnly(NAntPropertyProjectDefault, StringUtils.ConvertNullToEmpty(DefaultTargetName)); } #endregion Protected Instance Methods #region Internal Instance Methods /// /// This method is only meant to be used by the /// class and . /// internal void InitializeProjectDocument(XmlDocument doc) { // load line and column number information into position map LocationMap.Add(doc); // initialize targets first foreach (XmlNode childNode in doc.DocumentElement.ChildNodes) { // skip non-nant namespace elements and special elements like // comments, pis, text, etc. if (childNode.LocalName.Equals(TargetXml) && childNode.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) { Target target = new Target(); target.Project = this; target.Parent = this; target.NamespaceManager = NamespaceManager; target.Initialize(childNode); Targets.Add(target); } } // initialize datatypes and execute global tasks foreach (XmlNode childNode in doc.DocumentElement.ChildNodes) { // skip targets that were handled above, skip non-nant namespace // elements and special elements like comments, pis, text, etc. if (!(childNode.NodeType == XmlNodeType.Element) || !childNode.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant")) || childNode.LocalName.Equals(TargetXml)) { continue; } if (TypeFactory.TaskBuilders.Contains(childNode.Name)) { // create task instance Task task = CreateTask(childNode); task.Parent = this; // execute task task.Execute(); } else if (TypeFactory.DataTypeBuilders.Contains(childNode.Name)) { // we are an datatype declaration DataTypeBase dataType = CreateDataTypeBase(childNode); Log(Level.Debug, "Adding a {0} reference with id '{1}'.", childNode.Name, dataType.ID); if (! DataTypeReferences.Contains(dataType.ID)) { DataTypeReferences.Add(dataType.ID, dataType); } else { DataTypeReferences[dataType.ID] = dataType; // overwrite with the new reference. } } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA1071"), childNode.Name), LocationMap.GetLocation(childNode)); } } } #endregion Internal Instance Methods #region Private Instance Methods /// /// Creates a new based on the project /// definition. /// /// /// The full path to the build file. /// This can be of any form that accepts. /// /// /// An based on the specified project /// definition. /// private XmlDocument LoadBuildFile(string uriOrFilePath) { string path = uriOrFilePath; //if the source is not a valid uri, pass it thru. //if the source is a file uri, pass the localpath of it thru. try { Uri testURI = new Uri(uriOrFilePath); if (testURI.IsFile) { path = testURI.LocalPath; } } catch (Exception ex) { logger.Debug("Error creating URI in project constructor. Moving on... ", ex); } finally { if (path == null) { path = uriOrFilePath; } } XmlDocument doc = new XmlDocument(); try { doc.Load(path); } catch (XmlException ex) { Location location = new Location(path, ex.LineNumber, ex.LinePosition); throw new BuildException("Error loading buildfile.", location, ex); } catch (Exception ex) { Location location = new Location(path); throw new BuildException("Error loading buildfile.", location, ex); } return doc; } /// /// Configures the platform properties for the current platform. /// /// NAnt does not support the current platform. private void ConfigurePlatformProperties() { Properties.AddReadOnly(NAntPlatformName, PlatformName); switch (PlatformName) { case "win32": Properties.AddReadOnly(NAntPlatform + ".unix", "false"); Properties.AddReadOnly(NAntPlatform + "." + PlatformName, "true"); break; case "unix": Properties.AddReadOnly(NAntPlatform + "." + PlatformName, "true"); Properties.AddReadOnly(NAntPlatform + ".win32", "false"); break; default: throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA1060"), Environment.OSVersion.Platform.ToString(CultureInfo.InvariantCulture), (int) Environment.OSVersion.Platform)); } } /// /// Updates dependent properties when the /// is set. /// private void UpdateTargetFrameworkProperties() { Properties["nant.settings.currentframework"] = TargetFramework.Name; Properties["nant.settings.currentframework.version"] = TargetFramework.Version.ToString(); Properties["nant.settings.currentframework.description"] = TargetFramework.Description; Properties["nant.settings.currentframework.frameworkdirectory"] = TargetFramework.FrameworkDirectory.FullName; if (TargetFramework.SdkDirectory != null) { Properties["nant.settings.currentframework.sdkdirectory"] = TargetFramework.SdkDirectory.FullName; } else { Properties["nant.settings.currentframework.sdkdirectory"] = ""; } Properties["nant.settings.currentframework.frameworkassemblydirectory"] = TargetFramework.FrameworkAssemblyDirectory.FullName; if (TargetFramework.RuntimeEngine != null) { Properties["nant.settings.currentframework.runtimeengine"] = TargetFramework.RuntimeEngine.Name; } else { Properties["nant.settings.currentframework.runtimeengine"] = ""; } } private XmlNode GetConfigurationNode() { XmlNode configurationNode = ConfigurationSettings.GetConfig("nant") as XmlNode; if (configurationNode == null) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "The NAnt configuration settings in file '{0}' could" + " not be loaded. Please ensure this file is available" + " and contains a 'nant' settings node.", AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)); } return configurationNode; } #endregion Private Instance Methods /// /// Topologically sorts a set of targets. /// /// The name of the root target. The sort is created in such a way that the sequence of targets up to the root target is the minimum possible such sequence. Must not be . /// A collection of instances. /// /// A collection of instances in sorted order. /// /// There is a cyclic dependecy among the targets, or a named target does not exist. public TargetCollection TopologicalTargetSort(string root, TargetCollection targets) { TargetCollection executeTargets = new TargetCollection(); Hashtable state = new Hashtable(); Stack visiting = new Stack(); // We first run a DFS based sort using the root as the starting node. // This creates the minimum sequence of Targets to the root node. // We then do a sort on any remaining unVISITED targets. // This is unnecessary for doing our build, but it catches // circular dependencies or missing Targets on the entire // dependency tree, not just on the Targets that depend on the // build Target. TopologicalTargetSort(root, targets, state, visiting, executeTargets); Log(Level.Debug, "Build sequence for target `" + root + "' is " + executeTargets); foreach (Target target in targets) { string st = (string) state[target.Name]; if (st == null) { TopologicalTargetSort(target.Name, targets, state, visiting, executeTargets); } else if (st == Project.Visiting) { throw new Exception("Unexpected node in visiting state: " + target.Name); } } Log(Level.Debug, "Complete build sequence is " + executeTargets); return executeTargets; } /// /// /// Performs a single step in a recursive depth-first-search traversal /// of the target dependency tree. /// /// /// The current target is first set to the "visiting" state, and pushed /// onto the "visiting" stack. /// /// /// An exception is then thrown if any child of the current node is in /// the visiting state, as that implies a circular dependency. The /// exception contains details of the cycle, using elements of the /// "visiting" stack. /// /// /// If any child has not already been "visited", this method is called /// recursively on it. /// /// /// The current target is then added to the ordered list of targets. /// Note that this is performed after the children have been visited in /// order to get the correct order. The current target is set to the /// "visited" state. /// /// /// By the time this method returns, the ordered list contains the /// sequence of targets up to and including the current target. /// /// /// The current target to inspect. Must not be . /// A collection of instances. /// A mapping from targets to states The states in question are "VISITING" and "VISITED". Must not be . /// A stack of targets which are currently being visited. Must not be . /// The list to add target names to. This will end up containing the complete list of depenencies in dependency order. Must not be . /// /// A non-existent target is specified /// -or- /// A circular dependency is detected. /// private void TopologicalTargetSort(string root, TargetCollection targets, Hashtable state, Stack visiting, TargetCollection executeTargets) { state[root] = Project.Visiting; visiting.Push(root); Target target = (Target) targets.Find(root); if (target == null) { // check if there's a wildcard target defined target = (Target) targets.Find(WildTarget); if (target != null) { // if a wildcard target exists, then treat the wildcard // target as the requested target target = target.Clone(); target.Name = root; } else { StringBuilder sb = new StringBuilder("Target '"); sb.Append(root); sb.Append("' does not exist in this project."); visiting.Pop(); if (visiting.Count > 0) { string parent = (string) visiting.Peek(); sb.Append(" "); sb.Append("It is used from target '"); sb.Append(parent); sb.Append("'."); } throw new BuildException(sb.ToString()); } } foreach (string dependency in target.Dependencies) { string m = (string) state[dependency]; if (m == null) { // Not been visited TopologicalTargetSort(dependency, targets, state, visiting, executeTargets); } else if (m == Project.Visiting) { // Currently visiting this node, so have a cycle throw CreateCircularException(dependency, visiting); } } string p = (string) visiting.Pop(); if (root != p) { throw new Exception("Unexpected internal error: expected to pop " + root + " but got " + p); } state[root] = Project.Visited; executeTargets.Add(target); } /// /// Builds an appropriate exception detailing a specified circular /// dependency. /// /// The dependency to stop at. Must not be . /// A stack of dependencies. Must not be . /// /// A detailing the specified circular /// dependency. /// private static BuildException CreateCircularException(string end, Stack stack) { StringBuilder sb = new StringBuilder("Circular dependency: "); sb.Append(end); string c; do { c = (string) stack.Pop(); sb.Append(" <- "); sb.Append(c); } while (!c.Equals(end)); return new BuildException(sb.ToString()); } } /// /// Allow the project construction to be optimized. /// /// /// Use this with care! /// internal enum Optimizations { /// /// Do not perform any optimizations. /// None = 0, /// /// The project base directory must not be automatically scanned /// for extension assemblies. /// SkipAutomaticDiscovery = 1, /// /// Do not scan the project configuration for frameworks, and /// do not configure the runtime and target framework. /// SkipFrameworkConfiguration = 2, } }