|
// 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.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.TaskHost.Resources;
using Microsoft.Build.TaskHost.Utilities;
namespace Microsoft.Build.TaskHost;
/// <summary>
/// Class for loading tasks.
/// </summary>
internal static class TaskLoader
{
/// <summary>
/// For saving the assembly that was loaded by the TypeLoader
/// We only use this when the assembly failed to load properly into the appdomain.
/// </summary>
private static LoadedType? s_resolverLoadedType;
/// <summary>
/// Delegate for logging task loading errors.
/// </summary>
internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message);
/// <summary>
/// Checks if the given type is a task factory.
/// </summary>
/// <remarks>This method is used as a type filter delegate.</remarks>
/// <returns>true, if specified type is a task</returns>
internal static bool IsTaskClass(Type type, object unused)
=> type.IsClass && !type.IsAbstract && type.GetInterface("Microsoft.Build.Framework.ITask") != null;
/// <summary>
/// Creates an ITask instance and returns it.
/// </summary>
internal static ITask? CreateTask(
LoadedType loadedType,
string taskName,
string taskLocation,
int taskLine,
int taskColumn,
LogError logError,
AppDomainSetup appDomainSetup,
Action<AppDomain>? appDomainCreated,
bool isOutOfProc,
out AppDomain? taskAppDomain)
{
bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute;
s_resolverLoadedType = null;
taskAppDomain = null;
ITask? taskInstanceInOtherAppDomain = null;
try
{
if (separateAppDomain)
{
if (!loadedType.IsMarshalByRef)
{
logError(taskLocation, taskLine, taskColumn, string.Format(SR.TaskNotMarshalByRef, taskName));
return null;
}
else
{
// Our task depend on this name to be precisely that, so if you change it make sure
// you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it.
// Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain
// has done the binding redirection in code, that we will get those settings as well.
AppDomainSetup appDomainInfo = new AppDomainSetup();
// Get the current app domain setup settings
byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes();
// Apply the appdomain settings to the new appdomain before creating it
appDomainInfo.SetConfigurationBytes(currentAppdomainBytes);
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver;
s_resolverLoadedType = loadedType;
taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo);
if (loadedType.LoadedAssembly != null)
{
taskAppDomain.Load(loadedType.LoadedAssemblyName);
}
// Hook up last minute dumping of any exceptions
taskAppDomain.UnhandledException += ExceptionHandling.UnhandledExceptionHandler;
appDomainCreated?.Invoke(taskAppDomain);
}
}
else
{
// perf improvement for the same appdomain case - we already have the type object
// and don't want to go through reflection to recreate it from the name.
return (ITask?)Activator.CreateInstance(loadedType.Type);
}
if (loadedType.AssemblyFilePath != null)
{
taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.AssemblyFilePath, loadedType.Type.FullName);
// this will force evaluation of the task class type and try to load the task assembly
Type taskType = taskInstanceInOtherAppDomain.GetType();
// If the types don't match, we have a problem. It means that our AppDomain was able to load
// a task assembly using Load, and loaded a different one. I don't see any other choice than
// to fail here.
if (taskType != loadedType.Type)
{
logError(taskLocation, taskLine, taskColumn, string.Format(SR.ConflictingTaskAssembly, loadedType.AssemblyFilePath, loadedType.Type.Assembly.Location));
taskInstanceInOtherAppDomain = null;
}
}
else
{
taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName);
}
return taskInstanceInOtherAppDomain;
}
finally
{
// Don't leave appdomains open
if (taskAppDomain != null && taskInstanceInOtherAppDomain == null)
{
AppDomain.Unload(taskAppDomain);
RemoveAssemblyResolver();
}
}
}
/// <summary>
/// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help
/// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it
/// </summary>
private static Assembly? AssemblyResolver(object sender, ResolveEventArgs args)
{
if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName.FullName, StringComparison.OrdinalIgnoreCase))
{
if (s_resolverLoadedType == null || s_resolverLoadedType.Path == null)
{
return null;
}
return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path);
}
return null;
}
/// <summary>
/// Check if we added a resolver and remove it
/// </summary>
internal static void RemoveAssemblyResolver()
{
if (s_resolverLoadedType != null)
{
AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolver;
s_resolverLoadedType = null;
}
}
}
|