PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.Contracts;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Runtime.InteropServices.WindowsRuntime;
  8. using DaveSexton.Labs.Build.Properties;
  9. using Microsoft.Build.Framework;
  10. using Microsoft.Build.Utilities;
  11. namespace DaveSexton.Labs.Build
  12. {
  13. internal sealed class FindLabsContext : MarshalByRefObject
  14. {
  15. #region Public Properties
  16. public TaskLoggingHelper Log
  17. {
  18. get
  19. {
  20. Contract.Ensures(Contract.Result<TaskLoggingHelper>() != null);
  21. return log;
  22. }
  23. }
  24. public ITaskItem[] SourceCode
  25. {
  26. get
  27. {
  28. Contract.Ensures(Contract.Result<ITaskItem[]>() != null);
  29. return sourceCode;
  30. }
  31. }
  32. public ITaskItem[] SourceXaml
  33. {
  34. get
  35. {
  36. Contract.Ensures(Contract.Result<ITaskItem[]>() != null);
  37. return sourceXaml;
  38. }
  39. }
  40. public LabSource[] Labs
  41. {
  42. get;
  43. set;
  44. }
  45. public string RootNamespace
  46. {
  47. get
  48. {
  49. Contract.Ensures(Contract.Result<string>() != null);
  50. return rootNamespace;
  51. }
  52. }
  53. public bool IsSilverlight
  54. {
  55. get
  56. {
  57. return isSilverlight;
  58. }
  59. }
  60. #endregion
  61. #region Private / Protected
  62. private const string domainDataName = "Context";
  63. private const string actionDataName = "Callback";
  64. private string TargetLabsAssemblyFile
  65. {
  66. get
  67. {
  68. Contract.Ensures(Contract.Result<string>() != null);
  69. return targetLabsAssemblyFile;
  70. }
  71. }
  72. private string DaveSextonLabsAssemblyFile
  73. {
  74. get
  75. {
  76. Contract.Ensures(Contract.Result<string>() != null);
  77. return daveSextonLabsAssemblyFile;
  78. }
  79. }
  80. private List<string> ProbingPaths
  81. {
  82. get
  83. {
  84. Contract.Ensures(Contract.Result<List<string>>() != null);
  85. return probingPaths;
  86. }
  87. }
  88. private TaskLoggingHelper log;
  89. private string targetLabsAssemblyFile;
  90. private string daveSextonLabsAssemblyFile;
  91. private ITaskItem[] sourceCode, sourceXaml;
  92. private List<string> probingPaths;
  93. private string rootNamespace;
  94. private bool isSilverlight;
  95. #endregion
  96. #region Constructors
  97. /// <summary>
  98. /// Constructs a new instance of the <see cref="FindLabsContext" /> class.
  99. /// </summary>
  100. public FindLabsContext(
  101. TaskLoggingHelper log,
  102. string targetLabsAssemblyFile,
  103. string daveSextonLabsAssemblyFile,
  104. string rootNamespace,
  105. bool isSilverlight,
  106. ITaskItem[] sourceCode,
  107. ITaskItem[] sourceXaml,
  108. ITaskItem[] probingPaths)
  109. {
  110. Contract.Requires(log != null);
  111. Contract.Requires(targetLabsAssemblyFile != null);
  112. Contract.Requires(daveSextonLabsAssemblyFile != null);
  113. Contract.Requires(sourceCode != null);
  114. this.log = log;
  115. this.targetLabsAssemblyFile = targetLabsAssemblyFile;
  116. this.daveSextonLabsAssemblyFile = daveSextonLabsAssemblyFile;
  117. this.rootNamespace = rootNamespace ?? string.Empty;
  118. this.isSilverlight = isSilverlight;
  119. this.sourceCode = sourceCode;
  120. this.sourceXaml = sourceXaml ?? new ITaskItem[0];
  121. this.probingPaths = (probingPaths ?? new TaskItem[0]).Select(item => item.GetMetadata("FullPath")).ToList();
  122. }
  123. #endregion
  124. #region Methods
  125. [ContractInvariantMethod]
  126. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
  127. private void ObjectInvariant()
  128. {
  129. Contract.Invariant(log != null);
  130. Contract.Invariant(targetLabsAssemblyFile != null);
  131. Contract.Invariant(daveSextonLabsAssemblyFile != null);
  132. Contract.Invariant(rootNamespace != null);
  133. Contract.Invariant(sourceCode != null);
  134. Contract.Invariant(sourceXaml != null);
  135. Contract.Invariant(probingPaths != null);
  136. }
  137. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
  138. Justification = "Error is logged as a warning because the AppDomain failing to unload is not an exceptional circumstance in this case.")]
  139. public void Execute(Action<FindLabsContext, Assembly, Assembly> action)
  140. {
  141. Contract.Requires(action != null);
  142. Contract.Ensures(Labs != null);
  143. log.LogMessage(MessageImportance.Low, DiagnosticText.CreatingAppDomainForLabs);
  144. var thisAssembly = typeof(FindLabs).Assembly;
  145. AppDomain domain = null;
  146. try
  147. {
  148. domain = AppDomain.CreateDomain(
  149. "FindLabs",
  150. AppDomain.CurrentDomain.Evidence,
  151. new AppDomainSetup()
  152. {
  153. ApplicationBase = Path.GetDirectoryName(thisAssembly.Location)
  154. });
  155. Contract.Assume(domain != null);
  156. domain.SetData(domainDataName, this);
  157. domain.SetData(actionDataName, action);
  158. domain.DoCallBack(Execute);
  159. Contract.Assume(Labs != null);
  160. }
  161. finally
  162. {
  163. try
  164. {
  165. if (domain != null)
  166. {
  167. AppDomain.Unload(domain);
  168. }
  169. }
  170. catch (Exception ex)
  171. {
  172. log.LogWarningFromException(ex, showStackTrace: false);
  173. }
  174. }
  175. }
  176. private static void Execute()
  177. {
  178. AppDomain domain = AppDomain.CurrentDomain;
  179. var context = (FindLabsContext) domain.GetData(domainDataName);
  180. var action = (Action<FindLabsContext, Assembly, Assembly>) domain.GetData(actionDataName);
  181. Contract.Assume(context != null);
  182. Contract.Assume(action != null);
  183. // Must read properties instead of backing fields because fields cannot be read over remoting channels.
  184. var log = context.Log;
  185. var labsCoreAssemblyFile = context.DaveSextonLabsAssemblyFile;
  186. var labsSourceAssemblyFile = context.TargetLabsAssemblyFile;
  187. var probingPaths = context.ProbingPaths;
  188. log.LogMessage(MessageImportance.Low, DiagnosticText.LoadingAssemblyReferencesInAppDomain);
  189. var labsCoreAssembly = Assembly.ReflectionOnlyLoadFrom(labsCoreAssemblyFile);
  190. Contract.Assume(labsCoreAssembly != null);
  191. InitializeAssemblyResolution(log, domain, labsCoreAssembly, GetProbingPaths(domain, probingPaths));
  192. var labsSourceAssembly = Assembly.ReflectionOnlyLoadFrom(labsSourceAssemblyFile);
  193. Contract.Assume(labsSourceAssembly != null);
  194. try
  195. {
  196. action(context, labsCoreAssembly, labsSourceAssembly);
  197. }
  198. catch (FileLoadException ex)
  199. {
  200. /* See the catch block in the InitializeAssemblyResolution method for more information.
  201. * Note that ex.FileName and ex.FusionLog were null during testing.
  202. */
  203. log.LogError(ex.Message);
  204. log.LogMessage(MessageImportance.Low, ex.ToString());
  205. if (context.Labs == null)
  206. {
  207. context.Labs = new LabSource[0];
  208. }
  209. }
  210. Contract.Assume(context.Labs != null);
  211. }
  212. private static void InitializeAssemblyResolution(TaskLoggingHelper log, AppDomain domain, Assembly labsCoreAssembly, IEnumerable<string> probingPaths)
  213. {
  214. Contract.Requires(log != null);
  215. Contract.Requires(domain != null);
  216. Contract.Requires(labsCoreAssembly != null);
  217. Contract.Requires(probingPaths != null);
  218. ResolveEventHandler handler = (sender, e) => ResolveAssembly(e.Name, log, labsCoreAssembly, probingPaths);
  219. domain.AssemblyResolve += handler;
  220. domain.ReflectionOnlyAssemblyResolve += handler;
  221. WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve += (sender, e) =>
  222. {
  223. var resolved = ResolveAssembly(
  224. e.NamespaceName,
  225. log,
  226. labsCoreAssembly,
  227. probingPaths,
  228. WindowsRuntimeMetadata.ResolveNamespace(e.NamespaceName, null));
  229. if (resolved != null)
  230. {
  231. e.ResolvedAssemblies.Add(resolved);
  232. }
  233. };
  234. }
  235. private static Assembly ResolveAssembly(string assemblyName, TaskLoggingHelper log, Assembly labsCoreAssembly, IEnumerable<string> probingPaths, IEnumerable<string> winStoreResolved = null)
  236. {
  237. if (string.Equals(assemblyName, labsCoreAssembly.FullName, StringComparison.Ordinal))
  238. {
  239. return labsCoreAssembly;
  240. }
  241. else
  242. {
  243. log.LogMessage(DiagnosticText.ResolvingAssemblyReferenceFormat, assemblyName);
  244. if (winStoreResolved != null)
  245. {
  246. var file = winStoreResolved.Select(Assembly.ReflectionOnlyLoadFrom).FirstOrDefault();
  247. if (file != null)
  248. {
  249. return file;
  250. }
  251. }
  252. var name = new AssemblyName(assemblyName);
  253. try
  254. {
  255. // Although this may seem redundant, it's actually required for the reflection-only load context because it doesn't
  256. // probe for assemblies automatically. The loader is required to handle the ReflectionOnlyAssemblyResolve event to
  257. // manually load assemblies into the reflection-only load context, except for those that have been preloaded. During
  258. // testing, even System.dll was not preloaded, which is why this code defaults to using Assembly.ReflectionOnlyLoad
  259. // along with the assembly display name that was passed to the event handler, if the exact file cannot be found in
  260. // the manual probing paths.
  261. return (from directory in probingPaths
  262. from file in Directory.EnumerateFiles(directory, name.Name + ".dll", SearchOption.TopDirectoryOnly)
  263. .Concat(Directory.EnumerateFiles(directory, name.Name + ".exe", SearchOption.TopDirectoryOnly))
  264. select Assembly.ReflectionOnlyLoadFrom(file))
  265. .FirstOrDefault()
  266. ?? Assembly.ReflectionOnlyLoad(assemblyName);
  267. }
  268. catch (FileNotFoundException)
  269. {
  270. /* This error occurred in testing when the Assembly.ReflectionOnlyLoad fallback failed due to a required dependency
  271. * directory having been accidentally omitted. When an exception is thrown in this assembly-resolution event handler
  272. * it's swallowed by .NET, so MSBuild never sees it unless we explicitly log it; however, by returning null the
  273. * exception will be rethrown to the caller that caused the assembly resolution in the first place. Therefore, the
  274. * exception is being caught again and logged elsewhere.
  275. */
  276. return null;
  277. }
  278. }
  279. }
  280. private static IList<string> GetProbingPaths(AppDomain domain, IList<string> probingPaths)
  281. {
  282. Contract.Requires(domain != null);
  283. Contract.Requires(probingPaths != null);
  284. Contract.Ensures(Contract.Result<IList<string>>() != null);
  285. var fullList = new List<string>();
  286. foreach (var directory in probingPaths)
  287. {
  288. if (!string.IsNullOrWhiteSpace(directory) && Directory.Exists(directory))
  289. {
  290. fullList.Add(directory);
  291. }
  292. }
  293. var fullBase = Path.GetFullPath(domain.BaseDirectory);
  294. if (!fullList.Contains(fullBase, StringComparer.OrdinalIgnoreCase))
  295. {
  296. fullList.Add(fullBase);
  297. }
  298. return fullList.AsReadOnly();
  299. }
  300. #endregion
  301. }
  302. }