Skip to content

Commit

Permalink
Proactively check for loaded MSBuild (#14)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
rainersigwald authored Feb 20, 2018
1 parent e81f366 commit e60d679
Showing 1 changed file with 26 additions and 1 deletion.
27 changes: 26 additions & 1 deletion src/MSBuildLocator/MSBuildLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,25 @@ 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);
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;
Expand All @@ -79,6 +94,16 @@ 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);
}

private static IEnumerable<VisualStudioInstance> GetInstances()
{
var devConsole = GetDevConsoleInstance();
Expand Down

0 comments on commit e60d679

Please sign in to comment.