using System;
using System.Collections;
using System.Collections.Generic;
+using System.Linq;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
throw new ArgumentNullException ("buildTask");
buildTasks.Remove (buildTask);
}
-
- internal bool Build ()
+
+ bool Build ()
{
- bool deps;
- bool result;
+ 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;
- deps = BuildDependencies (GetDependencies ());
- result = deps ? DoBuild () : false;
+#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 ();
}
+ ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
+ foreach (ITaskItem item in outputs) {
+ item.SetMetadata ("MSBuildProjectFile", TargetFile);
+ item.SetMetadata ("MSBuildTargetName", Name);
+ }
+ project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
+
return result;
}
- List <Target> GetDependencies ()
+ bool BuildDependencies (out bool executeOnErrors)
{
- List <Target> list = new List <Target> ();
- Target t;
- string [] targetNames;
- Expression deps;
-
- if (DependsOnTargets != String.Empty) {
- deps = new Expression ();
- deps.Parse (DependsOnTargets, true);
- targetNames = (string []) deps.ConvertTo (Project, typeof (string []));
- foreach (string name in targetNames) {
- t = project.Targets [name.Trim ()];
- if (t == null)
- throw new InvalidProjectFileException (String.Format ("Target '{0}' not found.", name.Trim ()));
- list.Add (t);
- }
- }
- return list;
+ 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 BuildDependencies (List <Target> deps)
+ bool BuildAfterThisTargets (out bool executeOnErrors)
{
- foreach (Target t in deps) {
+ 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 ())
+ if (!t.Build (null, out executeOnErrors))
return false;
+
if (t.BuildState == BuildState.Started)
throw new InvalidProjectFileException ("Cycle in target dependencies detected");
}
return true;
}
- bool DoBuild ()
+ bool DoBuild (out bool executeOnErrors)
{
- bool executeOnErrors;
+ executeOnErrors = false;
bool result = true;
if (BuildTasks.Count == 0)
try {
result = batchingImpl.Build (this, out executeOnErrors);
} catch (Exception e) {
- LogError ("Error building target {0}: {1}", Name, e.ToString ());
- throw;
+ LogError ("Error building target {0}: {1}", Name, e.Message);
+ LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
+ return false;
}
if (executeOnErrors == true)
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 ();
}
}
+ void LogTargetSkipped ()
+ {
+ BuildMessageEventArgs bmea;
+ 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);
+ }
+
void LogError (string message, params object [] messageArgs)
{
if (message == null)
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; }
}
get { return buildState; }
}
- internal ITaskItem [] Outputs {
+ 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, true);
+ e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
}