public Task<MSBuildResult> Run (
			ProjectConfigurationInfo[] configurations,
			ILogWriter logWriter,
			MSBuildVerbosity verbosity,
			string[] runTargets,
			string[] evaluateItems,
			string[] evaluateProperties,
			Dictionary<string, string> globalProperties,
			CancellationToken cancellationToken
		)
		{
			// Get an id for the task, and get ready to cancel it if the cancellation token is signalled
			var taskId = Interlocked.Increment (ref lastTaskId);
			var cr = RegisterCancellation (cancellationToken, taskId);

			var t = Task.Run (() => {
				try {
					BeginOperation ();
					var res = builder.Run (configurations, logWriter, verbosity, runTargets, evaluateItems, evaluateProperties, globalProperties, taskId);
					if (res == null && cancellationToken.IsCancellationRequested) {
						MSBuildTargetResult err = new MSBuildTargetResult (file, false, "", "", file, 1, 1, 1, 1, "Build cancelled", "");
						return new MSBuildResult (new [] { err });
					}
					if (res == null)
						throw new Exception ("Unknown failure");
					return res;
				} catch (Exception ex) {
					CheckDisconnected ();
					LoggingService.LogError ("RunTarget failed", ex);
					MSBuildTargetResult err = new MSBuildTargetResult (file, false, "", "", file, 1, 1, 1, 1, "Unknown MSBuild failure. Please try building the project again", "");
					MSBuildResult res = new MSBuildResult (new [] { err });
					return res;
				} finally {
					EndOperation ();
				}
			});

			// Dispose the cancel registration
			t.ContinueWith (r => cr.Dispose ());

			return t;
		}
		public async Task<AssemblyReference[]> ResolveAssemblyReferences (ProjectConfigurationInfo[] configurations, CancellationToken cancellationToken)
		{
			AssemblyReference[] refs = null;
			var id = configurations [0].Configuration + "|" + configurations [0].Platform;

			using (await referenceCacheLock.EnterAsync ()) {
				// Check the cache before starting the task
				if (referenceCache.TryGetValue (id, out refs))
					return refs;
			}

			// Get an id for the task, it will be used later on to cancel the task if necessary
			var taskId = Interlocked.Increment (ref lastTaskId);
			IDisposable cr = null;

			refs = await Task.Run (async () => {
				using (await referenceCacheLock.EnterAsync ()) {
					// Check again the cache, maybe the value was set while the task was starting
					if (referenceCache.TryGetValue (id, out refs))
						return refs;

					// Get ready to cancel the task if the cancellation token is signalled
					cr = RegisterCancellation (cancellationToken, taskId);

					MSBuildResult result;
					bool locked = false;
					try {
						BeginOperation ();
						locked = await engine.Semaphore.WaitAsync (Timeout.Infinite, cancellationToken).ConfigureAwait (false);
						// FIXME: This lock should not be necessary, but remoting seems to have problems when doing many concurrent calls.
						result = builder.Run (
									configurations, null, MSBuildVerbosity.Normal,
									new [] { "ResolveAssemblyReferences" }, new [] { "ReferencePath" }, null, null, taskId
								);
					} catch (Exception ex) {
						CheckDisconnected ();
						LoggingService.LogError ("ResolveAssemblyReferences failed", ex);
						return new AssemblyReference [0];
					} finally {
						if (locked)
							engine.Semaphore.Release ();
						EndOperation ();
					}

					List<MSBuildEvaluatedItem> items;
					if (result.Items.TryGetValue ("ReferencePath", out items) && items != null) {
						string aliases;
						refs = items.Select (i => new AssemblyReference (i.ItemSpec, i.Metadata.TryGetValue ("Aliases", out aliases) ? aliases : "")).ToArray ();
					} else
						refs = new AssemblyReference [0];

					referenceCache [id] = refs;
				}
				return refs;
			});

			// Dispose the cancel registration
			if (cr != null)
				cr.Dispose ();
			
			return refs;
		}
		public MSBuildResult Run (
			ProjectConfigurationInfo[] configurations, ILogWriter logWriter, MSBuildVerbosity verbosity,
			string[] runTargets, string[] evaluateItems, string[] evaluateProperties, Dictionary<string,string> globalProperties, int taskId)
		{
			MSBuildResult result = null;
			BuildEngine.RunSTA (taskId, delegate {
				try {
					var project = SetupProject (configurations);
					InitLogger (logWriter);

					buildEngine.Engine.UnregisterAllLoggers ();

					var logger = new LocalLogger (file);
					buildEngine.Engine.RegisterLogger (logger);
					if (logWriter != null) {
						buildEngine.Engine.RegisterLogger (consoleLogger);
						consoleLogger.Verbosity = GetVerbosity (verbosity);
					}

					if (runTargets != null && runTargets.Length > 0) {
						if (globalProperties != null) {
							foreach (var p in globalProperties)
								project.GlobalProperties.SetProperty (p.Key, p.Value);
                        }

						// We are using this BuildProject overload and the BuildSettings.None argument as a workaround to
						// an xbuild bug which causes references to not be resolved after the project has been built once.
						buildEngine.Engine.BuildProject (project, runTargets, new Hashtable (), BuildSettings.None);

						if (globalProperties != null) {
							foreach (var p in globalProperties.Keys) {
								project.GlobalProperties.RemoveProperty (p);
								buildEngine.Engine.GlobalProperties.RemoveProperty (p);
							}
						}
					}

					result = new MSBuildResult (logger.BuildResult.ToArray ());

					if (evaluateProperties != null) {
						foreach (var name in evaluateProperties)
							result.Properties [name] = project.GetEvaluatedProperty (name);
					}

					if (evaluateItems != null) {
						foreach (var name in evaluateItems) {
							BuildItemGroup grp = project.GetEvaluatedItemsByName (name);
							var list = new List<MSBuildEvaluatedItem> ();
							foreach (BuildItem item in grp) {
								var evItem = new MSBuildEvaluatedItem (name, UnescapeString (item.FinalItemSpec));
								foreach (DictionaryEntry de in (IDictionary) evaluatedMetadataField.GetValue (item)) {
									evItem.Metadata [(string)de.Key] = UnescapeString ((string)de.Value);
								}
								list.Add (evItem);
							}
							result.Items[name] = list;
						}
					}
				} catch (InvalidProjectFileException ex) {
					var r = new MSBuildTargetResult (
						file, false, ex.ErrorSubcategory, ex.ErrorCode, ex.ProjectFile,
						ex.LineNumber, ex.ColumnNumber, ex.EndLineNumber, ex.EndColumnNumber,
						ex.BaseMessage, ex.HelpKeyword);
					LogWriteLine (r.ToString ());
					result = new MSBuildResult (new [] { r });
				} finally {
					DisposeLogger ();
				}
			});
			return result;
		}
		Project SetupProject (ProjectConfigurationInfo[] configurations)
		{
			Project project = null;

			var slnConfigContents = GenerateSolutionConfigurationContents (configurations);

			foreach (var pc in configurations) {
				var p = buildEngine.Engine.GetLoadedProject (pc.ProjectFile);

				if (p != null && pc.ProjectFile == file) {
					// building the project may create new items and/or modify some properties,
					// so we always need to use a new instance of the project when building
					buildEngine.Engine.UnloadProject (p);
					p = null;
				}

				Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (file));

				if (p == null) {
					p = new Project (buildEngine.Engine);
					var content = buildEngine.GetUnsavedProjectContent (pc.ProjectFile);
					if (content == null) {
						p.Load (pc.ProjectFile);
					} else {
						p.FullFileName = Path.GetFullPath (pc.ProjectFile);

						if (HasXbuildFileBug ()) {
							// Workaround for Xamarin bug #14295: Project.Load incorrectly resets the FullFileName property
							var t = p.GetType ();
							t.InvokeMember ("PushThisFileProperty", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, p, new object[] { p.FullFileName });
							t.InvokeMember ("DoLoad", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, p, new object[] { new StringReader (content) });
						} else {
							p.Load (new StringReader (content));
						}
					}
				}

				p.GlobalProperties.SetProperty ("CurrentSolutionConfigurationContents", slnConfigContents);
				p.GlobalProperties.SetProperty ("Configuration", pc.Configuration);
				if (!string.IsNullOrEmpty (pc.Platform))
					p.GlobalProperties.SetProperty ("Platform", pc.Platform);
				else
					p.GlobalProperties.RemoveProperty ("Platform");
				if (pc.ProjectFile == file)
					project = p;
			}

			Environment.CurrentDirectory = Path.GetDirectoryName (Path.GetFullPath (file));
			return project;
		}
		string GenerateSolutionConfigurationContents (ProjectConfigurationInfo[] configurations)
		{
			// can't use XDocument because of the 2.0 builder
			// and don't just build a string because things may need escaping

			var doc = new XmlDocument ();
			var root = doc.CreateElement ("SolutionConfiguration");
			doc.AppendChild (root);
			foreach (var config in configurations) {
				var el = doc.CreateElement ("ProjectConfiguration");
				root.AppendChild (el);
				el.SetAttribute ("Project", config.ProjectGuid);
				el.SetAttribute ("AbsolutePath", config.ProjectFile);
				el.SetAttribute ("BuildProjectInSolution", config.Enabled ? "True" : "False");
				el.InnerText = string.Format (config.Configuration + "|" + config.Platform);
			}

			//match MSBuild formatting
			var options = new XmlWriterSettings {
				Indent = true,
				IndentChars = "",
				OmitXmlDeclaration = true,
			};
			using (var sw = new StringWriter ())
			using (var xw = XmlWriter.Create (sw, options)) {
				doc.WriteTo (xw);
				xw.Flush ();
				return sw.ToString ();
			}
		}
		public MSBuildResult Run (
			ProjectConfigurationInfo[] configurations, ILogWriter logWriter, MSBuildVerbosity verbosity,
			string[] runTargets, string[] evaluateItems, string[] evaluateProperties, Dictionary<string,string> globalProperties, int taskId)
		{
			if (runTargets == null || runTargets.Length == 0)
				throw new ArgumentException ("runTargets is empty");

			MSBuildResult result = null;
			BuildEngine.RunSTA (taskId, delegate {
				try {
					var project = SetupProject (configurations);
					InitLogger (logWriter);

					ILogger[] loggers;
					var logger = new LocalLogger (file);
					if (logWriter != null) {
						var consoleLogger = new ConsoleLogger (GetVerbosity (verbosity), LogWriteLine, null, null);
						loggers = new ILogger[] { logger, consoleLogger };
					} else {
						loggers = new ILogger[] { logger };
					}

					//building the project will create items and alter properties, so we use a new instance
					var pi = project.CreateProjectInstance ();

					if (globalProperties != null)
						foreach (var p in globalProperties)
							pi.SetProperty (p.Key, p.Value);
					
					pi.Build (runTargets, loggers);

					result = new MSBuildResult (logger.BuildResult.ToArray ());

					if (evaluateProperties != null) {
						foreach (var name in evaluateProperties) {
							var prop = pi.GetProperty (name);
							result.Properties [name] = prop != null? prop.EvaluatedValue : null;
						}
					}

					if (evaluateItems != null) {
						foreach (var name in evaluateItems) {
							var grp = pi.GetItems (name);
							var list = new List<MSBuildEvaluatedItem> ();
							foreach (var item in grp) {
								var evItem = new MSBuildEvaluatedItem (name, UnescapeString (item.EvaluatedInclude));
								foreach (var m in item.Metadata) {
									evItem.Metadata [m.Name] = UnescapeString (m.EvaluatedValue);
								}
								list.Add (evItem);
							}
							result.Items[name] = list;
						}
					}
				} catch (Microsoft.Build.Exceptions.InvalidProjectFileException ex) {
					var r = new MSBuildTargetResult (
						file, false, ex.ErrorSubcategory, ex.ErrorCode, ex.ProjectFile,
						ex.LineNumber, ex.ColumnNumber, ex.EndLineNumber, ex.EndColumnNumber,
						ex.BaseMessage, ex.HelpKeyword);
					LogWriteLine (r.ToString ());
					result = new MSBuildResult (new [] { r });
				} finally {
					DisposeLogger ();
				}
			});
			return result;
		}
		Project SetupProject (ProjectConfigurationInfo[] configurations)
		{
			Project project = null;

			var slnConfigContents = GenerateSolutionConfigurationContents (configurations);

			foreach (var pc in configurations) {
				var p = ConfigureProject (pc.ProjectFile, pc.Configuration, pc.Platform, slnConfigContents);
				if (pc.ProjectFile == file)
					project = p;
			}

			Environment.CurrentDirectory = Path.GetDirectoryName (file);
			return project;
		}