public void SetUp() { LoggingServiceFactory loggingFactory = new LoggingServiceFactory(LoggerMode.Synchronous, 1); _loggingService = loggingFactory.CreateInstance(BuildComponentType.LoggingService) as LoggingService; _customLogger = new MyCustomLogger(); _mockHost = new MockHost(); _mockHost.LoggingService = _loggingService; _loggingService.RegisterLogger(_customLogger); _elementLocation = ElementLocation.Create("MockFile", 5, 5); BuildRequest buildRequest = new BuildRequest(1 /* submissionId */, 1, 1, new List<string>(), null, BuildEventContext.Invalid, null); BuildRequestConfiguration configuration = new BuildRequestConfiguration(1, new BuildRequestData("Nothing", new Dictionary<string, string>(), "4.0", new string[0], null), "2.0"); configuration.Project = new ProjectInstance(ProjectRootElement.Create()); BuildRequestEntry entry = new BuildRequestEntry(buildRequest, configuration); BuildResult buildResult = new BuildResult(buildRequest, false); buildResult.AddResultsForTarget("Build", new TargetResult(new TaskItem[] { new TaskItem("IamSuper", configuration.ProjectFullPath) }, TestUtilities.GetSkippedResult())); _mockRequestCallback = new MockIRequestBuilderCallback(new BuildResult[] { buildResult }); entry.Builder = (IRequestBuilder)_mockRequestCallback; _taskHost = new TaskHost(_mockHost, entry, _elementLocation, null /*Dont care about the callback either unless doing a build*/); _taskHost.LoggingContext = new TaskLoggingContext(_loggingService, BuildEventContext.Invalid); }
public void TearDown() { _customLogger = null; _mockHost = null; _elementLocation = null; _taskHost = null; }
/// <summary> /// Recomputes the task's "ContinueOnError" setting. /// </summary> /// <param name="bucket">The bucket being executed.</param> /// <param name="taskHost">The task host to use.</param> /// <remarks> /// There are four possible values: /// false - Error and stop if the task fails. /// true - Warn and continue if the task fails. /// ErrorAndContinue - Error and continue if the task fails. /// WarnAndContinue - Same as true. /// </remarks> private void UpdateContinueOnError(ItemBucket bucket, TaskHost taskHost) { string continueOnErrorAttribute = _taskNode.ContinueOnError; _continueOnError = ContinueOnError.ErrorAndStop; if (_taskNode.ContinueOnErrorLocation != null) { string expandedValue = bucket.Expander.ExpandIntoStringAndUnescape(continueOnErrorAttribute, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata try { if (String.Equals(XMakeAttributes.ContinueOnErrorValues.errorAndContinue, expandedValue, StringComparison.OrdinalIgnoreCase)) { _continueOnError = ContinueOnError.ErrorAndContinue; } else if (String.Equals(XMakeAttributes.ContinueOnErrorValues.warnAndContinue, expandedValue, StringComparison.OrdinalIgnoreCase)) { _continueOnError = ContinueOnError.WarnAndContinue; } else if (String.Equals(XMakeAttributes.ContinueOnErrorValues.errorAndStop, expandedValue, StringComparison.OrdinalIgnoreCase)) { _continueOnError = ContinueOnError.ErrorAndStop; } else { // if attribute doesn't exist, default to "false" // otherwise, convert its value to a boolean bool value = ConversionUtilities.ConvertStringToBool(expandedValue); _continueOnError = value ? ContinueOnError.WarnAndContinue : ContinueOnError.ErrorAndStop; } } catch (ArgumentException e) { // handle errors in string-->bool conversion ProjectErrorUtilities.VerifyThrowInvalidProject(false, _taskNode.ContinueOnErrorLocation, "InvalidContinueOnErrorAttribute", _taskNode.Name, e.Message); } } // We need to access an internal method of the EngineProxy in order to update the value // of continueOnError that will be returned to the task when the task queries IBuildEngine for it taskHost.ContinueOnError = (_continueOnError != ContinueOnError.ErrorAndStop); taskHost.ConvertErrorsToWarnings = (_continueOnError == ContinueOnError.WarnAndContinue); }
/// <summary> /// Execute a task object for a given bucket. /// </summary> /// <param name="taskExecutionHost">The host used to execute the task.</param> /// <param name="taskLoggingContext">The logging context.</param> /// <param name="taskHost">The task host for the task.</param> /// <param name="bucket">The batching bucket</param> /// <param name="howToExecuteTask">The task execution mode</param> /// <returns>The result of running the task.</returns> private async Task<WorkUnitResult> ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) { UpdateContinueOnError(bucket, taskHost); bool taskResult = false; WorkUnitResultCode resultCode = WorkUnitResultCode.Success; WorkUnitActionCode actionCode = WorkUnitActionCode.Continue; if (!taskExecutionHost.SetTaskParameters(_taskNode.ParametersForBuild)) { // The task cannot be initialized. ProjectErrorUtilities.VerifyThrowInvalidProject(false, _targetChildInstance.Location, "TaskParametersError", _taskNode.Name, String.Empty); } else { bool taskReturned = false; Exception taskException = null; // If this is the MSBuild task, we need to execute it's special internal method. TaskExecutionHost host = taskExecutionHost as TaskExecutionHost; Type taskType = host.TaskInstance.GetType(); try { if (taskType == typeof(MSBuild)) { MSBuild msbuildTask = host.TaskInstance as MSBuild; ErrorUtilities.VerifyThrow(msbuildTask != null, "Unexpected MSBuild internal task."); _targetBuilderCallback.EnterMSBuildCallbackState(); try { taskResult = await msbuildTask.ExecuteInternal(); } finally { _targetBuilderCallback.ExitMSBuildCallbackState(); } } else if (taskType == typeof(CallTarget)) { CallTarget callTargetTask = host.TaskInstance as CallTarget; taskResult = await callTargetTask.ExecuteInternal(); } else { using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) { taskResult = taskExecutionHost.Execute(); } } } catch (Exception ex) { if (ExceptionHandling.IsCriticalException(ex) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) { throw; } taskException = ex; } if (taskException == null) { taskReturned = true; // Set the property "MSBuildLastTaskResult" to reflect whether the task succeeded or not. // The main use of this is if ContinueOnError is true -- so that the next task can consult the result. // So we want it to be "false" even if ContinueOnError is true. // The constants "true" and "false" should NOT be localized. They become property values. bucket.Lookup.SetProperty(ProjectPropertyInstance.Create(ReservedPropertyNames.lastTaskResult, taskResult ? "true" : "false", true/* may be reserved */, _buildRequestEntry.RequestConfiguration.Project.IsImmutable)); } else { Type type = taskException.GetType(); if (type == typeof(LoggerException)) { // if a logger has failed, abort immediately // Polite logger failure _continueOnError = ContinueOnError.ErrorAndStop; // Rethrow wrapped in order to avoid losing the callstack throw new LoggerException(taskException.Message, taskException); } else if (type == typeof(InternalLoggerException)) { // Logger threw arbitrary exception _continueOnError = ContinueOnError.ErrorAndStop; InternalLoggerException ex = taskException as InternalLoggerException; // Rethrow wrapped in order to avoid losing the callstack throw new InternalLoggerException(taskException.Message, taskException, ex.BuildEventArgs, ex.ErrorCode, ex.HelpKeyword, ex.InitializationException); } else if (type == typeof(ThreadAbortException)) { Thread.ResetAbort(); _continueOnError = ContinueOnError.ErrorAndStop; // Cannot rethrow wrapped as ThreadAbortException is sealed and has no appropriate constructor // Stack will be lost throw taskException; } else if (type == typeof(BuildAbortedException)) { _continueOnError = ContinueOnError.ErrorAndStop; // Rethrow wrapped in order to avoid losing the callstack throw new BuildAbortedException(taskException.Message, ((BuildAbortedException)taskException)); } else if (type == typeof(CircularDependencyException)) { _continueOnError = ContinueOnError.ErrorAndStop; ProjectErrorUtilities.ThrowInvalidProject(taskLoggingContext.Task.Location, "CircularDependency", taskLoggingContext.TargetLoggingContext.Target.Name); } else if (type == typeof(InvalidProjectFileException)) { // Just in case this came out of a task, make sure it's not // marked as having been logged. InvalidProjectFileException ipex = (InvalidProjectFileException)taskException; ipex.HasBeenLogged = false; if (_continueOnError != ContinueOnError.ErrorAndStop) { taskLoggingContext.LogInvalidProjectFileError(ipex); taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { // Rethrow wrapped in order to avoid losing the callstack throw new InvalidProjectFileException(ipex.Message, ipex); } } else if (type == typeof(Exception) || type.IsSubclassOf(typeof(Exception))) { // Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with // a debugger attached to break on 2nd chance exceptions. // That requires that there needs to be a way to not catch here, by setting an environment variable. if (ExceptionHandling.IsCriticalException(taskException) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) { // Wrapping in an Exception will unfortunately mean that this exception would fly through any IsCriticalException above. // However, we should not have any, also we should not have stashed such an exception anyway. throw new Exception(taskException.Message, taskException); } Exception exceptionToLog = taskException; if (exceptionToLog is TargetInvocationException) { exceptionToLog = exceptionToLog.InnerException; } // handle any exception thrown by the task during execution // NOTE: We catch ALL exceptions here, to attempt to completely isolate the Engine // from failures in the task. if (_continueOnError == ContinueOnError.WarnAndContinue) { taskLoggingContext.LogTaskWarningFromException ( new BuildEventFileInfo(_targetChildInstance.Location), exceptionToLog, _taskNode.Name ); // Log a message explaining why we converted the previous error into a warning. taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { taskLoggingContext.LogFatalTaskError ( new BuildEventFileInfo(_targetChildInstance.Location), exceptionToLog, _taskNode.Name ); } } else { ErrorUtilities.ThrowInternalErrorUnreachable(); } } // If the task returned attempt to gather its outputs. If gathering outputs fails set the taskResults // to false if (taskReturned) { taskResult = GatherTaskOutputs(taskExecutionHost, howToExecuteTask, bucket) && taskResult; } // If the taskResults are false look at ContinueOnError. If ContinueOnError=false (default) // mark the taskExecutedSuccessfully=false. Otherwise let the task succeed but log a normal // pri message that says this task is continuing because ContinueOnError=true resultCode = taskResult ? WorkUnitResultCode.Success : WorkUnitResultCode.Failed; actionCode = WorkUnitActionCode.Continue; if (resultCode == WorkUnitResultCode.Failed) { if (_continueOnError == ContinueOnError.ErrorAndStop) { actionCode = WorkUnitActionCode.Stop; } else { // This is the ErrorAndContinue or WarnAndContinue case... string settingString = "true"; if (_taskNode.ContinueOnErrorLocation != null) { settingString = bucket.Expander.ExpandIntoStringAndUnescape(_taskNode.ContinueOnError, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata } taskLoggingContext.LogComment ( MessageImportance.Normal, "TaskContinuedDueToContinueOnError", "ContinueOnError", _taskNode.Name, settingString ); actionCode = WorkUnitActionCode.Continue; } } } WorkUnitResult result = new WorkUnitResult(resultCode, actionCode, null); return result; }
/// <summary> /// Initializes and executes the task. /// </summary> private async Task<WorkUnitResult> InitializeAndExecuteTask(TaskLoggingContext taskLoggingContext, ItemBucket bucket, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { if (!_taskExecutionHost.InitializeForBatch(taskLoggingContext, bucket, taskIdentityParameters)) { ProjectErrorUtilities.ThrowInvalidProject(_targetChildInstance.Location, "TaskDeclarationOrUsageError", _taskNode.Name); } try { // UNDONE: Move this and the task host. taskHost.LoggingContext = taskLoggingContext; WorkUnitResult executionResult = await ExecuteInstantiatedTask(_taskExecutionHost, taskLoggingContext, taskHost, bucket, howToExecuteTask); ErrorUtilities.VerifyThrow(executionResult != null, "Unexpected null execution result"); return executionResult; } finally { _taskExecutionHost.CleanupForBatch(); } }
private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); Thread staThread = null; Exception exceptionFromExecution = null; ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); try { ThreadStart taskRunnerDelegate = delegate () { Lookup.Scope scope = bucket.Lookup.EnterScope("STA Thread for Task"); try { taskResult = InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask).Result; } catch (Exception e) { if (ExceptionHandling.IsCriticalException(e)) { throw; } exceptionFromExecution = e; } finally { scope.LeaveScope(); taskRunnerFinished.Set(); } }; staThread = new Thread(taskRunnerDelegate); staThread.SetApartmentState(ApartmentState.STA); staThread.Name = "MSBuild STA task runner thread"; staThread.CurrentCulture = _componentHost.BuildParameters.Culture; staThread.CurrentUICulture = _componentHost.BuildParameters.UICulture; staThread.Start(); // TODO: Why not just Join on the thread??? taskRunnerFinished.WaitOne(); } finally { taskRunnerFinished.Close(); taskRunnerFinished = null; } if (exceptionFromExecution != null) { // Unfortunately this will reset the callstack throw exceptionFromExecution; } return taskResult; }
/// <summary> /// Execute a single bucket /// </summary> /// <returns>true if execution succeeded</returns> private async Task<WorkUnitResult> ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary<string, string> lookupHash) { // On Intrinsic tasks, we do not allow batchable params, therefore metadata is excluded. ParserOptions parserOptions = (_taskNode == null) ? ParserOptions.AllowPropertiesAndItemLists : ParserOptions.AllowAll; WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); bool condition = ConditionEvaluator.EvaluateCondition ( _targetChildInstance.Condition, parserOptions, bucket.Expander, ExpanderOptions.ExpandAll, _buildRequestEntry.ProjectRootDirectory, _targetChildInstance.ConditionLocation, _targetLoggingContext.LoggingService, _targetLoggingContext.BuildEventContext ); if (!condition) { LogSkippedTask(bucket, howToExecuteTask); taskResult = new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null); return taskResult; } // If this is an Intrinsic task, it gets handled in a special fashion. if (_taskNode == null) { ExecuteIntrinsicTask(bucket); taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); } else { if (_componentHost.BuildParameters.SaveOperatingEnvironment) { // Change to the project root directory. // If that directory does not exist, do nothing. (Do not check first as it is almost always there and it is slow) // This is because if the project has not been saved, this directory may not exist, yet it is often useful to still be able to build the project. // No errors are masked by doing this: errors loading the project from disk are reported at load time, if necessary. NativeMethodsShared.SetCurrentDirectory(_buildRequestEntry.ProjectRootDirectory); } if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs) { // We need to find the task before logging the task started event so that the using task statement comes before the task started event IDictionary<string, string> taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander); TaskRequirements? requirements = _taskExecutionHost.FindTask(taskIdentityParameters); if (requirements != null) { TaskLoggingContext taskLoggingContext = _targetLoggingContext.LogTaskBatchStarted(_projectFullPath, _targetChildInstance); try { if ( ((requirements.Value & TaskRequirements.RequireSTAThread) == TaskRequirements.RequireSTAThread) && (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) ) { taskResult = ExecuteTaskInSTAThread(bucket, taskLoggingContext, taskIdentityParameters, taskHost, howToExecuteTask); } else { taskResult = await InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask); } if (lookupHash != null) { List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash); if (overrideMessages != null) { foreach (string s in overrideMessages) { taskLoggingContext.LogCommentFromText(MessageImportance.Low, s); } } } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TaskFinished. Otherwise, // the log is confusing. taskLoggingContext.LogInvalidProjectFileError(e); _continueOnError = ContinueOnError.ErrorAndStop; } finally { // Flag the completion of the task. taskLoggingContext.LogTaskBatchFinished(_projectFullPath, taskResult.ResultCode == WorkUnitResultCode.Success || taskResult.ResultCode == WorkUnitResultCode.Skipped); if (taskResult.ResultCode == WorkUnitResultCode.Failed && _continueOnError == ContinueOnError.WarnAndContinue) { // We coerce the failing result to a successful result. taskResult = new WorkUnitResult(WorkUnitResultCode.Success, taskResult.ActionCode, taskResult.Exception); } } } } else { ErrorUtilities.VerifyThrow(howToExecuteTask == TaskExecutionMode.InferOutputsOnly, "should be inferring"); ErrorUtilities.VerifyThrow ( GatherTaskOutputs(null, howToExecuteTask, bucket), "The method GatherTaskOutputs() should never fail when inferring task outputs." ); if (lookupHash != null) { List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash); if (overrideMessages != null) { foreach (string s in overrideMessages) { _targetLoggingContext.LogCommentFromText(MessageImportance.Low, s); } } } taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); } } return taskResult; }
/// <summary> /// Called to execute a task within a target. This method instantiates the task, sets its parameters, and executes it. /// </summary> /// <returns>true, if successful</returns> private async Task<WorkUnitResult> ExecuteTask(TaskExecutionMode mode, Lookup lookup) { ErrorUtilities.VerifyThrowArgumentNull(lookup, "lookup"); WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); TaskHost taskHost = null; List<ItemBucket> buckets = null; try { if (_taskNode != null) { taskHost = new TaskHost(_componentHost, _buildRequestEntry, _targetChildInstance.Location, _targetBuilderCallback); _taskExecutionHost.InitializeForTask(taskHost, _targetLoggingContext, _buildRequestEntry.RequestConfiguration.Project, _taskNode.Name, _taskNode.Location, _taskHostObject, _continueOnError != ContinueOnError.ErrorAndStop, taskHost.AppDomainSetup, taskHost.IsOutOfProc, _cancellationToken); } List<string> taskParameterValues = CreateListOfParameterValues(); buckets = BatchingEngine.PrepareBatchingBuckets(taskParameterValues, lookup, _targetChildInstance.Location); Dictionary<string, string> lookupHash = null; // Only create a hash table if there are more than one bucket as this is the only time a property can be overridden if (buckets.Count > 1) { lookupHash = new Dictionary<string, string>(MSBuildNameIgnoreCaseComparer.Default); } WorkUnitResult aggregateResult = new WorkUnitResult(); // Loop through each of the batch buckets and execute them one at a time for (int i = 0; i < buckets.Count; i++) { // Execute the batch bucket, pass in which bucket we are executing so that we know when to get a new taskId for the bucket. taskResult = await ExecuteBucket(taskHost, (ItemBucket)buckets[i], mode, lookupHash); aggregateResult = aggregateResult.AggregateResult(taskResult); if (aggregateResult.ActionCode == WorkUnitActionCode.Stop) { break; } } taskResult = aggregateResult; } finally { _taskExecutionHost.CleanupForTask(); if (taskHost != null) { taskHost.MarkAsInactive(); } // Now all task batches are done, apply all item adds to the outer // target batch; we do this even if the task wasn't found (in that case, // no items or properties will have been added to the scope) if (buckets != null) { foreach (ItemBucket bucket in buckets) { bucket.LeaveScope(); } } } return taskResult; }
/// <summary> /// /// </summary> /// <returns>True if the operation was successful</returns> internal static async Task <bool> ExecuteTargets ( ITaskItem[] projects, Hashtable propertiesTable, string[] undefineProperties, ArrayList targetLists, bool stopOnFirstFailure, bool rebaseOutputs, IBuildEngine3 buildEngine, TaskLoggingHelper log, ArrayList targetOutputs, bool useResultsCache, bool unloadProjectsOnCompletion, string toolsVersion ) { bool success = true; // We don't log a message about the project and targets we're going to // build, because it'll all be in the immediately subsequent ProjectStarted event. string[] projectDirectory = new string[projects.Length]; string[] projectNames = new string[projects.Length]; string[] toolsVersions = new string[projects.Length]; IList <IDictionary <string, ITaskItem[]> > targetOutputsPerProject = null; IDictionary[] projectProperties = new IDictionary[projects.Length]; List <string>[] undefinePropertiesPerProject = new List <string> [projects.Length]; for (int i = 0; i < projectNames.Length; i++) { projectNames[i] = null; projectProperties[i] = propertiesTable; if (projects[i] != null) { // Retrieve projectDirectory only the first time. It never changes anyway. string projectPath = FileUtilities.AttemptToShortenPath(projects[i].ItemSpec); projectDirectory[i] = Path.GetDirectoryName(projectPath); projectNames[i] = projects[i].ItemSpec; toolsVersions[i] = toolsVersion; // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (!String.IsNullOrEmpty(projects[i].GetMetadata("Properties"))) { Hashtable preProjectPropertiesTable; if (!PropertyParser.GetTableWithEscaping (log, ResourceUtilities.FormatResourceString("General.OverridingProperties", projectNames[i]), "Properties", projects[i].GetMetadata("Properties").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries), out preProjectPropertiesTable) ) { return(false); } projectProperties[i] = preProjectPropertiesTable; } if (undefineProperties != null) { undefinePropertiesPerProject[i] = new List <string>(undefineProperties); } // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. string projectUndefineProperties = projects[i].GetMetadata("UndefineProperties"); if (!String.IsNullOrEmpty(projectUndefineProperties)) { string[] propertiesToUndefine = projectUndefineProperties.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (undefinePropertiesPerProject[i] == null) { undefinePropertiesPerProject[i] = new List <string>(propertiesToUndefine.Length); } if (log != null && propertiesToUndefine.Length > 0) { log.LogMessageFromResources(MessageImportance.Low, "General.ProjectUndefineProperties", projectNames[i]); foreach (string property in propertiesToUndefine) { undefinePropertiesPerProject[i].Add(property); log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); } } } // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (!String.IsNullOrEmpty(projects[i].GetMetadata("AdditionalProperties"))) { Hashtable additionalProjectPropertiesTable; if (!PropertyParser.GetTableWithEscaping (log, ResourceUtilities.FormatResourceString("General.AdditionalProperties", projectNames[i]), "AdditionalProperties", projects[i].GetMetadata("AdditionalProperties").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries), out additionalProjectPropertiesTable) ) { return(false); } Hashtable combinedTable = new Hashtable(StringComparer.OrdinalIgnoreCase); // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null) { foreach (DictionaryEntry entry in projectProperties[i]) { if (!additionalProjectPropertiesTable.Contains(entry.Key)) { combinedTable.Add(entry.Key, entry.Value); } } } // Add all the additional properties foreach (DictionaryEntry entry in additionalProjectPropertiesTable) { combinedTable.Add(entry.Key, entry.Value); } projectProperties[i] = combinedTable; } // If the user specified a different toolsVersion for this project - then override the setting if (!String.IsNullOrEmpty(projects[i].GetMetadata("ToolsVersion"))) { toolsVersions[i] = projects[i].GetMetadata("ToolsVersion"); } } } foreach (string[] targetList in targetLists) { if (stopOnFirstFailure && !success) { // Inform the user that we skipped the remaining targets StopOnFirstFailure=true. log.LogMessageFromResources(MessageImportance.Low, "MSBuild.SkippingRemainingTargets"); // We have encountered a failure. Caller has requested that we not // continue with remaining targets. break; } // Send the project off to the build engine. By passing in null to the // first param, we are indicating that the project to build is the same // as the *calling* project file. bool currentTargetResult = true; TaskHost taskHost = (TaskHost)buildEngine; BuildEngineResult result = await taskHost.InternalBuildProjects(projectNames, targetList, projectProperties, undefinePropertiesPerProject, toolsVersions, true /* ask that target outputs are returned in the buildengineresult */); currentTargetResult = result.Result; targetOutputsPerProject = result.TargetOutputsPerProject; success = success && currentTargetResult; // If the engine was able to satisfy the build request if (currentTargetResult) { for (int i = 0; i < projects.Length; i++) { IEnumerable nonNullTargetList = (targetList != null) ? targetList : targetOutputsPerProject[i].Keys; foreach (string targetName in nonNullTargetList) { if (targetOutputsPerProject[i].ContainsKey(targetName)) { ITaskItem[] outputItemsFromTarget = (ITaskItem[])targetOutputsPerProject[i][targetName]; foreach (ITaskItem outputItemFromTarget in outputItemsFromTarget) { // No need to rebase if the calling project is the same as the callee project // (project == null). Also no point in trying to copy item metadata either, // because no items were passed into the Projects parameter! if (projects[i] != null) { // Rebase the output item paths if necessary. No need to rebase if the calling // project is the same as the callee project (project == null). if (rebaseOutputs) { try { outputItemFromTarget.ItemSpec = Path.Combine(projectDirectory[i], outputItemFromTarget.ItemSpec); } catch (ArgumentException e) { log.LogWarningWithCodeFromResources(null, projects[i].ItemSpec, 0, 0, 0, 0, "MSBuild.CannotRebaseOutputItemPath", outputItemFromTarget.ItemSpec, e.Message); } } // Copy the custom item metadata from the "Projects" items to these // output items. projects[i].CopyMetadataTo(outputItemFromTarget); // Set a metadata on the output items called "MSBuildProjectFile" which tells you which project file produced this item. if (String.IsNullOrEmpty(outputItemFromTarget.GetMetadata(ItemMetadataNames.msbuildSourceProjectFile))) { outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceProjectFile, projects[i].GetMetadata(FileUtilities.ItemSpecModifiers.FullPath)); } } // Set a metadata on the output items called "MSBuildTargetName" which tells you which target produced this item. if (String.IsNullOrEmpty(outputItemFromTarget.GetMetadata(ItemMetadataNames.msbuildSourceTargetName))) { outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceTargetName, targetName); } } targetOutputs.AddRange(outputItemsFromTarget); } } } } } return(success); }