Merge pull request #1404 from woodsb02/mono-route
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildEngine4.cs
index e415f187e9417b4ada8e571658b1b84bec1f2377..93244292518240bf21ad073e30bca39812003d0f 100644 (file)
@@ -35,15 +35,14 @@ using System.Linq;
 using System.IO;
 using Microsoft.Build.Exceptions;
 using System.Globalization;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Internal.Expressions;
+using System.Xml;
 
 namespace Microsoft.Build.Internal
 {
        class BuildEngine4
-#if NET_4_5
                : IBuildEngine4
-#else
-               : IBuildEngine3
-#endif
        {
                public BuildEngine4 (BuildSubmission submission)
                {
@@ -85,7 +84,7 @@ namespace Microsoft.Build.Internal
                        if (toolset == null)
                                throw new InvalidOperationException (string.Format ("Toolset version '{0}' was not resolved to valid toolset", toolsVersion));
                        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);
+                       var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (toolset), new BuildTaskDatabase (this, submission.BuildRequest.ProjectInstance));
                        BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
                }
 
@@ -119,17 +118,23 @@ namespace Microsoft.Build.Internal
                        
                        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 initialGlobalPropertiesFormatted = "Initial Global Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Where (p => p.IsImmutable).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ());
+                               LogMessageEvent (new BuildMessageEventArgs (initialGlobalPropertiesFormatted, null, null, MessageImportance.Low));
+                               var initialProjectPropertiesFormatted = "Initial Project Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Where (p => !p.IsImmutable).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ());
+                               LogMessageEvent (new BuildMessageEventArgs (initialProjectPropertiesFormatted, 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(!)
+                               foreach (var targetName in (request.ProjectInstance.InitialTargets).Where (t => t != null))
+                                       BuildTargetByName (targetName, args);
                                if (request.TargetNames == null)
-                                       args.Result.OverallResult = BuildResultCode.Success;
+                                       args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
                                else {
-                                       foreach (var targetName in (args.TargetNames ?? request.TargetNames).Where (t => t != null))
-                                               BuildTargetByName (targetName, args);
+                                       foreach (var targetName in (args.TargetNames ?? request.TargetNames).Where (t => t != null)) {
+                                               if (!BuildTargetByName (targetName, args))
+                                                       break;
+                                       }
                        
                                        // 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;
@@ -161,6 +166,7 @@ namespace Microsoft.Build.Internal
 
                        // 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)) {
                                LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because condition '{1}' was not met.", target.Name, target.Condition), null, null, MessageImportance.Low));
@@ -179,9 +185,10 @@ namespace Microsoft.Build.Internal
                        
                                event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath));
                                try {
-                                       if (!string.IsNullOrEmpty (target.Inputs) != !string.IsNullOrEmpty (target.Outputs)) {
+                                       // 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) => {
@@ -224,7 +231,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;
@@ -235,46 +243,53 @@ namespace Microsoft.Build.Internal
                                return false;
                        }
                        
-                       var propsToRestore = new Dictionary<string,string> ();
-                       var itemsToRemove = new List<ProjectItemInstance> ();
                        try {
-                               // Evaluate additional target properties
-                               foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
-                                       if (!args.Project.EvaluateCondition (c.Condition))
-                                               continue;
-                                       foreach (var p in c.Properties) {
-                                               if (!args.Project.EvaluateCondition (p.Condition))
+                               foreach (var child in target.Children) {
+                                       // Evaluate additional target properties
+                                       var tp = child as ProjectPropertyGroupTaskInstance;
+                                       if (tp != null) {
+                                               if (!args.Project.EvaluateCondition (tp.Condition))
                                                        continue;
-                                               var value = args.Project.ExpandString (p.Value);
-                                               propsToRestore.Add (p.Name, project.GetPropertyValue (value));
-                                               project.SetProperty (p.Name, value);
-                                       }
-                               }
-                               
-                               // Evaluate additional target items
-                               foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
-                                       if (!args.Project.EvaluateCondition (c.Condition))
+                                               foreach (var p in tp.Properties) {
+                                                       if (!args.Project.EvaluateCondition (p.Condition))
+                                                               continue;
+                                                       var value = args.Project.ExpandString (p.Value);
+                                                       project.SetProperty (p.Name, value);
+                                               }
                                                continue;
-                                       foreach (var item in c.Items) {
-                                               if (!args.Project.EvaluateCondition (item.Condition))
+                                       }
+
+                                       var ii = child as ProjectItemGroupTaskInstance;
+                                       if (ii != null) {
+                                               if (!args.Project.EvaluateCondition (ii.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 item in ii.Items) {
+                                                       if (!args.Project.EvaluateCondition (item.Condition))
+                                                               continue;
+                                                       project.AddItem (item.ItemType, project.ExpandString (item.Include));
+                                               }
+                                               continue;
                                        }
-                               }
-                               
-                               // run tasks
-                               foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) {
-                                       current_task = ti;
-                                       if (!args.Project.EvaluateCondition (ti.Condition)) {
-                                               LogMessageEvent (new BuildMessageEventArgs (string.Format ("Task '{0}' was skipped because condition '{1}' wasn't met.", ti.Name, ti.Condition), null, null, MessageImportance.Low));
+                                       
+                                       var task = child as ProjectTaskInstance;
+                                       if (task != null) {
+                                               current_task = task;
+                                               if (!args.Project.EvaluateCondition (task.Condition)) {
+                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Task '{0}' was skipped because condition '{1}' wasn't met.", task.Name, task.Condition), null, null, MessageImportance.Low));
+                                                       continue;
+                                               }
+                                               if (!RunBuildTask (target, task, targetResult, args))
+                                                       return false;
                                                continue;
                                        }
-                                       if (!RunBuildTask (target, ti, targetResult, args))
-                                               return false;
+
+                                       var onError = child as ProjectOnErrorInstance;
+                                       if (onError != null)
+                                               continue; // evaluated under catch clause.
+
+                                       throw new NotSupportedException (string.Format ("Unexpected Target element children \"{0}\"", child.GetType ()));
                                }
-                       } catch {
+                       } catch (Exception ex) {
                                // fallback task specified by OnError element
                                foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
                                        if (!args.Project.EvaluateCondition (c.Condition))
@@ -282,17 +297,11 @@ namespace Microsoft.Build.Internal
                                        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) {
-                                       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 true;
                }
@@ -305,18 +314,19 @@ namespace Microsoft.Build.Internal
                        
                        // Create Task instance.
                        var factoryIdentityParameters = new Dictionary<string,string> ();
-                       #if NET_4_5
                        factoryIdentityParameters ["MSBuildRuntime"] = taskInstance.MSBuildRuntime;
                        factoryIdentityParameters ["MSBuildArchitecture"] = taskInstance.MSBuildArchitecture;
-                       #endif
                        var task = args.BuildTaskFactory.CreateTask (taskInstance.Name, factoryIdentityParameters, this);
-                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", taskInstance.Name, task.GetType ().AssemblyQualifiedName), null, null, MessageImportance.Low));
+                       if (task == null)
+                               throw new InvalidOperationException (string.Format ("TaskFactory {0} returned null Task", args.BuildTaskFactory));
+                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", taskInstance.Name, task.GetType ()), null, null, MessageImportance.Low));
                        task.HostObject = host;
                        task.BuildEngine = this;
                        
                        // Prepare task parameters.
-                       var evaluatedTaskParams = taskInstance.Parameters.Select (p => new KeyValuePair<string,string> (p.Key, project.ExpandString (p.Value)));
-                       
+                       var evaluator = new ExpressionEvaluator (project);
+                       var evaluatedTaskParams = taskInstance.Parameters.Select (p => new KeyValuePair<string,string> (p.Key, project.ExpandString (evaluator, p.Value)));
+
                        var requiredProps = task.GetType ().GetProperties ()
                                .Where (p => p.CanWrite && p.GetCustomAttributes (typeof (RequiredAttribute), true).Any ());
                        var missings = requiredProps.Where (p => !evaluatedTaskParams.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase)));
@@ -338,8 +348,7 @@ namespace Microsoft.Build.Internal
                                if (string.IsNullOrEmpty (p.Value) && !requiredProps.Contains (prop))
                                        continue;
                                try {
-                                       var valueInstance = ConvertTo (p.Value, prop.PropertyType);
-                                       prop.SetValue (task, valueInstance, null);
+                                       prop.SetValue (task, ConvertTo (p.Value, prop.PropertyType, evaluator), null);
                                } catch (Exception ex) {
                                        throw new InvalidOperationException (string.Format ("Failed to convert '{0}' for property '{1}' of type {2}", p.Value, prop.Name, prop.PropertyType), ex);
                                }
@@ -369,14 +378,29 @@ namespace Microsoft.Build.Internal
                                                        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));
+                                               var value = pi.GetValue (task, null);
+                                               var valueString = ConvertFrom (value);
                                                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);
+                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Item {0} from TaskParameter {1}: {2}", toItem.ItemType, toItem.TaskParameter, valueString), null, null, MessageImportance.Low));
+                                                       Action<ITaskItem> addItem = i => {
+                                                               var metadata = new ArrayList (i.MetadataNames).ToArray ().Cast<string> ().Select (n => new KeyValuePair<string,string> (n, i.GetMetadata (n)));
+                                                               args.Project.AddItem (toItem.ItemType, i.ItemSpec, metadata);
+                                                       };
+                                                       var taskItemArray = value as ITaskItem [];
+                                                       if (taskItemArray != null) {
+                                                               foreach (var ti in taskItemArray)
+                                                                       addItem (ti);
+                                                       } else {
+                                                               var taskItem = value as ITaskItem;
+                                                               if (taskItem != null) 
+                                                                       addItem (taskItem);
+                                                               else
+                                                                       foreach (var item in valueString.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);
+                                                       LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Property {0} from TaskParameter {1}: {2}", toProp.PropertyName, toProp.TaskParameter, valueString), null, null, MessageImportance.Low));
+                                                       args.Project.SetProperty (toProp.PropertyName, valueString);
                                                }
                                        }
                                }
@@ -385,15 +409,21 @@ namespace Microsoft.Build.Internal
                        }
                        return true;
                }
-               
-               object ConvertTo (string source, Type targetType)
+
+               object ConvertTo (string source, Type targetType, ExpressionEvaluator evaluator)
                {
-                       if (targetType == typeof(ITaskItem) || targetType.IsSubclassOf (typeof(ITaskItem)))
-                               return new TargetOutputTaskItem () { ItemSpec = WindowsCompatibilityExtensions.NormalizeFilePath (source.Trim ()) };
+                       if (targetType == typeof (ITaskItem) || targetType.IsSubclassOf (typeof (ITaskItem))) {
+                               var item = evaluator.EvaluatedTaskItems.FirstOrDefault (i => string.Equals (i.ItemSpec, source.Trim (), StringComparison.OrdinalIgnoreCase));
+                               var ret = new TargetOutputTaskItem () { ItemSpec = source.Trim () };
+                               if (item != null)
+                                       foreach (string name in item.MetadataNames)
+                                               ret.SetMetadata (name, item.GetMetadata (name));
+                               return ret;
+                       }
                        if (targetType.IsArray)
-                               return new ArrayList (source.Split (';').Select (s => s.Trim ()).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 (), evaluator)).ToArray ())
                                                .ToArray (targetType.GetElementType ());
-                       if (targetType == typeof(bool)) {
+                       if (targetType == typeof (bool)) {
                                switch (source != null ? source.ToLower (CultureInfo.InvariantCulture) : string.Empty) {
                                case "true":
                                case "yes":
@@ -432,7 +462,7 @@ namespace Microsoft.Build.Internal
                        }
                        public void SetMetadataValueLiteral (string metadataName, string metadataValue)
                        {
-                               metadata [metadataName] = ProjectCollection.Unescape (metadataValue);
+                               metadata [metadataName] = WindowsCompatibilityExtensions.NormalizeFilePath (ProjectCollection.Unescape (metadataValue));
                        }
                        public IDictionary CloneCustomMetadataEscaped ()
                        {
@@ -461,7 +491,8 @@ namespace Microsoft.Build.Internal
                                var wk = ProjectCollection.GetWellKnownMetadata (metadataName, ItemSpec, Path.GetFullPath, null);
                                if (wk != null)
                                        return wk;
-                               return (string) metadata [metadataName];
+                               var ret = (string) metadata [metadataName];
+                               return ret ?? string.Empty;
                        }
                        public void RemoveMetadata (string metadataName)
                        {
@@ -469,7 +500,7 @@ namespace Microsoft.Build.Internal
                        }
                        public void SetMetadata (string metadataName, string metadataValue)
                        {
-                               metadata [metadataName] = metadataValue;
+                               metadata [metadataName] = WindowsCompatibilityExtensions.NormalizeFilePath (metadataValue);
                        }
                        public string ItemSpec { get; set; }
                        public int MetadataCount {
@@ -479,9 +510,13 @@ namespace Microsoft.Build.Internal
                                get { return metadata.Keys; }
                        }
                        #endregion
+
+                       public override string ToString ()
+                       {
+                               return ItemSpec;
+                       }
                }
                
-#if NET_4_5
                #region IBuildEngine4 implementation
                
                // task objects are not in use anyways though...
@@ -522,7 +557,6 @@ namespace Microsoft.Build.Internal
                        return reg.Object;
                }
                #endregion
-#endif
 
                #region IBuildEngine3 implementation
 
@@ -548,13 +582,20 @@ namespace Microsoft.Build.Internal
                // 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)
                {
+                       toolsVersion = string.IsNullOrEmpty (toolsVersion) ? project.ToolsVersion : toolsVersion;
                        var globalPropertiesThatMakeSense = new Dictionary<string,string> ();
                        foreach (DictionaryEntry p in globalProperties)
                                globalPropertiesThatMakeSense [(string) p.Key] = (string) 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;
+                       var projectToBuild = new ProjectInstance (ProjectRootElement.Create (XmlReader.Create (projectFileName)), globalPropertiesThatMakeSense, toolsVersion, Projects);
+                       // Not very sure if ALL of these properties should be added, but some are certainly needed. 
+                       foreach (var p in this.project.Properties.Where (p => !globalProperties.Contains (p.Name)))
+                               projectToBuild.SetProperty (p.Name, p.EvaluatedValue);
+                       
+                       IDictionary<string,TargetResult> outs;
+                       var ret = projectToBuild.Build (targetNames ?? new string [] {"Build"}, Projects.Loggers, out outs);
+                       foreach (var p in outs)
+                               targetOutputs [p.Key] = p.Value.Items ?? new ITaskItem [0];
+                       return ret;
                }
 
                public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion)