Merge pull request #799 from kebby/master
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / TargetBatchingImpl.cs
index fb394bf1731e449cbc882bc2d1bf3449094ad506..3873a93c498f64c124ae70f214f56bada47ac940 100644 (file)
@@ -28,8 +28,6 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-#if NET_2_0
-
 using System;
 using System.IO;
 using System.Collections.Generic;
@@ -43,6 +41,7 @@ namespace Microsoft.Build.BuildEngine {
        {
                string          inputs;
                string          outputs;
+               string          name;
 
                public TargetBatchingImpl (Project project, XmlElement targetElement)
                        : base (project)
@@ -52,19 +51,24 @@ namespace Microsoft.Build.BuildEngine {
 
                        inputs = targetElement.GetAttribute ("Inputs");
                        outputs = targetElement.GetAttribute ("Outputs");
+                       name = targetElement.GetAttribute ("Name");
                }
 
                public bool Build (Target target, out bool executeOnErrors)
                {
                        executeOnErrors = false;
                        try {
-                               if (!BuildTargetNeeded ()) {
+                               string reason;
+                               if (!BuildTargetNeeded (out reason)) {
                                        LogTargetStarted (target);
-                                       LogTargetSkipped (target);
+                                       LogTargetSkipped (target, reason);
                                        LogTargetFinished (target, true);
                                        return true;
                                }
 
+                               if (!String.IsNullOrEmpty (reason))
+                                       target.Engine.LogMessage (MessageImportance.Low, reason);
+
                                Init ();
 
                                ParseTargetAttributes (target);
@@ -83,73 +87,61 @@ namespace Microsoft.Build.BuildEngine {
                bool Run (Target target, out bool executeOnErrors)
                {
                        executeOnErrors = false;
-                       if (buckets.Count > 0)
-                               return RunBatched (target, out executeOnErrors);
-                       else
-                               return RunUnbatched (target, out executeOnErrors);
-               }
+                       if (buckets.Count > 0) {
+                               foreach (Dictionary<string, BuildItemGroup> bucket in buckets)
+                                       if (!RunTargetWithBucket (bucket, target, out executeOnErrors))
+                                               return false;
 
-               bool RunBatched (Target target, out bool executeOnErrors)
-               {
-                       bool result = true;
-                       executeOnErrors = false;
-                       foreach (Dictionary<string, BuildItemGroup> bucket in buckets) {
-                               LogTargetStarted (target);
-                               try {
-                                       project.SetBatchedItems (bucket, commonItemsByName);
-                                       if (!BuildTargetNeeded ()) {
-                                               LogTargetSkipped (target);
-                                               continue;
-                                       }
-
-                                       for (int i = 0; i < target.BuildTasks.Count; i ++) {
-                                               //required setting here, as batchtask.Run resets
-                                               //these to null before returning!
-                                               project.SetBatchedItems (bucket, commonItemsByName);
-
-                                               //FIXME: parsing attributes repeatedly
-                                               BuildTask task = target.BuildTasks [i];
-                                               result = new TaskBatchingImpl (project).Build (task, out executeOnErrors);
-                                               if (!result && !task.ContinueOnError) {
-                                                       executeOnErrors = true;
-                                                       break;
-                                               }
-                                       }
-                               } finally {
-                                       LogTargetFinished (target, result);
-                               }
+                               return true;
+                       } else {
+                               return RunTargetWithBucket (null, target, out executeOnErrors);
                        }
-                       project.SetBatchedItems (null, null);
-
-                       return result;
                }
 
-               bool RunUnbatched (Target target, out bool executeOnErrors)
+               bool RunTargetWithBucket (Dictionary<string, BuildItemGroup> bucket, Target target, out bool executeOnErrors)
                {
-                       bool result = true;
+                       bool target_result = true;
                        executeOnErrors = false;
+
                        LogTargetStarted (target);
+                       if (bucket != null)
+                               project.PushBatch (bucket, commonItemsByName);
                        try {
-                               if (!BuildTargetNeeded ()) {
-                                       LogTargetSkipped (target);
-                                       LogTargetFinished (target, true);
+                               string reason;
+                               if (!BuildTargetNeeded (out reason)) {
+                                       LogTargetSkipped (target, reason);
                                        return true;
                                }
 
-                               foreach (BuildTask bt in target.BuildTasks) {
+                               if (!String.IsNullOrEmpty (reason))
+                                       target.Engine.LogMessage (MessageImportance.Low, reason);
+
+                               for (int i = 0; i < target.BuildTasks.Count; i ++) {
+                                       //FIXME: parsing attributes repeatedly
+                                       IBuildTask bt = target.BuildTasks [i];
+
                                        TaskBatchingImpl batchingImpl = new TaskBatchingImpl (project);
-                                       result = batchingImpl.Build (bt, out executeOnErrors);
+                                       bool task_result = batchingImpl.Build (bt, out executeOnErrors);
+                                       if (task_result)
+                                               continue;
 
-                                       if (!result && !bt.ContinueOnError) {
+                                       // task failed, if ContinueOnError,
+                                       // ignore failed state for target
+                                       target_result = bt.ContinueOnError;
+
+                                       if (!bt.ContinueOnError) {
                                                executeOnErrors = true;
-                                               break;
+                                               return false;
                                        }
+
                                }
                        } finally {
-                               LogTargetFinished (target, result);
+                               if (bucket != null)
+                                       project.PopBatch ();
+                               LogTargetFinished (target, target_result);
                        }
 
-                       return result;
+                       return target_result;
                }
 
                // Parse target's Input and Output attributes to get list of referenced
@@ -163,80 +155,94 @@ namespace Microsoft.Build.BuildEngine {
                                ParseAttribute (outputs);
                }
 
-               bool BuildTargetNeeded ()
+               bool BuildTargetNeeded (out string reason)
                {
+                       reason = String.Empty;
                        ITaskItem [] inputFiles;
                        ITaskItem [] outputFiles;
-                       DateTime oldestInput, youngestOutput;
+                       DateTime youngestInput, oldestOutput;
 
                        if (String.IsNullOrEmpty (inputs.Trim ()))
                                return true;
 
-                       if (String.IsNullOrEmpty (outputs.Trim ()))
+                       if (String.IsNullOrEmpty (outputs.Trim ())) {
+                               project.ParentEngine.LogError ("Target {0} has inputs but no outputs specified.", name);
                                return true;
+                       }
 
                        Expression e = new Expression ();
-                       e.Parse (inputs, true);
-                       inputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]));
+                       e.Parse (inputs, ParseOptions.AllowItemsMetadataAndSplit);
+                       inputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
 
                        e = new Expression ();
-                       e.Parse (outputs, true);
-                       outputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]));
+                       e.Parse (outputs, ParseOptions.AllowItemsMetadataAndSplit);
+                       outputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
 
-                       if (inputFiles == null || inputFiles.Length == 0)
+                       if (outputFiles == null || outputFiles.Length == 0) {
+                               reason = String.Format ("No output files were specified for target {0}, skipping.", name);
                                return false;
+                       }
 
-                       //FIXME: if input specified, then output must also
-                       //       be there, add tests and confirm
-                       if (outputFiles == null || outputFiles.Length == 0)
+                       if (inputFiles == null || inputFiles.Length == 0) {
+                               reason = String.Format ("No input files were specified for target {0}, skipping.", name);
                                return false;
+                       }
 
-                       if (File.Exists (inputFiles [0].ItemSpec))
-                               oldestInput = File.GetLastWriteTime (inputFiles [0].ItemSpec);
-                       else
-                               return true;
-
-                       if (File.Exists (outputFiles [0].ItemSpec))
-                               youngestOutput = File.GetLastWriteTime (outputFiles [0].ItemSpec);
-                       else
-                               return true;
+                       youngestInput = DateTime.MinValue;
+                       oldestOutput = DateTime.MaxValue;
 
+                       string youngestInputFile, oldestOutputFile;
+                       youngestInputFile = oldestOutputFile = String.Empty;
                        foreach (ITaskItem item in inputFiles) {
-                               string file = item.ItemSpec;
-                               if (file.Trim () == String.Empty)
+                               string file = item.ItemSpec.Trim ();
+                               if (file.Length == 0)
                                        continue;
 
-                               if (File.Exists (file.Trim ())) {
-                                       if (File.GetLastWriteTime (file.Trim ()) > oldestInput)
-                                               oldestInput = File.GetLastWriteTime (file.Trim ());
-                               } else {
+                               if (!File.Exists (file)) {
+                                       reason = String.Format ("Target {0} needs to be built as input file '{1}' does not exist.", name, file);
                                        return true;
                                }
+
+                               DateTime lastWriteTime = File.GetLastWriteTime (file);
+                               if (lastWriteTime > youngestInput) {
+                                       youngestInput = lastWriteTime;
+                                       youngestInputFile = file;
+                               }
                        }
 
                        foreach (ITaskItem item in outputFiles) {
-                               string file = item.ItemSpec;
-                               if (file.Trim () == String.Empty)
+                               string file = item.ItemSpec.Trim ();
+                               if (file.Length == 0)
                                        continue;
 
-                               if (File.Exists (file.Trim ())) {
-                                       if (File.GetLastWriteTime (file.Trim ()) < youngestOutput)
-                                               youngestOutput = File.GetLastWriteTime (file.Trim ());
-                               } else
+                               if (!File.Exists (file)) {
+                                       reason = String.Format ("Target {0} needs to be built as output file '{1}' does not exist.", name, file);
                                        return true;
+                               }
+
+                               DateTime lastWriteTime = File.GetLastWriteTime (file);
+                               if (lastWriteTime < oldestOutput) {
+                                       oldestOutput = lastWriteTime;
+                                       oldestOutputFile = file;
+                               }
                        }
 
-                       if (oldestInput > youngestOutput)
+                       if (youngestInput > oldestOutput) {
+                               reason = String.Format ("Target {0} needs to be built as input file '{1}' is newer than output file '{2}'",
+                                               name, youngestInputFile, oldestOutputFile);
                                return true;
-                       else
-                               return false;
+                       }
+
+                       return false;
                }
 
-               void LogTargetSkipped (Target target)
+               void LogTargetSkipped (Target target, string reason)
                {
                        BuildMessageEventArgs bmea;
-                       bmea = new BuildMessageEventArgs (String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.",
-                               target.Name), null, "MSBuild", MessageImportance.Normal);
+                       bmea = new BuildMessageEventArgs (String.IsNullOrEmpty (reason)
+                                                               ? String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.", target.Name)
+                                                               : reason,
+                                                       null, "MSBuild", MessageImportance.Normal);
                        target.Engine.EventSource.FireMessageRaised (this, bmea);
                }
 
@@ -244,7 +250,8 @@ namespace Microsoft.Build.BuildEngine {
                {
                        TargetStartedEventArgs tsea;
                        string projectFile = project.FullFileName;
-                       tsea = new TargetStartedEventArgs (String.Format ("Target {0} started.", target.Name), null, target.Name, projectFile, null);
+                       tsea = new TargetStartedEventArgs (String.Format ("Target {0} started.", target.Name), null,
+                                       target.Name, projectFile, target.TargetFile);
                        target.Engine.EventSource.FireTargetStarted (this, tsea);
                }
 
@@ -252,11 +259,10 @@ namespace Microsoft.Build.BuildEngine {
                {
                        TargetFinishedEventArgs tfea;
                        string projectFile = project.FullFileName;
-                       tfea = new TargetFinishedEventArgs (String.Format ("Target {0} finished.", target.Name), null, target.Name, projectFile, null, succeeded);
+                       tfea = new TargetFinishedEventArgs (String.Format ("Target {0} finished.", target.Name), null,
+                                       target.Name, projectFile, target.TargetFile, succeeded);
                        target.Engine.EventSource.FireTargetFinished (this, tfea);
                }
 
        }
 }
-
-#endif