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