From 34dd20ad452122ca04c00c54be2293737dba1315 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 2 Mar 2011 02:16:22 +0530 Subject: [PATCH] [xbuild] Add support for Before/AfterTargets. 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. --- .../Microsoft.Build.BuildEngine/Project.cs | 45 +++++++- .../Microsoft.Build.BuildEngine/Target.cs | 106 +++++++++++++----- .../TargetCollection.cs | 6 + .../Microsoft.Build.BuildEngine/TargetTest.cs | 56 +++++++++ .../TestMessageLogger.cs | 8 ++ 5 files changed, 191 insertions(+), 30 deletions(-) diff --git a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Project.cs b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Project.cs index f41ba855164..533f7b4f3a5 100644 --- a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Project.cs +++ b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Project.cs @@ -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) { diff --git a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs index 439c56fd94b..b201619615d 100644 --- a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs +++ b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs @@ -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 GetDependencies () + bool BuildDependencies (out bool executeOnErrors) { - List list = new List (); - 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 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 targetNames, Action 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 BeforeThisTargets { get; set; } + internal List AfterThisTargets { get; set; } +#endif + internal List BuildTasks { get { return buildTasks; } } diff --git a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/TargetCollection.cs b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/TargetCollection.cs index 940ff9181da..d3dfddeb910 100644 --- a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/TargetCollection.cs +++ b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/TargetCollection.cs @@ -86,6 +86,12 @@ namespace Microsoft.Build.BuildEngine { yield return kvp.Value; } + internal IEnumerable AsIEnumerable () + { + foreach (KeyValuePair kvp in targetsByName) + yield return kvp.Value; + } + public void RemoveTarget (Target targetToRemove) { if (targetToRemove == null) diff --git a/mcs/class/Microsoft.Build.Engine/Test/Microsoft.Build.BuildEngine/TargetTest.cs b/mcs/class/Microsoft.Build.Engine/Test/Microsoft.Build.BuildEngine/TargetTest.cs index 65b01a82b79..ae0f18e78c9 100644 --- a/mcs/class/Microsoft.Build.Engine/Test/Microsoft.Build.BuildEngine/TargetTest.cs +++ b/mcs/class/Microsoft.Build.Engine/Test/Microsoft.Build.BuildEngine/TargetTest.cs @@ -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 = @" + + + + + + + + + + + + + + + + + + + + + "; + + 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 + } } diff --git a/mcs/class/Microsoft.Build.Tasks/Test/Microsoft.Build.Tasks/TestMessageLogger.cs b/mcs/class/Microsoft.Build.Tasks/Test/Microsoft.Build.Tasks/TestMessageLogger.cs index e05673dfbe2..be855ade2d7 100644 --- a/mcs/class/Microsoft.Build.Tasks/Test/Microsoft.Build.Tasks/TestMessageLogger.cs +++ b/mcs/class/Microsoft.Build.Tasks/Test/Microsoft.Build.Tasks/TestMessageLogger.cs @@ -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++; }; -- 2.25.1