|
// 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;
#nullable disable
namespace Microsoft.Build.Shared
{
/// <summary>
/// Class for loading tasks
/// </summary>
internal static class TaskLoader
{
#if FEATURE_APPDOMAIN
/// <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;
#endif
/// <summary>
/// Delegate for logging task loading errors.
/// </summary>
internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs);
/// <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)
{
return type.GetTypeInfo().IsClass && !type.GetTypeInfo().IsAbstract && (
type.GetTypeInfo().GetInterface("Microsoft.Build.Framework.ITask") != null);
}
/// <summary>
/// Creates an ITask instance and returns it.
/// </summary>
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
internal static ITask CreateTask(
LoadedType loadedType,
string taskName,
string taskLocation,
int taskLine,
int taskColumn,
LogError logError,
#if FEATURE_APPDOMAIN
AppDomainSetup appDomainSetup,
Action<AppDomain> appDomainCreated,
#endif
bool isOutOfProc
#if FEATURE_APPDOMAIN
, out AppDomain taskAppDomain
#endif
)
#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
{
#if FEATURE_APPDOMAIN
bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute;
s_resolverLoadedType = null;
taskAppDomain = null;
ITask taskInstanceInOtherAppDomain = null;
#endif
try
{
#if FEATURE_APPDOMAIN
if (separateAppDomain)
{
if (!loadedType.IsMarshalByRef)
{
logError(
taskLocation,
taskLine,
taskColumn,
"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);
if (BuildEnvironmentHelper.Instance.RunningTests)
{
// Prevent the new app domain from looking in the VS test runner location. If this
// is not done, we will not be able to find Microsoft.Build.* assemblies.
appDomainInfo.ApplicationBase = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory;
appDomainInfo.ConfigurationFile = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile;
}
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
#endif
{
// 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 FEATURE_APPDOMAIN
if (loadedType.Assembly.AssemblyFile != null)
{
taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, 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,
"ConflictingTaskAssembly",
loadedType.Assembly.AssemblyFile,
loadedType.Type.GetTypeInfo().Assembly.Location);
taskInstanceInOtherAppDomain = null;
}
}
else
{
taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.GetTypeInfo().Assembly.FullName, loadedType.Type.FullName);
}
return taskInstanceInOtherAppDomain;
#endif
}
finally
{
#if FEATURE_APPDOMAIN
// Don't leave appdomains open
if (taskAppDomain != null && taskInstanceInOtherAppDomain == null)
{
AppDomain.Unload(taskAppDomain);
RemoveAssemblyResolver();
}
#endif
}
}
#if FEATURE_APPDOMAIN
/// <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>
internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args)
{
if (args.Name.Equals(s_resolverLoadedType.LoadedAssemblyName.FullName, StringComparison.OrdinalIgnoreCase))
{
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;
}
}
#endif
}
}
|