csharp/AArnott/Nerdbank.MSBuildExtension/src/Nerdbank.MSBuildExtension/netstandard1.5/ContextIsolatedTask.cs

netstandard1.5
ContextIsolatedTask.cs
namespace Nerdbank.MSBuildExtension
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Loader;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;

    partial clast ContextIsolatedTask : Task
    {
        /// 
        /// The context the inner task is loaded within.
        /// 
        private astemblyLoadContext ctxt;

        /// 
        public sealed override bool Execute()
        {
            try
            {
                string taskastemblyPath = new Uri(this.GetType().GetTypeInfo().astembly.CodeBase).LocalPath;
                this.ctxt = new CustomastemblyLoader(this);
                astembly inContextastembly = this.ctxt.LoadFromastemblyPath(taskastemblyPath);
                Type innerTaskType = inContextastembly.GetType(this.GetType().FullName);

                object innerTask = Activator.CreateInstance(innerTaskType);
                return this.ExecuteInnerTask(innerTask);
            }
            catch (OperationCanceledException)
            {
                this.Log.LogMessage(MessageImportance.High, "Canceled.");
                return false;
            }
        }

        /// 
        /// 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)
        {
            if (this.ctxt == null)
            {
                throw new InvalidOperationException("astemblyLoadContext must be set first.");
            }

            return this.ctxt.LoadFromastemblyPath(astemblyPath);
        }

        private bool ExecuteInnerTask(object innerTask)
        {
            Type innerTaskType = innerTask.GetType();
            Type innerTaskBaseType = innerTaskType;
            while (innerTaskBaseType.FullName != typeof(ContextIsolatedTask).FullName)
            {
                innerTaskBaseType = innerTaskBaseType.GetTypeInfo().BaseType;
            }

            var outerProperties = this.GetType().GetRuntimeProperties().ToDictionary(i => i.Name);
            var innerProperties = innerTaskType.GetRuntimeProperties().ToDictionary(i => i.Name);
            var propertiesDiscovery = from outerProperty in outerProperties.Values
                                      where outerProperty.SetMethod != null && outerProperty.GetMethod != null
                                      let innerProperty = innerProperties[outerProperty.Name]
                                      select new { outerProperty, innerProperty };
            var propertiesMap = propertiesDiscovery.ToArray();
            var outputPropertiesMap = propertiesMap.Where(pair => pair.outerProperty.GetCustomAttribute() != null).ToArray();

            foreach (var propertyPair in propertiesMap)
            {
                object outerPropertyValue = propertyPair.outerProperty.GetValue(this);
                propertyPair.innerProperty.SetValue(innerTask, outerPropertyValue);
            }

            // Forward any cancellation requests
            MethodInfo innerCancelMethod = innerTaskType.GetMethod(nameof(Cancel));
            using (this.CancellationToken.Register(() => innerCancelMethod.Invoke(innerTask, new object[0])))
            {
                this.CancellationToken.ThrowIfCancellationRequested();

                // Execute the inner task.
                var executeInnerMethod = innerTaskType.GetMethod(nameof(ExecuteIsolated), BindingFlags.Instance | BindingFlags.NonPublic);
                bool result = (bool)executeInnerMethod.Invoke(innerTask, new object[0]);

                // Retrieve any output properties.
                foreach (var propertyPair in outputPropertiesMap)
                {
                    propertyPair.outerProperty.SetValue(this, propertyPair.innerProperty.GetValue(innerTask));
                }

                return result;
            }
        }

        private clast CustomastemblyLoader : astemblyLoadContext
        {
            private readonly ContextIsolatedTask loaderTask;

            internal CustomastemblyLoader(ContextIsolatedTask loaderTask)
            {
                this.loaderTask = loaderTask;
            }

            protected override astembly Load(astemblyName astemblyName)
            {
                return this.loaderTask.LoadastemblyByName(astemblyName)
                    ?? Default.LoadFromastemblyName(astemblyName);
            }

            protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
            {
                string unmanagedDllPath = Directory.EnumerateFiles(
                    this.loaderTask.UnmanagedDllDirectory,
                    $"{unmanagedDllName}.*").Concat(
                        Directory.EnumerateFiles(
                            this.loaderTask.UnmanagedDllDirectory,
                            $"lib{unmanagedDllName}.*"))
                    .FirstOrDefault();
                if (unmanagedDllPath != null)
                {
                    return this.LoadUnmanagedDllFromPath(unmanagedDllPath);
                }

                return base.LoadUnmanagedDll(unmanagedDllName);
            }
        }
    }
}