net45
ContextIsolatedTask.cs
namespace Nerdbank.MSBuildExtension
{
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
partial clast ContextIsolatedTask : AppDomainIsolatedTask // We need MarshalByRefObject -- we don't care for MSBuild's AppDomain though.
{
///
/// A guard against stack overflows in the astembly resolver.
///
private readonly ThreadLocal alreadyInastemblyResolve = new ThreadLocal();
///
/// Initializes a new instance of the clast.
///
public ContextIsolatedTask()
{
}
///
public sealed override bool Execute()
{
try
{
// We have to hook our own AppDomain so that the TransparentProxy works properly.
AppDomain.CurrentDomain.astemblyResolve += this.CurrentDomain_astemblyResolve;
// On .NET Framework (on Windows), we find native binaries by adding them to our PATH.
if (this.UnmanagedDllDirectory != null)
{
string pathEnvVar = Environment.GetEnvironmentVariable("PATH");
string[] searchPaths = pathEnvVar.Split(Path.PathSeparator);
if (!searchPaths.Contains(this.UnmanagedDllDirectory, StringComparer.OrdinalIgnoreCase))
{
pathEnvVar += Path.PathSeparator + this.UnmanagedDllDirectory;
Environment.SetEnvironmentVariable("PATH", pathEnvVar);
}
}
// Run under our own AppDomain so we can apply the .config file of the MSBuild Task we're hosting.
// This gives the owner the control over binding redirects to be applied.
var appDomainSetup = new AppDomainSetup();
string pathToTaskastembly = this.GetType().astembly.Location;
appDomainSetup.ApplicationBase = Path.GetDirectoryName(pathToTaskastembly);
appDomainSetup.ConfigurationFile = pathToTaskastembly + ".config";
var appDomain = AppDomain.CreateDomain("ContextIsolatedTask: " + this.GetType().Name, AppDomain.CurrentDomain.Evidence, appDomainSetup);
string taskastemblyFullName = this.GetType().astembly.GetName().FullName;
string taskFullName = this.GetType().FullName;
var isolatedTask = (ContextIsolatedTask)appDomain.CreateInstanceAndUnwrap(taskastemblyFullName, taskFullName);
return this.ExecuteInnerTask(isolatedTask, this.GetType());
}
catch (OperationCanceledException)
{
this.Log.LogMessage(MessageImportance.High, "Canceled.");
return false;
}
finally
{
AppDomain.CurrentDomain.astemblyResolve -= this.CurrentDomain_astemblyResolve;
}
}
///
/// Loads the astembly at the specified path within the isolated context.
///
/// The path to the astembly to be loaded.
/// The loaded astembly.
protected astembly LoadastemblyByPath(string astemblyPath)
{
return astembly.LoadFile(astemblyPath);
}
private bool ExecuteInnerTask(ContextIsolatedTask innerTask, Type innerTaskType)
{
if (innerTask == null)
{
throw new ArgumentNullException(nameof(innerTask));
}
try
{
Type innerTaskBaseType = innerTaskType;
while (innerTaskBaseType.FullName != typeof(ContextIsolatedTask).FullName)
{
innerTaskBaseType = innerTaskBaseType.GetTypeInfo().BaseType;
if (innerTaskBaseType == null)
{
throw new ArgumentException($"Unable to find {nameof(ContextIsolatedTask)} in type hierarchy.");
}
}
var properties = this.GetType().GetRuntimeProperties()
.Where(property => property.GetMethod != null && property.SetMethod != null);
foreach (var property in properties)
{
object value = property.GetValue(this);
property.SetValue(innerTask, value);
}
// Forward any cancellation requests
using (this.CancellationToken.Register(innerTask.Cancel))
{
this.CancellationToken.ThrowIfCancellationRequested();
// Execute the inner task.
bool result = innerTask.ExecuteIsolated();
// Retrieve any output properties.
foreach (var property in properties)
{
object value = property.GetValue(innerTask);
property.SetValue(this, value);
}
return result;
}
}
catch (OperationCanceledException)
{
this.Log.LogMessage(MessageImportance.High, "Canceled.");
return false;
}
}
private astembly CurrentDomain_astemblyResolve(object sender, ResolveEventArgs args)
{
if (alreadyInastemblyResolve.Value)
{
// Guard against stack overflow exceptions.
return null;
}
alreadyInastemblyResolve.Value = true;
try
{
return astembly.Load(args.Name);
}
catch
{
return null;
}
finally
{
alreadyInastemblyResolve.Value = false;
}
}
}
}