[xbuild] Add support for Before/AfterTargets.
authorAnkit Jain <radical@corewars.org>
Tue, 1 Mar 2011 20:46:22 +0000 (02:16 +0530)
committerAnkit Jain <radical@corewars.org>
Tue, 1 Mar 2011 20:51:15 +0000 (02:21 +0530)
A target 'foo' can have BeforeTargets or AfterTargets attributes,
which list the targets before or after, 'foo' should be run.
This is a 4.0 feature.

* Project.cs (ProcessBeforeAndAfterTargets): New.
* Target.cs: Run the before/after targets in the correct order.

* TargetTest.cs (TestBeforeAndAfterTargets): New.

mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Project.cs
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/TargetCollection.cs
mcs/class/Microsoft.Build.Engine/Test/Microsoft.Build.BuildEngine/TargetTest.cs
mcs/class/Microsoft.Build.Tasks/Test/Microsoft.Build.Tasks/TestMessageLogger.cs

index f41ba855164d9df2c567a5a3404f22ba5bbbb831..533f7b4f3a550131495daf81580eab3b93442844 100644 (file)
@@ -32,6 +32,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.IO;
+using System.Linq;
 using System.Reflection;
 using System.Text;
 using System.Xml;
@@ -314,7 +315,11 @@ namespace Microsoft.Build.BuildEngine {
                                needToReevaluate = false;
                                Reevaluate ();
                        }
-                       
+
+#if NET_4_0
+                       ProcessBeforeAndAfterTargets ();
+#endif
+
                        if (targetNames == null || targetNames.Length == 0) {
                                if (defaultTargets != null && defaultTargets.Length != 0) {
                                        targetNames = defaultTargets;
@@ -388,6 +393,44 @@ namespace Microsoft.Build.BuildEngine {
                        return sb.ToString ();
                }
 
+#if NET_4_0
+               void ProcessBeforeAndAfterTargets ()
+               {
+                       var beforeTable = Targets.AsIEnumerable ()
+                                               .SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
+                                                               (target, before_target) => new {before_target, name = target.Name})
+                                               .ToLookup (x => x.before_target, x => x.name)
+                                               .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
+
+                       foreach (var pair in beforeTable) {
+                               if (targets.Exists (pair.Key))
+                                       targets [pair.Key].BeforeThisTargets = pair.Value;
+                               else
+                                       LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
+                       }
+
+                       var afterTable = Targets.AsIEnumerable ()
+                                               .SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
+                                                               (target, after_target) => new {after_target, name = target.Name})
+                                               .ToLookup (x => x.after_target, x => x.name)
+                                               .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
+
+                       foreach (var pair in afterTable) {
+                               if (targets.Exists (pair.Key))
+                                       targets [pair.Key].AfterThisTargets = pair.Value;
+                               else
+                                       LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
+                       }
+               }
+
+               string[] GetTargetNamesFromString (string targets)
+               {
+                       Expression expr = new Expression ();
+                       expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
+                       return (string []) expr.ConvertTo (this, typeof (string []));
+               }
+#endif
+
                [MonoTODO]
                public string [] GetConditionedPropertyValues (string propertyName)
                {
index 439c56fd94b14b4887d57ace200ec0d5f5e82e71..b201619615d2ebcebd6114e768b7f41dfba44950 100644 (file)
@@ -157,14 +157,15 @@ namespace Microsoft.Build.BuildEngine {
 
                        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) {
@@ -179,36 +180,70 @@ namespace Microsoft.Build.BuildEngine {
                        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, ParseOptions.AllowItemsNoMetadataAndSplit);
-                               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;
+               }
+
+#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, out bool executeOnErrors)
+               bool BuildAfterThisTargets (out bool executeOnErrors)
                {
                        executeOnErrors = false;
-                       foreach (Target t in deps) {
+                       //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");
                        }
@@ -321,6 +356,19 @@ namespace Microsoft.Build.BuildEngine {
                        }
                }
 
+#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; }
                }
index 940ff9181daf2a03df0ab9d64b07bf73de16e40a..d3dfddeb910136d8b6717d88b600d4c6a5086237 100644 (file)
@@ -86,6 +86,12 @@ namespace Microsoft.Build.BuildEngine {
                                yield return kvp.Value;
                }
 
+               internal IEnumerable<Target> AsIEnumerable ()
+               {
+                       foreach (KeyValuePair <string, Target> kvp in targetsByName)
+                               yield return kvp.Value;
+               }
+
                public void RemoveTarget (Target targetToRemove)
                {
                        if (targetToRemove == null)
index 65b01a82b79ffacd8eb670f1a02d3959f6e5132a..ae0f18e78c914a7692c55064c16fb46d162fb7da 100644 (file)
@@ -420,5 +420,61 @@ namespace MonoTests.Microsoft.Build.BuildEngine {
                        Assert.AreEqual (0, logger.NormalMessageCount, "Unexpected extra messages found");
                }
 
+#if NET_4_0
+               [Test]
+               public void TestBeforeAndAfterTargets ()
+               {
+                       Engine engine;
+                       Project project;
+
+                       string projectString = @"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" ToolsVersion=""4.0"">
+                         <Target Name=""DefaultBeforeTarget1"" BeforeTargets=""Default"">
+                           <Message Text=""Hello from DefaultBeforeTarget1""/>
+                         </Target>
+
+                         <Target Name=""DefaultBeforeTarget2"" BeforeTargets=""Default;Default;NonExistant"">
+                           <Message Text=""Hello from DefaultBeforeTarget2""/>
+                         </Target>
+
+
+                         <Target Name=""DefaultAfterTarget"" AfterTargets=""Default  ; Foo"">
+                           <Message Text=""Hello from DefaultAfterTarget""/>
+                         </Target>
+
+                         <Target Name=""Default"" DependsOnTargets=""DefaultDependsOn"">
+                           <Message Text=""Hello from Default""/>
+                         </Target>
+
+                         <Target Name=""DefaultDependsOn"">
+                           <Message Text=""Hello from DefaultDependsOn""/>
+                         </Target>
+                       </Project>";
+
+                       engine = new Engine ();
+                       project = engine.CreateNewProject ();
+                       project.LoadXml (projectString);
+
+                       MonoTests.Microsoft.Build.Tasks.TestMessageLogger logger =
+                               new MonoTests.Microsoft.Build.Tasks.TestMessageLogger ();
+                       engine.RegisterLogger (logger);
+
+                       if (!project.Build ("Default")) {
+                               logger.DumpMessages ();
+                               Assert.Fail ("Build failed");
+                       }
+
+                       logger.CheckLoggedMessageHead ("Hello from DefaultDependsOn", "A1");
+                       logger.CheckLoggedMessageHead ("Hello from DefaultBeforeTarget1", "A1");
+                       logger.CheckLoggedMessageHead ("Hello from DefaultBeforeTarget2", "A1");
+                       logger.CheckLoggedMessageHead ("Hello from Default", "A1");
+                       logger.CheckLoggedMessageHead ("Hello from DefaultAfterTarget", "A1");
+
+                       Assert.AreEqual (0, logger.NormalMessageCount, "Unexpected messages found");
+
+                       //warnings for referencing unknown targets: NonExistant and Foo
+                       Assert.AreEqual (2, logger.WarningsCount, "Expected warnings not raised");
+               }
+#endif
+
        }
 }
index e05673dfbe260ab49cd86a006e032e0957b0e11b..be855ade2d78a20931632ee7fdce04cddab81ff5 100644 (file)
@@ -69,6 +69,9 @@ namespace MonoTests.Microsoft.Build.Tasks
                        set { task_finished = value; }
                }
 
+               public int WarningsCount { get; set; }
+               public int ErrorsCount { get; set; }
+
                public int Count
                {
                        get { return messages.Count; }
@@ -81,8 +84,13 @@ namespace MonoTests.Microsoft.Build.Tasks
                public void Initialize (IEventSource eventSource)
                {
                        eventSource.MessageRaised += new BuildMessageEventHandler (MessageHandler);
+
                        eventSource.ErrorRaised += new BuildErrorEventHandler (AllMessagesHandler);
+                       eventSource.ErrorRaised += (e,o) => ErrorsCount ++;
+
                        eventSource.WarningRaised += new BuildWarningEventHandler(AllMessagesHandler);
+                       eventSource.WarningRaised += (e,o) => WarningsCount ++;
+
                        eventSource.TargetStarted += delegate { target_started++; };
                        eventSource.TargetFinished += delegate { target_finished++; };
                        eventSource.TaskStarted += delegate { task_started++; };