From c54eac38ab21620b29a3fcb6ea10c6281827cd6b Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 9 Nov 2017 10:44:57 -0800 Subject: [PATCH 1/6] Add proper Platform-sniffing code in OmniSharp --- .../Utilities/Platform.cs | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 src/OmniSharp.Abstractions/Utilities/Platform.cs diff --git a/src/OmniSharp.Abstractions/Utilities/Platform.cs b/src/OmniSharp.Abstractions/Utilities/Platform.cs new file mode 100644 index 0000000000..c0889888b4 --- /dev/null +++ b/src/OmniSharp.Abstractions/Utilities/Platform.cs @@ -0,0 +1,172 @@ +using System; +using System.IO; + +namespace OmniSharp.Utilities +{ + public enum OperatingSystem + { + Unknown, + Windows, + MacOS, + Linux + } + + public enum Architecture + { + Unknown, + x86, + x64 + } + + public sealed class Platform + { + public static Platform Current { get; } = GetCurrentPlatform(); + + public OperatingSystem OperatingSystem { get; } + public Architecture Architecture { get; } + public Version Version { get; } + public string LinuxDistributionName { get; } + + private Platform( + OperatingSystem os = OperatingSystem.Unknown, + Architecture architecture = Architecture.Unknown, + Version version = null, + string linuxDistributionName = null) + { + OperatingSystem = os; + Architecture = architecture; + Version = version ?? new Version(0, 0); + LinuxDistributionName = linuxDistributionName ?? string.Empty; + } + + private static Platform GetCurrentPlatform() + { + var os = OperatingSystem.Unknown; + var architecture = Architecture.Unknown; + + // Simple check to see if this is Windows + var platformId = (int)Environment.OSVersion.Platform; + if (platformId <= 3 || platformId == 5) + { + os = OperatingSystem.Windows; + + if (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") == "x86" && + Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432") == null) + { + architecture = Architecture.x86; + } + else + { + architecture = Architecture.x64; + } + } + else + { + // If this is not Windows, run 'uname' on Bash to get the OS name and architecture. + var output = RunOnBashAndCaptureOutput("uname", "-s -m"); + if (string.IsNullOrEmpty(output)) + { + return new Platform(); + } + + var values = output.Split(' '); + var osName = values[0]; + var osArch = values[1]; + + os = osName.Equals("Darwin", StringComparison.OrdinalIgnoreCase) + ? OperatingSystem.MacOS + : OperatingSystem.Linux; + + if (osArch.Equals("x86", StringComparison.OrdinalIgnoreCase)) + { + architecture = Architecture.x86; + } + else if (osArch.Equals("x86_64", StringComparison.OrdinalIgnoreCase)) + { + architecture = Architecture.x64; + } + else + { + architecture = Architecture.Unknown; + } + } + + switch (os) + { + case OperatingSystem.Windows: + return new Platform(os, architecture, Environment.OSVersion.Version); + case OperatingSystem.MacOS: + return new Platform(os, architecture, GetMacOSVersion()); + case OperatingSystem.Linux: + ReadDistroNameAndVersion(out var distroName, out var version); + return new Platform(os, architecture, version, distroName); + + default: + throw new NotSupportedException("Could not detect the current platform."); + } + } + + private static Version GetMacOSVersion() + { + var versionText = RunOnBashAndCaptureOutput("sw_vers", "-productVersion"); + return ParseVersion(versionText); + } + + private static void ReadDistroNameAndVersion(out string distroName, out Version version) + { + // Details: https://www.freedesktop.org/software/systemd/man/os-release.html + var lines = File.ReadAllLines("/etc/os-release"); + + distroName = null; + version = null; + + foreach (var line in lines) + { + var equalsIndex = line.IndexOf('='); + if (equalsIndex >= 0) + { + var key = line.Substring(0, equalsIndex).Trim(); + var value = line.Substring(equalsIndex + 1).Trim(); + value = value.Trim('"'); + + if (key == "ID") + { + distroName = value; + } + else if (key == "VERSION_ID") + { + version = ParseVersion(value); + } + + if (distroName != null && version != null) + { + break; + } + } + } + + if (distroName == null) + { + distroName = "Unknown"; + } + } + + private static Version ParseVersion(string versionText) + { + if (!versionText.Contains(".")) + { + versionText += ".0"; + } + + if (Version.TryParse(versionText, out var version)) + { + return version; + } + + return null; + } + + private static string RunOnBashAndCaptureOutput(string fileName, string arguments) + => ProcessHelper.RunAndCaptureOutput("/bin/bash", $"-c '{fileName} {arguments}'"); + } +} From 0208c9865696d5eceb41d288ce083901a1680395 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 9 Nov 2017 11:03:49 -0800 Subject: [PATCH 2/6] Print current platform name at OmniSharp start up --- src/OmniSharp.Abstractions/Utilities/Platform.cs | 5 +++++ src/OmniSharp.Http/Startup.cs | 6 ++++-- src/OmniSharp.Stdio/Host.cs | 15 ++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/OmniSharp.Abstractions/Utilities/Platform.cs b/src/OmniSharp.Abstractions/Utilities/Platform.cs index c0889888b4..79f0bb32b4 100644 --- a/src/OmniSharp.Abstractions/Utilities/Platform.cs +++ b/src/OmniSharp.Abstractions/Utilities/Platform.cs @@ -39,6 +39,11 @@ private Platform( LinuxDistributionName = linuxDistributionName ?? string.Empty; } + public override string ToString() + => !string.IsNullOrEmpty(LinuxDistributionName) + ? $"{LinuxDistributionName} {Version} ({Architecture})" + : $"{OperatingSystem} {Version} ({Architecture})"; + private static Platform GetCurrentPlatform() { var os = OperatingSystem.Unknown; diff --git a/src/OmniSharp.Http/Startup.cs b/src/OmniSharp.Http/Startup.cs index 9929b20e60..e7952f2c69 100644 --- a/src/OmniSharp.Http/Startup.cs +++ b/src/OmniSharp.Http/Startup.cs @@ -9,9 +9,8 @@ using OmniSharp.Eventing; using OmniSharp.Http.Middleware; using OmniSharp.Options; -using OmniSharp.Roslyn; -using OmniSharp.Services; using OmniSharp.Stdio.Services; +using OmniSharp.Utilities; namespace OmniSharp.Http { @@ -52,6 +51,7 @@ public void Configure( { var workspace = _compositionHost.GetExport(); var logger = loggerFactory.CreateLogger(); + loggerFactory.AddConsole((category, level) => { if (HostHelpers.LogFilter(category, level, _environment)) return true; @@ -64,6 +64,8 @@ public void Configure( return false; }); + logger.LogInformation($"Starting OmniSharp on {Platform.Current}"); + app.UseRequestLogging(); app.UseExceptionHandler("/error"); app.UseMiddleware(_compositionHost); diff --git a/src/OmniSharp.Stdio/Host.cs b/src/OmniSharp.Stdio/Host.cs index 3997c9acce..2a8d05bbc2 100644 --- a/src/OmniSharp.Stdio/Host.cs +++ b/src/OmniSharp.Stdio/Host.cs @@ -30,6 +30,7 @@ class Host : IDisposable private readonly IDictionary> _endpointHandlers; private readonly CompositionHost _compositionHost; private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; private readonly IOmniSharpEnvironment _environment; private readonly CancellationTokenSource _cancellationTokenSource; private readonly CachedStringBuilder _cachedStringBuilder; @@ -45,6 +46,9 @@ public Host( _configuration = configuration; _serviceProvider = serviceProvider; _loggerFactory = loggerFactory.AddStdio(_writer, (category, level) => HostHelpers.LogFilter(category, level, _environment)); + _logger = loggerFactory.CreateLogger(); + + _logger.LogInformation($"Starting OmniSharp on {Platform.Current}"); _compositionHost = compositionHostBuilder.Build(); _cachedStringBuilder = new CachedStringBuilder(); @@ -57,7 +61,6 @@ private IDictionary> Initialize() { var workspace = _compositionHost.GetExport(); var projectSystems = _compositionHost.GetExports(); - var logger = _loggerFactory.CreateLogger(); var endpointMetadatas = _compositionHost.GetExports>() .Select(x => x.Metadata) .ToArray(); @@ -93,7 +96,7 @@ private IDictionary> Initialize() updateEndpointHandler = new Lazy>(() => null); } - return EndpointHandler.Factory(handler, _compositionHost, logger, endpoint, handlers, updateEndpointHandler, Enumerable.Empty()); + return EndpointHandler.Factory(handler, _compositionHost, _logger, endpoint, handlers, updateEndpointHandler, Enumerable.Empty()); }), StringComparer.OrdinalIgnoreCase ); @@ -132,9 +135,7 @@ public void Dispose() public void Start() { - var logger = _loggerFactory.CreateLogger(); - - WorkspaceInitializer.Initialize(_serviceProvider, _compositionHost, _configuration, logger); + WorkspaceInitializer.Initialize(_serviceProvider, _compositionHost, _configuration, _logger); Task.Factory.StartNew(async () => { @@ -155,7 +156,7 @@ public void Start() { try { - await HandleRequest(line, logger); + await HandleRequest(line, _logger); } catch (Exception e) { @@ -169,7 +170,7 @@ public void Start() } }); - logger.LogInformation($"Omnisharp server running using {nameof(TransportType.Stdio)} at location '{_environment.TargetDirectory}' on host {_environment.HostProcessId}."); + _logger.LogInformation($"Omnisharp server running using {nameof(TransportType.Stdio)} at location '{_environment.TargetDirectory}' on host {_environment.HostProcessId}."); Console.CancelKeyPress += (sender, e) => { From c01e43b935cbeeaadf20dd3a8d1511ef9e2b2e53 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 9 Nov 2017 11:10:33 -0800 Subject: [PATCH 3/6] Don't use Mono MSBuild if it does not exist and display a helpful message for Linux users Fixes #1011 --- .../Discovery/Providers/MonoInstanceProvider.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 6247503056..2c25e6378d 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -82,6 +82,22 @@ public override ImmutableArray GetInstances() return NoInstances; } + // Look for Microsoft.Build.dll in the tools path. If it isn't there, this is likely a Mono layout on Linux + // where the 'msbuild' package has not been installed. + var microsoftBuildPath = Path.Combine(toolsPath, "Microsoft.Build.dll"); + if (!File.Exists(microsoftBuildPath)) + { + Logger.LogDebug($"Mono MSBuild could not be used because '{microsoftBuildPath}' does not exist."); + + if (Platform.Current.OperatingSystem == Utilities.OperatingSystem.Linux) + { + Logger.LogWarning(@"It looks like you have Mono 5.2.0 or greater installed but MSBuild could not be found. +Try installing MSBuild into Mono (e.g. 'sudo apt-get install msbuild') to enable better MSBuild support."); + } + + return NoInstances; + } + var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); var localMSBuildPath = FindLocalMSBuildDirectory(); From 5cacd7117c7df271c81b3912ac2410928e175c98 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 9 Nov 2017 11:56:28 -0800 Subject: [PATCH 4/6] Don't use VS 2017 MSBuild if it is RTM and display a helpful message for users Fixes #1014 --- src/OmniSharp.Host/CompositionHostBuilder.cs | 52 +++++++++++++++---- .../MSBuild/Discovery/Extensions.cs | 13 +++++ 2 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/Extensions.cs diff --git a/src/OmniSharp.Host/CompositionHostBuilder.cs b/src/OmniSharp.Host/CompositionHostBuilder.cs index 6a786d8e5e..2b3e5793c4 100644 --- a/src/OmniSharp.Host/CompositionHostBuilder.cs +++ b/src/OmniSharp.Host/CompositionHostBuilder.cs @@ -53,20 +53,13 @@ public CompositionHost Build() var fileSystemWatcher = new ManualFileSystemWatcher(); var metadataHelper = new MetadataHelper(assemblyLoader); + var logger = loggerFactory.CreateLogger(); + // We must register an MSBuild instance before composing MEF to ensure that // our AssemblyResolve event is hooked up first. var msbuildLocator = _serviceProvider.GetRequiredService(); - var instances = msbuildLocator.GetInstances(); - var instance = instances.FirstOrDefault(); - if (instance != null) - { - msbuildLocator.RegisterInstance(instance); - } - else - { - var logger = loggerFactory.CreateLogger(); - logger.LogError("Could not locate MSBuild instance to register with OmniSharp"); - } + + RegisterMSBuildInstance(msbuildLocator, logger); config = config .WithProvider(MefValueProvider.From(_serviceProvider)) @@ -94,6 +87,43 @@ public CompositionHost Build() return config.CreateContainer(); } + private static void RegisterMSBuildInstance(IMSBuildLocator msbuildLocator, ILogger logger) + { + MSBuildInstance instanceToRegister = null; + var invalidVSFound = false; + + foreach (var instance in msbuildLocator.GetInstances()) + { + if (instance.IsInvalidVisualStudio()) + { + invalidVSFound = true; + } + else + { + instanceToRegister = instance; + break; + } + } + + + if (instanceToRegister != null) + { + // Did we end up choosing the standalone MSBuild because there was an invalid Visual Studio? + // If so, provide a helpful message to the user. + if (invalidVSFound && instanceToRegister.DiscoveryType == DiscoveryType.StandAlone) + { + logger.LogWarning(@"It looks like you have Visual Studio 2017 RTM installed. +Try updating Visual Studio 2017 to the most recent release to enable better MSBuild support."); + } + + msbuildLocator.RegisterInstance(instanceToRegister); + } + else + { + logger.LogError("Could not locate MSBuild instance to register with OmniSharp"); + } + } + private static IEnumerable SafeGetTypes(Assembly a) { try diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Extensions.cs b/src/OmniSharp.Host/MSBuild/Discovery/Extensions.cs new file mode 100644 index 0000000000..1909290843 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Extensions.cs @@ -0,0 +1,13 @@ +namespace OmniSharp.MSBuild.Discovery +{ + internal static class Extensions + { + public static bool IsInvalidVisualStudio(this MSBuildInstance instance) + // MSBuild from Visual Studio 2017 RTM cannot be used. + => instance.Version.Major == 15 + && instance.Version.Minor == 0 + && (instance.DiscoveryType == DiscoveryType.DeveloperConsole + || instance.DiscoveryType == DiscoveryType.VisualStudioSetup); + + } +} From d0b224e13ac7ab28be57b72ca93d10c3480dd40c Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 9 Nov 2017 12:08:00 -0800 Subject: [PATCH 5/6] Update changelog for 1.26.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe404bd6a1..58397ec28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All changes to the project will be documented in this file. +## [1.26.2] - 2017-11-09 + +* Fixed issue with discovering MSBuild under Mono even when it is missing. ([#1011](https://github.com/OmniSharp/omnisharp-roslyn/issues/1011)) +* Fixed issue to not use Visual Studio 2017 MSBuild if it is from VS 2017 RTM. ([#1014](https://github.com/OmniSharp/omnisharp-roslyn/issues/1014)) + ## [1.27.0] - 2017-11-07 * Significant changes made to the MSBuild project system that fix several issues. (PR: [#1003](https://github.com/OmniSharp/omnisharp-roslyn/pull/1003)) From 32e372ba42e7cfa743d08c22cb313051820e34aa Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 9 Nov 2017 13:56:58 -0800 Subject: [PATCH 6/6] Add comment explaining how Windows check happens in Platform helper --- src/OmniSharp.Abstractions/Utilities/Platform.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Abstractions/Utilities/Platform.cs b/src/OmniSharp.Abstractions/Utilities/Platform.cs index 79f0bb32b4..a38bb32b4e 100644 --- a/src/OmniSharp.Abstractions/Utilities/Platform.cs +++ b/src/OmniSharp.Abstractions/Utilities/Platform.cs @@ -49,7 +49,20 @@ private static Platform GetCurrentPlatform() var os = OperatingSystem.Unknown; var architecture = Architecture.Unknown; - // Simple check to see if this is Windows + // Simple check to see if this is Windows. Note: this check is derived from the fact that the + // System.PlatformID enum has six values (https://msdn.microsoft.com/en-us/library/3a8hyw88.aspx) + // + // * Win32 = 0 + // * Win32Windows = 1 + // * Win32NT = 2 + // * WinCE = 3 + // * Unix = 4 + // * Xbox = 5 + // * MacOSX = 6 + // + // Essentially, we check to see if this is one of the "windows" values or Xbox. The other values + // can be a little unreliable, so we'll shell out to 'uname' for Linux and macOS. + var platformId = (int)Environment.OSVersion.Platform; if (platformId <= 3 || platformId == 5) {