in src/MSBuildLocator/MSBuildLocator.cs [165:280]
public static void RegisterMSBuildPath(string[] msbuildSearchPaths)
{
if (msbuildSearchPaths.Length < 1)
{
throw new ArgumentException("Must provide at least one search path to RegisterMSBuildPath.");
}
List<ArgumentException> nullOrWhiteSpaceExceptions = new List<ArgumentException>();
for (int i = 0; i < msbuildSearchPaths.Length; i++)
{
if (string.IsNullOrWhiteSpace(msbuildSearchPaths[i]))
{
nullOrWhiteSpaceExceptions.Add(new ArgumentException($"Value at position {i+1} may not be null or whitespace", nameof(msbuildSearchPaths)));
}
}
if (nullOrWhiteSpaceExceptions.Count > 0)
{
throw new AggregateException("Search paths for MSBuild assemblies cannot be null and must contain non-whitespace characters.", nullOrWhiteSpaceExceptions);
}
IEnumerable<string> paths = msbuildSearchPaths.Where(path => !Directory.Exists(path));
if (paths.Any())
{
throw new AggregateException($"A directory or directories in \"{nameof(msbuildSearchPaths)}\" do not exist", paths.Select(path => new ArgumentException($"Directory \"{path}\" does not exist", nameof(msbuildSearchPaths))));
}
if (!CanRegister)
{
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 +
"This dependency arises from when a method is just-in-time compiled, so if it breaks even if the reference to a Microsoft.Build type has not been executed." +
Environment.NewLine +
"For more details, see aka.ms/RegisterMSBuildLocator" +
Environment.NewLine +
"Loaded MSBuild assemblies: " +
loadedAssemblyList;
throw new InvalidOperationException(error);
}
// AssemblyResolve event can fire multiple times for the same assembly, so keep track of what's already been loaded.
var loadedAssemblies = new Dictionary<string, Assembly>();
#if NET46
// MSBuild can be loaded from the x86 or x64 folder. Before 17.0, it looked next to the executing assembly in some cases and constructed a path that assumed x86 in others.
// This overrides the latter assumption to let it find the right MSBuild.
foreach (string path in msbuildSearchPaths)
{
string msbuildExe = Path.Combine(path, "MSBuild.exe");
if (File.Exists(msbuildExe))
{
FileVersionInfo ver = FileVersionInfo.GetVersionInfo(msbuildExe);
if (ver.FileMajorPart < 17 || (ver.FileMajorPart == 17 && ver.FileMinorPart < 1))
{
if (Path.GetDirectoryName(msbuildExe).EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase))
{
msbuildExe = Path.Combine(path.Substring(0, path.Length - 6), "MSBuild.exe");
}
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msbuildExe);
}
break;
}
}
#endif
// Saving the handler in a static field so it can be unregistered later.
#if NET46
s_registeredHandler = (_, eventArgs) =>
{
var assemblyName = new AssemblyName(eventArgs.Name);
return TryLoadAssembly(new AssemblyName(eventArgs.Name));
};
AppDomain.CurrentDomain.AssemblyResolve += s_registeredHandler;
#else
s_registeredHandler = (_, assemblyName) =>
{
return TryLoadAssembly(assemblyName);
};
AssemblyLoadContext.Default.Resolving += s_registeredHandler;
#endif
return;
Assembly TryLoadAssembly(AssemblyName assemblyName)
{
// Assembly resolution is not thread-safe.
lock (loadedAssemblies)
{
if (loadedAssemblies.TryGetValue(assemblyName.FullName, out Assembly assembly))
{
return assembly;
}
// Look in the MSBuild folder for any unresolved reference. It may be a dependency
// of MSBuild or a task.
foreach (string msbuildPath in msbuildSearchPaths)
{
string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll");
if (File.Exists(targetAssembly))
{
assembly = Assembly.LoadFrom(targetAssembly);
loadedAssemblies.Add(assemblyName.FullName, assembly);
return assembly;
}
}
return null;
}
}
}