Skip to content
57 changes: 48 additions & 9 deletions src/MSBuildLocator/MSBuildLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,50 @@ public static void RegisterInstance(VisualStudioInstance instance)
/// </param>
public static void RegisterMSBuildPath(string msbuildPath)
{
if (string.IsNullOrWhiteSpace(msbuildPath))
RegisterMSBuildPath(new string[] {
msbuildPath
#if NET46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs an if-not-mono, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but it's worth noting that without it, it would just do an extra check without actually failing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also occurred to me that we don't support mono quite yet, but that's changing soon.

// Finds and loads NuGet assemblies if msbuildPath is in a VS installation
, Path.GetFullPath(Path.Combine(msbuildPath, "..", "..", "..", "Common7", "IDE", "CommonExtensions", "Microsoft", "NuGet"))
#endif
});
}

/// <summary>
/// Add assembly resolution for Microsoft.Build core dlls in the current AppDomain from the specified
/// path.
/// </summary>
/// <param name="msbuildSearchPaths">
/// Paths to directories containing a deployment of MSBuild binaries.
/// A minimal MSBuild deployment would be the publish result of the Microsoft.Build.Runtime package.
///
/// In order to restore and build real projects, one needs a deployment that contains the rest of the toolchain (nuget, compilers, etc.).
/// Such deployments can be found in installations such as Visual Studio or dotnet CLI.
/// </param>
public static void RegisterMSBuildPath(string[] msbuildSearchPaths)
{
if (msbuildSearchPaths.Length < 1)
{
throw new ArgumentException("Value may not be null or whitespace", nameof(msbuildPath));
throw new ArgumentException("Must provide at least one search path to RegisterMSBuildPath.");
}

if (!Directory.Exists(msbuildPath))
List<ArgumentException> nullOrWhiteSpaceExceptions = new List<ArgumentException>();
for (int i = 0; i < msbuildSearchPaths.Length; i++)
{
throw new ArgumentException($"Directory \"{msbuildPath}\" does not exist", nameof(msbuildPath));
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.FirstOrDefault() == null)
{
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)
Expand Down Expand Up @@ -202,12 +238,15 @@ Assembly TryLoadAssembly(AssemblyName assemblyName)

// Look in the MSBuild folder for any unresolved reference. It may be a dependency
// of MSBuild or a task.
string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll");
if (File.Exists(targetAssembly))
foreach (string msbuildPath in msbuildSearchPaths)
{
assembly = Assembly.LoadFrom(targetAssembly);
loadedAssemblies.Add(assemblyName.FullName, assembly);
return assembly;
string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll");
if (File.Exists(targetAssembly))
{
assembly = Assembly.LoadFrom(targetAssembly);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not unique to this PR but should we improve error handling here? Like if LoadFrom fails because it exists but is a native DLL or something? Shouldn't be a major problem since the naming we're searching is very restricted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked to see what (or how bad) the error message is for an invalid dll, but it would seem unnecessarily confusing to put out two assemblies with identical names and extensions in (at least roughly) the same spot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's just leave it.

loadedAssemblies.Add(assemblyName.FullName, assembly);
return assembly;
}
}

return null;
Expand Down