using System;
using System.Collections;
+using System.Collections.Generic;
using System.Xml;
using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
namespace Microsoft.Build.BuildEngine {
public class Target : IEnumerable {
- BatchingImpl batchingImpl;
+ TargetBatchingImpl batchingImpl;
BuildState buildState;
- XmlAttribute condition;
- XmlAttribute dependsOnTargets;
Engine engine;
- bool isImported;
+ ImportedProject importedProject;
string name;
Project project;
XmlElement targetElement;
- //ArrayList taskElements;
- ArrayList onErrorElements;
+ List <XmlElement> onErrorElements;
+ List <BuildTask> buildTasks;
- ArrayList buildTasks;
-
- internal Target (Project project, string name)
+ internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
{
if (project == null)
throw new ArgumentNullException ("project");
-
- if (name == null)
- throw new ArgumentNullException ("name");
-
- this.buildState = BuildState.NotStarted;
- this.project = project;
- this.engine = project.ParentEngine;
- this.name = name;
- this.isImported = false;;
- //this.taskElements = new ArrayList ();
- this.onErrorElements = new ArrayList ();
-
- this.buildTasks = new ArrayList ();
- }
-
- internal void BindToXml (XmlElement targetElement)
- {
if (targetElement == null)
throw new ArgumentNullException ("targetElement");
-
+
this.targetElement = targetElement;
- // FIXME: check if Target element is valid
- this.condition = targetElement.GetAttributeNode ("Condition");
- this.dependsOnTargets = targetElement.GetAttributeNode ("DependsOnTargets");
- this.batchingImpl = new BatchingImpl (project, this.targetElement);
+ this.name = targetElement.GetAttribute ("Name");
+
+ this.project = project;
+ this.engine = project.ParentEngine;
+ this.importedProject = importedProject;
+
+ this.onErrorElements = new List <XmlElement> ();
+ this.buildState = BuildState.NotStarted;
+ this.buildTasks = new List <BuildTask> ();
+ this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
+
+ bool onErrorFound = false;
foreach (XmlNode xn in targetElement.ChildNodes) {
if (xn is XmlElement) {
XmlElement xe = (XmlElement) xn;
if (xe.Name == "OnError") {
onErrorElements.Add (xe);
- continue;
- }
- //TaskElement te = new TaskElement ();
- //te.BindToXml (xe, this);
- BuildTask bt = new BuildTask (xe, this);
- //taskElements.Add (te);
- buildTasks.Add (bt);
+ onErrorFound = true;
+ } else if (onErrorFound)
+ throw new InvalidProjectFileException (
+ "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
+ else
+ buildTasks.Add (new BuildTask (xe, this));
}
}
}
- internal void Build ()
- {
- buildState = BuildState.Started;
- if (dependsOnTargets == null) {
- ;
- } else if (dependsOnTargets.Value == "") {
- ;
- } else {
- Expression dependencies = new Expression (Project, dependsOnTargets.Value);
- string[] targetsToBuildFirst = (string[]) dependencies.ToArray (typeof (string[]));
- foreach (string target in targetsToBuildFirst) {
- string trimmed = target.Trim ();
- Target t = (Target) project.Targets [trimmed];
- if (t == null)
- throw new InvalidProjectFileException (String.Format ("Target {0} not found.", trimmed));
- if (t.BuildState == BuildState.NotStarted) {
- t.Build ();
- }
- if (t.BuildState == BuildState.Started)
- throw new InvalidProjectFileException ("Cycle in target dependencies detected.");
+ [MonoTODO]
+ public BuildTask AddNewTask (string taskName)
+ {
+ if (taskName == null)
+ throw new ArgumentNullException ("taskName");
+
+ XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
+ targetElement.AppendChild (task);
+ BuildTask bt = new BuildTask (task, this);
+ buildTasks.Add (bt);
+
+ return bt;
+ }
+
+ public IEnumerator GetEnumerator ()
+ {
+ foreach (BuildTask bt in buildTasks)
+ yield return bt;
+ }
+
+ // FIXME: shouldn't we remove it from XML?
+ public void RemoveTask (BuildTask buildTask)
+ {
+ if (buildTask == null)
+ throw new ArgumentNullException ("buildTask");
+ buildTasks.Remove (buildTask);
+ }
+
+ bool Build ()
+ {
+ return Build (null);
+ }
+
+ internal bool Build (string built_targets_key)
+ {
+ bool executeOnErrors;
+ return Build (built_targets_key, out executeOnErrors);
+ }
+
+ bool Build (string built_targets_key, out bool executeOnErrors)
+ {
+ project.PushThisFileProperty (TargetFile);
+ try {
+ return BuildActual (built_targets_key, out executeOnErrors);
+ } finally {
+ project.PopThisFileProperty ();
+ }
+ }
+
+ bool BuildActual (string built_targets_key, out bool executeOnErrors)
+ {
+ bool result = false;
+ executeOnErrors = false;
+
+ // built targets are keyed by the particular set of global
+ // properties. So, a different set could allow a target
+ // to run again
+ built_targets_key = project.GetKeyForTarget (Name);
+ if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
+ LogTargetSkipped ();
+ return true;
+ }
+
+ // Push a null/empty batch, effectively clearing it
+ project.PushBatch (null, null);
+ if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
+ LogMessage (MessageImportance.Low,
+ "Target {0} skipped due to false condition: {1}",
+ Name, Condition);
+ project.PopBatch ();
+ return true;
+ }
+
+ try {
+ buildState = BuildState.Started;
+
+#if NET_4_0
+ result = BuildDependencies (out executeOnErrors) &&
+ BuildBeforeThisTargets (out executeOnErrors) &&
+ DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
+ BuildAfterThisTargets (out executeOnErrors);
+#else
+ result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
+#endif
+
+ buildState = BuildState.Finished;
+ } catch (Exception e) {
+ LogError ("Error building target {0}: {1}", Name, e.ToString ());
+ return false;
+ } finally {
+ project.PopBatch ();
+ }
+
+ project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = (ITaskItem[]) OutputsAsITaskItems.Clone ();
+
+ return result;
+ }
+
+ bool BuildDependencies (out bool executeOnErrors)
+ {
+ executeOnErrors = false;
+
+ if (String.IsNullOrEmpty (DependsOnTargets))
+ return true;
+
+ var expr = new Expression ();
+ expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
+ string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
+
+ bool result = BuildOtherTargets (targetNames,
+ tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
+ tname, Name),
+ out executeOnErrors);
+ if (!result && executeOnErrors)
+ ExecuteOnErrors ();
+
+ return result;
+ }
+
+#if NET_4_0
+ bool BuildBeforeThisTargets (out bool executeOnErrors)
+ {
+ executeOnErrors = false;
+ bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
+ if (!result && executeOnErrors)
+ ExecuteOnErrors ();
+
+ return result;
+ }
+
+ bool BuildAfterThisTargets (out bool executeOnErrors)
+ {
+ executeOnErrors = false;
+ //missing_target handler not required as these are picked from actual target's
+ //"Before/AfterTargets attributes!
+ bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
+ if (!result && executeOnErrors)
+ ExecuteOnErrors ();
+
+ return result;
+ }
+#endif
+
+ bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
+ {
+ executeOnErrors = false;
+ if (targetNames == null)
+ // nothing to build
+ return true;
+
+ foreach (string target_name in targetNames) {
+ var t = project.Targets [target_name.Trim ()];
+ if (t == null) {
+ if (missing_target != null)
+ missing_target (target_name);
+ return false;
}
+
+ if (t.BuildState == BuildState.NotStarted)
+ if (!t.Build (null, out executeOnErrors))
+ return false;
+
+ if (t.BuildState == BuildState.Started)
+ throw new InvalidProjectFileException ("Cycle in target dependencies detected");
}
- RealBuild ();
- buildState = BuildState.Finished;
+
+ return true;
}
- private void RealBuild ()
+ bool DoBuild (out bool executeOnErrors)
{
- bool executeOnErrors = false;
+ executeOnErrors = false;
bool result = true;
+
+ if (BuildTasks.Count == 0)
+ // nothing to do
+ return true;
- LogTargetStarted ();
-
- if (this.batchingImpl.BuildNeeded ()) {
- foreach (BuildTask bt in buildTasks) {
- if (this.batchingImpl.BatchBuildTask (bt) == false && bt.ContinueOnError == false) {
- executeOnErrors = true;
- result = false;
- break;
- }
- }
- } else {
- LogTargetSkipped ();
+ try {
+ result = batchingImpl.Build (this, out executeOnErrors);
+ } catch (Exception e) {
+ LogError ("Error building target {0}: {1}", Name, e.Message);
+ LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
+ return false;
}
- LogTargetFinished (result);
-
if (executeOnErrors == true)
ExecuteOnErrors ();
+
+ return result;
}
- private void ExecuteOnErrors ()
+ void ExecuteOnErrors ()
{
foreach (XmlElement onError in onErrorElements) {
- // FIXME: add condition
if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
+
+ string on_error_condition = onError.GetAttribute ("Condition");
+ if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
+ LogMessage (MessageImportance.Low,
+ "OnError for target {0} skipped due to false condition: {1}",
+ Name, on_error_condition);
+ continue;
+ }
+
string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
foreach (string t in targetsToExecute)
this.project.Targets [t].Build ();
}
}
-
- private void LogTargetSkipped ()
+
+ void LogTargetSkipped ()
{
BuildMessageEventArgs bmea;
- bmea = new BuildMessageEventArgs (String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.",
- name), null, "MSBuild", MessageImportance.Normal);
- engine.EventSource.FireMessageRaised (this, bmea);
- }
-
- private void LogTargetStarted ()
- {
- TargetStartedEventArgs tsea;
- string projectFile = project.FullFileName;
- tsea = new TargetStartedEventArgs ("Target " + name + " started.", null, name, projectFile, null);
- engine.EventSource.FireTargetStarted (this, tsea);
- }
-
- private void LogTargetFinished (bool succeeded)
- {
- TargetFinishedEventArgs tfea;
- string projectFile = project.FullFileName;
- tfea = new TargetFinishedEventArgs ("Target " + name + " finished.", null, name, projectFile, null, succeeded);
- engine.EventSource.FireTargetFinished (this, tfea);
- }
-
- public BuildTask AddNewTask (string taskName)
- {
- //TaskElement te = new TaskElement ();
- //taskElements.Add (te);
- //return te;
- return new BuildTask (null, this);
+ bmea = new BuildMessageEventArgs (String.Format (
+ "Target {0} skipped, as it has already been built.", Name),
+ null, null, MessageImportance.Low);
+
+ project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
}
- public IEnumerator GetEnumerator ()
+ void LogError (string message, params object [] messageArgs)
{
- foreach (BuildTask bt in buildTasks) {
- yield return bt;
- }
+ if (message == null)
+ throw new ArgumentException ("message");
+
+ BuildErrorEventArgs beea = new BuildErrorEventArgs (
+ null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
+ null, null);
+ engine.EventSource.FireErrorRaised (this, beea);
}
- public void RemoveTask (BuildTask buildTask)
+ void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
{
- //taskElements.Remove (taskElement);
- buildTasks.Remove (buildTask);
- }
+ if (message == null)
+ throw new ArgumentNullException ("message");
+ BuildMessageEventArgs bmea = new BuildMessageEventArgs (
+ String.Format (message, messageArgs), null,
+ null, importance);
+ engine.EventSource.FireMessageRaised (this, bmea);
+ }
+
public string Condition {
- get { return condition.Value; }
- set { condition.Value = value; }
+ get { return targetElement.GetAttribute ("Condition"); }
+ set { targetElement.SetAttribute ("Condition", value); }
}
public string DependsOnTargets {
- get {
- if (dependsOnTargets == null)
- return null;
- else
- return dependsOnTargets.Value;
- }
- set {
- if (dependsOnTargets != null)
- dependsOnTargets.Value = value;
- }
+ get { return targetElement.GetAttribute ("DependsOnTargets"); }
+ set { targetElement.SetAttribute ("DependsOnTargets", value); }
}
public bool IsImported {
- get { return isImported; }
- internal set { isImported = value; }
+ get { return importedProject != null; }
}
public string Name {
internal Project Project {
get { return project; }
}
+
+ internal string TargetFile {
+ get {
+ if (importedProject != null)
+ return importedProject.FullFileName;
+ return project != null ? project.FullFileName : String.Empty;
+ }
+ }
+
+#if NET_4_0
+ internal string BeforeTargets {
+ get { return targetElement.GetAttribute ("BeforeTargets"); }
+ }
+
+ internal string AfterTargets {
+ get { return targetElement.GetAttribute ("AfterTargets"); }
+ }
+
+ internal List<string> BeforeThisTargets { get; set; }
+ internal List<string> AfterThisTargets { get; set; }
+#endif
+
+ internal List<BuildTask> BuildTasks {
+ get { return buildTasks; }
+ }
+
+ internal Engine Engine {
+ get { return engine; }
+ }
internal BuildState BuildState {
get { return buildState; }
}
+
+ public string Outputs {
+ get { return targetElement.GetAttribute ("Outputs"); }
+ set { targetElement.SetAttribute ("Outputs", value); }
+ }
+
+ ITaskItem [] OutputsAsITaskItems {
+ get {
+ string outputs = targetElement.GetAttribute ("Outputs");
+ if (outputs == String.Empty)
+ return new ITaskItem [0];
+
+ Expression e = new Expression ();
+ e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+ return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
+ }
+ }
}
internal enum BuildState {
}
}
-#endif
\ No newline at end of file
+#endif