Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve MSBuild discovery for future scenarios #1328

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;

namespace OmniSharp.MSBuild.Discovery
Expand All @@ -17,6 +18,49 @@ protected MSBuildInstanceProvider(ILoggerFactory loggerFactory)

public abstract ImmutableArray<MSBuildInstance> GetInstances();

/// <summary>
/// Handles locating the MSBuild tools path given a base path (typically a Visual Studio install path).
/// </summary>
protected string FindMSBuildToolsPath(string basePath)
{
if (TryGetToolsPath("Current", "Bin", out var result) ||
TryGetToolsPath("Current", "bin", out result) ||
TryGetToolsPath("15.0", "Bin", out result) ||
TryGetToolsPath("15.0", "bin", out result))
{
return result;
}

Logger.LogDebug($"Could not locate MSBuild tools path within {basePath}");
return null;

bool TryGetToolsPath(string versionPath, string binPath, out string toolsPath)
{
toolsPath = null;

var baseDir = new DirectoryInfo(basePath);
if (!baseDir.Exists)
{
return false;
}

var versionDir = baseDir.EnumerateDirectories().FirstOrDefault(di => di.Name == versionPath);
if (versionDir == null)
{
return false;
}

var binDir = versionDir.EnumerateDirectories().FirstOrDefault(di => di.Name == binPath);
if (binDir == null)
{
return false;
}

toolsPath = binDir.FullName;
return true;
}
}

protected static string FindLocalMSBuildDirectory()
{
// If OmniSharp is running normally, MSBuild is located in an 'msbuild' folder beneath OmniSharp.exe.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Immutable;
using System.IO;
using Microsoft.Extensions.Logging;
using OmniSharp.Utilities;

Expand All @@ -27,8 +26,8 @@ public override ImmutableArray<MSBuildInstance> GetInstances()
return NoInstances;
}

var toolsPath = Path.Combine(path, "MSBuild", "15.0", "Bin");
if (!Directory.Exists(toolsPath))
var toolsPath = FindMSBuildToolsPath(path);
if (toolsPath == null)
{
return NoInstances;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ public override ImmutableArray<MSBuildInstance> GetInstances()
return NoInstances;
}

var toolsPath = Path.Combine(path, "15.0", "bin");
if (!Directory.Exists(toolsPath))
var toolsPath = FindMSBuildToolsPath(path);
if (toolsPath == null)
{
Logger.LogDebug($"Mono MSBuild could not be used because '{toolsPath}' does not exist.");
Logger.LogDebug($"Mono MSBuild could not be used because an MSBuild tools path could not be found.");
return NoInstances;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -48,25 +47,36 @@ public override ImmutableArray<MSBuildInstance> GetInstances()
var instance = (ISetupInstance2)instances[0];
var state = instance.GetState();

if (!Version.TryParse(instance.GetInstallationVersion(), out var version))
var installVersion = instance.GetInstallationVersion();
var installPath = instance.GetInstallationPath();

if (!Version.TryParse(installVersion, out var version))
{
Logger.LogDebug($"Found Visual Studio installation with strange version number: {installVersion} ({installPath})");
continue;
}

if (state != InstanceState.Complete)
{
Logger.LogDebug($"Found incomplete Visual Studio installation ({installPath})");
continue;
}

if (!instance.GetPackages().Any(package => package.GetId() == "Microsoft.VisualStudio.Component.Roslyn.Compiler"))
{
Logger.LogDebug($"Found Visual Studio installation with no C# package installed ({installPath})");
continue;
}

if (state == InstanceState.Complete &&
instance.GetPackages().Any(package => package.GetId() == "Microsoft.VisualStudio.Component.Roslyn.Compiler"))
var toolsPath = FindMSBuildToolsPath(installPath);
if (toolsPath != null)
{
// Note: The code below will likely fail if MSBuild's version increments.
var toolsPath = Path.Combine(instance.GetInstallationPath(), "MSBuild", "15.0", "Bin");
if (Directory.Exists(toolsPath))
{
builder.Add(
new MSBuildInstance(
instance.GetDisplayName(),
toolsPath,
version,
DiscoveryType.VisualStudioSetup));
}
builder.Add(
new MSBuildInstance(
instance.GetDisplayName(),
toolsPath,
version,
DiscoveryType.VisualStudioSetup));
}
}
while (fetched > 0);
Expand Down
6 changes: 6 additions & 0 deletions src/OmniSharp.MSBuild/Options/MSBuildOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ internal class MSBuildOptions
public string CscToolPath { get; set; }
public string CscToolExe { get; set; }

/// <summary>
/// When set to true, the MSBuild project system will generate binary logs for each project that
/// it loads.
/// </summary>
public bool GenerateBinaryLogs { get; set; }

// TODO: Allow loose properties
// public IConfiguration Properties { get; set; }
}
Expand Down
55 changes: 37 additions & 18 deletions src/OmniSharp.MSBuild/ProjectLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,27 @@ private static Dictionary<string, string> CreateGlobalProperties(

var projectInstance = evaluatedProject.CreateProjectInstance();
var msbuildLogger = new MSBuildLogger(_logger);

var loggers = new List<MSB.Framework.ILogger>()
{
msbuildLogger
};

if (_options.GenerateBinaryLogs)
{
var binlogPath = Path.ChangeExtension(projectInstance.FullPath, ".binlog");
var binaryLogger = new MSB.Logging.BinaryLogger()
Copy link
Member

Choose a reason for hiding this comment

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

this is a great addition, will definitely be useful in troubleshooting

{
CollectProjectImports = MSB.Logging.BinaryLogger.ProjectImportsCollectionMode.Embed,
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 is actually the default behaviour so maybe we can skip explicitly specifying it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I guess it is the default: https://github.com/Microsoft/msbuild/blob/master/src/Build/Logging/BinaryLogger/BinaryLogger.cs#L68

That's OK, let's keep the code specific so there's no question. I think our use case is definitely to Embed.

Parameters = binlogPath
};

loggers.Add(binaryLogger);
}

var buildResult = projectInstance.Build(
targets: new string[] { TargetNames.Compile, TargetNames.CoreCompile },
loggers: new[] { msbuildLogger });
loggers);

var diagnostics = msbuildLogger.GetDiagnostics();

Expand Down Expand Up @@ -135,13 +153,19 @@ private static void SetTargetFrameworkIfNeeded(MSB.Evaluation.Project evaluatedP
}
}

private static string GetLegalToolsetVersion(string toolsVersion, ICollection<MSB.Evaluation.Toolset> toolsets)
private string GetLegalToolsetVersion(string toolsVersion, ICollection<MSB.Evaluation.Toolset> toolsets)
{
// It's entirely possible the the toolset specified does not exist. In that case, we'll try to use
// the highest version available.
var version = new Version(toolsVersion);
// Does the expected tools version exist? If so, use it.
foreach (var toolset in toolsets)
{
if (toolset.ToolsVersion == toolsVersion)
{
return toolsVersion;
}
}

// If not, try to find the highest version available and use that instead.

bool exists = false;
Version highestVersion = null;

var legalToolsets = new SortedList<Version, MSB.Evaluation.Toolset>(toolsets.Count);
Expand All @@ -159,25 +183,20 @@ private static string GetLegalToolsetVersion(string toolsVersion, ICollection<MS
{
highestVersion = toolsetVersion;
}

if (toolsetVersion == version)
{
exists = true;
}
}
}

if (highestVersion == null)
if (legalToolsets.Count == 0 || highestVersion == null)
{
throw new InvalidOperationException("No legal MSBuild toolsets available.");
_logger.LogError($"No legal MSBuild tools available, defaulting to {toolsVersion}.");
return toolsVersion;
}

if (!exists)
{
toolsVersion = legalToolsets[highestVersion].ToolsPath;
}
var result = legalToolsets[highestVersion].ToolsVersion;

_logger.LogInformation($"Using MSBuild tools version: {result}");

return toolsVersion;
return result;
}
}
}