[xbuild] Set ProjectFile and TargetName metadata on target outputs.
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Target.cs
index e76394e7abf2fd104440c14c41f45f4ed2dac582..92e369b888b5ba56542aaa6c14f89511f044f252 100644 (file)
@@ -30,6 +30,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Linq;
 using System.Xml;
 using Microsoft.Build.Framework;
 using Microsoft.Build.Utilities;
@@ -110,74 +111,145 @@ namespace Microsoft.Build.BuildEngine {
                        buildTasks.Remove (buildTask);
                }
 
-               internal bool Build ()
+               bool Build ()
+               {
+                       return Build (null);
+               }
+
+               internal bool Build (string built_targets_key)
                {
                        bool executeOnErrors;
-                       return Build (out 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 ();
+                       }
                }
 
-               internal bool Build (out bool executeOnErrors)
+               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;
-                               result = BuildDependencies (GetDependencies (), out executeOnErrors);
-
-                               if (!result && executeOnErrors)
-                                       ExecuteOnErrors ();
 
-                               if (result)
-                                       // deps built fine, do main build
-                                       result = DoBuild (out executeOnErrors);
+#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 dep_name in targetNames) {
-                                       t = project.Targets [dep_name.Trim ()];
-                                       if (t == null)
-                                               throw new InvalidProjectFileException (String.Format (
-                                                               "Target '{0}', a dependency of target '{1}', not found.",
-                                                               dep_name.Trim (), Name));
-                                       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;
                }
 
-               bool BuildDependencies (List <Target> deps, out bool executeOnErrors)
+#if NET_4_0
+               bool BuildBeforeThisTargets (out bool executeOnErrors)
                {
                        executeOnErrors = false;
-                       foreach (Target t in deps) {
+                       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 (out executeOnErrors))
+                                       if (!t.Build (null, out executeOnErrors))
                                                return false;
+
                                if (t.BuildState == BuildState.Started)
                                        throw new InvalidProjectFileException ("Cycle in target dependencies detected");
                        }
@@ -228,6 +300,16 @@ namespace Microsoft.Build.BuildEngine {
                        }
                }
 
+               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)
@@ -272,6 +354,27 @@ namespace Microsoft.Build.BuildEngine {
                        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; }
                }
@@ -284,14 +387,19 @@ namespace Microsoft.Build.BuildEngine {
                        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 []));
                        }