context: //
  // BuildEngine4.cs
  //
  // Author:
  //   Atsushi Enomoto (atsushi@xamarin.com)
  //
  // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using Microsoft.Build.Execution;
  using Microsoft.Build.Framework;
  using Microsoft.Build.Evaluation;
  using System.Linq;
  using System.IO;
  using Microsoft.Build.Exceptions;
  using System.Globalization;
  using Microsoft.Build.Construction;
  using Microsoft.Build.Internal.Expressions;
  using System.Xml;
  
  namespace Microsoft.Build.Internal
  {
  	class BuildEngine4
  #if NET_4_5
  		: IBuildEngine4
  #else
  		: IBuildEngine3
  #endif
  	{
  		public BuildEngine4 (BuildSubmission submission)
  		{
  			this.submission = submission;
  			event_source = new Microsoft.Build.BuildEngine.EventSource ();
  			if (submission.BuildManager.OngoingBuildParameters.Loggers != null)
  				foreach (var l in submission.BuildManager.OngoingBuildParameters.Loggers)
  					l.Initialize (event_source);
  		}
  
  		BuildSubmission submission;
  		ProjectInstance project;
  		ProjectTaskInstance current_task;
  		Microsoft.Build.BuildEngine.EventSource event_source;
  		
  		public ProjectCollection Projects {
  			get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; }
  		}
  
  		// FIXME:
  		// While we are not faced to implement those features, there are some modern task execution requirements.
  		//
  		// This will have to be available for "out of process" nodes (see NodeAffinity).
  		// NodeAffinity is set per project file at BuildManager.HostServices.
  		// When NodeAffinity is set to OutOfProc, it should probably launch different build host
  		// that runs separate build tasks. (.NET has MSBuildTaskHost.exe which I guess is about that.)
  		//
  		// Also note that the complete implementation has to support LoadInSeparateAppDomainAttribute
  		// (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run
  		// in separate AppDomain.
  		//
  		public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
  		{
  			if (toolsVersion == null)
  				throw new ArgumentNullException ("toolsVersion");
  			
  			var parameters = submission.BuildManager.OngoingBuildParameters;
  			var toolset = parameters.GetToolset (toolsVersion);
  			if (toolset == null)
  				throw new InvalidOperationException (string.Format ("Toolset version '{0}' was not resolved to valid toolset", toolsVersion));
  			LogMessageEvent (new BuildMessageEventArgs (string.Format ("Using Toolset version {0}.", toolsVersion), null, null, MessageImportance.Low));
  			var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (toolset), new BuildTaskDatabase (this, submission.BuildRequest.ProjectInstance));
  			BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
  		}
  
  		class InternalBuildArguments
  		{
  			public Func<bool> CheckCancel;
  			public BuildResult Result;
  			public ProjectInstance Project;
  			public IEnumerable<string> TargetNames;
  			public IDictionary<string,string> GlobalProperties;
  			public IDictionary<string,string> TargetOutputs;
  			public string ToolsVersion;
  			public BuildTaskFactory BuildTaskFactory;
  			
  			public void AddTargetResult (string targetName, TargetResult targetResult)
  			{
  				if (!Result.HasResultsForTarget (targetName))
  					Result.AddResultsForTarget (targetName, targetResult);
  			}
  		}
  		
  		void BuildProject (InternalBuildArguments args)
  		{
  			var request = submission.BuildRequest;
  			var parameters = submission.BuildManager.OngoingBuildParameters;
  			this.project = args.Project;
  
  			string directoryBackup = Directory.GetCurrentDirectory ();
  			Directory.SetCurrentDirectory (project.Directory);
  			event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null, DateTime.Now));
  			
  			try {
  				
  				var initialGlobalPropertiesFormatted = "Initial Global Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Where (p => p.IsImmutable).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ());
  				LogMessageEvent (new BuildMessageEventArgs (initialGlobalPropertiesFormatted, null, null, MessageImportance.Low));
  				var initialProjectPropertiesFormatted = "Initial Project Properties:\n" + string.Join (Environment.NewLine, project.Properties.OrderBy (p => p.Name).Where (p => !p.IsImmutable).Select (p => string.Format ("{0} = {1}", p.Name, p.EvaluatedValue)).ToArray ());
  				LogMessageEvent (new BuildMessageEventArgs (initialProjectPropertiesFormatted, null, null, MessageImportance.Low));
  				var initialItemsFormatted = "Initial Items:\n" + string.Join (Environment.NewLine, project.Items.OrderBy (i => i.ItemType).Select (i => string.Format ("{0} : {1}", i.ItemType, i.EvaluatedInclude)).ToArray ());
  				LogMessageEvent (new BuildMessageEventArgs (initialItemsFormatted, null, null, MessageImportance.Low));
  				
  				// null targets -> success. empty targets -> success(!)
  				foreach (var targetName in (request.ProjectInstance.InitialTargets).Where (t => t != null))
  					BuildTargetByName (targetName, args);
  				if (request.TargetNames == null)
  					args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
  				else {
  					foreach (var targetName in (args.TargetNames ?? request.TargetNames).Where (t => t != null)) {
  						if (!BuildTargetByName (targetName, args))
  							break;
  					}
  			
  					// FIXME: check .NET behavior, whether cancellation always results in failure.
  					args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Any (p => p.Value.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
  				}
  			} catch (Exception ex) {
  				args.Result.OverallResult = BuildResultCode.Failure;
  				LogErrorEvent (new BuildErrorEventArgs (null, null, project.FullPath, 0, 0, 0, 0, "Unhandled exception occured during a build", null, null));
  				LogMessageEvent (new BuildMessageEventArgs ("Exception details: " + ex, null, null, MessageImportance.Low));
  				throw; // BuildSubmission re-catches this.
  			} finally {
  				event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success, DateTime.Now));
  				Directory.SetCurrentDirectory (directoryBackup);
  			}
  		}
  		
  		bool BuildTargetByName (string targetName, InternalBuildArguments args)
  		{
  			var request = submission.BuildRequest;
  			var parameters = submission.BuildManager.OngoingBuildParameters;
  			ProjectTargetInstance target;
  			TargetResult dummyResult;
  
  			if (args.Result.ResultsByTarget.TryGetValue (targetName, out dummyResult) && dummyResult.ResultCode == TargetResultCode.Success) {
  				LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because it was already built successfully.", targetName), null, null, MessageImportance.Low));
  				return true; // do not add result.
  			}
  			
  			var targetResult = new TargetResult ();
  
  			// null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
  			if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
  				// FIXME: from MSBuild.exe it is given MSB4057. Can we assign a number too?
  				throw new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath));
  			else if (!args.Project.EvaluateCondition (target.Condition)) {
  				LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because condition '{1}' was not met.", target.Name, target.Condition), null, null, MessageImportance.Low));
  				targetResult.Skip ();
  			} else {
  				// process DependsOnTargets first.
  				foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s))) {
  					LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' depends on '{1}'.", target.Name, dep), null, null, MessageImportance.Low));
  					if (!BuildTargetByName (dep, args)) {
  						LogMessageEvent (new BuildMessageEventArgs (string.Format ("Quit target '{0}', as dependency target '{1}' has failed.", target.Name, dep), null, null, MessageImportance.Low));
  						return false;
  					}
  				}
  				
  				Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s };
  			
  				event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, target.Name, project.FullPath, target.FullPath));
  				try {
  					// FIXME: examine in which scenario Inputs/Outputs inconsistency results in errors. Now it rather prevents csproj build.
  					/*if (!string.IsNullOrEmpty (target.Inputs) != !string.IsNullOrEmpty (target.Outputs)) {
  						targetResult.Failure (new InvalidProjectFileException (target.Location, null, string.Format ("Target {0} has mismatching Inputs and Outputs specification. When one is specified, another one has to be specified too.", targetName), null, null, null));
  					} else*/ {
  						bool skip = false;
  						if (!string.IsNullOrEmpty (target.Inputs)) {
  							var inputs = args.Project.GetAllItems (target.Inputs, string.Empty, creator, creator, s => true, (t, s) => {
  							});
  							if (!inputs.Any ()) {
  								LogMessageEvent (new BuildMessageEventArgs (string.Format ("Target '{0}' was skipped because there is no input.", target.Name), null, null, MessageImportance.Low));
  								skip = true;
  							} else {
  								var outputs = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
  								});
  								var needsUpdates = GetOlderOutputsThanInputs (inputs, outputs).FirstOrDefault ();
  								if (needsUpdates != null)
  									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));
  								else {
  									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));
  									skip = true;
  								}
  							}
  						}
  						if (skip) {
  							targetResult.Skip ();
  						} else {
  
gt: 							if (DoBuildTarget (target, targetResult, args)) {