|
// 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.Generic;
using System.Reflection;
#if FEATURE_APPDOMAIN
using System.Threading.Tasks;
#endif
using Microsoft.Build.BackEnd.Components.RequestBuilder;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
#if NETFRAMEWORK
using Microsoft.IO;
#else
using System.IO;
#endif
using ElementLocation = Microsoft.Build.Construction.ElementLocation;
using TargetLoggingContext = Microsoft.Build.BackEnd.Logging.TargetLoggingContext;
using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext;
using Microsoft.Build.Execution;
using Microsoft.Build.BackEnd.Logging;
using Constants = Microsoft.Build.Framework.Constants;
#nullable disable
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// The assembly task factory is used to wrap and construct tasks which are from .net assemblies.
/// </summary>
internal class AssemblyTaskFactory : ITaskFactory3
{
#region Data
/// <summary>
/// The type loader to load types which derrive from ITask or ITask2
/// </summary>
private readonly TypeLoader _typeLoader = new TypeLoader(TaskLoader.IsTaskClass);
/// <summary>
/// Name of the task wrapped by the task factory
/// </summary>
private string _taskName = null;
/// <summary>
/// The loaded type (type, assembly name / file) of the task wrapped by the factory
/// </summary>
private LoadedType _loadedType;
#if FEATURE_APPDOMAIN
/// <summary>
/// A cache of tasks and the AppDomains they are loaded in.
/// </summary>
private Dictionary<ITask, AppDomain> _tasksAndAppDomains = new Dictionary<ITask, AppDomain>();
#endif
/// <summary>
/// Parameters owned by this particular task host.
/// </summary>
private TaskHostParameters _factoryIdentityParameters;
/// <summary>
/// Need to store away the taskloggingcontext used by CreateTaskInstance so that
/// TaskLoader will be able to call back with errors.
/// </summary>
private TaskLoggingContext _taskLoggingContext;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="AssemblyTaskFactory"/> class.
/// </summary>
internal AssemblyTaskFactory()
{
}
#region Public Members
/// <summary>
/// Name of the factory. In this case the name is the assembly name which is wrapped by the factory
/// </summary>
public string FactoryName
{
get
{
return _loadedType.Assembly.AssemblyLocation;
}
}
/// <summary>
/// Gets the type of task this factory creates.
/// </summary>
public Type TaskType
{
get { return _loadedType.Type; }
}
/// <summary>
/// Initializes this factory for instantiating tasks with a particular inline task block.
/// </summary>
/// <param name="taskName">Name of the task.</param>
/// <param name="parameterGroup">The parameter group.</param>
/// <param name="taskBody">The task body.</param>
/// <param name="taskFactoryLoggingHost">The task factory logging host.</param>
/// <returns>A value indicating whether initialization was successful.</returns>
/// <remarks>
/// <para>MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the factory can be asked
/// whether or not task names can be created by the factory.</para>
/// <para>
/// The taskFactoryLoggingHost will log messages in the context of the target where the task is first used.
/// </para>
/// </remarks>
public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost)
{
ErrorUtilities.ThrowInternalError("Use internal call to properly initialize the assembly task factory");
return false;
}
/// <summary>
/// Initializes this factory for instantiating tasks with a particular inline task block and a set of UsingTask parameters.
/// </summary>
/// <param name="taskName">Name of the task.</param>
/// <param name="factoryIdentityParameters">Special parameters that the task factory can use to modify how it executes tasks,
/// such as Runtime and Architecture. The key is the name of the parameter and the value is the parameter's value. This
/// is the set of parameters that was set on the UsingTask using e.g. the UsingTask Runtime and Architecture parameters.</param>
/// <param name="parameterGroup">The parameter group.</param>
/// <param name="taskBody">The task body.</param>
/// <param name="taskFactoryLoggingHost">The task factory logging host.</param>
/// <returns>A value indicating whether initialization was successful.</returns>
/// <remarks>
/// <para>MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the
/// factory can be asked whether or not task names can be created by the factory. If a task factory implements ITaskFactory2,
/// this Initialize method will be called in place of ITaskFactory.Initialize.</para>
/// <para>
/// The taskFactoryLoggingHost will log messages in the context of the target where the task is first used.
/// </para>
/// </remarks>
public bool Initialize(string taskName, IDictionary<string, string> factoryIdentityParameters, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost)
{
ErrorUtilities.ThrowInternalError("Use internal call to properly initialize the assembly task factory");
return false;
}
/// <summary>
/// Get a list of parameters for the task.
/// </summary>
public TaskPropertyInfo[] GetTaskParameters()
{
return _loadedType.Properties;
}
/// <summary>
/// Create an instance of the task to be used.
/// The task factory logging host will log messages in the context of the task.
/// </summary>
/// <param name="taskFactoryLoggingHost">
/// The task factory logging host will log messages in the context of the task.
/// </param>
/// <returns>
/// The generated task, or <c>null</c> if the task failed to be created.
/// </returns>
public ITask CreateTask(IBuildEngine taskFactoryLoggingHost)
{
ErrorUtilities.ThrowInternalError("Use internal call to properly create a task instance from the assembly task factory");
return null;
}
/// <summary>
/// Create an instance of the task to be used.
/// </summary>
/// <param name="taskFactoryLoggingHost">
/// The task factory logging host will log messages in the context of the task.
/// </param>
/// <param name="taskIdentityParameters">
/// Special parameters that the task factory can use to modify how it executes tasks, such as Runtime and Architecture.
/// The key is the name of the parameter and the value is the parameter's value. This is the set of parameters that was
/// set to the task invocation itself, via e.g. the special MSBuildRuntime and MSBuildArchitecture parameters.
/// </param>
/// <remarks>
/// If a task factory implements ITaskFactory2, MSBuild will call this method instead of ITaskFactory.CreateTask.
/// </remarks>
/// <returns>
/// The generated task, or <c>null</c> if the task failed to be created.
/// </returns>
public ITask CreateTask(IBuildEngine taskFactoryLoggingHost, IDictionary<string, string> taskIdentityParameters)
{
ErrorUtilities.ThrowInternalError("Use internal call to properly create a task instance from the assembly task factory");
return null;
}
/// <summary>
/// Cleans up any context or state that may have been built up for a given task.
/// </summary>
/// <param name="task">The task to clean up.</param>
/// <remarks>
/// For many factories, this method is a no-op. But some factories may have built up
/// an AppDomain as part of an individual task instance, and this is their opportunity
/// to shutdown the AppDomain.
/// </remarks>
public void CleanupTask(ITask task)
{
ErrorUtilities.VerifyThrowArgumentNull(task);
#if FEATURE_APPDOMAIN
AppDomain appDomain;
if (_tasksAndAppDomains.TryGetValue(task, out appDomain))
{
_tasksAndAppDomains.Remove(task);
if (appDomain != null)
{
AssemblyLoadsTracker.StopTracking(appDomain);
// Unload the AppDomain asynchronously to avoid a deadlock that can happen because
// AppDomain.Unload blocks for the process's one Finalizer thread to finalize all
// objects. Some objects are RCWs for STA COM objects and as such would need the
// VS main thread to be processing messages in order to finalize. But if the main thread
// is blocked in a non-pumping wait waiting for this build request to complete, we would
// deadlock. By unloading asynchronously, the AppDomain unload can block till the main
// thread is available, even if it isn't available until after this MSBuild Task has
// finished executing.
Task.Run(() => AppDomain.Unload(appDomain));
}
}
#endif
TaskHostTask taskAsTaskHostTask = task as TaskHostTask;
if (taskAsTaskHostTask != null)
{
taskAsTaskHostTask.Cleanup();
}
else
{
#if FEATURE_APPDOMAIN
// It's really not necessary to do it for TaskHostTasks
TaskLoader.RemoveAssemblyResolver();
#endif
}
}
#endregion
#region Internal Members
/// <summary>
/// Initialize the factory from the task registry.
/// </summary>
internal LoadedType InitializeFactory(
AssemblyLoadInfo loadInfo,
string taskName,
IDictionary<string, TaskPropertyInfo> taskParameters,
string taskElementContents,
in TaskHostParameters taskFactoryIdentityParameters,
bool taskHostExplicitlyRequested,
LoggingContext targetLoggingContext,
ElementLocation elementLocation,
string taskProjectFile)
{
ErrorUtilities.VerifyThrowArgumentNull(loadInfo);
VerifyThrowIdentityParametersValid(taskFactoryIdentityParameters, elementLocation, taskName, "Runtime", "Architecture");
bool taskHostParamsMatchCurrentProc = true;
if (!taskFactoryIdentityParameters.IsEmpty)
{
taskHostParamsMatchCurrentProc = TaskHostParametersMatchCurrentProcess(taskFactoryIdentityParameters);
_factoryIdentityParameters = taskFactoryIdentityParameters;
}
try
{
ErrorUtilities.VerifyThrowArgumentLength(taskName);
_taskName = taskName;
string assemblyName = loadInfo.AssemblyName ?? Path.GetFileName(loadInfo.AssemblyFile);
using var assemblyLoadsTracker = AssemblyLoadsTracker.StartTracking(targetLoggingContext, AssemblyLoadingContext.TaskRun, assemblyName);
_loadedType = _typeLoader.Load(taskName, loadInfo, targetLoggingContext.LogWarning, taskHostExplicitlyRequested, taskHostParamsMatchCurrentProc);
ProjectErrorUtilities.VerifyThrowInvalidProject(_loadedType != null, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty);
}
catch (TargetInvocationException e)
{
// Exception thrown by the called code itself
// Log the stack, so the task vendor can fix their code
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, Environment.NewLine + e.InnerException.ToString());
}
catch (ReflectionTypeLoadException e)
{
// ReflectionTypeLoadException.LoaderExceptions may contain nulls
foreach (Exception exception in e.LoaderExceptions)
{
if (exception != null)
{
targetLoggingContext.LogError(new BuildEventFileInfo(taskProjectFile), "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, exception.Message);
}
}
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message);
}
catch (ArgumentNullException e)
{
// taskName may be null
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message);
}
catch (Exception e) when (!ExceptionHandling.NotExpectedReflectionException(e))
{
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message);
}
return _loadedType;
}
/// <summary>
/// Create an instance of the wrapped ITask for a batch run of the task.
/// </summary>
internal ITask CreateTaskInstance(
ElementLocation taskLocation,
TaskLoggingContext taskLoggingContext,
IBuildComponentHost buildComponentHost,
in TaskHostParameters taskIdentityParameters,
string projectFile,
HostServices hostServices,
#if FEATURE_APPDOMAIN
AppDomainSetup appDomainSetup,
#endif
bool isOutOfProc,
int scheduledNodeId,
Func<string, ProjectPropertyInstance> getProperty,
TaskEnvironment taskEnvironment)
{
// If the type was loaded via MetadataLoadContext, we MUST use TaskFactory since it didn't load any task assemblies in memory.
bool useTaskFactory = _loadedType.LoadedViaMetadataLoadContext;
TaskHostParameters mergedParameters = TaskHostParameters.Empty;
_taskLoggingContext = taskLoggingContext;
// Optimization for the common (vanilla AssemblyTaskFactory) case -- only calculate
// the task factory parameters if we have any to calculate; otherwise even if we
// still launch the task factory, it will be with parameters corresponding to the
// current process.
if (!_factoryIdentityParameters.IsEmpty || !taskIdentityParameters.IsEmpty)
{
VerifyThrowIdentityParametersValid(taskIdentityParameters, taskLocation, _taskName, "MSBuildRuntime", "MSBuildArchitecture");
mergedParameters = MergeTaskFactoryParameterSets(_factoryIdentityParameters, taskIdentityParameters);
useTaskFactory = _loadedType.LoadedViaMetadataLoadContext || !TaskHostParametersMatchCurrentProcess(mergedParameters);
}
// Multi-threaded mode routing: Determine if non-thread-safe tasks need TaskHost isolation.
if (!useTaskFactory
&& _loadedType?.Type != null
&& buildComponentHost?.BuildParameters?.MultiThreaded == true)
{
if (TaskRouter.NeedsTaskHostInMultiThreadedMode(_loadedType.Type))
{
useTaskFactory = true;
}
}
_taskLoggingContext?.TargetLoggingContext?.ProjectLoggingContext?.ProjectTelemetry?.AddTaskExecution(GetType().FullName, isTaskHost: useTaskFactory);
if (useTaskFactory)
{
ErrorUtilities.VerifyThrowInternalNull(buildComponentHost);
mergedParameters = UpdateTaskHostParameters(mergedParameters);
mergedParameters = AddNetHostParamsIfNeeded(mergedParameters, getProperty);
// Sidecar here means that the task host is launched with /nodeReuse:true and doesn't terminate
// after the task execution. This improves performance for tasks that run multiple times in a build.
// If the task host factory is explicitly requested, do not act as a sidecar task host.
// This is important as customers use task host factories for short lived tasks to release
// potential locks.
bool useSidecarTaskHost = !(_factoryIdentityParameters.TaskHostFactoryExplicitlyRequested ?? false);
TaskHostTask task = new(
taskLocation,
taskLoggingContext,
buildComponentHost,
mergedParameters,
_loadedType,
useSidecarTaskHost: useSidecarTaskHost,
projectFile,
#if FEATURE_APPDOMAIN
appDomainSetup,
#endif
hostServices,
scheduledNodeId,
taskEnvironment: taskEnvironment);
return task;
}
else
{
#if FEATURE_APPDOMAIN
AppDomain taskAppDomain = null;
#endif
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
ITask taskInstance = TaskLoader.CreateTask(
_loadedType,
_taskName,
taskLocation.File,
taskLocation.Line,
taskLocation.Column,
new TaskLoader.LogError(ErrorLoggingDelegate),
#if FEATURE_APPDOMAIN
appDomainSetup,
appDomain => AssemblyLoadsTracker.StartTracking(taskLoggingContext, AssemblyLoadingContext.TaskRun, _loadedType.Type, appDomain),
#endif
isOutOfProc
#if FEATURE_APPDOMAIN
, out taskAppDomain
#endif
);
#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
#if FEATURE_APPDOMAIN
if (taskAppDomain != null && taskInstance != null)
{
_tasksAndAppDomains[taskInstance] = taskAppDomain;
}
else if (taskAppDomain != null)
{
AssemblyLoadsTracker.StopTracking(taskAppDomain);
}
#endif
// Track non-sealed subclasses of Microsoft-owned MSBuild tasks
if (taskInstance != null)
{
bool isMicrosoftOwned = IsMicrosoftAuthoredTask();
_taskLoggingContext?.TargetLoggingContext?.ProjectLoggingContext?.ProjectTelemetry?.TrackTaskSubclassing(_loadedType.Type, isMicrosoftOwned);
}
return taskInstance;
}
}
/// <summary>
/// Overrides runtime/architecture with values from task host parameters if available.
/// The explicitly specified parameters always take precedence over assembly metadata.
/// </summary>
private TaskHostParameters UpdateTaskHostParameters(TaskHostParameters taskHostParameters)
{
// Determine runtime: prefer explicit parameter, fallback to loaded type's runtime or current.
string runtime = taskHostParameters.Runtime
?? _loadedType?.Runtime
?? XMakeAttributes.GetCurrentMSBuildRuntime();
// Determine architecture: prefer explicit parameter, fallback to loaded type's architecture or current
string architecture = taskHostParameters.Architecture
?? _loadedType?.Architecture
?? XMakeAttributes.GetCurrentMSBuildArchitecture();
return new TaskHostParameters(
runtime: runtime,
architecture: architecture,
dotnetHostPath: taskHostParameters.DotnetHostPath,
msBuildAssemblyPath: taskHostParameters.MSBuildAssemblyPath,
taskHostFactoryExplicitlyRequested: taskHostParameters.TaskHostFactoryExplicitlyRequested);
}
/// <summary>
/// Is the given task name able to to be created by the task factory. In the case of an assembly task factory
/// this question is answered by checking the assembly wrapped by the task factory to see if it exists.
/// </summary>
internal bool TaskNameCreatableByFactory(string taskName, in TaskHostParameters taskIdentityParameters, string taskProjectFile, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation)
{
if (!TaskIdentityParametersMatchFactory(_factoryIdentityParameters, taskIdentityParameters))
{
return false;
}
try
{
ErrorUtilities.VerifyThrowArgumentLength(taskName, "TaskName");
// Parameters match, so now we check to see if the task exists.
return _typeLoader.ReflectionOnlyLoad(taskName, _loadedType.Assembly) != null;
}
catch (TargetInvocationException e)
{
// Exception thrown by the called code itself
// Log the stack, so the task vendor can fix their code
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, Environment.NewLine + e.InnerException.ToString());
}
catch (ReflectionTypeLoadException e)
{
// ReflectionTypeLoadException.LoaderExceptions may contain nulls
foreach (Exception exception in e.LoaderExceptions)
{
if (exception != null)
{
targetLoggingContext.LogError(new BuildEventFileInfo(taskProjectFile), "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, exception.Message);
}
}
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message);
}
catch (ArgumentNullException e)
{
// taskName may be null
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message);
}
#if NETCOREAPP
catch (FileNotFoundException e)
{
// The specified task assembly could not be found, it might be misspelled. It usually discovered during MetadataLoadContext run.
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message);
}
#endif
catch (Exception e) when (!ExceptionHandling.NotExpectedReflectionException(e))
{
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message);
}
return false;
}
#endregion
#region Private members
/// <summary>
/// Validates the given set of parameters, logging the appropriate errors as necessary.
/// </summary>
private static void VerifyThrowIdentityParametersValid(in TaskHostParameters identityParameters, IElementLocation errorLocation, string taskName, string runtimeName, string architectureName)
{
// validate the task factory parameters
if (identityParameters.Runtime != null)
{
if (!XMakeAttributes.IsValidMSBuildRuntimeValue(identityParameters.Runtime))
{
ProjectErrorUtilities.ThrowInvalidProject(
errorLocation,
"TaskLoadFailureInvalidTaskHostFactoryParameter",
taskName,
identityParameters.Runtime,
runtimeName,
XMakeAttributes.MSBuildRuntimeValues.clr2,
XMakeAttributes.MSBuildRuntimeValues.clr4,
XMakeAttributes.MSBuildRuntimeValues.currentRuntime,
XMakeAttributes.MSBuildRuntimeValues.any);
}
}
if (identityParameters.Architecture != null)
{
if (!XMakeAttributes.IsValidMSBuildArchitectureValue(identityParameters.Architecture))
{
ProjectErrorUtilities.ThrowInvalidProject(
errorLocation,
"TaskLoadFailureInvalidTaskHostFactoryParameter",
taskName,
identityParameters.Architecture,
architectureName,
XMakeAttributes.MSBuildArchitectureValues.x86,
XMakeAttributes.MSBuildArchitectureValues.x64,
XMakeAttributes.MSBuildArchitectureValues.currentArchitecture,
XMakeAttributes.MSBuildArchitectureValues.any);
}
}
}
/// <summary>
/// Given the set of parameters that are set to the factory, and the set of parameters coming from the task invocation that we're searching for
/// a matching record to, determine whether the parameters match this record.
/// </summary>
private static bool TaskIdentityParametersMatchFactory(in TaskHostParameters factoryIdentityParameters, in TaskHostParameters taskIdentityParameters)
{
if (taskIdentityParameters.IsEmpty || factoryIdentityParameters.IsEmpty)
{
// either the task or the using task doesn't care about anything, in which case we match by default.
return true;
}
if (XMakeAttributes.RuntimeValuesMatch(taskIdentityParameters.Runtime, factoryIdentityParameters.Runtime))
{
if (XMakeAttributes.ArchitectureValuesMatch(taskIdentityParameters.Architecture, factoryIdentityParameters.Architecture))
{
// both match
return true;
}
}
// one or more does not match, so we don't match.
return false;
}
/// <summary>
/// Given a set of task parameters from the UsingTask and from the task invocation, generate a dictionary that combines the two, or throws if the merge
/// is impossible (we shouldn't ever get to this point if it is ...)
/// </summary>
private static TaskHostParameters MergeTaskFactoryParameterSets(
in TaskHostParameters factoryIdentityParameters,
in TaskHostParameters taskIdentityParameters)
{
if (factoryIdentityParameters.IsEmpty && taskIdentityParameters.IsEmpty)
{
return TaskHostParameters.Empty;
}
if (taskIdentityParameters.IsEmpty)
{
string normalizedRuntime = XMakeAttributes.GetExplicitMSBuildRuntime(factoryIdentityParameters.Runtime);
string normalizedArch = XMakeAttributes.GetExplicitMSBuildArchitecture(factoryIdentityParameters.Architecture);
return normalizedRuntime == factoryIdentityParameters.Runtime && normalizedArch == factoryIdentityParameters.Architecture
? factoryIdentityParameters
: new TaskHostParameters(normalizedRuntime, normalizedArch);
}
if (factoryIdentityParameters.IsEmpty)
{
string normalizedRuntime = XMakeAttributes.GetExplicitMSBuildRuntime(taskIdentityParameters.Runtime);
string normalizedArch = XMakeAttributes.GetExplicitMSBuildArchitecture(taskIdentityParameters.Architecture);
return normalizedRuntime == taskIdentityParameters.Runtime && normalizedArch == taskIdentityParameters.Architecture
? taskIdentityParameters
: new TaskHostParameters(normalizedRuntime, normalizedArch);
}
if (!XMakeAttributes.TryMergeRuntimeValues(taskIdentityParameters.Runtime, factoryIdentityParameters.Runtime, out var mergedRuntime))
{
ErrorUtilities.ThrowInternalError("How did we get two runtime values that were unmergeable? " +
$"TaskIdentity Runtime: {taskIdentityParameters.Runtime}, FactoryIdentity Runtime: {factoryIdentityParameters.Runtime}.");
}
if (!XMakeAttributes.TryMergeArchitectureValues(taskIdentityParameters.Architecture, factoryIdentityParameters.Architecture, out var mergedArchitecture))
{
ErrorUtilities.ThrowInternalError("How did we get two architecture values that were unmergeable? " +
$"TaskIdentity Architecture: {taskIdentityParameters.Architecture}, FactoryIdentity Architecture: {factoryIdentityParameters.Architecture}.");
}
return new TaskHostParameters(
runtime: mergedRuntime,
architecture: mergedArchitecture);
}
/// <summary>
/// Adds the properties necessary for .NET task host instantiation if the runtime is .NET.
/// Returns a new TaskHostParameters with .NET host parameters added, or the original if not needed.
/// </summary>
private static TaskHostParameters AddNetHostParamsIfNeeded(
in TaskHostParameters currentParams,
Func<string, ProjectPropertyInstance> getProperty)
{
// Only add .NET host parameters if runtime is .NET
if (currentParams.Runtime == null ||
!currentParams.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.net, StringComparison.OrdinalIgnoreCase))
{
return currentParams;
}
string dotnetHostPath = getProperty(Constants.DotnetHostPathEnvVarName)?.EvaluatedValue;
string netCoreSdkRoot = getProperty(Constants.NetCoreSdkRoot)?.EvaluatedValue?.TrimEnd('/', '\\');
// The NetCoreSdkRoot property got added with .NET 11, so for earlier SDKs we fall back to the RID graph path
if (string.IsNullOrEmpty(netCoreSdkRoot))
{
string ridGraphPath = getProperty(Constants.RuntimeIdentifierGraphPath)?.EvaluatedValue;
if (!string.IsNullOrEmpty(ridGraphPath))
{
netCoreSdkRoot = Path.GetDirectoryName(ridGraphPath);
}
}
// Both DOTNET_HOST_PATH and NetCoreSdkRoot are required to launch .NET task host.
// If both are not present, return the original parameters.
if (string.IsNullOrEmpty(dotnetHostPath) || string.IsNullOrEmpty(netCoreSdkRoot))
{
return currentParams;
}
return new TaskHostParameters(
runtime: currentParams.Runtime,
architecture: currentParams.Architecture,
dotnetHostPath: dotnetHostPath,
msBuildAssemblyPath: netCoreSdkRoot);
}
/// <summary>
/// Returns true if the provided set of task host parameters matches the current process,
/// and false otherwise.
/// </summary>
private static bool TaskHostParametersMatchCurrentProcess(in TaskHostParameters mergedParameters)
{
if (mergedParameters.IsEmpty)
{
// We don't care, so they match by default.
return true;
}
if (mergedParameters.Runtime != null)
{
string currentRuntime = XMakeAttributes.GetExplicitMSBuildRuntime(XMakeAttributes.MSBuildRuntimeValues.currentRuntime);
if (!currentRuntime.Equals(XMakeAttributes.GetExplicitMSBuildRuntime(mergedParameters.Runtime), StringComparison.OrdinalIgnoreCase))
{
// runtime doesn't match
return false;
}
}
if (mergedParameters.Architecture != null)
{
string currentArchitecture = XMakeAttributes.GetCurrentMSBuildArchitecture();
if (!currentArchitecture.Equals(XMakeAttributes.GetExplicitMSBuildArchitecture(mergedParameters.Architecture), StringComparison.OrdinalIgnoreCase))
{
// architecture doesn't match
return false;
}
}
// if it doesn't not match, then it matches
return true;
}
/// <summary>
/// Determines whether the current task is Microsoft-authored based on the assembly location and name.
/// </summary>
private bool IsMicrosoftAuthoredTask()
{
if (_loadedType?.Type == null)
{
return false;
}
// Check if the assembly is a Microsoft assembly by name
string assemblyName = _loadedType.Assembly.AssemblyName;
if (!string.IsNullOrEmpty(assemblyName) && FileClassifier.IsMicrosoftAssembly(assemblyName))
{
return true;
}
// Check if the assembly is from a Microsoft-controlled location
string assemblyFile = _loadedType.Assembly.AssemblyFile;
if (!string.IsNullOrEmpty(assemblyFile))
{
// Check if it's built-in MSBuild logic (e.g., from MSBuild installation)
if (FileClassifier.Shared.IsBuiltInLogic(assemblyFile))
{
return true;
}
// Check if it's a Microsoft package from NuGet cache
if (FileClassifier.Shared.IsMicrosoftPackageInNugetCache(assemblyFile))
{
return true;
}
}
return false;
}
/// <summary>
/// Log errors from TaskLoader.
/// </summary>
private void ErrorLoggingDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs)
{
_taskLoggingContext.LogError(new BuildEventFileInfo(taskLocation, taskLine, taskColumn), message, messageArgs);
}
/// <summary>
/// Initializes this factory for instantiating tasks with a particular inline task block and a set of UsingTask parameters.
/// </summary>
/// <param name="taskName">Name of the task.</param>
/// <param name="factoryIdentityParameters">Special parameters that the task factory can use to modify how it executes tasks,
/// such as Runtime and Architecture. The key is the name of the parameter and the value is the parameter's value. This
/// is the set of parameters that was set on the UsingTask using e.g. the UsingTask Runtime and Architecture parameters.</param>
/// <param name="parameterGroup">The parameter group.</param>
/// <param name="taskBody">The task body.</param>
/// <param name="taskFactoryLoggingHost">The task factory logging host.</param>
/// <returns>A value indicating whether initialization was successful.</returns>
/// <remarks>
/// <para>MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the
/// factory can be asked whether or not task names can be created by the factory. If a task factory implements ITaskFactory2,
/// this Initialize method will be called in place of ITaskFactory.Initialize.</para>
/// <para>
/// The taskFactoryLoggingHost will log messages in the context of the target where the task is first used.
/// </para>
/// </remarks>
public bool Initialize(string taskName, TaskHostParameters factoryIdentityParameters, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost)
{
ErrorUtilities.ThrowInternalError("Use internal call to properly initialize the assembly task factory");
return false;
}
/// <summary>
/// Create an instance of the task to be used.
/// </summary>
/// <param name="taskFactoryLoggingHost">
/// The task factory logging host will log messages in the context of the task.
/// </param>
/// <param name="taskIdentityParameters">
/// Special parameters that the task factory can use to modify how it executes tasks, such as Runtime and Architecture.
/// The key is the name of the parameter and the value is the parameter's value. This is the set of parameters that was
/// set to the task invocation itself, via e.g. the special MSBuildRuntime and MSBuildArchitecture parameters.
/// </param>
/// <remarks>
/// If a task factory implements ITaskFactory2, MSBuild will call this method instead of ITaskFactory.CreateTask.
/// </remarks>
/// <returns>
/// The generated task, or <c>null</c> if the task failed to be created.
/// </returns>
public ITask CreateTask(IBuildEngine taskFactoryLoggingHost, TaskHostParameters taskIdentityParameters)
{
ErrorUtilities.ThrowInternalError("Use internal call to properly create a task instance from the assembly task factory");
return null;
}
#endregion
}
}
|