Fix ContinueOnError evaluation (was almost always true). Add more logging.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildEngine4.cs
index eb34627e1d6b21a9449521b46554bf660c68eaab..071122a84e2c383daa063df72f589bd94d525bb9 100644 (file)
@@ -1,3 +1,30 @@
+//
+// BuildEngine4.cs
+//
+// Author:
+//   Atsushi Enomoto (atsushi@xamarin.com)
+//
+// Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
 using System;
 using System.Collections;
 using System.Collections.Generic;
@@ -10,69 +37,271 @@ using System.IO;
 
 namespace Microsoft.Build.Internal
 {
-       class BuildEngine4 : IBuildEngine4
+       class BuildEngine4
+#if NET_4_5
+               : IBuildEngine4
+#else
+               : IBuildEngine3
+#endif
        {
                public BuildEngine4 (BuildSubmission submission)
                {
                        this.submission = submission;
                        event_source = new EventSource ();
+                       if (submission.BuildManager.OngoingBuildParameters.Loggers != null)
+                               foreach (var l in submission.BuildManager.OngoingBuildParameters.Loggers)
+                                       l.Initialize (event_source);
                }
 
                BuildSubmission submission;
-               ProjectInstance current_project;
+               ProjectInstance project;
                ProjectTaskInstance current_task;
                EventSource event_source;
                
                public ProjectCollection Projects {
                        get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; }
                }
-               
-               class BuildTask : ITask
+
+               // FIXME:
+               // While we are not faced to implement those features, there are some modern task execution requirements.
+               //
+               // This will have to be available for "out of process" nodes (see NodeAffinity).
+               // NodeAffinity is set per project file at BuildManager.HostServices.
+               // When NodeAffinity is set to OutOfProc, it should probably launch different build host
+               // that runs separate build tasks. (.NET has MSBuildTaskHost.exe which I guess is about that.)
+               //
+               // Also note that the complete implementation has to support LoadInSeparateAppDomainAttribute
+               // (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run
+               // in separate AppDomain.
+               //
+               public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
                {
-                       public IBuildEngine BuildEngine { get; set; }
-                       public ITaskHost HostObject { get; set; }
+                       var parameters = submission.BuildManager.OngoingBuildParameters;
+                       var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (parameters.GetToolset (toolsVersion)), submission.BuildRequest.ProjectInstance.TaskDatabase);
+                       BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
                }
 
-               public BuildResult BuildProject (Func<bool> checkCancel, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
+               class InternalBuildArguments
+               {
+                       public Func<bool> CheckCancel;
+                       public BuildResult Result;
+                       public ProjectInstance Project;
+                       public IEnumerable<string> TargetNames;
+                       public IDictionary<string,string> GlobalProperties;
+                       public IDictionary<string,string> TargetOutputs;
+                       public string ToolsVersion;
+                       public BuildTaskFactory BuildTaskFactory;
+               }
+               
+               void BuildProject (InternalBuildArguments args)
                {
-                       var result = new BuildResult () { SubmissionId = submission.SubmissionId };
                        var request = submission.BuildRequest;
                        var parameters = submission.BuildManager.OngoingBuildParameters;
+                       this.project = args.Project;
                        
+                       event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null));
+                                               
                        // null targets -> success. empty targets -> success(!)
                        if (request.TargetNames == null)
-                               result.OverallResult = BuildResultCode.Success;
+                               args.Result.OverallResult = BuildResultCode.Success;
                        else {
-                               foreach (var targetName in request.TargetNames.Where (t => t != null)) {
-                                       if (checkCancel ())
-                                               break;
-
-                                       ProjectTargetInstance target;
-                                       // null key is allowed and regarded as blind success(!)
-                                       if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
-                                               result.AddResultsForTarget (targetName, new TargetResult (new ITaskItem [0], TargetResultCode.Failure));
-                                       else {
-                                               foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ())
-                                                       throw new NotImplementedException ();
-                                               foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ())
-                                                       throw new NotImplementedException ();
-                                               foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ())
-                                                       throw new NotImplementedException ();
-                                               foreach (var c in target.Children.OfType<ProjectTaskInstance> ()) {
-                                                       var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, targetName, c.Name);
-                                                       var task = new BuildTask () { BuildEngine = this, HostObject = host };
-                                                       
-                                                       throw new NotImplementedException ();
-                                               }
+                               foreach (var targetName in request.TargetNames.Where (t => t != null))
+                                       args.Result.AddResultsForTarget (targetName, BuildTarget (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;
+                       }                       
+                       event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success));
+               }
+               
+               TargetResult BuildTarget (string targetName, InternalBuildArguments args)
+               {
+                       var targetResult = new TargetResult ();
+
+                       var request = submission.BuildRequest;
+                       var parameters = submission.BuildManager.OngoingBuildParameters;
+                       ProjectTargetInstance target;
+                       
+                       // FIXME: check skip condition
+                       if (false)
+                               targetResult.Skip ();
+                       // null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
+                       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);
+                                               return targetResult;
                                        }
                                }
                                
-                               // FIXME: check .NET behavior, whether cancellation always results in failure.
-                               result.OverallResult = checkCancel () ? BuildResultCode.Failure : result.ResultsByTarget.Select (p => p.Value).Any (r => r.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
+                               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;
+                               }
+                               
+                               foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
+                                       if (!args.Project.EvaluateCondition (c.Condition))
+                                               continue;
+                                       throw new NotImplementedException ();
+                               }
+                               foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
+                                       if (!args.Project.EvaluateCondition (c.Condition))
+                                               continue;
+                                       throw new NotImplementedException ();
+                               }
+                               foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
+                                       if (!args.Project.EvaluateCondition (c.Condition))
+                                               continue;
+                                       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))
+                                               continue;
+                                       current_task = ti;
+                                       
+                                       var factoryIdentityParameters = new Dictionary<string,string> ();
+                                       #if NET_4_5
+                                       factoryIdentityParameters ["MSBuildRuntime"] = ti.MSBuildRuntime;
+                                       factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
+                                       #endif
+                                       var task = args.BuildTaskFactory.CreateTask (ti.Name, factoryIdentityParameters, this);
+                                       task.HostObject = host;
+                                       task.BuildEngine = this;
+                                       // FIXME: this cannot be that simple, value has to be converted to the appropriate target type.
+                                       var props = task.GetType ().GetProperties ()
+                                               .Where (p => p.CanWrite && p.GetCustomAttributes (typeof(RequiredAttribute), true).Any ());
+                                       var missings = props.Where (p => !ti.Parameters.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase)));
+                                       if (missings.Any ())
+                                               throw new InvalidOperationException (string.Format ("Task {0} of type {1} is used without specifying mandatory property: {2}",
+                                                       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);
+                                               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);
+                                       }
+                                       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;
+                                               }
+                                       } else {
+                                               event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, ti.FullPath, ti.Name, true));
+                                               foreach (var to in ti.Outputs) {
+                                                       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", ti.Name, toItem.TaskParameter));
+                                                       if (!pi.CanRead)
+                                                               throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", ti.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)));
+                                               }
+                                       }
+                               }
+                               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 result;
+                       return targetResult;
                }
                
+               object ConvertTo (string source, Type targetType)
+               {
+                       if (targetType.IsSubclassOf (typeof (ITaskItem)))
+                               return new TargetOutputTaskItem () { ItemSpec = source };
+                       if (targetType.IsArray)
+                               return new ArrayList (source.Split (';').Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
+                                               .ToArray (targetType.GetElementType ());
+                       else
+                               return Convert.ChangeType (source, targetType);
+               }
+               
+               string ConvertFrom (object source)
+               {
+                       if (source == null)
+                               return string.Empty;
+                       var type = source.GetType ();
+                       if (type.IsSubclassOf (typeof (ITaskItem)))
+                               return ((ITaskItem) source).ItemSpec;
+                       if (type.IsArray)
+                               return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
+                       else
+                               return (string) Convert.ChangeType (source, typeof (string));
+               }
+               
+               class TargetOutputTaskItem : ITaskItem2
+               {
+                       #region ITaskItem2 implementation
+                       public string GetMetadataValueEscaped (string metadataName)
+                       {
+                               return null;
+                       }
+                       public void SetMetadataValueLiteral (string metadataName, string metadataValue)
+                       {
+                               throw new NotSupportedException ();
+                       }
+                       public IDictionary CloneCustomMetadataEscaped ()
+                       {
+                               return new Hashtable ();
+                       }
+                       public string EvaluatedIncludeEscaped {
+                               get { return ProjectCollection.Escape (ItemSpec); }
+                               set { ItemSpec = ProjectCollection.Unescape (value); }
+                       }
+                       #endregion
+                       #region ITaskItem implementation
+                       public IDictionary CloneCustomMetadata ()
+                       {
+                               return new Hashtable ();
+                       }
+                       public void CopyMetadataTo (ITaskItem destinationItem)
+                       {
+                               // do nothing
+                       }
+                       public string GetMetadata (string metadataName)
+                       {
+                               return null;
+                       }
+                       public void RemoveMetadata (string metadataName)
+                       {
+                               // do nothing
+                       }
+                       public void SetMetadata (string metadataName, string metadataValue)
+                       {
+                               throw new NotSupportedException ();
+                       }
+                       public string ItemSpec { get; set; }
+                       public int MetadataCount {
+                               get { return 0; }
+                       }
+                       public ICollection MetadataNames {
+                               get { return new ArrayList (); }
+                       }
+                       #endregion
+               }
+               
+#if NET_4_5
                #region IBuildEngine4 implementation
                
                // task objects are not in use anyways though...
@@ -112,8 +341,8 @@ namespace Microsoft.Build.Internal
                                task_objects.Remove (reg);
                        return reg.Object;
                }
-
                #endregion
+#endif
 
                #region IBuildEngine3 implementation
 
@@ -138,12 +367,13 @@ namespace Microsoft.Build.Internal
 
                public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
                {
-                       var project = GetProjectInstance (projectFileName, 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> ();
-                       var result = BuildProject (() => false, project, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
+                       BuildProject (() => false, result, proj, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
                        foreach (var p in outputs)
                                targetOutputs [p.Key] = p.Value;
                        return result.OverallResult == BuildResultCode.Success;
@@ -203,15 +433,24 @@ namespace Microsoft.Build.Internal
                }
 
                public int ColumnNumberOfTaskNode {
-                       get { return current_task.Location.Column; }
+                       get { return current_task.Location != null ? current_task.Location.Column : 0; }
                }
 
                public bool ContinueOnError {
-                       get { return current_project.EvaluateCondition (current_task.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);
+                       }
                }
 
                public int LineNumberOfTaskNode {
-                       get { return current_task.Location.Line; }
+                       get { return current_task.Location != null ? current_task.Location.Line : 0; }
                }
 
                public string ProjectFileOfTaskNode {