/Main/DaveSexton.Labs/Source/DaveSexton.Labs.Build/FindLabsContext.cs
C# | 360 lines | 283 code | 58 blank | 19 comment | 48 complexity | 2e702f4ffc7aa55ee59a404d5dc69fe6 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.Contracts;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.InteropServices.WindowsRuntime;
- using DaveSexton.Labs.Build.Properties;
- using Microsoft.Build.Framework;
- using Microsoft.Build.Utilities;
-
- namespace DaveSexton.Labs.Build
- {
- internal sealed class FindLabsContext : MarshalByRefObject
- {
- #region Public Properties
- public TaskLoggingHelper Log
- {
- get
- {
- Contract.Ensures(Contract.Result<TaskLoggingHelper>() != null);
-
- return log;
- }
- }
-
- public ITaskItem[] SourceCode
- {
- get
- {
- Contract.Ensures(Contract.Result<ITaskItem[]>() != null);
-
- return sourceCode;
- }
- }
-
- public ITaskItem[] SourceXaml
- {
- get
- {
- Contract.Ensures(Contract.Result<ITaskItem[]>() != null);
-
- return sourceXaml;
- }
- }
-
- public LabSource[] Labs
- {
- get;
- set;
- }
-
- public string RootNamespace
- {
- get
- {
- Contract.Ensures(Contract.Result<string>() != null);
-
- return rootNamespace;
- }
- }
-
- public bool IsSilverlight
- {
- get
- {
- return isSilverlight;
- }
- }
- #endregion
-
- #region Private / Protected
- private const string domainDataName = "Context";
- private const string actionDataName = "Callback";
-
- private string TargetLabsAssemblyFile
- {
- get
- {
- Contract.Ensures(Contract.Result<string>() != null);
-
- return targetLabsAssemblyFile;
- }
- }
-
- private string DaveSextonLabsAssemblyFile
- {
- get
- {
- Contract.Ensures(Contract.Result<string>() != null);
-
- return daveSextonLabsAssemblyFile;
- }
- }
-
- private List<string> ProbingPaths
- {
- get
- {
- Contract.Ensures(Contract.Result<List<string>>() != null);
-
- return probingPaths;
- }
- }
-
- private TaskLoggingHelper log;
- private string targetLabsAssemblyFile;
- private string daveSextonLabsAssemblyFile;
- private ITaskItem[] sourceCode, sourceXaml;
- private List<string> probingPaths;
- private string rootNamespace;
- private bool isSilverlight;
- #endregion
-
- #region Constructors
- /// <summary>
- /// Constructs a new instance of the <see cref="FindLabsContext" /> class.
- /// </summary>
- public FindLabsContext(
- TaskLoggingHelper log,
- string targetLabsAssemblyFile,
- string daveSextonLabsAssemblyFile,
- string rootNamespace,
- bool isSilverlight,
- ITaskItem[] sourceCode,
- ITaskItem[] sourceXaml,
- ITaskItem[] probingPaths)
- {
- Contract.Requires(log != null);
- Contract.Requires(targetLabsAssemblyFile != null);
- Contract.Requires(daveSextonLabsAssemblyFile != null);
- Contract.Requires(sourceCode != null);
-
- this.log = log;
- this.targetLabsAssemblyFile = targetLabsAssemblyFile;
- this.daveSextonLabsAssemblyFile = daveSextonLabsAssemblyFile;
- this.rootNamespace = rootNamespace ?? string.Empty;
- this.isSilverlight = isSilverlight;
- this.sourceCode = sourceCode;
- this.sourceXaml = sourceXaml ?? new ITaskItem[0];
- this.probingPaths = (probingPaths ?? new TaskItem[0]).Select(item => item.GetMetadata("FullPath")).ToList();
- }
- #endregion
-
- #region Methods
- [ContractInvariantMethod]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
- private void ObjectInvariant()
- {
- Contract.Invariant(log != null);
- Contract.Invariant(targetLabsAssemblyFile != null);
- Contract.Invariant(daveSextonLabsAssemblyFile != null);
- Contract.Invariant(rootNamespace != null);
- Contract.Invariant(sourceCode != null);
- Contract.Invariant(sourceXaml != null);
- Contract.Invariant(probingPaths != null);
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
- Justification = "Error is logged as a warning because the AppDomain failing to unload is not an exceptional circumstance in this case.")]
- public void Execute(Action<FindLabsContext, Assembly, Assembly> action)
- {
- Contract.Requires(action != null);
- Contract.Ensures(Labs != null);
-
- log.LogMessage(MessageImportance.Low, DiagnosticText.CreatingAppDomainForLabs);
-
- var thisAssembly = typeof(FindLabs).Assembly;
-
- AppDomain domain = null;
- try
- {
- domain = AppDomain.CreateDomain(
- "FindLabs",
- AppDomain.CurrentDomain.Evidence,
- new AppDomainSetup()
- {
- ApplicationBase = Path.GetDirectoryName(thisAssembly.Location)
- });
-
- Contract.Assume(domain != null);
-
- domain.SetData(domainDataName, this);
- domain.SetData(actionDataName, action);
-
- domain.DoCallBack(Execute);
-
- Contract.Assume(Labs != null);
- }
- finally
- {
- try
- {
- if (domain != null)
- {
- AppDomain.Unload(domain);
- }
- }
- catch (Exception ex)
- {
- log.LogWarningFromException(ex, showStackTrace: false);
- }
- }
- }
-
- private static void Execute()
- {
- AppDomain domain = AppDomain.CurrentDomain;
-
- var context = (FindLabsContext) domain.GetData(domainDataName);
- var action = (Action<FindLabsContext, Assembly, Assembly>) domain.GetData(actionDataName);
-
- Contract.Assume(context != null);
- Contract.Assume(action != null);
-
- // Must read properties instead of backing fields because fields cannot be read over remoting channels.
- var log = context.Log;
- var labsCoreAssemblyFile = context.DaveSextonLabsAssemblyFile;
- var labsSourceAssemblyFile = context.TargetLabsAssemblyFile;
- var probingPaths = context.ProbingPaths;
-
- log.LogMessage(MessageImportance.Low, DiagnosticText.LoadingAssemblyReferencesInAppDomain);
-
- var labsCoreAssembly = Assembly.ReflectionOnlyLoadFrom(labsCoreAssemblyFile);
-
- Contract.Assume(labsCoreAssembly != null);
-
- InitializeAssemblyResolution(log, domain, labsCoreAssembly, GetProbingPaths(domain, probingPaths));
-
- var labsSourceAssembly = Assembly.ReflectionOnlyLoadFrom(labsSourceAssemblyFile);
-
- Contract.Assume(labsSourceAssembly != null);
-
- try
- {
- action(context, labsCoreAssembly, labsSourceAssembly);
- }
- catch (FileLoadException ex)
- {
- /* See the catch block in the InitializeAssemblyResolution method for more information.
- * Note that ex.FileName and ex.FusionLog were null during testing.
- */
- log.LogError(ex.Message);
- log.LogMessage(MessageImportance.Low, ex.ToString());
-
- if (context.Labs == null)
- {
- context.Labs = new LabSource[0];
- }
- }
-
- Contract.Assume(context.Labs != null);
- }
-
- private static void InitializeAssemblyResolution(TaskLoggingHelper log, AppDomain domain, Assembly labsCoreAssembly, IEnumerable<string> probingPaths)
- {
- Contract.Requires(log != null);
- Contract.Requires(domain != null);
- Contract.Requires(labsCoreAssembly != null);
- Contract.Requires(probingPaths != null);
-
- ResolveEventHandler handler = (sender, e) => ResolveAssembly(e.Name, log, labsCoreAssembly, probingPaths);
-
- domain.AssemblyResolve += handler;
- domain.ReflectionOnlyAssemblyResolve += handler;
-
- WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve += (sender, e) =>
- {
- var resolved = ResolveAssembly(
- e.NamespaceName,
- log,
- labsCoreAssembly,
- probingPaths,
- WindowsRuntimeMetadata.ResolveNamespace(e.NamespaceName, null));
-
- if (resolved != null)
- {
- e.ResolvedAssemblies.Add(resolved);
- }
- };
- }
-
- private static Assembly ResolveAssembly(string assemblyName, TaskLoggingHelper log, Assembly labsCoreAssembly, IEnumerable<string> probingPaths, IEnumerable<string> winStoreResolved = null)
- {
- if (string.Equals(assemblyName, labsCoreAssembly.FullName, StringComparison.Ordinal))
- {
- return labsCoreAssembly;
- }
- else
- {
- log.LogMessage(DiagnosticText.ResolvingAssemblyReferenceFormat, assemblyName);
-
- if (winStoreResolved != null)
- {
- var file = winStoreResolved.Select(Assembly.ReflectionOnlyLoadFrom).FirstOrDefault();
-
- if (file != null)
- {
- return file;
- }
- }
-
- var name = new AssemblyName(assemblyName);
-
- try
- {
- // Although this may seem redundant, it's actually required for the reflection-only load context because it doesn't
- // probe for assemblies automatically. The loader is required to handle the ReflectionOnlyAssemblyResolve event to
- // manually load assemblies into the reflection-only load context, except for those that have been preloaded. During
- // testing, even System.dll was not preloaded, which is why this code defaults to using Assembly.ReflectionOnlyLoad
- // along with the assembly display name that was passed to the event handler, if the exact file cannot be found in
- // the manual probing paths.
- return (from directory in probingPaths
- from file in Directory.EnumerateFiles(directory, name.Name + ".dll", SearchOption.TopDirectoryOnly)
- .Concat(Directory.EnumerateFiles(directory, name.Name + ".exe", SearchOption.TopDirectoryOnly))
- select Assembly.ReflectionOnlyLoadFrom(file))
- .FirstOrDefault()
- ?? Assembly.ReflectionOnlyLoad(assemblyName);
- }
- catch (FileNotFoundException)
- {
- /* This error occurred in testing when the Assembly.ReflectionOnlyLoad fallback failed due to a required dependency
- * directory having been accidentally omitted. When an exception is thrown in this assembly-resolution event handler
- * it's swallowed by .NET, so MSBuild never sees it unless we explicitly log it; however, by returning null the
- * exception will be rethrown to the caller that caused the assembly resolution in the first place. Therefore, the
- * exception is being caught again and logged elsewhere.
- */
- return null;
- }
- }
- }
-
- private static IList<string> GetProbingPaths(AppDomain domain, IList<string> probingPaths)
- {
- Contract.Requires(domain != null);
- Contract.Requires(probingPaths != null);
- Contract.Ensures(Contract.Result<IList<string>>() != null);
-
- var fullList = new List<string>();
-
- foreach (var directory in probingPaths)
- {
- if (!string.IsNullOrWhiteSpace(directory) && Directory.Exists(directory))
- {
- fullList.Add(directory);
- }
- }
-
- var fullBase = Path.GetFullPath(domain.BaseDirectory);
-
- if (!fullList.Contains(fullBase, StringComparer.OrdinalIgnoreCase))
- {
- fullList.Add(fullBase);
- }
-
- return fullList.AsReadOnly();
- }
- #endregion
- }
- }