Merge pull request #1336 from esdrubal/datatablereadxmlschema
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildEngine4.cs
1 //
2 // BuildEngine4.cs
3 //
4 // Author:
5 //   Atsushi Enomoto (atsushi@xamarin.com)
6 //
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using Microsoft.Build.Execution;
32 using Microsoft.Build.Framework;
33 using Microsoft.Build.Evaluation;
34 using System.Linq;
35 using System.IO;
36 using Microsoft.Build.Exceptions;
37 using System.Globalization;
38 using Microsoft.Build.Construction;
39 using Microsoft.Build.Internal.Expressions;
40 using System.Xml;
41
42 namespace Microsoft.Build.Internal
43 {
44         class BuildEngine4
45 #if NET_4_5
46                 : IBuildEngine4
47 #else
48                 : IBuildEngine3
49 #endif
50         {
51                 public BuildEngine4 (BuildSubmission submission)
52                 {
53                         this.submission = submission;
54                         event_source = new Microsoft.Build.BuildEngine.EventSource ();
55                         if (submission.BuildManager.OngoingBuildParameters.Loggers != null)
56                                 foreach (var l in submission.BuildManager.OngoingBuildParameters.Loggers)
57                                         l.Initialize (event_source);
58                 }
59
60                 BuildSubmission submission;
61                 ProjectInstance project;
62                 ProjectTaskInstance current_task;
63                 Microsoft.Build.BuildEngine.EventSource event_source;
64                 
65                 public ProjectCollection Projects {
66                         get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; }
67                 }
68
69                 // FIXME:
70                 // While we are not faced to implement those features, there are some modern task execution requirements.
71                 //
72                 // This will have to be available for "out of process" nodes (see NodeAffinity).
73                 // NodeAffinity is set per project file at BuildManager.HostServices.
74                 // When NodeAffinity is set to OutOfProc, it should probably launch different build host
75                 // that runs separate build tasks. (.NET has MSBuildTaskHost.exe which I guess is about that.)
76                 //
77                 // Also note that the complete implementation has to support LoadInSeparateAppDomainAttribute
78                 // (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run
79                 // in separate AppDomain.
80                 //
81                 public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
82                 {
83                         if (toolsVersion == null)
84                                 throw new ArgumentNullException ("toolsVersion");
85                         
86                         var parameters = submission.BuildManager.OngoingBuildParameters;
87                         var toolset = parameters.GetToolset (toolsVersion);
88                         if (toolset == null)
89                                 throw new InvalidOperationException (string.Format ("Toolset version '{0}' was not resolved to valid toolset", toolsVersion));
90                         LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using Toolset version {0}.", toolsVersion), null, null, MessageImportance.Low));
91                         var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (toolset), new BuildTaskDatabase (this, submission.BuildRequest.ProjectInstance));
92                         BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
93                 }
94
95                 class InternalBuildArguments
96                 {
97                         public Func<bool> CheckCancel;
98                         public BuildResult Result;
99                         public ProjectInstance Project;
100                         public IEnumerable<string> TargetNames;
101                         public IDictionary<string,string> GlobalProperties;
102                         public IDictionary<string,string> TargetOutputs;
103                         public string ToolsVersion;
104                         public BuildTaskFactory BuildTaskFactory;
105                         
106                         public void AddTargetResult (string targetName, TargetResult targetResult)
107                         {
108                                 if (!Result.HasResultsForTarget (targetName))
109                                         Result.AddResultsForTarget (targetName, targetResult);
110                         }
111                 }
112                 
113                 void BuildProject (InternalBuildArguments args)
114                 {
115                         var request = submission.BuildRequest;
116                         var parameters = submission.BuildManager.OngoingBuildParameters;
117                         this.project = args.Project;
118
119                         string directoryBackup = Directory.GetCurrentDirectory ();
120                         Directory.SetCurrentDirectory (project.Directory);
121                         event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null, DateTime.Now));
122                         
123                         try {
124                                 
125                                 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 ());
126                                 LogMessageEvent (new BuildMessageEventArgs (initialGlobalPropertiesFormatted, null, null, MessageImportance.Low));
127                                 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 ());
128                                 LogMessageEvent (new BuildMessageEventArgs (initialProjectPropertiesFormatted, null, null, MessageImportance.Low));
129                                 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 ());
130                                 LogMessageEvent (new BuildMessageEventArgs (initialItemsFormatted, null, null, MessageImportance.Low));
131                                 
132                                 // null targets -> success. empty targets -> success(!)
133                                 foreach (var targetName in (request.ProjectInstance.InitialTargets).Where (t => t != null))
134                                         BuildTargetByName (targetName, args);
135                                 if (request.TargetNames == null)
136                                         args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
137                                 else {
138                                         foreach (var targetName in (args.TargetNames ?? request.TargetNames).Where (t => t != null)) {
139                                                 if (!BuildTargetByName (targetName, args))
140                                                         break;
141                                         }
142                         
143                                         // FIXME: check .NET behavior, whether cancellation always results in failure.
144                                         args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
145                                 }
146                         } catch (Exception ex) {
147                                 args.Result.OverallResult = BuildResultCode.Failure;
148                                 LogErrorEvent (new BuildErrorEventArgs (null, null, project.FullPath, 0, 0, 0, 0, "Unhandled exception occured during a build", null, null));
149                                 LogMessageEvent (new BuildMessageEventArgs ("Exception details: " + ex, null, null, MessageImportance.Low));
150                                 throw; // BuildSubmission re-catches this.
151                         } finally {
152                                 event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success, DateTime.Now));
153                                 Directory.SetCurrentDirectory (directoryBackup);
154                         }
155                 }
156                 
157                 bool BuildTargetByName (string targetName, InternalBuildArguments args)
158                 {
159                         var request = submission.BuildRequest;
160                         var parameters = submission.BuildManager.OngoingBuildParameters;
161                         ProjectTargetInstance target;
162                         TargetResult dummyResult;
163
164                         if (args.Result.ResultsByTarget.TryGetValue (targetName, out dummyResult) && dummyResult.ResultCode == TargetResultCode.Success) {
165                                 LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because it was already built successfully.", targetName), null, null, MessageImportance.Low));
166                                 return true; // do not add result.
167                         }
168                         
169                         var targetResult = new TargetResult ();
170
171                         // null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
172                         if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
173                                 // FIXME: from MSBuild.exe it is given MSB4057. Can we assign a number too?
174                                 throw new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath));
175                         else if (!args.Project.EvaluateCondition (target.Condition)) {
176                                 LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because condition '{1}' was not met.", target.Name, target.Condition), null, null, MessageImportance.Low));
177                                 targetResult.Skip ();
178                         } else {
179                                 // process DependsOnTargets first.
180                                 foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s))) {
181                                         LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' depends on '{1}'.", target.Name, dep), null, null, MessageImportance.Low));
182                                         if (!BuildTargetByName (dep, args)) {
183                                                 LogMessageEvent (new BuildMessageEventArgs (string.Format ("Quit target '{0}', as dependency target '{1}' has failed.", target.Name, dep), null, null, MessageImportance.Low));
184                                                 return false;
185                                         }
186                                 }
187                                 
188                                 Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s };
189                         
190                                 event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath));
191                                 try {
192                                         // FIXME: examine in which scenario Inputs/Outputs inconsistency results in errors. Now it rather prevents csproj build.
193                                         /*if (!string.IsNullOrEmpty (target.Inputs) != !string.IsNullOrEmpty (target.Outputs)) {
194                                                 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));
195                                         } else*/ {
196                                                 bool skip = false;
197                                                 if (!string.IsNullOrEmpty (target.Inputs)) {
198                                                         var inputs = args.Project.GetAllItems (target.Inputs, string.Empty, creator, creator, s => true, (t, s) => {
199                                                         });
200                                                         if (!inputs.Any ()) {
201                                                                 LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because there is no input.", target.Name), null, null, MessageImportance.Low));
202                                                                 skip = true;
203                                                         } else {
204                                                                 var outputs = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
205                                                                 });
206                                                                 var needsUpdates = GetOlderOutputsThanInputs (inputs, outputs).FirstOrDefault ();
207                                                                 if (needsUpdates != null)
208                                                                         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));
209                                                                 else {
210                                                                         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));
211                                                                         skip = true;
212                                                                 }
213                                                         }
214                                                 }
215                                                 if (skip) {
216                                                         targetResult.Skip ();
217                                                 } else {
218                                                         if (DoBuildTarget (target, targetResult, args)) {
219                                                                 var items = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
220                                                                 });
221                                                                 targetResult.Success (items);
222                                                         }
223                                                 }
224                                         }
225                                 } finally {
226                                         event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Finished", null, targetName, project.FullPath, target.FullPath, targetResult.ResultCode != TargetResultCode.Failure));
227                                 }
228                         }
229                         args.AddTargetResult (targetName, targetResult);
230                         
231                         return targetResult.ResultCode != TargetResultCode.Failure;
232                 }
233                 
234                 IEnumerable<ITaskItem> GetOlderOutputsThanInputs (IEnumerable<ITaskItem> inputs, IEnumerable<ITaskItem> outputs)
235                 {
236                         return outputs.Where (o => !File.Exists (o.GetMetadata ("FullPath")) || inputs.Any (i => string.CompareOrdinal (i.GetMetadata ("LastModifiedTime"), o.GetMetadata ("LastModifiedTime")) > 0));
237                 }
238
239                 // FIXME: Exception should be caught at caller site.
240                 bool DoBuildTarget (ProjectTargetInstance target, TargetResult targetResult, InternalBuildArguments args)
241                 {
242                         var request = submission.BuildRequest;
243         
244                         // Here we check cancellation (only after TargetStarted event).
245                         if (args.CheckCancel ()) {
246                                 targetResult.Failure (new BuildAbortedException ("Build has canceled"));
247                                 return false;
248                         }
249                         
250                         try {
251                                 foreach (var child in target.Children) {
252                                         // Evaluate additional target properties
253                                         var tp = child as ProjectPropertyGroupTaskInstance;
254                                         if (tp != null) {
255                                                 if (!args.Project.EvaluateCondition (tp.Condition))
256                                                         continue;
257                                                 foreach (var p in tp.Properties) {
258                                                         if (!args.Project.EvaluateCondition (p.Condition))
259                                                                 continue;
260                                                         var value = args.Project.ExpandString (p.Value);
261                                                         project.SetProperty (p.Name, value);
262                                                 }
263                                                 continue;
264                                         }
265
266                                         var ii = child as ProjectItemGroupTaskInstance;
267                                         if (ii != null) {
268                                                 if (!args.Project.EvaluateCondition (ii.Condition))
269                                                         continue;
270                                                 foreach (var item in ii.Items) {
271                                                         if (!args.Project.EvaluateCondition (item.Condition))
272                                                                 continue;
273                                                         project.AddItem (item.ItemType, project.ExpandString (item.Include));
274                                                 }
275                                                 continue;
276                                         }
277                                         
278                                         var task = child as ProjectTaskInstance;
279                                         if (task != null) {
280                                                 current_task = task;
281                                                 if (!args.Project.EvaluateCondition (task.Condition)) {
282                                                         LogMessageEvent (new BuildMessageEventArgs (string.Format ("Task '{0}' was skipped because condition '{1}' wasn't met.", task.Name, task.Condition), null, null, MessageImportance.Low));
283                                                         continue;
284                                                 }
285                                                 if (!RunBuildTask (target, task, targetResult, args))
286                                                         return false;
287                                                 continue;
288                                         }
289
290                                         var onError = child as ProjectOnErrorInstance;
291                                         if (onError != null)
292                                                 continue; // evaluated under catch clause.
293
294                                         throw new NotSupportedException (string.Format ("Unexpected Target element children \"{0}\"", child.GetType ()));
295                                 }
296                         } catch (Exception ex) {
297                                 // fallback task specified by OnError element
298                                 foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
299                                         if (!args.Project.EvaluateCondition (c.Condition))
300                                                 continue;
301                                         foreach (var fallbackTarget in project.ExpandString (c.ExecuteTargets).Split (';'))
302                                                 BuildTargetByName (fallbackTarget, args);
303                                 }
304                                 int line = target.Location != null ? target.Location.Line : 0;
305                                 int col = target.Location != null ? target.Location.Column : 0;
306                                 LogErrorEvent (new BuildErrorEventArgs (null, null, target.FullPath, line, col, 0, 0, ex.Message, null, null));
307                                 targetResult.Failure (ex);
308                                 return false;
309                         }
310                         return true;
311                 }
312                 
313                 bool RunBuildTask (ProjectTargetInstance target, ProjectTaskInstance taskInstance, TargetResult targetResult, InternalBuildArguments args)
314                 {
315                         var request = submission.BuildRequest;
316
317                         var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, target.Name, taskInstance.Name);
318                         
319                         // Create Task instance.
320                         var factoryIdentityParameters = new Dictionary<string,string> ();
321                         #if NET_4_5
322                         factoryIdentityParameters ["MSBuildRuntime"] = taskInstance.MSBuildRuntime;
323                         factoryIdentityParameters ["MSBuildArchitecture"] = taskInstance.MSBuildArchitecture;
324                         #endif
325                         var task = args.BuildTaskFactory.CreateTask (taskInstance.Name, factoryIdentityParameters, this);
326                         if (task == null)
327                                 throw new InvalidOperationException (string.Format ("TaskFactory {0} returned null Task", args.BuildTaskFactory));
328                         LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using task {0} from {1}", taskInstance.Name, task.GetType ()), null, null, MessageImportance.Low));
329                         task.HostObject = host;
330                         task.BuildEngine = this;
331                         
332                         // Prepare task parameters.
333                         var evaluator = new ExpressionEvaluator (project);
334                         var evaluatedTaskParams = taskInstance.Parameters.Select (p => new KeyValuePair<string,string> (p.Key, project.ExpandString (evaluator, p.Value)));
335
336                         var requiredProps = task.GetType ().GetProperties ()
337                                 .Where (p => p.CanWrite && p.GetCustomAttributes (typeof (RequiredAttribute), true).Any ());
338                         var missings = requiredProps.Where (p => !evaluatedTaskParams.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase)));
339                         if (missings.Any ())
340                                 throw new InvalidOperationException (string.Format ("Task {0} of type {1} is used without specifying mandatory property: {2}",
341                                         taskInstance.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ())));
342                         
343                         foreach (var p in evaluatedTaskParams) {
344                                 switch (p.Key.ToLower ()) {
345                                 case "condition":
346                                 case "continueonerror":
347                                         continue;
348                                 }
349                                 var prop = task.GetType ().GetProperty (p.Key);
350                                 if (prop == null)
351                                         throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", taskInstance.Name, p.Key));
352                                 if (!prop.CanWrite)
353                                         throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", taskInstance.Name, p.Key));
354                                 if (string.IsNullOrEmpty (p.Value) && !requiredProps.Contains (prop))
355                                         continue;
356                                 try {
357                                         prop.SetValue (task, ConvertTo (p.Value, prop.PropertyType, evaluator), null);
358                                 } catch (Exception ex) {
359                                         throw new InvalidOperationException (string.Format ("Failed to convert '{0}' for property '{1}' of type {2}", p.Value, prop.Name, prop.PropertyType), ex);
360                                 }
361                         }
362                         
363                         // Do execute task.
364                         bool taskSuccess = false;
365                         event_source.FireTaskStarted (this, new TaskStartedEventArgs ("Task Started", null, project.FullPath, taskInstance.FullPath, taskInstance.Name));
366                         try {
367                                 taskSuccess = task.Execute ();
368                         
369                                 if (!taskSuccess) {
370                                         targetResult.Failure (null);
371                                         if (!ContinueOnError) {
372                                                 return false;
373                                         }
374                                 } else {
375                                         // Evaluate task output properties and items.
376                                         foreach (var to in taskInstance.Outputs) {
377                                                 if (!project.EvaluateCondition (to.Condition))
378                                                         continue;
379                                                 var toItem = to as ProjectTaskOutputItemInstance;
380                                                 var toProp = to as ProjectTaskOutputPropertyInstance;
381                                                 string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter;
382                                                 var pi = task.GetType ().GetProperty (taskParameter);
383                                                 if (pi == null)
384                                                         throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", taskInstance.Name, toItem.TaskParameter));
385                                                 if (!pi.CanRead)
386                                                         throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", taskInstance.Name, toItem.TaskParameter));
387                                                 var value = pi.GetValue (task, null);
388                                                 var valueString = ConvertFrom (value);
389                                                 if (toItem != null) {
390                                                         LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Item {0} from TaskParameter {1}: {2}", toItem.ItemType, toItem.TaskParameter, valueString), null, null, MessageImportance.Low));
391                                                         Action<ITaskItem> addItem = i => {
392                                                                 var metadata = new ArrayList (i.MetadataNames).ToArray ().Cast<string> ().Select (n => new KeyValuePair<string,string> (n, i.GetMetadata (n)));
393                                                                 args.Project.AddItem (toItem.ItemType, i.ItemSpec, metadata);
394                                                         };
395                                                         var taskItemArray = value as ITaskItem [];
396                                                         if (taskItemArray != null) {
397                                                                 foreach (var ti in taskItemArray)
398                                                                         addItem (ti);
399                                                         } else {
400                                                                 var taskItem = value as ITaskItem;
401                                                                 if (taskItem != null) 
402                                                                         addItem (taskItem);
403                                                                 else
404                                                                         foreach (var item in valueString.Split (';'))
405                                                                                 args.Project.AddItem (toItem.ItemType, item);
406                                                         }
407                                                 } else {
408                                                         LogMessageEvent (new BuildMessageEventArgs (string.Format ("Output Property {0} from TaskParameter {1}: {2}", toProp.PropertyName, toProp.TaskParameter, valueString), null, null, MessageImportance.Low));
409                                                         args.Project.SetProperty (toProp.PropertyName, valueString);
410                                                 }
411                                         }
412                                 }
413                         } finally {
414                                 event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, taskInstance.FullPath, taskInstance.Name, taskSuccess));
415                         }
416                         return true;
417                 }
418
419                 object ConvertTo (string source, Type targetType, ExpressionEvaluator evaluator)
420                 {
421                         if (targetType == typeof (ITaskItem) || targetType.IsSubclassOf (typeof (ITaskItem))) {
422                                 var item = evaluator.EvaluatedTaskItems.FirstOrDefault (i => string.Equals (i.ItemSpec, source.Trim (), StringComparison.OrdinalIgnoreCase));
423                                 var ret = new TargetOutputTaskItem () { ItemSpec = source.Trim () };
424                                 if (item != null)
425                                         foreach (string name in item.MetadataNames)
426                                                 ret.SetMetadata (name, item.GetMetadata (name));
427                                 return ret;
428                         }
429                         if (targetType.IsArray)
430                                 return new ArrayList (source.Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s)).Select (s => ConvertTo (s, targetType.GetElementType (), evaluator)).ToArray ())
431                                                 .ToArray (targetType.GetElementType ());
432                         if (targetType == typeof (bool)) {
433                                 switch (source != null ? source.ToLower (CultureInfo.InvariantCulture) : string.Empty) {
434                                 case "true":
435                                 case "yes":
436                                 case "on":
437                                         return true;
438                                 case "false":
439                                 case "no":
440                                 case "off":
441                                 case "":
442                                         return false;
443                                 }
444                         }
445                         return Convert.ChangeType (source == "" ? null : source, targetType);
446                 }
447                 
448                 string ConvertFrom (object source)
449                 {
450                         if (source == null)
451                                 return string.Empty;
452                         if (source is ITaskItem)
453                                 return ((ITaskItem) source).ItemSpec;
454                         if (source.GetType ().IsArray)
455                                 return string.Join (";", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
456                         else
457                                 return (string) Convert.ChangeType (source, typeof (string));
458                 }
459                 
460                 class TargetOutputTaskItem : ITaskItem2
461                 {
462                         Hashtable metadata = new Hashtable ();
463                         
464                         #region ITaskItem2 implementation
465                         public string GetMetadataValueEscaped (string metadataName)
466                         {
467                                 return ProjectCollection.Escape ((string) metadata [metadataName]);
468                         }
469                         public void SetMetadataValueLiteral (string metadataName, string metadataValue)
470                         {
471                                 metadata [metadataName] = WindowsCompatibilityExtensions.NormalizeFilePath (ProjectCollection.Unescape (metadataValue));
472                         }
473                         public IDictionary CloneCustomMetadataEscaped ()
474                         {
475                                 var ret = new Hashtable ();
476                                 foreach (DictionaryEntry e in metadata)
477                                         ret [e.Key] = ProjectCollection.Escape ((string) e.Value);
478                                 return ret;
479                         }
480                         public string EvaluatedIncludeEscaped {
481                                 get { return ProjectCollection.Escape (ItemSpec); }
482                                 set { ItemSpec = ProjectCollection.Unescape (value); }
483                         }
484                         #endregion
485                         #region ITaskItem implementation
486                         public IDictionary CloneCustomMetadata ()
487                         {
488                                 return new Hashtable (metadata);
489                         }
490                         public void CopyMetadataTo (ITaskItem destinationItem)
491                         {
492                                 foreach (DictionaryEntry e in metadata)
493                                         destinationItem.SetMetadata ((string) e.Key, (string) e.Value);
494                         }
495                         public string GetMetadata (string metadataName)
496                         {
497                                 var wk = ProjectCollection.GetWellKnownMetadata (metadataName, ItemSpec, Path.GetFullPath, null);
498                                 if (wk != null)
499                                         return wk;
500                                 var ret = (string) metadata [metadataName];
501                                 return ret ?? string.Empty;
502                         }
503                         public void RemoveMetadata (string metadataName)
504                         {
505                                 metadata.Remove (metadataName);
506                         }
507                         public void SetMetadata (string metadataName, string metadataValue)
508                         {
509                                 metadata [metadataName] = WindowsCompatibilityExtensions.NormalizeFilePath (metadataValue);
510                         }
511                         public string ItemSpec { get; set; }
512                         public int MetadataCount {
513                                 get { return metadata.Count; }
514                         }
515                         public ICollection MetadataNames {
516                                 get { return metadata.Keys; }
517                         }
518                         #endregion
519
520                         public override string ToString ()
521                         {
522                                 return ItemSpec;
523                         }
524                 }
525                 
526 #if NET_4_5
527                 #region IBuildEngine4 implementation
528                 
529                 // task objects are not in use anyways though...
530                 
531                 class TaskObjectRegistration
532                 {
533                         public TaskObjectRegistration (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
534                         {
535                                 Key = key;
536                                 Object = obj;
537                                 Lifetime = lifetime;
538                                 AllowEarlyCollection = allowEarlyCollection;
539                         }
540                         public object Key { get; private set; }
541                         public object Object { get; private set; }
542                         public RegisteredTaskObjectLifetime Lifetime { get; private set; }
543                         public bool AllowEarlyCollection { get; private set; }
544                 }
545                 
546                 List<TaskObjectRegistration> task_objects = new List<TaskObjectRegistration> ();
547
548                 public object GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
549                 {
550                         var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
551                         return reg != null ? reg.Object : null;
552                 }
553
554                 public void RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
555                 {
556                         task_objects.Add (new TaskObjectRegistration (key, obj, lifetime, allowEarlyCollection));
557                 }
558
559                 public object UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
560                 {
561                         var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
562                         if (reg != null)
563                                 task_objects.Remove (reg);
564                         return reg.Object;
565                 }
566                 #endregion
567 #endif
568
569                 #region IBuildEngine3 implementation
570
571                 public BuildEngineResult BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs)
572                 {
573                         throw new NotImplementedException ();
574                 }
575
576                 public void Reacquire ()
577                 {
578                         throw new NotImplementedException ();
579                 }
580
581                 public void Yield ()
582                 {
583                         throw new NotImplementedException ();
584                 }
585
586                 #endregion
587
588                 #region IBuildEngine2 implementation
589
590                 // To NOT reuse this IBuildEngine instance for different build, we create another BuildManager and BuildSubmisson and then run it.
591                 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
592                 {
593                         toolsVersion = string.IsNullOrEmpty (toolsVersion) ? project.ToolsVersion : toolsVersion;
594                         var globalPropertiesThatMakeSense = new Dictionary<string,string> ();
595                         foreach (DictionaryEntry p in globalProperties)
596                                 globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value;
597                         var projectToBuild = new ProjectInstance (ProjectRootElement.Create (XmlReader.Create (projectFileName)), globalPropertiesThatMakeSense, toolsVersion, Projects);
598                         // Not very sure if ALL of these properties should be added, but some are certainly needed. 
599                         foreach (var p in this.project.Properties.Where (p => !globalProperties.Contains (p.Name)))
600                                 projectToBuild.SetProperty (p.Name, p.EvaluatedValue);
601                         
602                         IDictionary<string,TargetResult> outs;
603                         var ret = projectToBuild.Build (targetNames ?? new string [] {"Build"}, Projects.Loggers, out outs);
604                         foreach (var p in outs)
605                                 targetOutputs [p.Key] = p.Value.Items ?? new ITaskItem [0];
606                         return ret;
607                 }
608
609                 public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion)
610                 {
611                         throw new NotImplementedException ();
612                 }
613
614                 public bool IsRunningMultipleNodes {
615                         get {
616                                 throw new NotImplementedException ();
617                         }
618                 }
619                 
620                 ProjectInstance GetProjectInstance (string projectFileName, string toolsVersion)
621                 {
622                         string fullPath = Path.GetFullPath (projectFileName);
623                         if (submission.BuildRequest.ProjectFullPath == fullPath)
624                                 return submission.BuildRequest.ProjectInstance;
625                         // FIXME: could also be filtered by global properties
626                         // http://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.projectcollection.getloadedprojects.aspx
627                         var project = Projects.GetLoadedProjects (projectFileName).FirstOrDefault (p => p.ToolsVersion == toolsVersion);
628                         if (project == null)
629                                 throw new InvalidOperationException (string.Format ("Project '{0}' is not loaded", projectFileName));
630                         return submission.BuildManager.GetProjectInstanceForBuild (project);
631                 }
632
633                 #endregion
634
635                 #region IBuildEngine implementation
636
637                 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
638                 {
639                         return BuildProjectFile (projectFileName, targetNames, globalProperties, targetOutputs, Projects.DefaultToolsVersion);
640                 }
641
642                 public void LogCustomEvent (CustomBuildEventArgs e)
643                 {
644                         event_source.FireCustomEventRaised (this, e);
645                 }
646
647                 public void LogErrorEvent (BuildErrorEventArgs e)
648                 {
649                         event_source.FireErrorRaised (this, e);
650                 }
651
652                 public void LogMessageEvent (BuildMessageEventArgs e)
653                 {
654                         event_source.FireMessageRaised (this, e);
655                 }
656
657                 public void LogWarningEvent (BuildWarningEventArgs e)
658                 {
659                         event_source.FireWarningRaised (this, e);
660                 }
661
662                 public int ColumnNumberOfTaskNode {
663                         get { return current_task.Location != null ? current_task.Location.Column : 0; }
664                 }
665
666                 public bool ContinueOnError {
667                         get { return current_task != null && project.EvaluateCondition (current_task.Condition) && EvaluateContinueOnError (current_task.ContinueOnError); }
668                 }
669                 
670                 bool EvaluateContinueOnError (string value)
671                 {
672                         switch (value) {
673                         case "WarnAndContinue":
674                         case "ErrorAndContinue":
675                                 return true;
676                         case "ErrorAndStop":
677                                 return false;
678                         }
679                         // empty means "stop on error", so don't pass empty string to EvaluateCondition().
680                         return !string.IsNullOrEmpty (value) && project.EvaluateCondition (value);
681                 }
682
683                 public int LineNumberOfTaskNode {
684                         get { return current_task.Location != null ? current_task.Location.Line : 0; }
685                 }
686
687                 public string ProjectFileOfTaskNode {
688                         get { return current_task.FullPath; }
689                 }
690
691                 #endregion
692         }
693 }
694