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