From a75a8b8e157c2ddd6b4b137db730e32ccaeeb2da Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 16 Feb 2018 11:15:38 -0600 Subject: [PATCH 1/2] Extract IsMSBuildAssembly --- src/MSBuildLocator/MSBuildLocator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 1e955960..1aa7d07a 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -69,7 +69,7 @@ public static void RegisterInstance(VisualStudioInstance instance) AppDomain.CurrentDomain.AssemblyResolve += (_, eventArgs) => { var assemblyName = new AssemblyName(eventArgs.Name); - if (s_msBuildAssemblies.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase)) + if (IsMSBuildAssembly(assemblyName)) { var targetAssembly = Path.Combine(instance.MSBuildPath, assemblyName.Name + ".dll"); return File.Exists(targetAssembly) ? Assembly.LoadFrom(targetAssembly) : null; @@ -79,6 +79,11 @@ public static void RegisterInstance(VisualStudioInstance instance) }; } + private static bool IsMSBuildAssembly(AssemblyName assemblyName) + { + return s_msBuildAssemblies.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase); + } + private static IEnumerable GetInstances() { var devConsole = GetDevConsoleInstance(); From 09cee93ffdabdea2d065a692007c6c802c173459 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 16 Feb 2018 11:37:43 -0600 Subject: [PATCH 2/2] Proactively check for loaded MSBuild The .NET assembly loader ensures that any assembly referenced by a method is loaded before the method is called. This requires that MSBuildLocator be called **before any method that references MSBuild**. That's a confusing requirement, and easy to forget. But it's easy to check for, and since we expect the locator to be called roughly once per process, not too expensive to check for at `RegisterInstance` time. Produces an error like ``` Unhandled Exception: System.InvalidOperationException: Microsoft.Build.Locator.MSBuildLocator.RegisterInstance was called, but MSBuild assemblies were already loaded. Ensure that RegisterInstance is called before any method that directly references types in the Microsoft.Build namespace has been called. Loaded MSBuild assemblies: Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a at Microsoft.Build.Locator.MSBuildLocator.RegisterInstance(VisualStudioInstance instance) at Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults() at MultiProcBuilderApplication.Program.Main(String[] args) in s:\work\MultiProcBuilderApplication\MultiProcBuilderApplication\Program.cs:line 13 ``` --- src/MSBuildLocator/MSBuildLocator.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 1aa7d07a..8c6b68e4 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -66,6 +66,21 @@ public static void RegisterInstance(VisualStudioInstance instance) if (instance == null) throw new ArgumentNullException(nameof(instance)); + var loadedMSBuildAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(IsMSBuildAssembly); + if (loadedMSBuildAssemblies.Any()) + { + var loadedAssemblyList = string.Join(Environment.NewLine, loadedMSBuildAssemblies.Select(a => a.GetName())); + + var error = $"{typeof(MSBuildLocator)}.{nameof(RegisterInstance)} was called, but MSBuild assemblies were already loaded." + + Environment.NewLine + + $"Ensure that {nameof(RegisterInstance)} is called before any method that directly references types in the Microsoft.Build namespace has been called." + + Environment.NewLine + + "Loaded MSBuild assemblies: " + + loadedAssemblyList; + + throw new InvalidOperationException(error); + } + AppDomain.CurrentDomain.AssemblyResolve += (_, eventArgs) => { var assemblyName = new AssemblyName(eventArgs.Name); @@ -79,6 +94,11 @@ public static void RegisterInstance(VisualStudioInstance instance) }; } + private static bool IsMSBuildAssembly(Assembly assembly) + { + return IsMSBuildAssembly(assembly.GetName()); + } + private static bool IsMSBuildAssembly(AssemblyName assemblyName) { return s_msBuildAssemblies.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase);