Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildEngine4.cs
index 48c50e1149e9248c82b0d2ab1ca7c21d711f22d6..4eebb8ac1e2a6a76f54e23dde4d859d91727e422 100644 (file)
@@ -35,6 +35,7 @@ using System.Linq;
 using System.IO;
 using Microsoft.Build.Exceptions;
 using System.Globalization;
+using Microsoft.Build.Construction;
 
 namespace Microsoft.Build.Internal
 {
@@ -84,7 +85,7 @@ namespace Microsoft.Build.Internal
                        var toolset = parameters.GetToolset (toolsVersion);
                        if (toolset == null)
                                throw new InvalidOperationException (string.Format ("Toolset version '{0}' was not resolved to valid toolset", toolsVersion));
-                       event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Using Toolset version {0}.", toolsVersion), null, null, MessageImportance.Low));
+                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using Toolset version {0}.", toolsVersion), null, null, MessageImportance.Low));
                        var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (toolset), submission.BuildRequest.ProjectInstance.TaskDatabase);
                        BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
                }
@@ -112,83 +113,110 @@ namespace Microsoft.Build.Internal
                        var request = submission.BuildRequest;
                        var parameters = submission.BuildManager.OngoingBuildParameters;
                        this.project = args.Project;
-                       
+
+                       string directoryBackup = Directory.GetCurrentDirectory ();
+                       Directory.SetCurrentDirectory (project.Directory);
                        event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null, DateTime.Now));
                        
-                       var initialPropertiesFormatted = "Initial Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ());
-                       event_source.FireMessageRaised (this, new BuildMessageEventArgs (initialPropertiesFormatted, null, null, MessageImportance.Low));
+                       try {
+                               
+                               var initialPropertiesFormatted = "Initial Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ());
+                               LogMessageEvent (new BuildMessageEventArgs (initialPropertiesFormatted, null, null, MessageImportance.Low));
+                               var initialItemsFormatted = "Initial Items:\n" + string.Join (Environment.NewLine, project.Items.OrderBy (i => i.ItemType).Select (i => string.Format ("{0} : {1}", i.ItemType, i.EvaluatedInclude)).ToArray ());
+                               LogMessageEvent (new BuildMessageEventArgs (initialItemsFormatted, null, null, MessageImportance.Low));
+                               
+                               // null targets -> success. empty targets -> success(!)
+                               if (request.TargetNames == null)
+                                       args.Result.OverallResult = BuildResultCode.Success;
+                               else {
+                                       foreach (var targetName in (args.TargetNames ?? request.TargetNames).Where (t => t != null))
+                                               BuildTargetByName (targetName, args);
                        
-                       // null targets -> success. empty targets -> success(!)
-                       if (request.TargetNames == null)
-                               args.Result.OverallResult = BuildResultCode.Success;
-                       else {
-                               foreach (var targetName in request.TargetNames.Where (t => t != null))
-                                       BuildTargetByName (targetName, args);
-               
-                               // FIXME: check .NET behavior, whether cancellation always results in failure.
-                               args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Select (p => p.Value).Any (r => r.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
+                                       // FIXME: check .NET behavior, whether cancellation always results in failure.
+                                       args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
+                               }
+                       } catch (Exception ex) {
+                               args.Result.OverallResult = BuildResultCode.Failure;
+                               LogErrorEvent (new BuildErrorEventArgs (null, null, project.FullPath, 0, 0, 0, 0, "Unhandled exception occured during a build", null, null));
+                               LogMessageEvent (new BuildMessageEventArgs ("Exception details: " + ex, null, null, MessageImportance.Low));
+                               throw; // BuildSubmission re-catches this.
+                       } finally {
+                               event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success, DateTime.Now));
+                               Directory.SetCurrentDirectory (directoryBackup);
                        }
-                       
-                       event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success, DateTime.Now));
                }
                
                bool BuildTargetByName (string targetName, InternalBuildArguments args)
                {
-                       var targetResult = new TargetResult ();
-
                        var request = submission.BuildRequest;
                        var parameters = submission.BuildManager.OngoingBuildParameters;
                        ProjectTargetInstance target;
+                       TargetResult dummyResult;
+
+                       if (args.Result.ResultsByTarget.TryGetValue (targetName, out dummyResult) && dummyResult.ResultCode == TargetResultCode.Success) {
+                               LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because it was already built successfully.", targetName), null, null, MessageImportance.Low));
+                               return true; // do not add result.
+                       }
                        
+                       var targetResult = new TargetResult ();
+
                        // null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
                        if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
+                               // FIXME: from MSBuild.exe it is given MSB4057. Can we assign a number too?
                                throw new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath));
                        else if (!args.Project.EvaluateCondition (target.Condition)) {
-                               event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because condition '{1}' wasn't met.", target.Name, target.Condition), null, null, MessageImportance.Low));
+                               LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because condition '{1}' was not met.", target.Name, target.Condition), null, null, MessageImportance.Low));
                                targetResult.Skip ();
                        } else {
                                // process DependsOnTargets first.
-                               foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Where (s => !string.IsNullOrEmpty (s)).Select (s => s.Trim ())) {
+                               foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s))) {
+                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' depends on '{1}'.", target.Name, dep), null, null, MessageImportance.Low));
                                        if (!BuildTargetByName (dep, args)) {
+                                               LogMessageEvent (new BuildMessageEventArgs (string.Format ("Quit target '{0}', as dependency target '{1}' has failed.", target.Name, dep), null, null, MessageImportance.Low));
                                                return false;
                                        }
                                }
                                
                                Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s };
-                               
-                                       event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath));
                        
-                                       if (!string.IsNullOrEmpty (target.Inputs) != !string.IsNullOrEmpty (target.Outputs)) {
+                               event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath));
+                               try {
+                                       // FIXME: examine in which scenario Inputs/Outputs inconsistency results in errors. Now it rather prevents csproj build.
+                                       /*if (!string.IsNullOrEmpty (target.Inputs) != !string.IsNullOrEmpty (target.Outputs)) {
                                                targetResult.Failure (new InvalidProjectFileException (target.Location, null, string.Format ("Target {0} has mismatching Inputs and Outputs specification. When one is specified, another one has to be specified too.", targetName), null, null, null));
-                                       } else {
+                                       } else*/ {
                                                bool skip = false;
                                                if (!string.IsNullOrEmpty (target.Inputs)) {
-                                                       var inputs = args.Project.GetAllItems (target.Inputs, string.Empty, creator, creator, s => true, (t, s) => {});
+                                                       var inputs = args.Project.GetAllItems (target.Inputs, string.Empty, creator, creator, s => true, (t, s) => {
+                                                       });
                                                        if (!inputs.Any ()) {
-                                                               event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because there is no input.", target.Name), null, null, MessageImportance.Low));
+                                                               LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because there is no input.", target.Name), null, null, MessageImportance.Low));
                                                                skip = true;
                                                        } else {
-                                                               var outputs = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {});
+                                                               var outputs = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
+                                                               });
                                                                var needsUpdates = GetOlderOutputsThanInputs (inputs, outputs).FirstOrDefault ();
                                                                if (needsUpdates != null)
-                                                                       event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Target '{0}' needs to be built because new output {1} is needed.", target.Name, needsUpdates.ItemSpec), null, null, MessageImportance.Low));
+                                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' needs to be built because new output {1} is needed.", target.Name, needsUpdates.ItemSpec), null, null, MessageImportance.Low));
                                                                else {
-                                                                       event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because all the outputs are newer than all the inputs.", target.Name), null, null, MessageImportance.Low));
-                                                                       skip =true;
+                                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because all the outputs are newer than all the inputs.", target.Name), null, null, MessageImportance.Low));
+                                                                       skip = true;
                                                                }
                                                        }
                                                }
                                                if (skip) {
                                                        targetResult.Skip ();
-                                               }
-                                               else {
+                                               } else {
                                                        if (DoBuildTarget (target, targetResult, args)) {
-                                                               var items = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {});
+                                                               var items = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
+                                                               });
                                                                targetResult.Success (items);
-                                                               event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Finished", null, targetName, project.FullPath, target.FullPath, true));
                                                        }
                                                }
                                        }
+                               } finally {
+                                       event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Finished", null, targetName, project.FullPath, target.FullPath, targetResult.ResultCode != TargetResultCode.Failure));
+                               }
                        }
                        args.AddTargetResult (targetName, targetResult);
                        
@@ -199,7 +227,8 @@ namespace Microsoft.Build.Internal
                {
                        return outputs.Where (o => !File.Exists (o.GetMetadata ("FullPath")) || inputs.Any (i => string.CompareOrdinal (i.GetMetadata ("LastModifiedTime"), o.GetMetadata ("LastModifiedTime")) > 0));
                }
-               
+
+               // FIXME: Exception should be caught at caller site.
                bool DoBuildTarget (ProjectTargetInstance target, TargetResult targetResult, InternalBuildArguments args)
                {
                        var request = submission.BuildRequest;
@@ -231,28 +260,37 @@ namespace Microsoft.Build.Internal
                                        if (!args.Project.EvaluateCondition (c.Condition))
                                                continue;
                                        foreach (var item in c.Items) {
+                                               if (!args.Project.EvaluateCondition (item.Condition))
+                                                       continue;
                                                Func<string,ProjectItemInstance> creator = i => new ProjectItemInstance (project, item.ItemType, item.Metadata.Select (m => new KeyValuePair<string,string> (m.Name, m.Value)), i);
                                                foreach (var ti in project.GetAllItems (item.Include, item.Exclude, creator, creator, s => s == item.ItemType, (ti, s) => ti.SetMetadata ("RecurseDir", s)))
                                                        itemsToRemove.Add (ti);
                                        }
                                }
                                
-                               foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
-                                       if (!args.Project.EvaluateCondition (c.Condition))
-                                               continue;
-                                       throw new NotImplementedException ();
-                               }
-                               
                                // run tasks
                                foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) {
                                        current_task = ti;
                                        if (!args.Project.EvaluateCondition (ti.Condition)) {
-                                               event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Task '{0}' was skipped because condition '{1}' wasn't met.", ti.Name, ti.Condition), null, null, MessageImportance.Low));
+                                               LogMessageEvent (new BuildMessageEventArgs (string.Format ("Task '{0}' was skipped because condition '{1}' wasn't met.", ti.Name, ti.Condition), null, null, MessageImportance.Low));
                                                continue;
                                        }
                                        if (!RunBuildTask (target, ti, targetResult, args))
                                                return false;
                                }
+                       } catch (Exception ex) {
+                               // fallback task specified by OnError element
+                               foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
+                                       if (!args.Project.EvaluateCondition (c.Condition))
+                                               continue;
+                                       foreach (var fallbackTarget in project.ExpandString (c.ExecuteTargets).Split (';'))
+                                               BuildTargetByName (fallbackTarget, args);
+                               }
+                               int line = target.Location != null ? target.Location.Line : 0;
+                               int col = target.Location != null ? target.Location.Column : 0;
+                               LogErrorEvent (new BuildErrorEventArgs (null, null, target.FullPath, line, col, 0, 0, ex.Message, null, null));
+                               targetResult.Failure (ex);
+                               return false;
                        } finally {
                                // restore temporary property state to the original state.
                                foreach (var p in propsToRestore) {
@@ -280,7 +318,7 @@ namespace Microsoft.Build.Internal
                        factoryIdentityParameters ["MSBuildArchitecture"] = taskInstance.MSBuildArchitecture;
                        #endif
                        var task = args.BuildTaskFactory.CreateTask (taskInstance.Name, factoryIdentityParameters, this);
-                       event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", taskInstance.Name, task.GetType ().AssemblyQualifiedName), null, null, MessageImportance.Low));
+                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", taskInstance.Name, task.GetType ().AssemblyQualifiedName), null, null, MessageImportance.Low));
                        task.HostObject = host;
                        task.BuildEngine = this;
                        
@@ -295,6 +333,11 @@ namespace Microsoft.Build.Internal
                                        taskInstance.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ())));
                        
                        foreach (var p in evaluatedTaskParams) {
+                               switch (p.Key.ToLower ()) {
+                               case "condition":
+                               case "continueonerror":
+                                       continue;
+                               }
                                var prop = task.GetType ().GetProperty (p.Key);
                                if (prop == null)
                                        throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", taskInstance.Name, p.Key));
@@ -311,37 +354,43 @@ namespace Microsoft.Build.Internal
                        }
                        
                        // Do execute task.
+                       bool taskSuccess = false;
                        event_source.FireTaskStarted (this, new TaskStartedEventArgs ("Task Started", null, project.FullPath, taskInstance.FullPath, taskInstance.Name));
-                       var taskSuccess = task.Execute ();
+                       try {
+                               taskSuccess = task.Execute ();
                        
-                       if (!taskSuccess) {
-                               event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, taskInstance.FullPath, taskInstance.Name, false));
-                               targetResult.Failure (null);
-                               if (!ContinueOnError) {
-                                       event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Failed", null, target.Name, project.FullPath, target.FullPath, false));
-                                       return false;
-                               }
-                       } else {
-                               // Evaluate task output properties and items.
-                               event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, taskInstance.FullPath, taskInstance.Name, true));
-                               foreach (var to in taskInstance.Outputs) {
-                                       if (!project.EvaluateCondition (to.Condition))
-                                               continue;
-                                       var toItem = to as ProjectTaskOutputItemInstance;
-                                       var toProp = to as ProjectTaskOutputPropertyInstance;
-                                       string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter;
-                                       var pi = task.GetType ().GetProperty (taskParameter);
-                                       if (pi == null)
-                                               throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", taskInstance.Name, toItem.TaskParameter));
-                                       if (!pi.CanRead)
-                                               throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", taskInstance.Name, toItem.TaskParameter));
-                                       if (toItem != null)
-                                               args.Project.AddItem (toItem.ItemType, ConvertFrom (pi.GetValue (task, null)));
-                                       else
-                                               args.Project.SetProperty (toProp.PropertyName, ConvertFrom (pi.GetValue (task, null)));
+                               if (!taskSuccess) {
+                                       targetResult.Failure (null);
+                                       if (!ContinueOnError) {
+                                               return false;
+                                       }
+                               } else {
+                                       // Evaluate task output properties and items.
+                                       foreach (var to in taskInstance.Outputs) {
+                                               if (!project.EvaluateCondition (to.Condition))
+                                                       continue;
+                                               var toItem = to as ProjectTaskOutputItemInstance;
+                                               var toProp = to as ProjectTaskOutputPropertyInstance;
+                                               string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter;
+                                               var pi = task.GetType ().GetProperty (taskParameter);
+                                               if (pi == null)
+                                                       throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", taskInstance.Name, toItem.TaskParameter));
+                                               if (!pi.CanRead)
+                                                       throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", taskInstance.Name, toItem.TaskParameter));
+                                               var value = ConvertFrom (pi.GetValue (task, null));
+                                               if (toItem != null) {
+                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Item {0} from TaskParameter {1}: {2}", toItem.ItemType, toItem.TaskParameter, value), null, null, MessageImportance.Low));
+                                                       foreach (var item in value.Split (';'))
+                                                               args.Project.AddItem (toItem.ItemType, item);
+                                               } else {
+                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Property {0} from TaskParameter {1}: {2}", toProp.PropertyName, toProp.TaskParameter, value), null, null, MessageImportance.Low));
+                                                       args.Project.SetProperty (toProp.PropertyName, value);
+                                               }
+                                       }
                                }
+                       } finally {
+                               event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, taskInstance.FullPath, taskInstance.Name, taskSuccess));
                        }
-                       
                        return true;
                }
                
@@ -350,7 +399,7 @@ namespace Microsoft.Build.Internal
                        if (targetType == typeof(ITaskItem) || targetType.IsSubclassOf (typeof(ITaskItem)))
                                return new TargetOutputTaskItem () { ItemSpec = WindowsCompatibilityExtensions.NormalizeFilePath (source.Trim ()) };
                        if (targetType.IsArray)
-                               return new ArrayList (source.Split (';').Where (s => !string.IsNullOrEmpty (s)).Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
+                               return new ArrayList (source.Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s)).Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
                                                .ToArray (targetType.GetElementType ());
                        if (targetType == typeof(bool)) {
                                switch (source != null ? source.ToLower (CultureInfo.InvariantCulture) : string.Empty) {
@@ -375,7 +424,7 @@ namespace Microsoft.Build.Internal
                        if (source is ITaskItem)
                                return ((ITaskItem) source).ItemSpec;
                        if (source.GetType ().IsArray)
-                               return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
+                               return string.Join (";", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
                        else
                                return (string) Convert.ChangeType (source, typeof (string));
                }
@@ -504,17 +553,15 @@ namespace Microsoft.Build.Internal
 
                #region IBuildEngine2 implementation
 
+               // To NOT reuse this IBuildEngine instance for different build, we create another BuildManager and BuildSubmisson and then run it.
                public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
                {
-                       var proj = GetProjectInstance (projectFileName, toolsVersion);
                        var globalPropertiesThatMakeSense = new Dictionary<string,string> ();
                        foreach (DictionaryEntry p in globalProperties)
                                globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value;
-                       var result = new BuildResult ();
-                       var outputs = new Dictionary<string, string> ();
-                       BuildProject (() => false, result, proj, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
-                       foreach (var p in outputs)
-                               targetOutputs [p.Key] = p.Value;
+                       var result = new BuildManager ().Build (this.submission.BuildManager.OngoingBuildParameters.Clone (), new BuildRequestData (projectFileName, globalPropertiesThatMakeSense, toolsVersion, targetNames, null));
+                       foreach (var p in result.ResultsByTarget)
+                               targetOutputs [p.Key] = p.Value.Items;
                        return result.OverallResult == BuildResultCode.Success;
                }