Add more diagnostic logging in BuildEngine4.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildEngine4.cs
index 071122a84e2c383daa063df72f589bd94d525bb9..3541d6b0d8184b89fcb360839ea600112c9e8a2a 100644 (file)
@@ -34,6 +34,7 @@ using Microsoft.Build.Framework;
 using Microsoft.Build.Evaluation;
 using System.Linq;
 using System.IO;
+using Microsoft.Build.Exceptions;
 
 namespace Microsoft.Build.Internal
 {
@@ -55,6 +56,7 @@ namespace Microsoft.Build.Internal
 
                BuildSubmission submission;
                ProjectInstance project;
+               ProjectTargetInstance current_target;
                ProjectTaskInstance current_task;
                EventSource event_source;
                
@@ -91,6 +93,12 @@ namespace Microsoft.Build.Internal
                        public IDictionary<string,string> TargetOutputs;
                        public string ToolsVersion;
                        public BuildTaskFactory BuildTaskFactory;
+                       
+                       public void AddTargetResult (string targetName, TargetResult targetResult)
+                       {
+                               if (!Result.HasResultsForTarget (targetName))
+                                       Result.AddResultsForTarget (targetName, targetResult);
+                       }
                }
                
                void BuildProject (InternalBuildArguments args)
@@ -100,13 +108,16 @@ namespace Microsoft.Build.Internal
                        this.project = args.Project;
                        
                        event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null));
-                                               
+                       
+                       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));
+                       
                        // 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))
-                                       args.Result.AddResultsForTarget (targetName, BuildTarget (targetName, args));
+                                       args.AddTargetResult (targetName, 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;
@@ -114,7 +125,7 @@ namespace Microsoft.Build.Internal
                        event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success));
                }
                
-               TargetResult BuildTarget (string targetName, InternalBuildArguments args)
+               TargetResult BuildTargetByName (string targetName, InternalBuildArguments args)
                {
                        var targetResult = new TargetResult ();
 
@@ -129,33 +140,67 @@ namespace Microsoft.Build.Internal
                        else if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
                                targetResult.Failure (new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath)));
                        else {
-                               // process DependsOnTargets first.
-                               foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Where (s => !string.IsNullOrEmpty (s))) {
-                                       var result = BuildTarget (dep, args);
-                                       args.Result.AddResultsForTarget (dep, result);
-                                       if (result.ResultCode == TargetResultCode.Failure) {
-                                               targetResult.Failure (null);
+                               current_target = target;
+                               try {
+                                       if (!DoBuildTarget (targetResult, args))
                                                return targetResult;
-                                       }
+                               } finally {
+                                       current_target = null;
                                }
-                               
-                               event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, targetName, project.FullPath, target.FullPath));
-                               
-                               // Here we check cancellation (only after TargetStarted event).
-                               if (args.CheckCancel ()) {
-                                       targetResult.Failure (new OperationCanceledException ("Build has canceled"));
-                                       return targetResult;
+                               Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = 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));
+                       }
+                       return targetResult;
+               }
+               
+               bool DoBuildTarget (TargetResult targetResult, InternalBuildArguments args)
+               {
+                       var request = submission.BuildRequest;
+                       var target = current_target;
+       
+                       // process DependsOnTargets first.
+                       foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Where (s => !string.IsNullOrEmpty (s)).Select (s => s.Trim ())) {
+                               var result = BuildTargetByName (dep, args);
+                               args.AddTargetResult (dep, result);
+                               if (result.ResultCode == TargetResultCode.Failure) {
+                                       targetResult.Failure (null);
+                                       return false;
                                }
-                               
+                       }
+                       
+                       event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath));
+                       
+                       // Here we check cancellation (only after TargetStarted event).
+                       if (args.CheckCancel ()) {
+                               targetResult.Failure (new BuildAbortedException ("Build has canceled"));
+                               return false;
+                       }
+                       
+                       var propsToRestore = new Dictionary<string,string> ();
+                       var itemsToRemove = new List<ProjectItemInstance> ();
+                       try {
                                foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
                                        if (!args.Project.EvaluateCondition (c.Condition))
                                                continue;
-                                       throw new NotImplementedException ();
+                                       foreach (var p in c.Properties) {
+                                               if (!args.Project.EvaluateCondition (p.Condition))
+                                                       continue;
+                                               var value = args.Project.ExpandString (p.Value);
+                                               propsToRestore.Add (p.Name, project.GetPropertyValue (value));
+                                               project.SetProperty (p.Name, value);
+                                       }
                                }
                                foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
                                        if (!args.Project.EvaluateCondition (c.Condition))
                                                continue;
-                                       throw new NotImplementedException ();
+                                       foreach (var item in c.Items) {
+                                               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))
@@ -163,9 +208,11 @@ namespace Microsoft.Build.Internal
                                        throw new NotImplementedException ();
                                }
                                foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) {
-                                       var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, targetName, ti.Name);
-                                       if (!args.Project.EvaluateCondition (ti.Condition))
+                                       var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, target.Name, ti.Name);
+                                       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));
                                                continue;
+                                       }
                                        current_task = ti;
                                        
                                        var factoryIdentityParameters = new Dictionary<string,string> ();
@@ -174,6 +221,7 @@ namespace Microsoft.Build.Internal
                                        factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
                                        #endif
                                        var task = args.BuildTaskFactory.CreateTask (ti.Name, factoryIdentityParameters, this);
+                                       event_source.FireMessageRaised (this, new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", ti.Name, task.GetType ().AssemblyQualifiedName), null, null, MessageImportance.Low));
                                        task.HostObject = host;
                                        task.BuildEngine = this;
                                        // FIXME: this cannot be that simple, value has to be converted to the appropriate target type.
@@ -185,19 +233,20 @@ namespace Microsoft.Build.Internal
                                                        ti.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ())));
                                        foreach (var p in ti.Parameters) {
                                                var prop = task.GetType ().GetProperty (p.Key);
+                                               var value = project.ExpandString (p.Value);
                                                if (prop == null)
                                                        throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", ti.Name, p.Key));
                                                if (!prop.CanWrite)
                                                        throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", ti.Name, p.Key));
-                                               prop.SetValue (task, ConvertTo (p.Value, prop.PropertyType), null);
+                                               prop.SetValue (task, ConvertTo (value, prop.PropertyType), null);
                                        }
                                        event_source.FireTaskStarted (this, new TaskStartedEventArgs ("Task Started", null, project.FullPath, ti.FullPath, ti.Name));
                                        if (!task.Execute ()) {
                                                event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, ti.FullPath, ti.Name, false));
                                                targetResult.Failure (null);
                                                if (!ContinueOnError) {
-                                                       event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Failed", null, targetName, project.FullPath, target.FullPath, false));
-                                                       return targetResult;
+                                                       event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Failed", null, target.Name, project.FullPath, target.FullPath, false));
+                                                       return false;
                                                }
                                        } else {
                                                event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, ti.FullPath, ti.Name, true));
@@ -217,21 +266,26 @@ namespace Microsoft.Build.Internal
                                                }
                                        }
                                }
-                               Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = 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 {
+                               // restore temporary property state to the original state.
+                               foreach (var p in propsToRestore) {
+                                       if (p.Value == string.Empty)
+                                               project.RemoveProperty (p.Key);
+                                       else
+                                               project.SetProperty (p.Key, p.Value);
+                               }
+                               foreach (var item in itemsToRemove)
+                                       project.RemoveItem (item);
                        }
-                       return targetResult;
+                       return true;
                }
                
                object ConvertTo (string source, Type targetType)
                {
-                       if (targetType.IsSubclassOf (typeof (ITaskItem)))
+                       if (targetType == typeof (ITaskItem) || targetType.IsSubclassOf (typeof (ITaskItem)))
                                return new TargetOutputTaskItem () { ItemSpec = source };
                        if (targetType.IsArray)
-                               return new ArrayList (source.Split (';').Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
+                               return new ArrayList (source.Split (';').Where (s => !string.IsNullOrEmpty (s)).Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
                                                .ToArray (targetType.GetElementType ());
                        else
                                return Convert.ChangeType (source, targetType);
@@ -241,10 +295,9 @@ namespace Microsoft.Build.Internal
                {
                        if (source == null)
                                return string.Empty;
-                       var type = source.GetType ();
-                       if (type.IsSubclassOf (typeof (ITaskItem)))
+                       if (source is ITaskItem)
                                return ((ITaskItem) source).ItemSpec;
-                       if (type.IsArray)
+                       if (source.GetType ().IsArray)
                                return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
                        else
                                return (string) Convert.ChangeType (source, typeof (string));
@@ -252,18 +305,23 @@ namespace Microsoft.Build.Internal
                
                class TargetOutputTaskItem : ITaskItem2
                {
+                       Hashtable metadata = new Hashtable ();
+                       
                        #region ITaskItem2 implementation
                        public string GetMetadataValueEscaped (string metadataName)
                        {
-                               return null;
+                               return ProjectCollection.Escape ((string) metadata [metadataName]);
                        }
                        public void SetMetadataValueLiteral (string metadataName, string metadataValue)
                        {
-                               throw new NotSupportedException ();
+                               metadata [metadataName] = ProjectCollection.Unescape (metadataValue);
                        }
                        public IDictionary CloneCustomMetadataEscaped ()
                        {
-                               return new Hashtable ();
+                               var ret = new Hashtable ();
+                               foreach (DictionaryEntry e in metadata)
+                                       ret [e.Key] = ProjectCollection.Escape ((string) e.Value);
+                               return ret;
                        }
                        public string EvaluatedIncludeEscaped {
                                get { return ProjectCollection.Escape (ItemSpec); }
@@ -273,30 +331,31 @@ namespace Microsoft.Build.Internal
                        #region ITaskItem implementation
                        public IDictionary CloneCustomMetadata ()
                        {
-                               return new Hashtable ();
+                               return new Hashtable (metadata);
                        }
                        public void CopyMetadataTo (ITaskItem destinationItem)
                        {
-                               // do nothing
+                               foreach (DictionaryEntry e in metadata)
+                                       destinationItem.SetMetadata ((string) e.Key, (string) e.Value);
                        }
                        public string GetMetadata (string metadataName)
                        {
-                               return null;
+                               return (string) metadata [metadataName];
                        }
                        public void RemoveMetadata (string metadataName)
                        {
-                               // do nothing
+                               metadata.Remove (metadataName);
                        }
                        public void SetMetadata (string metadataName, string metadataValue)
                        {
-                               throw new NotSupportedException ();
+                               metadata [metadataName] = metadataValue;
                        }
                        public string ItemSpec { get; set; }
                        public int MetadataCount {
-                               get { return 0; }
+                               get { return metadata.Count; }
                        }
                        public ICollection MetadataNames {
-                               get { return new ArrayList (); }
+                               get { return metadata.Keys; }
                        }
                        #endregion
                }
@@ -437,16 +496,20 @@ namespace Microsoft.Build.Internal
                }
 
                public bool ContinueOnError {
-                       get {
-                               switch (current_task.ContinueOnError) {
-                               case "WarnAndContinue":
-                               case "ErrorAndContinue":
-                                       return true;
-                               case "ErrorAndStop":
-                                       return false;
-                               }
-                               return !string.IsNullOrEmpty (current_task.ContinueOnError) && project.EvaluateCondition (current_task.ContinueOnError);
+                       get { return current_task != null && project.EvaluateCondition (current_task.Condition) && EvaluateContinueOnError (current_task.ContinueOnError); }
+               }
+               
+               bool EvaluateContinueOnError (string value)
+               {
+                       switch (value) {
+                       case "WarnAndContinue":
+                       case "ErrorAndContinue":
+                               return true;
+                       case "ErrorAndStop":
+                               return false;
                        }
+                       // empty means "stop on error", so don't pass empty string to EvaluateCondition().
+                       return !string.IsNullOrEmpty (value) && project.EvaluateCondition (value);
                }
 
                public int LineNumberOfTaskNode {