|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
#if FEATURE_APPDOMAIN
using System.Runtime.Remoting;
#endif
using System.Text;
using System.Threading;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
using Task = System.Threading.Tasks.Task;
#nullable disable
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// Flags returned by TaskExecutionHost.FindTask().
/// </summary>
[Flags]
internal enum TaskRequirements
{
/// <summary>
/// The task was not found.
/// </summary>
None = 0,
/// <summary>
/// The task must be executed on an STA thread.
/// </summary>
RequireSTAThread = 0x01,
/// <summary>
/// The task must be executed in a separate AppDomain.
/// </summary>
RequireSeparateAppDomain = 0x02
}
/// <summary>
/// The TaskExecutionHost is responsible for instantiating tasks, setting their parameters and gathering outputs using
/// reflection, and executing the task in the appropriate context.The TaskExecutionHost does not deal with any part of the task declaration or
/// XML.
/// </summary>
internal class TaskExecutionHost : IDisposable
{
/// <summary>
/// Time interval in miliseconds to wait between receiving a cancelation signal and emitting the first warning that a non-cancelable task has not finished
/// </summary>
private const int CancelFirstWarningWaitInterval = 5000;
/// <summary>
/// Time interval in miliseconds between subsequent warnings that a non-cancelable task has not finished
/// </summary>
private const int CancelWarningWaitInterval = 15000;
#if FEATURE_APPDOMAIN
/// <summary>
/// Resolver to assist in resolving types when a new appdomain is created
/// </summary>
private TaskEngineAssemblyResolver _resolver;
#endif
/// <summary>
/// The interface used to call back into the build engine.
/// </summary>
private IBuildEngine2 _buildEngine;
/// <summary>
/// The project instance in whose context we are executing
/// </summary>
private ProjectInstance _projectInstance;
// Items required for all batches of a task
/// <summary>
/// The logging context for the target.
/// </summary>
private TargetLoggingContext _targetLoggingContext;
/// <summary>
/// The logging context for the task.
/// </summary>
private TaskLoggingContext _taskLoggingContext;
/// <summary>
/// The registration which handles the callback when task cancellation is invoked.
/// </summary>
private CancellationTokenRegistration _cancellationTokenRegistration;
/// <summary>
/// The name of the task to execute.
/// </summary>
private string _taskName;
/// <summary>
/// The XML location of the task element.
/// </summary>
private ElementLocation _taskLocation;
/// <summary>
/// The arbitrary task host object.
/// </summary>
private ITaskHost _taskHost;
// Items required for a particular batch of a task
/// <summary>
/// The bucket used to evaluate items and properties.
/// </summary>
private ItemBucket _batchBucket;
/// <summary>
/// The task type retrieved from the assembly.
/// </summary>
private TaskFactoryWrapper _taskFactoryWrapper;
/// <summary>
/// Set to true if the execution has been cancelled.
/// </summary>
private bool _cancelled;
/// <summary>
/// Event which is signalled when a task is not executing. Used for cancellation.
/// </summary>
private readonly ManualResetEvent _taskExecutionIdle = new ManualResetEvent(true);
/// <summary>
/// The task items that we remoted across the appdomain boundary
/// we use this list to disconnect the task items once we're done.
/// </summary>
private List<TaskItem> _remotedTaskItems;
/// <summary>
/// We need access to the build component host so that we can get at the
/// task host node provider when running a task wrapped by TaskHostTask
/// </summary>
private readonly IBuildComponentHost _buildComponentHost;
/// <summary>
/// The set of intrinsic tasks mapped for this process.
/// </summary>
private readonly Dictionary<string, TaskFactoryWrapper> _intrinsicTasks = new Dictionary<string, TaskFactoryWrapper>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Constructor
/// </summary>
internal TaskExecutionHost(IBuildComponentHost host)
{
_buildComponentHost = host;
if (host?.BuildParameters != null)
{
LogTaskInputs = host.BuildParameters.LogTaskInputs;
}
// If this is false, check the environment variable to see if it's there:
if (!LogTaskInputs)
{
LogTaskInputs = Traits.Instance.EscapeHatches.LogTaskInputs;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="TaskExecutionHost"/> class
/// for unit testing only.
/// </summary>
internal TaskExecutionHost()
{
// do nothing
}
/// <summary>
/// Finalizes an instance of the <see cref="TaskExecutionHost"/> class.
/// </summary>
~TaskExecutionHost()
{
Debug.Fail("Unexpected finalization. Dispose should already have been called.");
Dispose(false);
}
/// <summary>
/// Flag to determine whether or not to log task inputs.
/// </summary>
public bool LogTaskInputs { get; }
/// <summary>
/// The associated project.
/// </summary>
public ProjectInstance ProjectInstance => _projectInstance;
/// <summary>
/// Gets the task instance
/// </summary>
internal ITask TaskInstance { get; private set; }
/// <summary>
/// FOR UNIT TESTING ONLY
/// </summary>
internal TaskFactoryWrapper _UNITTESTONLY_TaskFactoryWrapper
{
get => _taskFactoryWrapper;
set => _taskFactoryWrapper = value;
}
#if FEATURE_APPDOMAIN
/// <summary>
/// App domain configuration.
/// </summary>
internal AppDomainSetup AppDomainSetup { get; set; }
#endif
/// <summary>
/// Whether or not this is out-of-proc.
/// </summary>
internal bool IsOutOfProc { get; set; }
/// <summary>
/// Implementation of IDisposable
/// </summary>
public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#region ITaskExecutionHost Members
/// <summary>
/// Initialize to run a specific task.
/// </summary>
public void InitializeForTask(IBuildEngine2 buildEngine, TargetLoggingContext loggingContext, ProjectInstance projectInstance, string taskName, ElementLocation taskLocation, ITaskHost taskHost, bool continueOnError,
#if FEATURE_APPDOMAIN
AppDomainSetup appDomainSetup,
#endif
bool isOutOfProc, CancellationToken cancellationToken)
{
_buildEngine = buildEngine;
_projectInstance = projectInstance;
_targetLoggingContext = loggingContext;
_taskName = taskName;
_taskLocation = taskLocation;
_cancellationTokenRegistration = cancellationToken.Register(Cancel);
_taskHost = taskHost;
_taskExecutionIdle.Set();
#if FEATURE_APPDOMAIN
AppDomainSetup = appDomainSetup;
#endif
IsOutOfProc = isOutOfProc;
}
/// <summary>
/// Ask the task host to find its task in the registry and get it ready for initializing the batch
/// </summary>
/// <returns>The task requirements and task factory wrapper if the task is found, (null, null) otherwise.</returns>
public (TaskRequirements? requirements, TaskFactoryWrapper taskFactoryWrapper) FindTask(IDictionary<string, string> taskIdentityParameters)
{
_taskFactoryWrapper ??= FindTaskInRegistry(taskIdentityParameters);
if (_taskFactoryWrapper is null)
{
return (null, null);
}
TaskRequirements requirements = TaskRequirements.None;
if (_taskFactoryWrapper.TaskFactoryLoadedType.HasSTAThreadAttribute)
{
requirements |= TaskRequirements.RequireSTAThread;
}
if (_taskFactoryWrapper.TaskFactoryLoadedType.HasLoadInSeparateAppDomainAttribute)
{
requirements |= TaskRequirements.RequireSeparateAppDomain;
// we're going to be remoting across the appdomain boundary, so
// create the list that we'll use to disconnect the taskitems once we're done
_remotedTaskItems = new List<TaskItem>();
}
return (requirements, _taskFactoryWrapper);
}
/// <summary>
/// Initialize to run a specific batch of the current task.
/// </summary>
public bool InitializeForBatch(TaskLoggingContext loggingContext, ItemBucket batchBucket, IDictionary<string, string> taskIdentityParameters)
{
ErrorUtilities.VerifyThrowArgumentNull(loggingContext);
ErrorUtilities.VerifyThrowArgumentNull(batchBucket);
_taskLoggingContext = loggingContext;
_batchBucket = batchBucket;
if (_taskFactoryWrapper == null)
{
return false;
}
#if FEATURE_APPDOMAIN
// If the task assembly is loaded into a separate AppDomain using LoadFrom, then we have a problem
// to solve - when the task class Type is marshalled back into our AppDomain, it's not just transferred
// here. Instead, NDP will try to Load (not LoadFrom!) the task assembly into our AppDomain, and since
// we originally used LoadFrom, it will fail miserably not knowing where to find it.
// We need to temporarily subscribe to the AppDomain.AssemblyResolve event to fix it.
if (_resolver == null)
{
_resolver = new TaskEngineAssemblyResolver();
_resolver.Initialize(_taskFactoryWrapper.TaskFactoryLoadedType.Assembly.AssemblyFile);
_resolver.InstallHandler();
}
#endif
// We instantiate a new task object for each batch
TaskInstance = InstantiateTask(taskIdentityParameters);
if (TaskInstance == null)
{
return false;
}
string realTaskAssemblyLoaction = TaskInstance.GetType().Assembly.Location;
if (!string.IsNullOrWhiteSpace(realTaskAssemblyLoaction) &&
realTaskAssemblyLoaction != _taskFactoryWrapper.TaskFactoryLoadedType.Path)
{
_taskLoggingContext.LogComment(MessageImportance.Normal, "TaskAssemblyLocationMismatch", realTaskAssemblyLoaction, _taskFactoryWrapper.TaskFactoryLoadedType.Path);
}
TaskInstance.BuildEngine = _buildEngine;
TaskInstance.HostObject = _taskHost;
return true;
}
/// <summary>
/// Sets all of the specified parameters on the task.
/// </summary>
/// <param name="parameters">The name/value pairs for the parameters.</param>
/// <returns>True if the parameters were set correctly, false otherwise.</returns>
public bool SetTaskParameters(IDictionary<string, (string, ElementLocation)> parameters)
{
ErrorUtilities.VerifyThrowArgumentNull(parameters);
bool taskInitialized = true;
// Get the properties that exist on this task. We need to gather all of the ones that are marked
// "required" so that we can keep track of whether or not they all get set.
var setParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
IReadOnlyDictionary<string, string> requiredParameters = GetNamesOfPropertiesWithRequiredAttribute();
// look through all the attributes of the task element
foreach (KeyValuePair<string, (string, ElementLocation)> parameter in parameters)
{
bool taskParameterSet = false; // Did we actually call the setter on this task parameter?
bool success;
try
{
success = SetTaskParameter(parameter.Key, parameter.Value.Item1, parameter.Value.Item2, requiredParameters.ContainsKey(parameter.Key), out taskParameterSet);
}
catch (Exception e) when (!ExceptionHandling.NotExpectedReflectionException(e))
{
// Reflection related exception
_taskLoggingContext.LogError(new BuildEventFileInfo(_taskLocation), "TaskParametersError", _taskName, e.Message);
success = false;
}
if (!success)
{
// stop processing any more attributes
taskInitialized = false;
break;
}
else if (taskParameterSet)
{
// Keep track that we've set a value for this property. Note that this will
// keep track of non-required properties as well, but that's okay. We just
// to check at the end that there are no values in the requiredParameters
// table that aren't also in the setParameters table.
setParameters[parameter.Key] = String.Empty;
}
}
if (this.TaskInstance is IIncrementalTask incrementalTask)
{
incrementalTask.FailIfNotIncremental = _buildComponentHost.BuildParameters.Question;
}
if (taskInitialized)
{
// See if any required properties were not set
foreach (KeyValuePair<string, string> requiredParameter in requiredParameters)
{
ProjectErrorUtilities.VerifyThrowInvalidProject(
setParameters.ContainsKey(requiredParameter.Key),
_taskLocation,
"RequiredPropertyNotSetError",
_taskName,
requiredParameter.Key);
}
}
return taskInitialized;
}
/// <summary>
/// Retrieve the outputs from the task.
/// </summary>
/// <returns>True of the outputs were gathered successfully, false otherwise.</returns>
public bool GatherTaskOutputs(string parameterName, ElementLocation parameterLocation, bool outputTargetIsItem, string outputTargetName)
{
ErrorUtilities.VerifyThrow(_taskFactoryWrapper != null, "Need a taskFactoryWrapper to retrieve outputs from.");
bool gatheredGeneratedOutputsSuccessfully = true;
try
{
TaskPropertyInfo parameter = _taskFactoryWrapper.GetProperty(parameterName);
foreach (TaskPropertyInfo prop in _taskFactoryWrapper.TaskFactoryLoadedType.Properties)
{
if (prop.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase))
{
parameter = prop;
break;
}
}
// flag an error if we find a parameter that has no .NET property equivalent
ProjectErrorUtilities.VerifyThrowInvalidProject(
parameter != null,
parameterLocation,
"UnexpectedTaskOutputAttribute",
parameterName,
_taskName);
// output parameters must have their corresponding .NET properties marked with the Output attribute
ProjectErrorUtilities.VerifyThrowInvalidProject(
_taskFactoryWrapper.GetNamesOfPropertiesWithOutputAttribute.ContainsKey(parameterName),
parameterLocation,
"UnmarkedOutputTaskParameter",
parameter.Name,
_taskName);
EnsureParameterInitialized(parameter, _batchBucket.Lookup);
if (parameter.IsAssignableToITask)
{
ITaskItem[] outputs = GetItemOutputs(parameter);
GatherTaskItemOutputs(outputTargetIsItem, outputTargetName, outputs, parameterLocation, parameter);
}
else if (parameter.IsValueTypeOutputParameter)
{
string[] outputs = GetValueOutputs(parameter);
GatherArrayStringAndValueOutputs(outputTargetIsItem, outputTargetName, outputs, parameterLocation, parameter);
}
else
{
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"UnsupportedTaskParameterTypeError",
parameter.PropertyType.FullName,
parameter.Name,
_taskName);
}
}
catch (InvalidOperationException e)
{
// handle invalid TaskItems in task outputs
_targetLoggingContext.LogError(
new BuildEventFileInfo(parameterLocation),
"InvalidTaskItemsInTaskOutputs",
_taskName,
parameterName,
e.Message);
gatheredGeneratedOutputsSuccessfully = false;
}
catch (TargetInvocationException e)
{
// handle any exception thrown by the task's getter
// Exception thrown by the called code itself
// Log the stack, so the task vendor can fix their code
// Log the task line number, whatever the value of ContinueOnError;
// because this will be a hard error anyway.
_targetLoggingContext.LogFatalTaskError(
e.InnerException,
new BuildEventFileInfo(parameterLocation),
_taskName);
// We do not recover from a task exception while getting outputs,
// so do not merely set gatheredGeneratedOutputsSuccessfully = false; here
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"FailedToRetrieveTaskOutputs",
_taskName,
parameterName,
e.InnerException?.Message);
}
catch (Exception e) when (!ExceptionHandling.NotExpectedReflectionException(e))
{
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"FailedToRetrieveTaskOutputs",
_taskName,
parameterName,
e.Message);
}
return gatheredGeneratedOutputsSuccessfully;
}
/// <summary>
/// Cleans up after running a batch.
/// </summary>
public void CleanupForBatch()
{
try
{
if (_taskFactoryWrapper != null && TaskInstance != null)
{
_taskFactoryWrapper.TaskFactory.CleanupTask(TaskInstance);
}
}
finally
{
TaskInstance = null;
}
}
/// <summary>
/// Cleans up after running the task.
/// </summary>
public void CleanupForTask()
{
#if FEATURE_APPDOMAIN
if (_resolver != null)
{
_resolver.RemoveHandler();
_resolver = null;
}
#endif
_taskFactoryWrapper = null;
// We must null this out because it could be a COM object (or any other ref-counted object) which needs to
// be released.
_taskHost = null;
CleanupCancellationToken();
ErrorUtilities.VerifyThrow(TaskInstance == null, "Task Instance should be null");
}
/// <summary>
/// Executes the task.
/// </summary>
public bool Execute()
{
// If cancel is called before we get here, we simply don't execute and return failure. If cancel is called after this check
// the task needs to be able to handle the possibility that Cancel has been called before the task has done anything meaningful,
// and Execute may not even have been called yet.
_taskExecutionIdle.Reset();
if (_cancelled)
{
_taskExecutionIdle.Set();
return false;
}
bool taskReturnValue;
try
{
taskReturnValue = TaskInstance.Execute();
}
finally
{
_taskExecutionIdle.Set();
}
return taskReturnValue;
}
#endregion
/// <summary>
/// Implementation of IDisposable
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_taskExecutionIdle.Dispose();
CleanupCancellationToken();
}
#if FEATURE_APPDOMAIN
// if we've been asked to remote these items then
// we need to disconnect them from .NET Remoting now we're all done with them
if (_remotedTaskItems != null)
{
foreach (TaskItem item in _remotedTaskItems)
{
// Tell remoting to forget connections to the taskitem
RemotingServices.Disconnect(item);
}
}
_remotedTaskItems = null;
#endif
}
/// <summary>
/// Disposes of the cancellation token registration.
/// </summary>
private void CleanupCancellationToken()
{
_cancellationTokenRegistration.Dispose();
}
/// <summary>
/// Cancels the currently-running task.
/// Kick off a task to wait for the currently-running task and log the wait message.
/// </summary>
private void Cancel()
{
// This will prevent the current and any future tasks from running on this TaskExecutionHost, because we don't reset the cancelled flag.
_cancelled = true;
ITask currentInstance = TaskInstance;
ICancelableTask cancellableTask = null;
if (currentInstance != null)
{
cancellableTask = currentInstance as ICancelableTask;
}
if (cancellableTask != null)
{
try
{
cancellableTask.Cancel();
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
try
{
_taskLoggingContext.LogFatalTaskError(e, new BuildEventFileInfo(_taskLocation), ((ProjectTaskInstance)_taskLoggingContext.Task).Name);
}
// If this fails it could be due to the task logging context no longer being valid due to a race condition where the task completes while we
// are in this method. In that case we simply ignore the exception and carry on since we can't log anything anyhow.
catch (InternalErrorException) when (!_taskLoggingContext.IsValid)
{
}
}
}
// Let the task finish now. If cancellation worked, hopefully it finishes sooner than it would have otherwise.
// If the task builder crashed, this could have already been disposed
if (!_taskExecutionIdle.SafeWaitHandle.IsClosed)
{
// Kick off a task to log the message so that we don't block the calling thread.
Task.Run(async delegate
{
await _taskExecutionIdle.ToTask(CancelFirstWarningWaitInterval);
if (!_taskExecutionIdle.WaitOne(0))
{
DisplayCancelWaitMessage();
await _taskExecutionIdle.ToTask(CancelWarningWaitInterval);
while (!_taskExecutionIdle.WaitOne(0))
{
DisplayCancelWaitMessage();
await _taskExecutionIdle.ToTask(CancelWarningWaitInterval);
}
}
});
}
}
#region Local Methods
/// <summary>
/// Called on the local side.
/// </summary>
private bool SetTaskItemParameter(TaskPropertyInfo parameter, ITaskItem item)
{
return InternalSetTaskParameter(parameter, item);
}
/// <summary>
/// Called on the local side.
/// </summary>
private bool SetValueParameter(TaskPropertyInfo parameter, Type parameterType, string expandedParameterValue)
{
if (parameterType == typeof(bool))
{
// Convert the string to the appropriate datatype, and set the task's parameter.
return InternalSetTaskParameter(parameter, ConversionUtilities.ConvertStringToBool(expandedParameterValue));
}
else if (parameterType == typeof(string))
{
return InternalSetTaskParameter(parameter, expandedParameterValue);
}
else
{
return InternalSetTaskParameter(parameter, Convert.ChangeType(expandedParameterValue, parameterType, CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Called on the local side.
/// </summary>
private bool SetParameterArray(TaskPropertyInfo parameter, Type parameterType, IList<TaskItem> taskItems, ElementLocation parameterLocation)
{
TaskItem currentItem = null;
try
{
// Loop through all the TaskItems in our arraylist, and convert them.
ArrayList finalTaskInputs = new ArrayList(taskItems.Count);
if (parameterType != typeof(ITaskItem[]))
{
foreach (TaskItem item in taskItems)
{
currentItem = item;
if (parameterType == typeof(string[]))
{
finalTaskInputs.Add(item.ItemSpec);
}
else if (parameterType == typeof(bool[]))
{
finalTaskInputs.Add(ConversionUtilities.ConvertStringToBool(item.ItemSpec));
}
else
{
finalTaskInputs.Add(Convert.ChangeType(item.ItemSpec, parameterType.GetElementType(), CultureInfo.InvariantCulture));
}
}
}
else
{
foreach (TaskItem item in taskItems)
{
// if we've been asked to remote these items then
// remember them so we can disconnect them from remoting later
RecordItemForDisconnectIfNecessary(item);
finalTaskInputs.Add(item);
}
}
return InternalSetTaskParameter(parameter, finalTaskInputs.ToArray(parameterType.GetElementType()));
}
catch (Exception ex)
{
if (ex is InvalidCastException || // invalid type
ex is ArgumentException || // can't convert to bool
ex is FormatException || // bad string representation of a type
ex is OverflowException) // overflow when converting string representation of a numerical type
{
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"InvalidTaskParameterValueError",
currentItem.ItemSpec,
parameter.Name,
parameterType.FullName,
_taskName);
}
throw;
}
}
/// <summary>
/// Remember this TaskItem so that we can disconnect it when this Task has finished executing
/// Only if we're passing TaskItems to another AppDomain is this necessary. This call
/// Will make that determination for you.
/// </summary>
private void RecordItemForDisconnectIfNecessary(TaskItem item)
{
// remember that we need to disconnect this item
_remotedTaskItems?.Add(item);
}
/// <summary>
/// Gets the outputs (as an array of ITaskItem) from the specified output parameter.
/// </summary>
private ITaskItem[] GetItemOutputs(TaskPropertyInfo parameter)
{
object outputs = _taskFactoryWrapper.GetPropertyValue(TaskInstance, parameter);
if (!(outputs is ITaskItem[] taskItemOutputs))
{
taskItemOutputs = [(ITaskItem)outputs];
}
return taskItemOutputs;
}
/// <summary>
/// Gets the outputs (as an array of string values) from the specified output parameter.
/// </summary>
private string[] GetValueOutputs(TaskPropertyInfo parameter)
{
object outputs = _taskFactoryWrapper.GetPropertyValue(TaskInstance, parameter);
Array convertibleOutputs = parameter.PropertyType.IsArray ? (Array)outputs : new[] { outputs };
if (convertibleOutputs == null)
{
return null;
}
var stringOutputs = new string[convertibleOutputs.Length];
for (int i = 0; i < convertibleOutputs.Length; i++)
{
object output = convertibleOutputs.GetValue(i);
if (output != null)
{
stringOutputs[i] = (string)Convert.ChangeType(output, typeof(string), CultureInfo.InvariantCulture);
}
}
return stringOutputs;
}
#endregion
/// <summary>
/// Given the task name, this method tries to find the task. It uses the following search order:
/// 1) checks the tasks declared by the project, searching by exact name and task identity parameters
/// 2) checks the global task declarations (in *.TASKS in MSbuild bin dir), searching by exact name and task identity parameters
/// 3) checks the tasks declared by the project, searching by fuzzy match (missing namespace, etc.) and task identity parameters
/// 4) checks the global task declarations (in *.TASKS in MSbuild bin dir), searching by fuzzy match (missing namespace, etc.) and task identity parameters
/// 5) 1-4 again in order without the task identity parameters, to gather additional information for the user (if the task identity
/// parameters don't match, it is an error, but at least we can return them a more useful error in this case than just "could not
/// find task")
///
/// The search ordering is meant to reduce the number of assemblies we scan, because loading assemblies can be expensive.
/// The tasks and assemblies declared by the project are scanned first, on the assumption that if the project declared
/// them, they are likely used.
///
/// If the set of task identity parameters are defined, only tasks that match that identity are chosen.
/// </summary>
/// <returns>The Type of the task, or null if it was not found.</returns>
private TaskFactoryWrapper FindTaskInRegistry(IDictionary<string, string> taskIdentityParameters)
{
if (!_intrinsicTasks.TryGetValue(_taskName, out TaskFactoryWrapper returnClass))
{
returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, taskIdentityParameters, true /* exact match */, _targetLoggingContext, _taskLocation);
if (returnClass == null)
{
returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, taskIdentityParameters, false /* fuzzy match */, _targetLoggingContext, _taskLocation);
if (returnClass == null)
{
returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, null, true /* exact match */, _targetLoggingContext, _taskLocation);
if (returnClass == null)
{
returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, null, false /* fuzzy match */, _targetLoggingContext, _taskLocation);
if (returnClass == null)
{
_targetLoggingContext.LogError(
new BuildEventFileInfo(_taskLocation),
"MissingTaskError",
_taskName,
_projectInstance.TaskRegistry.Toolset.ToolsPath);
return null;
}
}
string usingTaskRuntime = null;
string usingTaskArchitecture = null;
if (returnClass.FactoryIdentityParameters != null)
{
returnClass.FactoryIdentityParameters.TryGetValue(XMakeAttributes.runtime, out usingTaskRuntime);
returnClass.FactoryIdentityParameters.TryGetValue(XMakeAttributes.architecture, out usingTaskArchitecture);
}
taskIdentityParameters.TryGetValue(XMakeAttributes.runtime, out string taskRuntime);
taskIdentityParameters.TryGetValue(XMakeAttributes.architecture, out string taskArchitecture);
_targetLoggingContext.LogError(
new BuildEventFileInfo(_taskLocation),
"TaskExistsButHasMismatchedIdentityError",
_taskName,
usingTaskRuntime ?? XMakeAttributes.MSBuildRuntimeValues.any,
usingTaskArchitecture ?? XMakeAttributes.MSBuildArchitectureValues.any,
taskRuntime ?? XMakeAttributes.MSBuildRuntimeValues.any,
taskArchitecture ?? XMakeAttributes.MSBuildArchitectureValues.any);
// if we've logged this error, even though we've found something, we want to act like we didn't.
return null;
}
}
// Map to an intrinsic task, if necessary.
if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.MSBuild", StringComparison.OrdinalIgnoreCase))
{
Assembly taskExecutionHostAssembly = typeof(TaskExecutionHost).GetTypeInfo().Assembly;
returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new LoadedType(typeof(MSBuild), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly, typeof(ITaskItem)), _taskName, null);
_intrinsicTasks[_taskName] = returnClass;
}
else if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.CallTarget", StringComparison.OrdinalIgnoreCase))
{
Assembly taskExecutionHostAssembly = typeof(TaskExecutionHost).GetTypeInfo().Assembly;
returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new LoadedType(typeof(CallTarget), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly, typeof(ITaskItem)), _taskName, null);
_intrinsicTasks[_taskName] = returnClass;
}
}
return returnClass;
}
/// <summary>
/// Instantiates the task.
/// </summary>
private ITask InstantiateTask(IDictionary<string, string> taskIdentityParameters)
{
ITask task = null;
try
{
if (_taskFactoryWrapper.TaskFactory is AssemblyTaskFactory assemblyTaskFactory)
{
task = assemblyTaskFactory.CreateTaskInstance(_taskLocation, _taskLoggingContext, _buildComponentHost, taskIdentityParameters,
#if FEATURE_APPDOMAIN
AppDomainSetup,
#endif
IsOutOfProc);
}
else
{
TaskFactoryLoggingHost loggingHost = new TaskFactoryLoggingHost(_buildEngine.IsRunningMultipleNodes, _taskLocation, _taskLoggingContext);
try
{
task = _taskFactoryWrapper.TaskFactory is ITaskFactory2 taskFactory2 ?
taskFactory2.CreateTask(loggingHost, taskIdentityParameters) :
_taskFactoryWrapper.TaskFactory.CreateTask(loggingHost);
}
finally
{
#if FEATURE_APPDOMAIN
loggingHost.MarkAsInactive();
#endif
}
}
}
catch (InvalidCastException e)
{
_taskLoggingContext.LogError(
new BuildEventFileInfo(_taskLocation),
"TaskInstantiationFailureErrorInvalidCast",
_taskName,
_taskFactoryWrapper.TaskFactory.FactoryName,
e.Message);
}
catch (TargetInvocationException e)
{
// Exception thrown by the called code itself
// Log the stack, so the task vendor can fix their code
_taskLoggingContext.LogError(
new BuildEventFileInfo(_taskLocation),
"TaskInstantiationFailureError",
_taskName,
_taskFactoryWrapper.TaskFactory.FactoryName,
Environment.NewLine + e.InnerException);
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
// Reflection related exception
_taskLoggingContext.LogError(
new BuildEventFileInfo(_taskLocation),
"TaskInstantiationFailureError",
_taskName,
_taskFactoryWrapper.TaskFactory.FactoryName,
e.Message);
}
return task;
}
/// <summary>
/// Set the specified parameter based on its type.
/// </summary>
private bool SetTaskParameter(
string parameterName,
string parameterValue,
ElementLocation parameterLocation,
bool isRequired,
out bool parameterSet)
{
bool success = false;
parameterSet = false;
try
{
// check if the task has a .NET property corresponding to the parameter
LoadedType loadedType = _taskFactoryWrapper.TaskFactoryLoadedType;
int indexOfParameter = -1;
for (int i = 0; i < loadedType.Properties.Length; i++)
{
if (loadedType.Properties[i].Name.Equals(parameterName))
{
indexOfParameter = i;
break;
}
}
// For most tasks, finding the parameter in our list of known properties is equivalent to
// saying the task was properly invoked, as far as this parameter is concerned. However,
// that is not true for CodeTaskFactories like RoslynCodeTaskFactory. In that case, they
// will often have a list of parameters under the UsingTask declaration. Fortunately, if
// your TaskFactory is RoslynCodeTaskFactory, it isn't TaskHostFactory, which means the
// types are fully loaded at this stage, and we can access them as we had in the past.
TaskPropertyInfo parameter = null;
Type parameterType = null;
if (indexOfParameter != -1)
{
parameter = loadedType.Properties[indexOfParameter];
parameterType = Type.GetType(
loadedType.PropertyAssemblyQualifiedNames?[indexOfParameter] ??
parameter.PropertyType.AssemblyQualifiedName);
}
else
{
parameter = _taskFactoryWrapper.GetProperty(parameterName);
if (parameter != null)
{
parameterType = Type.GetType(parameter.PropertyType.AssemblyQualifiedName);
}
}
if (parameter != null)
{
EnsureParameterInitialized(parameter, _batchBucket.Lookup);
// try to set the parameter
if (TaskParameterTypeVerifier.IsValidScalarInputParameter(parameterType))
{
success = InitializeTaskScalarParameter(
parameter,
parameterType,
parameterValue,
parameterLocation,
out parameterSet);
}
else if (TaskParameterTypeVerifier.IsValidVectorInputParameter(parameterType))
{
success = InitializeTaskVectorParameter(
parameter,
parameterType,
parameterValue,
parameterLocation,
isRequired,
out parameterSet);
}
else
{
_taskLoggingContext.LogError(
new BuildEventFileInfo(parameterLocation),
"UnsupportedTaskParameterTypeError",
parameterType.FullName,
parameter.Name,
_taskName);
}
if (!success)
{
// flag an error if the parameter could not be set
_taskLoggingContext.LogError(
new BuildEventFileInfo(parameterLocation),
"InvalidTaskAttributeError",
parameterName,
parameterValue,
_taskName);
}
}
else
{
// flag an error if we find a parameter that has no .NET property equivalent
_taskLoggingContext.LogError(
new BuildEventFileInfo(parameterLocation),
"UnexpectedTaskAttribute",
parameterName,
_taskName,
_taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssemblyName.FullName,
_taskFactoryWrapper.TaskFactoryLoadedType.Path);
}
}
catch (AmbiguousMatchException)
{
_taskLoggingContext.LogError(
new BuildEventFileInfo(parameterLocation),
"AmbiguousTaskParameterError",
_taskName,
parameterName);
}
catch (ArgumentException)
{
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"SetAccessorNotAvailableOnTaskParameter",
parameterName,
_taskName);
}
return success;
}
/// <summary>
/// Given an instantiated task, this helper method sets the specified scalar parameter based on its type.
/// </summary>
private bool InitializeTaskScalarParameter(
TaskPropertyInfo parameter,
Type parameterType,
string parameterValue,
ElementLocation parameterLocation,
out bool taskParameterSet)
{
taskParameterSet = false;
bool success;
try
{
if (parameterType == typeof(ITaskItem))
{
// We don't know how many items we're going to end up with, but we'll
// keep adding them to this arraylist as we find them.
IList<TaskItem> finalTaskItems = _batchBucket.Expander.ExpandIntoTaskItemsLeaveEscaped(parameterValue, ExpanderOptions.ExpandAll, parameterLocation);
if (finalTaskItems.Count == 0)
{
success = true;
}
else
{
if (finalTaskItems.Count != 1)
{
// We only allow a single item to be passed into a parameter of ITaskItem.
// Some of the computation (expansion) here is expensive, so don't switch to VerifyThrowInvalidProject.
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"CannotPassMultipleItemsIntoScalarParameter",
_batchBucket.Expander.ExpandIntoStringAndUnescape(parameterValue, ExpanderOptions.ExpandAll, parameterLocation),
parameter.Name,
parameterType.FullName,
_taskName);
}
RecordItemForDisconnectIfNecessary(finalTaskItems[0]);
success = SetTaskItemParameter(parameter, finalTaskItems[0]);
taskParameterSet = true;
}
}
else
{
// Expand out all the metadata, properties, and item vectors in the string.
string expandedParameterValue = _batchBucket.Expander.ExpandIntoStringAndUnescape(parameterValue, ExpanderOptions.ExpandAll, parameterLocation);
if (expandedParameterValue.Length == 0)
{
success = true;
}
else
{
success = SetValueParameter(parameter, parameterType, expandedParameterValue);
taskParameterSet = true;
}
}
}
catch (Exception ex)
{
if (ex is InvalidCastException || // invalid type
ex is ArgumentException || // can't convert to bool
ex is FormatException || // bad string representation of a type
ex is OverflowException) // overflow when converting string representation of a numerical type
{
ProjectErrorUtilities.ThrowInvalidProject(
parameterLocation,
"InvalidTaskParameterValueError",
_batchBucket.Expander.ExpandIntoStringAndUnescape(parameterValue, ExpanderOptions.ExpandAll, parameterLocation),
parameter.Name,
parameterType.FullName,
_taskName);
}
throw;
}
return success;
}
private void EnsureParameterInitialized(TaskPropertyInfo parameter, Lookup lookup)
{
if (parameter.Initialized)
{
return;
}
parameter.Initialized = true;
string taskAndParameterName = _taskName + "_" + parameter.Name;
string key = "DisableLogTaskParameter_" + taskAndParameterName;
string metadataKey = "DisableLogTaskParameterItemMetadata_" + taskAndParameterName;
if (string.Equals(lookup.GetProperty(key)?.EvaluatedValue, "true", StringComparison.OrdinalIgnoreCase))
{
parameter.Log = false;
}
else if (string.Equals(lookup.GetProperty(metadataKey)?.EvaluatedValue, "true", StringComparison.OrdinalIgnoreCase))
{
parameter.LogItemMetadata = false;
}
}
/// <summary>
/// Given an instantiated task, this helper method sets the specified vector parameter. Vector parameters can be composed
/// of multiple item vectors. The semicolon is the only separator allowed, and white space around the semicolon is
/// ignored. Any item separator strings are not allowed, and embedded item vectors are not allowed.
/// </summary>
/// <remarks>This method is marked "internal" for unit-testing purposes only -- it should be "private" ideally.</remarks>
/// <example>
/// If @(CPPFiles) is a vector for the files a.cpp and b.cpp, and @(IDLFiles) is a vector for the files a.idl and b.idl:
///
/// "@(CPPFiles)" converts to { a.cpp, b.cpp }
///
/// "@(CPPFiles); c.cpp; @(IDLFiles); c.idl" converts to { a.cpp, b.cpp, c.cpp, a.idl, b.idl, c.idl }
///
/// "@(CPPFiles,';')" converts to <error>
///
/// "xxx@(CPPFiles)xxx" converts to <error>
/// </example>
private bool InitializeTaskVectorParameter(
TaskPropertyInfo parameter,
Type parameterType,
string parameterValue,
ElementLocation parameterLocation,
bool isRequired,
out bool taskParameterSet)
{
ErrorUtilities.VerifyThrow(parameterValue != null, "Didn't expect null parameterValue in InitializeTaskVectorParameter");
taskParameterSet = false;
bool success;
IList<TaskItem> finalTaskItems = _batchBucket.Expander.ExpandIntoTaskItemsLeaveEscaped(parameterValue, ExpanderOptions.ExpandAll, parameterLocation);
// If there were no items, don't change the parameter's value. EXCEPT if it's marked as a required
// parameter, in which case we made an explicit decision to pass in an empty array. This is
// to avoid project authors having to add Conditions on all their tasks to avoid calling them
// when a particular item list is empty. This way, we just call the task with an empty list,
// the task will loop over an empty list, and return quickly.
if ((finalTaskItems.Count > 0) || isRequired)
{
// If the task parameter is not a ITaskItem[], then we need to convert
// all the TaskItem's in our arraylist to the appropriate datatype.
success = SetParameterArray(parameter, parameterType, finalTaskItems, parameterLocation);
taskParameterSet = true;
}
else
{
success = true;
}
return success;
}
private static readonly string TaskParameterFormatString = ItemGroupLoggingHelper.TaskParameterPrefix + "{0}={1}";
/// <summary>
/// Given an instantiated task, this helper method sets the specified parameter
/// </summary>
private bool InternalSetTaskParameter(
TaskPropertyInfo parameter,
object parameterValue)
{
bool success = false;
if (LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents)
{
IList parameterValueAsList = parameterValue as IList;
bool legacyBehavior = !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_12);
// Legacy textual logging for parameters that are not lists.
if (legacyBehavior && parameterValueAsList == null)
{
_taskLoggingContext.LogCommentFromText(
MessageImportance.Low,
TaskParameterFormatString,
parameter.Name,
ItemGroupLoggingHelper.GetStringFromParameterValue(parameterValue));
}
if (parameter.Log)
{
// Structured logging for all parameters that have logging enabled and are not empty lists.
if (parameterValueAsList?.Count > 0 || (parameterValueAsList == null && !legacyBehavior))
{
// Note: We're setting TaskParameterEventArgs.ItemType to parameter name for backward compatibility with
// older loggers and binlog viewers.
ItemGroupLoggingHelper.LogTaskParameter(
_taskLoggingContext,
TaskParameterMessageKind.TaskInput,
parameterName: parameter.Name,
propertyName: null,
itemType: parameter.Name,
parameterValueAsList ?? (object[])[parameterValue],
parameter.LogItemMetadata);
}
}
}
try
{
_taskFactoryWrapper.SetPropertyValue(TaskInstance, parameter, parameterValue);
success = true;
}
catch (TargetInvocationException e)
{
// handle any exception thrown by the task's setter itself
// At this point, the interesting stack is the internal exception.
// Log the task line number, whatever the value of ContinueOnError;
// because this will be a hard error anyway.
// Exception thrown by the called code itself
// Log the stack, so the task vendor can fix their code
_taskLoggingContext.LogFatalTaskError(
e.InnerException,
new BuildEventFileInfo(_taskLocation),
_taskName);
}
// If a logger has failed, abort immediately. This is the polite LoggerException.
// InternalLoggerException is an arbitrary logger exception.
catch (Exception e) when (e is not LoggerException && e is not InternalLoggerException && !ExceptionHandling.NotExpectedReflectionException(e))
{
_taskLoggingContext.LogFatalTaskError(
e,
new BuildEventFileInfo(_taskLocation),
_taskName);
}
return success;
}
/// <summary>
/// Gets task item outputs
/// </summary>
private void GatherTaskItemOutputs(bool outputTargetIsItem, string outputTargetName, ITaskItem[] outputs, ElementLocation parameterLocation, TaskPropertyInfo parameter)
{
// if the task has generated outputs (if it didn't, don't do anything)
if (outputs != null)
{
if (outputTargetIsItem)
{
// Only count non-null elements. We sometimes have a single-element array where the element is null
bool hasElements = false;
foreach (ITaskItem output in outputs)
{
// if individual items in the array are null, ignore them
if (output != null)
{
hasElements = true;
ProjectItemInstance newItem;
TaskItem outputAsProjectItem = output as TaskItem;
string parameterLocationEscaped = EscapingUtilities.EscapeWithCaching(parameterLocation.File);
if (outputAsProjectItem != null)
{
// The common case -- all items involved are Microsoft.Build.Execution.ProjectItemInstance.TaskItems.
// Furthermore, because that is true, we know by definition that they also implement ITaskItem2.
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, outputAsProjectItem.IncludeEscaped, parameterLocationEscaped);
newItem.SetMetadata(outputAsProjectItem.MetadataCollection); // copy-on-write!
}
else
{
if (output is ITaskItem2 outputAsITaskItem2)
{
// Probably a Microsoft.Build.Utilities.TaskItem. Not quite as good, but we can still preserve escaping.
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, outputAsITaskItem2.EvaluatedIncludeEscaped, parameterLocationEscaped);
// It would be nice to be copy-on-write here, but Utilities.TaskItem doesn't know about CopyOnWritePropertyDictionary.
newItem.SetMetadataOnTaskOutput(outputAsITaskItem2.CloneCustomMetadataEscaped().Cast<KeyValuePair<string, string>>());
}
else
{
// Not a ProjectItemInstance.TaskItem or even a ITaskItem2, so we have to fake it.
// Setting an item spec expects the escaped value, as does setting metadata.
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, EscapingUtilities.Escape(output.ItemSpec), parameterLocationEscaped);
newItem.SetMetadataOnTaskOutput(EnumerateMetadata(output.CloneCustomMetadata()));
static IEnumerable<KeyValuePair<string, string>> EnumerateMetadata(IDictionary customMetadata)
{
foreach (DictionaryEntry de in customMetadata)
{
yield return new KeyValuePair<string, string>((string)de.Key, EscapingUtilities.Escape((string)de.Value));
}
}
}
}
_batchBucket.Lookup.AddNewItem(newItem);
}
}
if (hasElements && LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents && parameter.Log)
{
ItemGroupLoggingHelper.LogTaskParameter(
_taskLoggingContext,
TaskParameterMessageKind.TaskOutput,
parameterName: parameter.Name,
propertyName: null,
itemType: outputTargetName,
outputs,
parameter.LogItemMetadata);
}
}
else
{
// to store an ITaskItem array in a property, join all the item-specs with semi-colons to make the
// property value, and ignore/discard the attributes on the ITaskItems.
//
// An empty ITaskItem[] should create a blank value property, for compatibility.
StringBuilder joinedOutputs = (outputs.Length == 0) ? new StringBuilder() : null;
foreach (ITaskItem output in outputs)
{
// if individual items in the array are null, ignore them
if (output != null)
{
joinedOutputs ??= new StringBuilder();
if (joinedOutputs.Length > 0)
{
joinedOutputs.Append(';');
}
if (output is ITaskItem2 outputAsITaskItem2)
{
joinedOutputs.Append(outputAsITaskItem2.EvaluatedIncludeEscaped);
}
else
{
joinedOutputs.Append(EscapingUtilities.Escape(output.ItemSpec));
}
}
}
if (joinedOutputs != null)
{
var outputString = joinedOutputs.ToString();
if (LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents)
{
if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_12))
{
// Note: We're setting TaskParameterEventArgs.ItemType to property name for backward compatibility with
// older loggers and binlog viewers.
ItemGroupLoggingHelper.LogTaskParameter(
_taskLoggingContext,
TaskParameterMessageKind.TaskOutput,
parameterName: parameter.Name,
propertyName: outputTargetName,
itemType: outputTargetName,
(object[])[outputString],
parameter.LogItemMetadata);
}
else
{
_taskLoggingContext.LogComment(MessageImportance.Low, "OutputPropertyLogMessage", outputTargetName, outputString);
}
}
_batchBucket.Lookup.SetProperty(ProjectPropertyInstance.Create(outputTargetName, outputString, parameterLocation, _projectInstance.IsImmutable));
}
}
}
}
/// <summary>
/// Gather task outputs in array form
/// </summary>
private void GatherArrayStringAndValueOutputs(bool outputTargetIsItem, string outputTargetName, string[] outputs, ElementLocation parameterLocation, TaskPropertyInfo parameter)
{
// if the task has generated outputs (if it didn't, don't do anything)
if (outputs != null)
{
if (outputTargetIsItem)
{
// to store the outputs as items, use the string representations of the outputs as item-specs
foreach (string output in outputs)
{
// if individual outputs in the array are null, ignore them
// attempting to put an empty string into an item is a no-op.
if (output?.Length > 0)
{
_batchBucket.Lookup.AddNewItem(new ProjectItemInstance(_projectInstance, outputTargetName, EscapingUtilities.Escape(output), EscapingUtilities.Escape(parameterLocation.File)));
}
}
if (LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents && outputs.Length > 0 && parameter.Log)
{
ItemGroupLoggingHelper.LogTaskParameter(
_taskLoggingContext,
TaskParameterMessageKind.TaskOutput,
parameterName: parameter.Name,
propertyName: null,
itemType: outputTargetName,
outputs,
parameter.LogItemMetadata);
}
}
else
{
// to store an object array in a property, join all the string representations of the objects with
// semi-colons to make the property value
//
// An empty ITaskItem[] should create a blank value property, for compatibility.
StringBuilder joinedOutputs = (outputs.Length == 0) ? new StringBuilder() : null;
foreach (string output in outputs)
{
// if individual outputs in the array are null, ignore them
if (output != null)
{
joinedOutputs ??= new StringBuilder();
if (joinedOutputs.Length > 0)
{
joinedOutputs.Append(';');
}
joinedOutputs.Append(EscapingUtilities.Escape(output));
}
}
if (joinedOutputs != null)
{
var outputString = joinedOutputs.ToString();
if (LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents)
{
if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_12))
{
// Note: We're setting TaskParameterEventArgs.ItemType to property name for backward compatibility with
// older loggers and binlog viewers.
ItemGroupLoggingHelper.LogTaskParameter(
_taskLoggingContext,
TaskParameterMessageKind.TaskOutput,
parameterName: parameter.Name,
propertyName: outputTargetName,
itemType: outputTargetName,
(object[])[outputString],
parameter.LogItemMetadata);
}
else
{
_taskLoggingContext.LogComment(MessageImportance.Low, "OutputPropertyLogMessage", outputTargetName, outputString);
}
}
_batchBucket.Lookup.SetProperty(ProjectPropertyInstance.Create(outputTargetName, outputString, parameterLocation, _projectInstance.IsImmutable));
}
}
}
}
/// <summary>
/// Finds all the task properties that are required.
/// Returns them as keys in a dictionary.
/// </summary>
/// <returns>Gets a list of properties which are required.</returns>
private IReadOnlyDictionary<string, string> GetNamesOfPropertiesWithRequiredAttribute()
{
ErrorUtilities.VerifyThrow(_taskFactoryWrapper != null, "Expected taskFactoryWrapper to not be null");
IReadOnlyDictionary<string, string> requiredParameters = null;
try
{
requiredParameters = _taskFactoryWrapper.GetNamesOfPropertiesWithRequiredAttribute;
}
catch (Exception e) when (!ExceptionHandling.NotExpectedReflectionException(e))
{
// Reflection related exception
_targetLoggingContext.LogError(new BuildEventFileInfo(_taskLocation), "AttributeTypeLoadError", _taskName, e.Message);
ProjectErrorUtilities.VerifyThrowInvalidProject(false, _taskLocation, "TaskDeclarationOrUsageError", _taskName);
}
return requiredParameters;
}
/// <summary>
/// Show a message that cancel has not yet finished.
/// </summary>
private void DisplayCancelWaitMessage()
{
string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(out string warningCode, out string helpKeyword, "UnableToCancelTask", _taskName);
try
{
_taskLoggingContext.LogWarningFromText(null, warningCode, helpKeyword, new BuildEventFileInfo(_taskLocation), message);
}
catch (InternalErrorException) when (!_taskLoggingContext.IsValid)
{
// We can get an exception from this when we encounter a race between a task finishing and a cancel occurring. In this situation
// if the task logging context is no longer valid, we choose to eat the exception because we can't log the message anyway.
}
}
}
}
|