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