From a03aba627b0c321b59ddb5f66e346d54cca50cbb Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 9 Oct 2017 15:29:39 -0700 Subject: [PATCH 01/17] Re-organize MSBuild folder This change re-organizes the layout of the MSBuild folder to more closely match the layout of Visual Studio: * "msbuild" - This will be the MSBuildExtensionsPath * "msbuild\15.0" - common props go here * "msbuild\15.0\bin" - The MSBuild tools path, where much of the MSBuild actually goes. In addition, the MSBuild runtime and library packages have been updated to the latest, and the package references for the libraries are marked as `PrivateAssets="all"` in the OmniSharp.MSBuild project. This last change means that OmniSharp no longer runs, since the MSBuild libraries aren't copied to the OmniSharp base application path any longer. Later commits will fix this. --- .travis.yml | 6 ++ build.cake | 87 ++++++++++++++----- .../OmniSharp.MSBuild.csproj | 8 +- tools/packages.config | 5 ++ 4 files changed, 79 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5a5651382c..9244ed26fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,12 @@ addons: - zlib1g - curl +before_install: + - | + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + rvm install ruby-2.3.3 + fi + install: - | # On Unix, build libuv from source. On OSX, install it via Homebrew. if [ "$TRAVIS_OS_NAME" == "linux" ]; then diff --git a/build.cake b/build.cake index e84dfb0ccf..29f96d26b1 100644 --- a/build.cake +++ b/build.cake @@ -245,60 +245,102 @@ Task("CreateMSBuildFolder") { DirectoryHelper.ForceCreate(env.Folders.MSBuild); + var msbuild15TargetFolder = CombinePaths(env.Folders.MSBuild, "15.0"); + var msbuild15BinTargetFolder = CombinePaths(msbuild15TargetFolder, "Bin"); + + var msbuildLibraries = new [] + { + "Microsoft.Build", + "Microsoft.Build.Framework", + "Microsoft.Build.Tasks.Core", + "Microsoft.Build.Utilities.Core" + }; + string sdkResolverTFM; if (Platform.Current.IsWindows) { Information("Copying MSBuild runtime..."); - var msbuildRuntimeFolder = CombinePaths(env.Folders.Tools, "Microsoft.Build.Runtime", "contentFiles", "any", "net46"); - DirectoryHelper.Copy(msbuildRuntimeFolder, env.Folders.MSBuild); + + var msbuildSourceFolder = CombinePaths(env.Folders.Tools, "Microsoft.Build.Runtime", "contentFiles", "any", "net46"); + DirectoryHelper.Copy(msbuildSourceFolder, msbuild15BinTargetFolder, copySubDirectories: false); + + var msbuild15SourceFolder = CombinePaths(msbuildSourceFolder, "15.0"); + DirectoryHelper.Copy(msbuild15SourceFolder, msbuild15TargetFolder); + + Information("Copying MSBuild libraries..."); + + foreach (var library in msbuildLibraries) + { + var libraryFileName = library + ".dll"; + var librarySourcePath = CombinePaths(env.Folders.Tools, library, "lib", "net46", libraryFileName); + var libraryTargetPath = CombinePaths(msbuild15BinTargetFolder, libraryFileName); + FileHelper.Copy(librarySourcePath, libraryTargetPath); + } + sdkResolverTFM = "net46"; } else { Information("Copying Mono MSBuild runtime..."); - DirectoryHelper.Copy(env.Folders.MonoMSBuildRuntime, env.Folders.MSBuild); + + var msbuildSourceFolder = env.Folders.MonoMSBuildRuntime; + DirectoryHelper.Copy(msbuildSourceFolder, msbuild15BinTargetFolder, copySubDirectories: false); + + var msbuild15SourceFolder = CombinePaths(msbuildSourceFolder, "15.0"); + DirectoryHelper.Copy(msbuild15SourceFolder, msbuild15TargetFolder); + + Information("Copying MSBuild libraries..."); + + foreach (var library in msbuildLibraries) + { + var libraryFileName = library + ".dll"; + var librarySourcePath = CombinePaths(env.Folders.MonoMSBuildLib, libraryFileName); + var libraryTargetPath = CombinePaths(msbuild15BinTargetFolder, libraryFileName); + FileHelper.Copy(librarySourcePath, libraryTargetPath); + } + sdkResolverTFM = "netstandard1.5"; } // Copy MSBuild SDK Resolver and DotNetHostResolver Information("Coping MSBuild SDK resolver..."); - var sdkResolverFolder = CombinePaths(env.Folders.Tools, "Microsoft.DotNet.MSBuildSdkResolver", "lib", sdkResolverTFM); - var msbuildSdkResolverFolder = CombinePaths(env.Folders.MSBuild, "SdkResolvers", "Microsoft.DotNet.MSBuildSdkResolver"); - DirectoryHelper.ForceCreate(msbuildSdkResolverFolder); + var sdkResolverSourceFolder = CombinePaths(env.Folders.Tools, "Microsoft.DotNet.MSBuildSdkResolver", "lib", sdkResolverTFM); + var sdkResolverTargetFolder = CombinePaths(msbuild15BinTargetFolder, "SdkResolvers", "Microsoft.DotNet.MSBuildSdkResolver"); + DirectoryHelper.ForceCreate(sdkResolverTargetFolder); FileHelper.Copy( - source: CombinePaths(sdkResolverFolder, "Microsoft.DotNet.MSBuildSdkResolver.dll"), - destination: CombinePaths(msbuildSdkResolverFolder, "Microsoft.DotNet.MSBuildSdkResolver.dll")); + source: CombinePaths(sdkResolverSourceFolder, "Microsoft.DotNet.MSBuildSdkResolver.dll"), + destination: CombinePaths(sdkResolverTargetFolder, "Microsoft.DotNet.MSBuildSdkResolver.dll")); if (Platform.Current.IsWindows) { - CopyDotNetHostResolver(env, "win", "x86", "hostfxr.dll", msbuildSdkResolverFolder, copyToArchSpecificFolder: true); - CopyDotNetHostResolver(env, "win", "x64", "hostfxr.dll", msbuildSdkResolverFolder, copyToArchSpecificFolder: true); + CopyDotNetHostResolver(env, "win", "x86", "hostfxr.dll", sdkResolverTargetFolder, copyToArchSpecificFolder: true); + CopyDotNetHostResolver(env, "win", "x64", "hostfxr.dll", sdkResolverTargetFolder, copyToArchSpecificFolder: true); } else if (Platform.Current.IsMacOS) { - CopyDotNetHostResolver(env, "osx", "x64", "libhostfxr.dylib", msbuildSdkResolverFolder, copyToArchSpecificFolder: false); + CopyDotNetHostResolver(env, "osx", "x64", "libhostfxr.dylib", sdkResolverTargetFolder, copyToArchSpecificFolder: false); } else if (Platform.Current.IsLinux) { - CopyDotNetHostResolver(env, "linux", "x64", "libhostfxr.so", msbuildSdkResolverFolder, copyToArchSpecificFolder: false); + CopyDotNetHostResolver(env, "linux", "x64", "libhostfxr.so", sdkResolverTargetFolder, copyToArchSpecificFolder: false); } // Copy content of Microsoft.Net.Compilers Information("Copying Microsoft.Net.Compilers..."); - var compilersFolder = CombinePaths(env.Folders.Tools, "Microsoft.Net.Compilers", "tools"); - var msbuildRoslynFolder = CombinePaths(env.Folders.MSBuild, "Roslyn"); + var compilersSourceFolder = CombinePaths(env.Folders.Tools, "Microsoft.Net.Compilers", "tools"); + var compilersTargetFolder = CombinePaths(msbuild15BinTargetFolder, "Roslyn"); - DirectoryHelper.Copy(compilersFolder, msbuildRoslynFolder); + DirectoryHelper.Copy(compilersSourceFolder, compilersTargetFolder); // Delete unnecessary files - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "Microsoft.CodeAnalysis.VisualBasic.dll")); - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "Microsoft.VisualBasic.Core.targets")); - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "VBCSCompiler.exe")); - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "VBCSCompiler.exe.config")); - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "vbc.exe")); - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "vbc.exe.config")); - FileHelper.Delete(CombinePaths(msbuildRoslynFolder, "vbc.rsp")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "Microsoft.CodeAnalysis.VisualBasic.dll")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "Microsoft.VisualBasic.Core.targets")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "VBCSCompiler.exe")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "VBCSCompiler.exe.config")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "vbc.exe")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "vbc.exe.config")); + FileHelper.Delete(CombinePaths(compilersTargetFolder, "vbc.rsp")); }); /// @@ -514,7 +556,6 @@ void CopyMonoBuild(BuildEnvironment env, string sourceFolder, string outputFolde // Copy MSBuild runtime and libraries DirectoryHelper.Copy($"{env.Folders.MSBuild}", CombinePaths(outputFolder, "msbuild")); - DirectoryHelper.Copy($"{env.Folders.MonoMSBuildLib}", outputFolder); // Included in Mono FileHelper.Delete(CombinePaths(outputFolder, "System.AppContext.dll")); diff --git a/src/OmniSharp.MSBuild/OmniSharp.MSBuild.csproj b/src/OmniSharp.MSBuild/OmniSharp.MSBuild.csproj index 89754b7c12..1628482986 100644 --- a/src/OmniSharp.MSBuild/OmniSharp.MSBuild.csproj +++ b/src/OmniSharp.MSBuild/OmniSharp.MSBuild.csproj @@ -12,10 +12,10 @@ - - - - + + + + diff --git a/tools/packages.config b/tools/packages.config index a062d1ffc8..1f585c47a3 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -2,6 +2,11 @@ + + + + + From 661430fe779a6665ce60332ee949c377811279e4 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 4 Oct 2017 16:12:12 -0700 Subject: [PATCH 02/17] Add MSBuildLocator service to handle locating MSBuild The MSBuildLocator service has provider model for discovering MSBuild in various scenarios: * DevConsole * VS 2017 installation * Mono * Stand alone (i.e. the MSBuild that we ship with OmniSharp) Once an MSBuild instance is discovered and registered, an AssemblyResolve event is used to ensure that various MSBuild assemblies are used from that instance rather than using MSBuild assemblies included with OmniSharp. --- build/Packages.props | 3 + .../Logging/LoggerExtensions.cs | 5 + .../MSBuild/Discovery/DiscoveryType.cs | 10 ++ .../MSBuild/Discovery/IMSBuildLocator.cs | 12 ++ .../MSBuild/Discovery/MSBuildInstance.cs | 31 ++++ .../OmniSharp.Abstractions.csproj | 1 + src/OmniSharp.Host/AssemblyInfo.cs | 3 +- src/OmniSharp.Host/CompositionHostBuilder.cs | 41 ++++- .../MSBuild/Discovery/Interop.cs | 36 +++++ .../Discovery/MSBuildInstanceProvider.cs | 17 ++ .../MSBuild/Discovery/MSBuildLocator.cs | 150 ++++++++++++++++++ .../Providers/DevConsoleInstanceProvider.cs | 47 ++++++ .../Providers/MonoInstanceProvider.cs | 39 +++++ .../Providers/StandAloneInstanceProvider.cs | 79 +++++++++ .../Providers/VisualStudioInstanceProvider.cs | 89 +++++++++++ src/OmniSharp.Host/OmniSharp.Host.csproj | 2 + .../Services/OmniSharpEnvironment.cs | 6 +- src/OmniSharp.MSBuild/Extensions.cs | 24 +++ src/OmniSharp.MSBuild/MSBuildProjectSystem.cs | 20 ++- .../ProjectFile/ProjectFileInfo.cs | 70 ++------ .../EndpointMiddlewareFacts.cs | 4 +- .../ProjectFileInfoTests.cs | 14 +- tests/TestUtility/OmniSharpTestHost.cs | 11 +- tests/TestUtility/TestServiceProvider.cs | 11 +- tools/packages.config | 1 - 25 files changed, 636 insertions(+), 90 deletions(-) create mode 100644 src/OmniSharp.Abstractions/MSBuild/Discovery/DiscoveryType.cs create mode 100644 src/OmniSharp.Abstractions/MSBuild/Discovery/IMSBuildLocator.cs create mode 100644 src/OmniSharp.Abstractions/MSBuild/Discovery/MSBuildInstance.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/Interop.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs create mode 100644 src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs diff --git a/build/Packages.props b/build/Packages.props index 69963af438..c8e71371e8 100644 --- a/build/Packages.props +++ b/build/Packages.props @@ -37,11 +37,14 @@ 1.1.0 15.0.0 15.3.0 + 1.14.114 + 15.0.12 9.0.1 4.3.0 4.3.0 4.3.0 4.3.0 + 1.4.0 1.0.31 1.4.2 4.6.0 diff --git a/src/OmniSharp.Abstractions/Logging/LoggerExtensions.cs b/src/OmniSharp.Abstractions/Logging/LoggerExtensions.cs index 87e2d181d4..28979560bc 100644 --- a/src/OmniSharp.Abstractions/Logging/LoggerExtensions.cs +++ b/src/OmniSharp.Abstractions/Logging/LoggerExtensions.cs @@ -4,6 +4,11 @@ namespace Microsoft.Extensions.Logging { public static class LoggerExceptions { + public static void LogDebug(this ILogger logger, Exception ex, string message, params object[] args) + { + logger.LogDebug(0, ex, message, args); + } + public static void LogError(this ILogger logger, Exception ex, string message, params object[] args) { logger.LogError(0, ex, message, args); diff --git a/src/OmniSharp.Abstractions/MSBuild/Discovery/DiscoveryType.cs b/src/OmniSharp.Abstractions/MSBuild/Discovery/DiscoveryType.cs new file mode 100644 index 0000000000..ff10a866c6 --- /dev/null +++ b/src/OmniSharp.Abstractions/MSBuild/Discovery/DiscoveryType.cs @@ -0,0 +1,10 @@ +namespace OmniSharp.MSBuild.Discovery +{ + public enum DiscoveryType + { + StandAlone = 0, + DeveloperConsole = 1, + VisualStudioSetup = 2, + Mono = 3 + } +} diff --git a/src/OmniSharp.Abstractions/MSBuild/Discovery/IMSBuildLocator.cs b/src/OmniSharp.Abstractions/MSBuild/Discovery/IMSBuildLocator.cs new file mode 100644 index 0000000000..c10142ada3 --- /dev/null +++ b/src/OmniSharp.Abstractions/MSBuild/Discovery/IMSBuildLocator.cs @@ -0,0 +1,12 @@ +using System.Collections.Immutable; + +namespace OmniSharp.MSBuild.Discovery +{ + public interface IMSBuildLocator + { + MSBuildInstance RegisteredInstance { get; } + + void RegisterInstance(MSBuildInstance instance); + ImmutableArray GetInstances(); + } +} diff --git a/src/OmniSharp.Abstractions/MSBuild/Discovery/MSBuildInstance.cs b/src/OmniSharp.Abstractions/MSBuild/Discovery/MSBuildInstance.cs new file mode 100644 index 0000000000..ae22705080 --- /dev/null +++ b/src/OmniSharp.Abstractions/MSBuild/Discovery/MSBuildInstance.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Immutable; + +namespace OmniSharp.MSBuild.Discovery +{ + public class MSBuildInstance + { + public string Name { get; } + public string MSBuildPath { get; } + public Version Version { get; } + public DiscoveryType DiscoveryType { get; } + public ImmutableDictionary PropertyOverrides { get; } + public bool SetMSBuildExePathVariable { get; } + + public MSBuildInstance( + string name, string msbuildPath, Version version, DiscoveryType discoveryType, + ImmutableDictionary propertyOverrides = null, + bool setMSBuildExePathVariable = false) + { + Name = name; + MSBuildPath = msbuildPath; + Version = version ?? new Version(0, 0); + DiscoveryType = discoveryType; + PropertyOverrides = propertyOverrides ?? ImmutableDictionary.Empty; + SetMSBuildExePathVariable = setMSBuildExePathVariable; + } + + public override string ToString() + => $"{Name} {Version} - \"{MSBuildPath}\""; + } +} diff --git a/src/OmniSharp.Abstractions/OmniSharp.Abstractions.csproj b/src/OmniSharp.Abstractions/OmniSharp.Abstractions.csproj index f1a5e6b8df..810b1fb324 100644 --- a/src/OmniSharp.Abstractions/OmniSharp.Abstractions.csproj +++ b/src/OmniSharp.Abstractions/OmniSharp.Abstractions.csproj @@ -15,6 +15,7 @@ + diff --git a/src/OmniSharp.Host/AssemblyInfo.cs b/src/OmniSharp.Host/AssemblyInfo.cs index 71fdf9edab..ad1fb7d350 100644 --- a/src/OmniSharp.Host/AssemblyInfo.cs +++ b/src/OmniSharp.Host/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("TestUtility")] \ No newline at end of file +[assembly: InternalsVisibleTo("OmniSharp.Http.Tests")] +[assembly: InternalsVisibleTo("TestUtility")] diff --git a/src/OmniSharp.Host/CompositionHostBuilder.cs b/src/OmniSharp.Host/CompositionHostBuilder.cs index c94ae51077..3bb646d3f2 100644 --- a/src/OmniSharp.Host/CompositionHostBuilder.cs +++ b/src/OmniSharp.Host/CompositionHostBuilder.cs @@ -12,6 +12,7 @@ using OmniSharp.Eventing; using OmniSharp.FileWatching; using OmniSharp.Mef; +using OmniSharp.MSBuild.Discovery; using OmniSharp.Options; using OmniSharp.Roslyn; using OmniSharp.Services; @@ -49,15 +50,19 @@ public CompositionHost Build() var assemblyLoader = _serviceProvider.GetRequiredService(); var config = new ContainerConfiguration(); - var assemblies = _assemblies - .Concat(new[] { typeof(OmniSharpWorkspace).GetTypeInfo().Assembly, typeof(IRequest).GetTypeInfo().Assembly }) - .Distinct(); - - config = config.WithAssemblies(assemblies); - var fileSystemWatcher = new ManualFileSystemWatcher(); var metadataHelper = new MetadataHelper(assemblyLoader); + // 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); + } + config = config .WithProvider(MefValueProvider.From(_serviceProvider)) .WithProvider(MefValueProvider.From(fileSystemWatcher)) @@ -69,11 +74,32 @@ public CompositionHost Build() .WithProvider(MefValueProvider.From(options.CurrentValue.FormattingOptions)) .WithProvider(MefValueProvider.From(assemblyLoader)) .WithProvider(MefValueProvider.From(metadataHelper)) + .WithProvider(MefValueProvider.From(msbuildLocator)) .WithProvider(MefValueProvider.From(_eventEmitter ?? NullEventEmitter.Instance)); + var parts = _assemblies + .Concat(new[] { typeof(OmniSharpWorkspace).GetTypeInfo().Assembly, typeof(IRequest).GetTypeInfo().Assembly }) + .Distinct() + .SelectMany(a => SafeGetTypes(a)) + .ToArray(); + + config = config.WithParts(parts); + return config.CreateContainer(); } + private static IEnumerable SafeGetTypes(Assembly a) + { + try + { + return a.DefinedTypes.Select(t => t.AsType()).ToArray(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types.Where(t => t != null).ToArray(); + } + } + public static IServiceProvider CreateDefaultServiceProvider(IConfiguration configuration, IServiceCollection services = null) { services = services ?? new ServiceCollection(); @@ -83,6 +109,9 @@ public static IServiceProvider CreateDefaultServiceProvider(IConfiguration confi services.AddSingleton(); services.AddOptions(); + // MSBuild + services.AddSingleton(sp => MSBuildLocator.CreateDefault(sp.GetService())); + // Setup the options from configuration services.Configure(configuration); services.AddLogging(); diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Interop.cs b/src/OmniSharp.Host/MSBuild/Discovery/Interop.cs new file mode 100644 index 0000000000..f053bc9964 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Interop.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Setup.Configuration; + +namespace OmniSharp.MSBuild.Discovery +{ + internal static class Interop + { + private const int REGDG_E_CLASSNOTREG = unchecked((int)0x80040154); + + [DllImport("Microsoft.VisualStudio.Setup.Configuration.Native.dll", ExactSpelling = true, PreserveSig = true)] + private static extern int GetSetupConfiguration( + [MarshalAs(UnmanagedType.Interface), Out] out ISetupConfiguration configuration, + IntPtr reserved); + + public static ISetupConfiguration2 GetSetupConfiguration() + { + try + { + return new SetupConfiguration(); + } + catch (COMException ex) when (ex.ErrorCode == REGDG_E_CLASSNOTREG) + { + // We could not CoCreate the SetupConfiguration object. If that fails, try p/invoking. + var hresult = GetSetupConfiguration(out var configuration, IntPtr.Zero); + + if (hresult < 0) + { + throw new COMException($"Failed to get {nameof(ISetupConfiguration)}", hresult); + } + + return configuration as ISetupConfiguration2; + } + } + } +} diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs new file mode 100644 index 0000000000..038dadd2f9 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs @@ -0,0 +1,17 @@ +using System.Collections.Immutable; +using Microsoft.Extensions.Logging; + +namespace OmniSharp.MSBuild.Discovery +{ + internal abstract class MSBuildInstanceProvider + { + protected readonly ILogger Logger; + + protected MSBuildInstanceProvider(ILoggerFactory loggerFactory) + { + Logger = loggerFactory.CreateLogger(this.GetType()); + } + + public abstract ImmutableArray GetInstances(); + } +} diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs new file mode 100644 index 0000000000..bc305ee390 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Text; +using Microsoft.Extensions.Logging; +using OmniSharp.MSBuild.Discovery.Providers; +using OmniSharp.Utilities; + +namespace OmniSharp.MSBuild.Discovery +{ + internal class MSBuildLocator : DisposableObject, IMSBuildLocator + { + private static readonly ImmutableHashSet s_msbuildAssemblies = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, + "Microsoft.Build", + "Microsoft.Build.Framework", + "Microsoft.Build.Tasks.Core", + "Microsoft.Build.Utilities.Core"); + + private readonly ILogger _logger; + private readonly ImmutableArray _providers; + private MSBuildInstance _registeredInstance; + + public MSBuildInstance RegisteredInstance => _registeredInstance; + + private MSBuildLocator(ILoggerFactory loggerFactory, ImmutableArray providers) + { + _logger = loggerFactory.CreateLogger(); + _providers = providers; + } + + protected override void DisposeCore(bool disposing) + { + if (_registeredInstance != null) + { + AppDomain.CurrentDomain.AssemblyResolve -= Resolve; + _registeredInstance = null; + } + } + + public static MSBuildLocator CreateDefault(ILoggerFactory loggerFactory) + => new MSBuildLocator(loggerFactory, + ImmutableArray.Create( + new DevConsoleInstanceProvider(loggerFactory), + new VisualStudioInstanceProvider(loggerFactory), + new MonoInstanceProvider(loggerFactory), + new StandAloneInstanceProvider(loggerFactory))); + + public static MSBuildLocator CreateStandAlone(ILoggerFactory loggerFactory) + => new MSBuildLocator(loggerFactory, + ImmutableArray.Create( + new StandAloneInstanceProvider(loggerFactory))); + + public void RegisterInstance(MSBuildInstance instance) + { + if (_registeredInstance != null) + { + throw new InvalidOperationException("An MSBuild instance is already registered."); + } + + _registeredInstance = instance ?? throw new ArgumentNullException(nameof(instance)); + + AppDomain.CurrentDomain.AssemblyResolve += Resolve; + + if (instance.SetMSBuildExePathVariable) + { + var msbuildExePath = Path.Combine(instance.MSBuildPath, "MSBuild.exe"); + var msbuildDllPath = Path.Combine(instance.MSBuildPath, "MSBuild.dll"); + + string msbuildPath = null; + if (File.Exists(msbuildExePath)) + { + msbuildPath = msbuildExePath; + } + else if (File.Exists(msbuildDllPath)) + { + msbuildPath = msbuildDllPath; + } + + if (!string.IsNullOrEmpty(msbuildPath)) + { + Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msbuildPath); + _logger.LogInformation($"MSBUILD_EXE_PATH environment variable set to '{msbuildPath}'"); + } + else + { + _logger.LogError("Could not find MSBuild executable path."); + } + } + + _logger.LogInformation($"Registered MSBuild instance: {instance}"); + } + + private Assembly Resolve(object sender, ResolveEventArgs e) + { + var assemblyName = new AssemblyName(e.Name); + + _logger.LogDebug($"Attempting to resolve '{assemblyName}'"); + + if (s_msbuildAssemblies.Contains(assemblyName.Name)) + { + var assemblyPath = Path.Combine(_registeredInstance.MSBuildPath, assemblyName.Name + ".dll"); + var result = File.Exists(assemblyPath) + ? Assembly.LoadFrom(assemblyPath) + : null; + + if (result != null) + { + _logger.LogDebug($"Resolved '{assemblyName.Name}' to '{assemblyPath}'"); + return result; + } + } + + return null; + } + + public ImmutableArray GetInstances() + { + var builder = ImmutableArray.CreateBuilder(); + + foreach (var provider in _providers) + { + foreach (var instance in provider.GetInstances()) + { + if (instance != null) + { + builder.Add(instance); + } + } + } + + var result = builder.ToImmutable(); + LogInstances(result); + return result; + } + + private void LogInstances(ImmutableArray instances) + { + var builder = new StringBuilder(); + + builder.Append($"Located {instances.Length} MSBuild instance(s)"); + for (int i = 0; i < instances.Length; i++) + { + builder.Append($"{Environment.NewLine} {i + 1}: {instances[i]}"); + } + + _logger.LogInformation(builder.ToString()); + } + } +} diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs new file mode 100644 index 0000000000..3d6d66f380 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.Extensions.Logging; +using OmniSharp.Utilities; + +namespace OmniSharp.MSBuild.Discovery.Providers +{ + internal class DevConsoleInstanceProvider : MSBuildInstanceProvider + { + public DevConsoleInstanceProvider(ILoggerFactory loggerFactory) + : base(loggerFactory) + { + } + + public override ImmutableArray GetInstances() + { + if (!PlatformHelper.IsWindows) + { + return ImmutableArray.Empty; + } + + var path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); + + if (string.IsNullOrEmpty(path)) + { + return ImmutableArray.Empty; + } + + var versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); + Version.TryParse(versionString, out var version); + + if (version == null) + { + versionString = Environment.GetEnvironmentVariable("VisualStudioVersion"); + Version.TryParse(versionString, out version); + } + + return ImmutableArray.Create( + new MSBuildInstance( + "DEVCONSOLE", + Path.Combine(path, "MSBuild", "15.0", "Bin"), + version, + DiscoveryType.DeveloperConsole)); + } + } +} diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs new file mode 100644 index 0000000000..7a7e090ece --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.Extensions.Logging; +using OmniSharp.Utilities; + +namespace OmniSharp.MSBuild.Discovery.Providers +{ + internal class MonoInstanceProvider : MSBuildInstanceProvider + { + public MonoInstanceProvider(ILoggerFactory loggerFactory) + : base(loggerFactory) + { + } + + public override ImmutableArray GetInstances() + { + if (!PlatformHelper.IsMono) + { + return ImmutableArray.Empty; + } + + var path = PlatformHelper.GetMonoMSBuildDirPath(); + if (path == null) + { + return ImmutableArray.Empty; + } + + var toolsPath = Path.Combine(path, "15.0", "bin"); + + return ImmutableArray.Create( + new MSBuildInstance( + "Mono", + toolsPath, + new Version(15, 0), + DiscoveryType.Mono)); + } + } +} diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs new file mode 100644 index 0000000000..bc71d2daed --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.Extensions.Logging; + +namespace OmniSharp.MSBuild.Discovery.Providers +{ + internal class StandAloneInstanceProvider : MSBuildInstanceProvider + { + public StandAloneInstanceProvider(ILoggerFactory loggerFactory) + : base(loggerFactory) + { + } + + public override ImmutableArray GetInstances() + { + var path = FindMSBuildDirectory(); + if (path == null) + { + return ImmutableArray.Empty; + } + + var extensionsPath = path; + var toolsPath = Path.Combine(extensionsPath, "15.0", "Bin"); + var roslynPath = Path.Combine(toolsPath, "Roslyn"); + + var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); + propertyOverrides.Add("MSBuildToolsPath", toolsPath); + propertyOverrides.Add("MSBuildExtensionsPath", extensionsPath); + propertyOverrides.Add("CscToolPath", roslynPath); + propertyOverrides.Add("CscToolExe", "csc.exe"); + + return ImmutableArray.Create( + new MSBuildInstance( + "STANDALONE", + toolsPath, + new Version(15, 0), + DiscoveryType.StandAlone, + propertyOverrides.ToImmutable(), + setMSBuildExePathVariable: true)); + } + + private static string FindMSBuildDirectory() + { + // If OmniSharp is running normally, MSBuild is located in an 'msbuild' folder beneath OmniSharp.exe. + var msbuildDirectory = Path.Combine(AppContext.BaseDirectory, "msbuild"); + + if (!Directory.Exists(msbuildDirectory)) + { + // If the 'msbuild' folder does not exists beneath "OmniSharp.exe", this is likely a development scenario, + // such as debugging or running unit tests. In that case, we try to locate the ".msbuild" folder at the + // solution level. + msbuildDirectory = FindLocalMSBuildFromSolution(); + } + + return msbuildDirectory; + } + + private static string FindLocalMSBuildFromSolution() + { + // Try to locate the .msbuild folder by search for the OmniSharp solution relative to the current folder. + var current = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(current, "OmniSharp.sln"))) + { + current = Path.GetDirectoryName(current); + if (Path.GetPathRoot(current) == current) + { + break; + } + } + + var result = Path.Combine(current, ".msbuild"); + + return Directory.Exists(result) + ? result + : null; + } + } +} diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs new file mode 100644 index 0000000000..1fce97a6a0 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.Setup.Configuration; +using OmniSharp.Utilities; + +namespace OmniSharp.MSBuild.Discovery.Providers +{ + internal class VisualStudioInstanceProvider : MSBuildInstanceProvider + { + public VisualStudioInstanceProvider(ILoggerFactory loggerFactory) + : base(loggerFactory) + { + } + + public override ImmutableArray GetInstances() + { + if (!PlatformHelper.IsWindows) + { + return ImmutableArray.Empty; + } + + try + { + var configuration = Interop.GetSetupConfiguration(); + if (configuration == null) + { + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + + var instanceEnum = configuration.EnumAllInstances(); + + int fetched; + var instances = new ISetupInstance[1]; + do + { + instanceEnum.Next(1, instances, out fetched); + if (fetched <= 0) + { + continue; + } + + var instance = instances[0]; + var state = ((ISetupInstance2)instance).GetState(); + + if (!Version.TryParse(instance.GetInstallationVersion(), out var version)) + { + continue; + } + + if (state == InstanceState.Complete) + { + // Note: The code below will likely fail if MSBuild's version increments. + builder.Add( + new MSBuildInstance( + instance.GetDisplayName(), + Path.Combine(instance.GetInstallationPath(), "MSBuild", "15.0", "Bin"), + version, + DiscoveryType.VisualStudioSetup)); + } + } + while (fetched < 0); + + return builder.ToImmutable(); + } + catch (COMException ex) + { + return LogExceptionAndReturnEmpty(ex); + } + catch (DllNotFoundException ex) + { + // This is OK, since it probably means that VS 2017 or later isn't installed. + // We'll log the exception for debugging though. + return LogExceptionAndReturnEmpty(ex); + } + } + + private ImmutableArray LogExceptionAndReturnEmpty(Exception ex) + { + Logger.LogDebug(ex, "An exception was thrown while retrieving Visual Studio instances."); + + return ImmutableArray.Empty; + } + } +} diff --git a/src/OmniSharp.Host/OmniSharp.Host.csproj b/src/OmniSharp.Host/OmniSharp.Host.csproj index 549dfde9bd..e3f6cdbe5c 100644 --- a/src/OmniSharp.Host/OmniSharp.Host.csproj +++ b/src/OmniSharp.Host/OmniSharp.Host.csproj @@ -24,6 +24,8 @@ + + diff --git a/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs b/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs index 3ffe842c88..089dd67575 100644 --- a/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs +++ b/src/OmniSharp.Host/Services/OmniSharpEnvironment.cs @@ -19,7 +19,7 @@ public class OmniSharpEnvironment : IOmniSharpEnvironment public OmniSharpEnvironment( string path = null, int hostPid = -1, - LogLevel traceType = LogLevel.None, + LogLevel logLevel = LogLevel.None, string[] additionalArguments = null) { if (string.IsNullOrEmpty(path)) @@ -42,7 +42,7 @@ public OmniSharpEnvironment( } HostProcessId = hostPid; - LogLevel = traceType; + LogLevel = logLevel; AdditionalArguments = additionalArguments; // On Windows: %USERPROFILE%\.omnisharp\omnisharp.json @@ -64,4 +64,4 @@ public static bool IsValidPath(string path) || (File.Exists(path) && Path.GetExtension(path).Equals(".sln", StringComparison.OrdinalIgnoreCase)); } } -} \ No newline at end of file +} diff --git a/src/OmniSharp.MSBuild/Extensions.cs b/src/OmniSharp.MSBuild/Extensions.cs index bcdaee1848..2812b2a191 100644 --- a/src/OmniSharp.MSBuild/Extensions.cs +++ b/src/OmniSharp.MSBuild/Extensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.Extensions.Logging; namespace OmniSharp.MSBuild @@ -23,5 +24,28 @@ public static void AddPropertyIfNeeded(this Dictionary propertie logger.LogDebug($"Using {name}: {value}"); } } + + public static void AddPropertyOverride( + this Dictionary properties, + string propertyName, + string userOverrideValue, + ImmutableDictionary propertyOverrides, + ILogger logger) + { + var overrideValue = propertyOverrides.GetValueOrDefault(propertyName); + + if (!string.IsNullOrEmpty(userOverrideValue)) + { + // If the user set the option, we should use that. + properties.Add(propertyName, userOverrideValue); + logger.LogDebug($"'{propertyName}' set to '{userOverrideValue}' (user override)"); + } + else if (!string.IsNullOrEmpty(overrideValue)) + { + // If we have a custom environment value, we should use that. + properties.Add(propertyName, overrideValue); + logger.LogDebug($"'{propertyName}' set to '{overrideValue}'"); + } + } } } diff --git a/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs b/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs index 4fa32cf6ce..dfe07e6f05 100644 --- a/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs +++ b/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs @@ -15,6 +15,7 @@ using OmniSharp.Models.FilesChanged; using OmniSharp.Models.UpdateBuffer; using OmniSharp.Models.WorkspaceInformation; +using OmniSharp.MSBuild.Discovery; using OmniSharp.MSBuild.Models; using OmniSharp.MSBuild.Models.Events; using OmniSharp.MSBuild.ProjectFile; @@ -30,6 +31,7 @@ public class MSBuildProjectSystem : IProjectSystem { private readonly IOmniSharpEnvironment _environment; private readonly OmniSharpWorkspace _workspace; + private readonly MSBuildInstance _msbuildInstance; private readonly DotNetCliService _dotNetCli; private readonly MetadataFileReferenceCache _metadataFileReferenceCache; private readonly IEventEmitter _eventEmitter; @@ -53,6 +55,7 @@ public class MSBuildProjectSystem : IProjectSystem public MSBuildProjectSystem( IOmniSharpEnvironment environment, OmniSharpWorkspace workspace, + IMSBuildLocator msbuildLocator, DotNetCliService dotNetCliService, MetadataFileReferenceCache metadataFileReferenceCache, IEventEmitter eventEmitter, @@ -61,6 +64,7 @@ public MSBuildProjectSystem( { _environment = environment; _workspace = workspace; + _msbuildInstance = msbuildLocator.RegisteredInstance; _dotNetCli = dotNetCliService; _metadataFileReferenceCache = metadataFileReferenceCache; _eventEmitter = eventEmitter; @@ -78,16 +82,10 @@ public void Initalize(IConfiguration configuration) _options = new MSBuildOptions(); ConfigurationBinder.Bind(configuration, _options); - if (!MSBuildEnvironment.IsInitialized) + if (_environment.LogLevel < LogLevel.Information) { - MSBuildEnvironment.Initialize(_logger); - - if (MSBuildEnvironment.IsInitialized && - _environment.LogLevel < LogLevel.Information) - { - var buildEnvironmentInfo = MSBuildHelpers.GetBuildEnvironmentInfo(); - _logger.LogDebug($"MSBuild environment: {Environment.NewLine}{buildEnvironmentInfo}"); - } + var buildEnvironmentInfo = MSBuildHelpers.GetBuildEnvironmentInfo(); + _logger.LogDebug($"MSBuild environment: {Environment.NewLine}{buildEnvironmentInfo}"); } var initialProjectPaths = GetInitialProjectPaths(); @@ -317,7 +315,7 @@ private ProjectFileInfo LoadProject(string projectFilePath) try { - project = ProjectFileInfo.Create(projectFilePath, _environment.TargetDirectory, _loggerFactory.CreateLogger(), _options, diagnostics); + project = ProjectFileInfo.Create(projectFilePath, _environment.TargetDirectory, _loggerFactory.CreateLogger(), _msbuildInstance, _options, diagnostics); if (project == null) { @@ -343,7 +341,7 @@ private void OnProjectChanged(string projectFilePath, bool allowAutoRestore) if (_projects.TryGetValue(projectFilePath, out var oldProjectFileInfo)) { var diagnostics = new List(); - var newProjectFileInfo = oldProjectFileInfo.Reload(_environment.TargetDirectory, _loggerFactory.CreateLogger(), _options, diagnostics); + var newProjectFileInfo = oldProjectFileInfo.Reload(_environment.TargetDirectory, _loggerFactory.CreateLogger(), _msbuildInstance, _options, diagnostics); if (newProjectFileInfo != null) { diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs index 6116943d12..30063ec482 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.Logging; using NuGet.Packaging.Core; +using OmniSharp.MSBuild.Discovery; using OmniSharp.MSBuild.Models.Events; using OmniSharp.Options; @@ -70,14 +71,14 @@ private ProjectFileInfo( public static ProjectFileInfo Create( string filePath, string solutionDirectory, ILogger logger, - MSBuildOptions options = null, ICollection diagnostics = null) + MSBuildInstance msbuildInstance, MSBuildOptions options = null, ICollection diagnostics = null) { if (!File.Exists(filePath)) { return null; } - var projectInstance = LoadProject(filePath, solutionDirectory, logger, options, diagnostics, out var targetFrameworks); + var projectInstance = LoadProject(filePath, solutionDirectory, logger, msbuildInstance, options, diagnostics, out var targetFrameworks); if (projectInstance == null) { return null; @@ -91,11 +92,11 @@ public static ProjectFileInfo Create( private static ProjectInstance LoadProject( string filePath, string solutionDirectory, ILogger logger, - MSBuildOptions options, ICollection diagnostics, out ImmutableArray targetFrameworks) + MSBuildInstance msbuildInstance, MSBuildOptions options, ICollection diagnostics, out ImmutableArray targetFrameworks) { options = options ?? new MSBuildOptions(); - var globalProperties = GetGlobalProperties(options, solutionDirectory, logger); + var globalProperties = GetGlobalProperties(msbuildInstance, options, solutionDirectory, logger); var collection = new ProjectCollection(globalProperties); @@ -219,9 +220,9 @@ private static ProjectData CreateProjectData(ProjectInstance projectInstance, Im public ProjectFileInfo Reload( string solutionDirectory, ILogger logger, - MSBuildOptions options = null, ICollection diagnostics = null) + MSBuildInstance msbuildInstance, MSBuildOptions options = null, ICollection diagnostics = null) { - var projectInstance = LoadProject(FilePath, solutionDirectory, logger, options, diagnostics, out var targetFrameworks); + var projectInstance = LoadProject(FilePath, solutionDirectory, logger, msbuildInstance, options, diagnostics, out var targetFrameworks); if (projectInstance == null) { return null; @@ -243,7 +244,7 @@ public bool IsUnityProject() }); } - private static Dictionary GetGlobalProperties(MSBuildOptions options, string solutionDirectory, ILogger logger) + private static Dictionary GetGlobalProperties(MSBuildInstance msbuildInstance, MSBuildOptions options, string solutionDirectory, ILogger logger) { var globalProperties = new Dictionary { @@ -258,53 +259,14 @@ private static Dictionary GetGlobalProperties(MSBuildOptions opt { PropertyNames.SkipCompilerExecution, "true" } }; - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.MSBuildExtensionsPath, - userOptionValue: options.MSBuildExtensionsPath, - environmentValue: MSBuildEnvironment.MSBuildExtensionsPath); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.TargetFrameworkRootPath, - userOptionValue: options.TargetFrameworkRootPath, - environmentValue: MSBuildEnvironment.TargetFrameworkRootPath); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.RoslynTargetsPath, - userOptionValue: options.RoslynTargetsPath, - environmentValue: MSBuildEnvironment.RoslynTargetsPath); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.CscToolPath, - userOptionValue: options.CscToolPath, - environmentValue: MSBuildEnvironment.CscToolPath); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.CscToolExe, - userOptionValue: options.CscToolExe, - environmentValue: MSBuildEnvironment.CscToolExe); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.VisualStudioVersion, - userOptionValue: options.VisualStudioVersion, - environmentValue: null); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.Configuration, - userOptionValue: options.Configuration, - environmentValue: null); - - globalProperties.AddPropertyIfNeeded( - logger, - PropertyNames.Platform, - userOptionValue: options.Platform, - environmentValue: null); + globalProperties.AddPropertyOverride(PropertyNames.MSBuildExtensionsPath, options.MSBuildExtensionsPath, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.TargetFrameworkRootPath, options.TargetFrameworkRootPath, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.RoslynTargetsPath, options.RoslynTargetsPath, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.CscToolPath, options.CscToolPath, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.CscToolExe, options.CscToolExe, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.VisualStudioVersion, options.VisualStudioVersion, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.Configuration, options.Configuration, msbuildInstance.PropertyOverrides, logger); + globalProperties.AddPropertyOverride(PropertyNames.Platform, options.Platform, msbuildInstance.PropertyOverrides, logger); return globalProperties; } diff --git a/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs b/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs index d2503768b4..f7b808168c 100644 --- a/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs +++ b/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs @@ -18,6 +18,7 @@ using OmniSharp.Models.GotoDefinition; using OmniSharp.Models.UpdateBuffer; using OmniSharp.Models.WorkspaceInformation; +using OmniSharp.MSBuild.Discovery; using OmniSharp.Services; using OmniSharp.Utilities; using TestUtility; @@ -118,7 +119,8 @@ private PlugInHost CreatePlugInHost(params Assembly[] assemblies) { var environment = new OmniSharpEnvironment(); var sharedTextWriter = new TestSharedTextWriter(this.TestOutput); - var serviceProvider = new TestServiceProvider(environment, this.LoggerFactory, sharedTextWriter, new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build()); + var msbuildLocator = MSBuildLocator.CreateStandAlone(this.LoggerFactory); + var serviceProvider = new TestServiceProvider(environment, this.LoggerFactory, sharedTextWriter, msbuildLocator, new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build()); var compositionHost = new CompositionHostBuilder(serviceProvider, environment, sharedTextWriter, NullEventEmitter.Instance) .WithAssemblies(assemblies) .Build(); diff --git a/tests/OmniSharp.MSBuild.Tests/ProjectFileInfoTests.cs b/tests/OmniSharp.MSBuild.Tests/ProjectFileInfoTests.cs index d96cc4ca75..71210e14d4 100644 --- a/tests/OmniSharp.MSBuild.Tests/ProjectFileInfoTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/ProjectFileInfoTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.Logging; +using OmniSharp.MSBuild.Discovery; using OmniSharp.MSBuild.ProjectFile; using OmniSharp.Services; using TestUtility; @@ -22,6 +23,13 @@ public ProjectFileInfoTests(ITestOutputHelper output) this._logger = this.LoggerFactory.CreateLogger(); } + private ProjectFileInfo CreateProjectFileInfo(OmniSharpTestHost host, ITestProject testProject, string projectFilePath) + { + var msbuildLocator = host.GetExport(); + + return ProjectFileInfo.Create(projectFilePath, testProject.Directory, this._logger, msbuildLocator.RegisteredInstance); + } + [Fact] public async Task HelloWorld_has_correct_property_values() { @@ -30,7 +38,7 @@ public async Task HelloWorld_has_correct_property_values() { var projectFilePath = Path.Combine(testProject.Directory, "HelloWorld.csproj"); - var projectFileInfo = ProjectFileInfo.Create(projectFilePath, testProject.Directory, this._logger); + var projectFileInfo = CreateProjectFileInfo(host, testProject, projectFilePath); Assert.NotNull(projectFileInfo); Assert.Equal(projectFilePath, projectFileInfo.FilePath); @@ -50,7 +58,7 @@ public async Task HelloWorldSlim_has_correct_property_values() { var projectFilePath = Path.Combine(testProject.Directory, "HelloWorldSlim.csproj"); - var projectFileInfo = ProjectFileInfo.Create(projectFilePath, testProject.Directory, this._logger); + var projectFileInfo = CreateProjectFileInfo(host, testProject, projectFilePath); Assert.NotNull(projectFileInfo); Assert.Equal(projectFilePath, projectFileInfo.FilePath); @@ -69,7 +77,7 @@ public async Task NetStandardAndNetCoreApp_has_correct_property_values() { var projectFilePath = Path.Combine(testProject.Directory, "NetStandardAndNetCoreApp.csproj"); - var projectFileInfo = ProjectFileInfo.Create(projectFilePath, testProject.Directory, this._logger); + var projectFileInfo = CreateProjectFileInfo(host, testProject, projectFilePath); Assert.NotNull(projectFileInfo); Assert.Equal(projectFilePath, projectFileInfo.FilePath); diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 61824f9412..cdb5ed476f 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -14,6 +14,7 @@ using OmniSharp.Eventing; using OmniSharp.Mef; using OmniSharp.MSBuild; +using OmniSharp.MSBuild.Discovery; using OmniSharp.Roslyn.CSharp.Services; using OmniSharp.Services; using OmniSharp.Utilities; @@ -111,16 +112,12 @@ public static OmniSharpTestHost Create(string path = null, ITestOutputHelper tes builder.AddInMemoryCollection(configurationData); var configuration = builder.Build(); - var environment = new OmniSharpEnvironment(path); + var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); var loggerFactory = new LoggerFactory().AddXunit(testOutput); var sharedTextWriter = new TestSharedTextWriter(testOutput); + var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory); - var serviceProvider = new TestServiceProvider(environment, loggerFactory, sharedTextWriter, configuration); - - if (!MSBuildEnvironment.IsInitialized) - { - MSBuildEnvironment.InitializeForTest(loggerFactory.CreateLogger()); - } + var serviceProvider = new TestServiceProvider(environment, loggerFactory, sharedTextWriter, msbuildLocator, configuration); var compositionHost = new CompositionHostBuilder(serviceProvider, environment, sharedTextWriter, NullEventEmitter.Instance) .WithAssemblies(s_lazyAssemblies.Value) diff --git a/tests/TestUtility/TestServiceProvider.cs b/tests/TestUtility/TestServiceProvider.cs index f4edfef31b..234c13d1af 100644 --- a/tests/TestUtility/TestServiceProvider.cs +++ b/tests/TestUtility/TestServiceProvider.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OmniSharp; +using OmniSharp.MSBuild.Discovery; using OmniSharp.Options; using OmniSharp.Services; using OmniSharp.Stdio.Services; @@ -20,7 +19,12 @@ public class TestServiceProvider : DisposableObject, IServiceProvider private readonly ILogger _logger; private readonly Dictionary _services = new Dictionary(); - public TestServiceProvider(IOmniSharpEnvironment environment, ILoggerFactory loggerFactory, ISharedTextWriter sharedTextWriter, IConfiguration configuration) + public TestServiceProvider( + IOmniSharpEnvironment environment, + ILoggerFactory loggerFactory, + ISharedTextWriter sharedTextWriter, + IMSBuildLocator msbuildLocator, + IConfiguration configuration) { _logger = loggerFactory.CreateLogger(); @@ -36,6 +40,7 @@ public TestServiceProvider(IOmniSharpEnvironment environment, ILoggerFactory log _services[typeof(IAssemblyLoader)] = new AssemblyLoader(loggerFactory); _services[typeof(IMemoryCache)] = new MemoryCache(new MemoryCacheOptions()); _services[typeof(ISharedTextWriter)] = sharedTextWriter; + _services[typeof(IMSBuildLocator)] = msbuildLocator; } ~TestServiceProvider() diff --git a/tools/packages.config b/tools/packages.config index 1f585c47a3..00999c515d 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,7 +1,6 @@ - From 0a0264a5c1b141e20ded2c93c13724b88c0a836d Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Oct 2017 10:12:32 -0700 Subject: [PATCH 03/17] Remove Travis script code to install newer Ruby --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9244ed26fc..5a5651382c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,12 +24,6 @@ addons: - zlib1g - curl -before_install: - - | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - rvm install ruby-2.3.3 - fi - install: - | # On Unix, build libuv from source. On OSX, install it via Homebrew. if [ "$TRAVIS_OS_NAME" == "linux" ]; then From f73d789ce33877e4f9d830ee60ad86ab75f49b73 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Oct 2017 10:53:04 -0700 Subject: [PATCH 04/17] Ensure that 'CscToolPath' and 'CscToolExe' are set on Mono --- .../MSBuild/Discovery/Providers/MonoInstanceProvider.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 7a7e090ece..54a42acf89 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -27,13 +27,19 @@ public override ImmutableArray GetInstances() } var toolsPath = Path.Combine(path, "15.0", "bin"); + var roslynPath = Path.Combine(toolsPath, "Roslyn"); + + var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); + propertyOverrides.Add("CscToolPath", roslynPath); + propertyOverrides.Add("CscToolExe", "csc.exe"); return ImmutableArray.Create( new MSBuildInstance( "Mono", toolsPath, new Version(15, 0), - DiscoveryType.Mono)); + DiscoveryType.Mono, + propertyOverrides.ToImmutable())); } } } From 8a1a11cd11f62869831c67b20f764e3dac4bc6d2 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Oct 2017 11:53:18 -0700 Subject: [PATCH 05/17] Improve MSBuild discovery robustness and delete MSBuildEnvironment class --- .../MSBuild/Discovery/MSBuildLocator.cs | 6 +- .../Providers/DevConsoleInstanceProvider.cs | 8 +- .../Providers/MonoInstanceProvider.cs | 14 +- .../Providers/StandAloneInstanceProvider.cs | 57 ++- .../Providers/VisualStudioInstanceProvider.cs | 16 +- src/OmniSharp.MSBuild/Extensions.cs | 19 - src/OmniSharp.MSBuild/MSBuildEnvironment.cs | 418 ------------------ .../EndpointMiddlewareFacts.cs | 2 +- tests/TestUtility/OmniSharpTestHost.cs | 2 +- 9 files changed, 89 insertions(+), 453 deletions(-) delete mode 100644 src/OmniSharp.MSBuild/MSBuildEnvironment.cs diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs index bc305ee390..dbb6c36ed5 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs @@ -44,12 +44,12 @@ public static MSBuildLocator CreateDefault(ILoggerFactory loggerFactory) new DevConsoleInstanceProvider(loggerFactory), new VisualStudioInstanceProvider(loggerFactory), new MonoInstanceProvider(loggerFactory), - new StandAloneInstanceProvider(loggerFactory))); + new StandAloneInstanceProvider(loggerFactory, allowMonoPaths: true))); - public static MSBuildLocator CreateStandAlone(ILoggerFactory loggerFactory) + public static MSBuildLocator CreateStandAlone(ILoggerFactory loggerFactory, bool allowMonoPaths) => new MSBuildLocator(loggerFactory, ImmutableArray.Create( - new StandAloneInstanceProvider(loggerFactory))); + new StandAloneInstanceProvider(loggerFactory, allowMonoPaths))); public void RegisterInstance(MSBuildInstance instance) { diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs index 3d6d66f380..20c85bf463 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs @@ -27,6 +27,12 @@ public override ImmutableArray GetInstances() return ImmutableArray.Empty; } + var toolsPath = Path.Combine(path, "MSBuild", "15.0", "Bin"); + if (!Directory.Exists(toolsPath)) + { + return ImmutableArray.Empty; + } + var versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); Version.TryParse(versionString, out var version); @@ -39,7 +45,7 @@ public override ImmutableArray GetInstances() return ImmutableArray.Create( new MSBuildInstance( "DEVCONSOLE", - Path.Combine(path, "MSBuild", "15.0", "Bin"), + toolsPath, version, DiscoveryType.DeveloperConsole)); } diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 54a42acf89..6a588c13ab 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -27,11 +27,19 @@ public override ImmutableArray GetInstances() } var toolsPath = Path.Combine(path, "15.0", "bin"); - var roslynPath = Path.Combine(toolsPath, "Roslyn"); + if (!Directory.Exists(toolsPath)) + { + return ImmutableArray.Empty; + } var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); - propertyOverrides.Add("CscToolPath", roslynPath); - propertyOverrides.Add("CscToolExe", "csc.exe"); + + var roslynPath = Path.Combine(toolsPath, "Roslyn"); + if (Directory.Exists(roslynPath)) + { + propertyOverrides.Add("CscToolPath", roslynPath); + propertyOverrides.Add("CscToolExe", "csc.exe"); + } return ImmutableArray.Create( new MSBuildInstance( diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs index bc71d2daed..73db3facf4 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs @@ -2,14 +2,18 @@ using System.Collections.Immutable; using System.IO; using Microsoft.Extensions.Logging; +using OmniSharp.Utilities; namespace OmniSharp.MSBuild.Discovery.Providers { internal class StandAloneInstanceProvider : MSBuildInstanceProvider { - public StandAloneInstanceProvider(ILoggerFactory loggerFactory) + private readonly bool _allowMonoPaths; + + public StandAloneInstanceProvider(ILoggerFactory loggerFactory, bool allowMonoPaths) : base(loggerFactory) { + _allowMonoPaths = allowMonoPaths; } public override ImmutableArray GetInstances() @@ -25,6 +29,23 @@ public override ImmutableArray GetInstances() var roslynPath = Path.Combine(toolsPath, "Roslyn"); var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); + + // To better support older versions of Mono that don't include + // MSBuild 15, we attempt to set property overrides to the locations + // of Mono's 'xbuild' and 'xbuild-frameworks' paths. + if (_allowMonoPaths && PlatformHelper.IsMono) + { + if (TryGetMonoXBuildPath(out var xbuildPath)) + { + extensionsPath = xbuildPath; + } + + if (TryGetMonoXBuildFrameworksPath(out var xbuildFrameworksPath)) + { + propertyOverrides.Add("TargetFrameworkRootPath", xbuildFrameworksPath); + } + } + propertyOverrides.Add("MSBuildToolsPath", toolsPath); propertyOverrides.Add("MSBuildExtensionsPath", extensionsPath); propertyOverrides.Add("CscToolPath", roslynPath); @@ -75,5 +96,39 @@ private static string FindLocalMSBuildFromSolution() ? result : null; } + + private static bool TryGetMonoXBuildPath(out string path) + { + path = null; + + var monoXBuildDirPath = PlatformHelper.GetMonoXBuildDirPath(); + if (monoXBuildDirPath == null) + { + return false; + } + + var monoXBuild15DirPath = Path.Combine(monoXBuildDirPath, "15.0"); + if (!Directory.Exists(monoXBuild15DirPath)) + { + return false; + } + + path = monoXBuildDirPath; + return true; + } + + private static bool TryGetMonoXBuildFrameworksPath(out string path) + { + path = null; + + var monoMSBuildXBuildFrameworksDirPath = PlatformHelper.GetMonoXBuildFrameworksDirPath(); + if (monoMSBuildXBuildFrameworksDirPath == null) + { + return false; + } + + path = monoMSBuildXBuildFrameworksDirPath; + return true; + } } } diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs index 1fce97a6a0..e6daa738ca 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs @@ -55,12 +55,16 @@ public override ImmutableArray GetInstances() if (state == InstanceState.Complete) { // Note: The code below will likely fail if MSBuild's version increments. - builder.Add( - new MSBuildInstance( - instance.GetDisplayName(), - Path.Combine(instance.GetInstallationPath(), "MSBuild", "15.0", "Bin"), - version, - DiscoveryType.VisualStudioSetup)); + var toolsPath = Path.Combine(instance.GetInstallationPath(), "MSBuild", "15.0", "Bin"); + if (Directory.Exists(toolsPath)) + { + builder.Add( + new MSBuildInstance( + instance.GetDisplayName(), + toolsPath, + version, + DiscoveryType.VisualStudioSetup)); + } } } while (fetched < 0); diff --git a/src/OmniSharp.MSBuild/Extensions.cs b/src/OmniSharp.MSBuild/Extensions.cs index 2812b2a191..c5df0a5bb7 100644 --- a/src/OmniSharp.MSBuild/Extensions.cs +++ b/src/OmniSharp.MSBuild/Extensions.cs @@ -6,25 +6,6 @@ namespace OmniSharp.MSBuild { internal static class Extensions { - public static void AddPropertyIfNeeded(this Dictionary properties, ILogger logger, string name, string userOptionValue, string environmentValue) - { - if (!string.IsNullOrWhiteSpace(userOptionValue)) - { - // If the user set the option, we should use that. - properties.Add(name, userOptionValue); - } - else if (!string.IsNullOrWhiteSpace(environmentValue)) - { - // If we have a custom environment value, we should use that. - properties.Add(name, environmentValue); - } - - if (properties.TryGetValue(name, out var value)) - { - logger.LogDebug($"Using {name}: {value}"); - } - } - public static void AddPropertyOverride( this Dictionary properties, string propertyName, diff --git a/src/OmniSharp.MSBuild/MSBuildEnvironment.cs b/src/OmniSharp.MSBuild/MSBuildEnvironment.cs deleted file mode 100644 index b14fff5786..0000000000 --- a/src/OmniSharp.MSBuild/MSBuildEnvironment.cs +++ /dev/null @@ -1,418 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Microsoft.Extensions.Logging; -using OmniSharp.Utilities; - -namespace OmniSharp.MSBuild -{ - public enum MSBuildEnvironmentKind - { - Unknown, - VisualStudio, - Mono, - StandAlone - } - - public static class MSBuildEnvironment - { - public const string MSBuildExePathName = "MSBUILD_EXE_PATH"; - - private static bool s_isInitialized; - private static MSBuildEnvironmentKind s_kind; - - private static string s_msbuildExePath; - private static string s_msbuildExtensionsPath; - private static string s_targetFrameworkRootPath; - private static string s_roslynTargetsPath; - private static string s_cscToolPath; - private static string s_cscToolExe; - - public static bool IsInitialized => s_isInitialized; - - public static MSBuildEnvironmentKind Kind - { - get - { - ThrowIfNotInitialized(); - return s_kind; - } - } - - public static string MSBuildExePath - { - get - { - ThrowIfNotInitialized(); - return s_msbuildExePath; - } - } - - public static string MSBuildExtensionsPath - { - get - { - ThrowIfNotInitialized(); - return s_msbuildExtensionsPath; - } - } - - public static string TargetFrameworkRootPath - { - get - { - ThrowIfNotInitialized(); - return s_targetFrameworkRootPath; - } - } - - public static string RoslynTargetsPath - { - get - { - ThrowIfNotInitialized(); - return s_roslynTargetsPath; - } - } - - public static string CscToolPath - { - get - { - ThrowIfNotInitialized(); - return s_cscToolPath; - } - } - - public static string CscToolExe - { - get - { - ThrowIfNotInitialized(); - return s_cscToolExe; - } - } - - private static void ThrowIfNotInitialized() - { - if (!s_isInitialized) - { - throw new InvalidOperationException("MSBuild environment has not been initialized."); - } - } - - public static void Initialize(ILogger logger) - { - if (s_isInitialized) - { - throw new InvalidOperationException("MSBuild environment is already initialized."); - } - - // If MSBuild can locate VS 2017 and set up a build environment, we don't need to do anything. - // MSBuild will take care of itself. - if (MSBuildHelpers.CanInitializeVisualStudioBuildEnvironment()) - { - s_kind = MSBuildEnvironmentKind.VisualStudio; - s_isInitialized = true; - } - else if (TryWithMonoMSBuild()) - { - s_kind = MSBuildEnvironmentKind.Mono; - s_isInitialized = true; - } - else if (TryWithLocalMSBuild()) - { - s_kind = MSBuildEnvironmentKind.StandAlone; - s_isInitialized = true; - } - else - { - s_kind = MSBuildEnvironmentKind.Unknown; - logger.LogError("MSBuild environment could not be initialized."); - s_isInitialized = false; - } - - if (!s_isInitialized) - { - return; - } - - LogSummary(logger); - } - - internal static void InitializeForTest(ILogger logger) - { - // For tests, we only initialize in standalone mode. - - if (s_isInitialized) - { - throw new InvalidOperationException("MSBuild environment is already initialized."); - } - - if (TryWithLocalMSBuild(allowMonoPaths: false)) - { - s_kind = MSBuildEnvironmentKind.StandAlone; - s_isInitialized = true; - } - else - { - s_kind = MSBuildEnvironmentKind.Unknown; - logger.LogError("MSBuild environment could not be initialized."); - s_isInitialized = false; - } - - if (!s_isInitialized) - { - return; - } - - LogSummary(logger); - } - - private static void LogSummary(ILogger logger) - { - var summary = new StringBuilder(); - switch (s_kind) - { - case MSBuildEnvironmentKind.VisualStudio: - summary.AppendLine("OmniSharp initialized with Visual Studio MSBuild."); - break; - case MSBuildEnvironmentKind.Mono: - summary.AppendLine("OmniSharp initialized with Mono MSBuild."); - break; - case MSBuildEnvironmentKind.StandAlone: - summary.AppendLine("Omnisharp will use local MSBuild."); - break; - } - - if (s_msbuildExePath != null) - { - summary.AppendLine($" MSBUILD_EXE_PATH: {s_msbuildExePath}"); - } - - if (s_msbuildExtensionsPath != null) - { - summary.AppendLine($" MSBuildExtensionsPath: {s_msbuildExtensionsPath}"); - } - - if (s_targetFrameworkRootPath != null) - { - summary.AppendLine($" TargetFrameworkRootPath: {s_targetFrameworkRootPath}"); - } - - if (s_roslynTargetsPath != null) - { - summary.AppendLine($" RoslynTargetsPath: {s_roslynTargetsPath}"); - } - - if (s_cscToolPath != null) - { - summary.AppendLine($" CscToolPath: {s_cscToolPath}"); - } - - logger.LogInformation(summary.ToString()); - } - - private static void SetMSBuildExePath(string msbuildExePath) - { - s_msbuildExePath = msbuildExePath; - Environment.SetEnvironmentVariable(MSBuildExePathName, msbuildExePath); - } - - private static bool TryWithMonoMSBuild() - { - if (!PlatformHelper.IsMono) - { - return false; - } - - var monoMSBuildDirPath = PlatformHelper.GetMonoMSBuildDirPath(); - if (monoMSBuildDirPath == null) - { - // No Mono msbuild folder? If so, use standalone mode. - return false; - } - - var monoMSBuildBinDirPath = Path.Combine(monoMSBuildDirPath, "15.0", "bin"); - - var msbuildExePath = Path.Combine(monoMSBuildBinDirPath, "MSBuild.dll"); - if (!File.Exists(msbuildExePath)) - { - // Could not locate Mono MSBuild. - return false; - } - - SetMSBuildExePath(msbuildExePath); - TrySetMSBuildExtensionsPathToXBuild(); - TrySetTargetFrameworkRootPathToXBuildFrameworks(); - TrySetRoslynTargetsPathAndCscTool(); - - return true; - } - - private static bool TrySetTargetFrameworkRootPathToXBuildFrameworks() - { - if (!PlatformHelper.IsMono) - { - return false; - } - - var monoMSBuildXBuildFrameworksDirPath = PlatformHelper.GetMonoXBuildFrameworksDirPath(); - if (monoMSBuildXBuildFrameworksDirPath == null) - { - return false; - } - - s_targetFrameworkRootPath = monoMSBuildXBuildFrameworksDirPath; - return true; - } - - private static bool TrySetMSBuildExtensionsPathToXBuild() - { - if (!PlatformHelper.IsMono) - { - return false; - } - - var monoXBuildDirPath = PlatformHelper.GetMonoXBuildDirPath(); - if (monoXBuildDirPath == null) - { - return false; - } - - var monoXBuild15DirPath = Path.Combine(monoXBuildDirPath, "15.0"); - if (!Directory.Exists(monoXBuild15DirPath)) - { - return false; - } - - s_msbuildExtensionsPath = monoXBuildDirPath; - - return true; - } - - private static bool TrySetRoslynTargetsPathAndCscTool() - { - if (!PlatformHelper.IsMono) - { - return false; - } - - // Use our version of the Roslyn compiler and targets. - // We do this for a couple of reasons: - // - // 1. Mono relocates csc.exe to a different location within Mono. - // 2. The latest Mono targets hardcode CscToolPath to that different location. - // - // In order to use the Compile target during MSBuild execution, csc.exe - // needs to be located successfully. - - var msbuildDirectory = FindMSBuildDirectory(); - if (msbuildDirectory != null) - { - var roslynTargetsPath = Path.Combine(msbuildDirectory, "Roslyn"); - if (Directory.Exists(roslynTargetsPath)) - { - s_roslynTargetsPath = roslynTargetsPath; - s_cscToolPath = roslynTargetsPath; - - // Ensure that CscToolExe is set to "csc.exe", since that's what - // is included in OmniSharp. On older versions of Mono, this would - // be mcs.exe, which will not be able to be located in our "Roslyn" - // directory. - s_cscToolExe = "csc.exe"; - - return true; - } - } - - return false; - } - - private static bool TryWithLocalMSBuild(bool allowMonoPaths = true) - { - var msbuildDirectory = FindMSBuildDirectory(); - if (msbuildDirectory == null) - { - return false; - } - - // Set the MSBUILD_EXE_PATH environment variable to the location of MSBuild.exe or MSBuild.dll. - var msbuildExePath = FindMSBuildExe(msbuildDirectory); - if (msbuildExePath == null) - { - return false; - } - - SetMSBuildExePath(msbuildExePath); - - s_msbuildExtensionsPath = msbuildDirectory; - - if (allowMonoPaths) - { - TrySetMSBuildExtensionsPathToXBuild(); - TrySetTargetFrameworkRootPathToXBuildFrameworks(); - } - - TrySetRoslynTargetsPathAndCscTool(); - - return true; - } - - private static string FindMSBuildDirectory() - { - // If OmniSharp is running normally, MSBuild is located in an 'msbuild' folder beneath OmniSharp.exe. - var msbuildDirectory = Path.Combine(AppContext.BaseDirectory, "msbuild"); - - if (!Directory.Exists(msbuildDirectory)) - { - // If the 'msbuild' folder does not exist beneath OmniSharp.exe, this is likely a development scenario, - // such as debugging or running unit tests. In that case, we use one of the .msbuild-* folders at the - // solution level. - msbuildDirectory = FindLocalMSBuildFromSolution(); - } - - return msbuildDirectory; - } - - private static string FindLocalMSBuildFromSolution() - { - // Try to locate the appropriate build-time msbuild folder by searching for - // the OmniSharp solution relative to the current folder. - - var current = AppContext.BaseDirectory; - while (!File.Exists(Path.Combine(current, "OmniSharp.sln"))) - { - current = Path.GetDirectoryName(current); - if (Path.GetPathRoot(current) == current) - { - break; - } - } - - var result = Path.Combine(current, ".msbuild"); - - return Directory.Exists(result) - ? result - : null; - } - - private static string FindMSBuildExe(string directory) - { - // We look for either MSBuild.exe or MSBuild.dll. - var msbuildExePath = Path.Combine(directory, "MSBuild.exe"); - if (File.Exists(msbuildExePath)) - { - return msbuildExePath; - } - - msbuildExePath = Path.Combine(directory, "MSBuild.dll"); - if (File.Exists(msbuildExePath)) - { - return msbuildExePath; - } - - return null; - } - } -} diff --git a/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs b/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs index f7b808168c..ca64394d96 100644 --- a/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs +++ b/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs @@ -119,7 +119,7 @@ private PlugInHost CreatePlugInHost(params Assembly[] assemblies) { var environment = new OmniSharpEnvironment(); var sharedTextWriter = new TestSharedTextWriter(this.TestOutput); - var msbuildLocator = MSBuildLocator.CreateStandAlone(this.LoggerFactory); + var msbuildLocator = MSBuildLocator.CreateStandAlone(this.LoggerFactory, allowMonoPaths: false); var serviceProvider = new TestServiceProvider(environment, this.LoggerFactory, sharedTextWriter, msbuildLocator, new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build()); var compositionHost = new CompositionHostBuilder(serviceProvider, environment, sharedTextWriter, NullEventEmitter.Instance) .WithAssemblies(assemblies) diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index cdb5ed476f..7f08e060f0 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -115,7 +115,7 @@ public static OmniSharpTestHost Create(string path = null, ITestOutputHelper tes var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); var loggerFactory = new LoggerFactory().AddXunit(testOutput); var sharedTextWriter = new TestSharedTextWriter(testOutput); - var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory); + var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory, allowMonoPaths: false); var serviceProvider = new TestServiceProvider(environment, loggerFactory, sharedTextWriter, msbuildLocator, configuration); From 06a41c38c9b8fe997926631b5ffceb870012918e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Oct 2017 13:50:07 -0700 Subject: [PATCH 06/17] Set 'CscToolPath' to OmniSharp's local MSBuild on Mono --- .../Discovery/MSBuildInstanceProvider.cs | 40 ++++++++++++++++++- .../Providers/MonoInstanceProvider.cs | 7 ++-- .../Providers/StandAloneInstanceProvider.cs | 38 +----------------- 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs index 038dadd2f9..1dec63f6c9 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs @@ -1,4 +1,6 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; +using System.IO; using Microsoft.Extensions.Logging; namespace OmniSharp.MSBuild.Discovery @@ -13,5 +15,41 @@ protected MSBuildInstanceProvider(ILoggerFactory loggerFactory) } public abstract ImmutableArray GetInstances(); + + protected static string FindLocalMSBuildDirectory() + { + // If OmniSharp is running normally, MSBuild is located in an 'msbuild' folder beneath OmniSharp.exe. + var msbuildDirectory = Path.Combine(AppContext.BaseDirectory, "msbuild"); + + if (!Directory.Exists(msbuildDirectory)) + { + // If the 'msbuild' folder does not exists beneath "OmniSharp.exe", this is likely a development scenario, + // such as debugging or running unit tests. In that case, we try to locate the ".msbuild" folder at the + // solution level. + msbuildDirectory = FindLocalMSBuildFromSolution(); + } + + return msbuildDirectory; + } + + private static string FindLocalMSBuildFromSolution() + { + // Try to locate the .msbuild folder by search for the OmniSharp solution relative to the current folder. + var current = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(current, "OmniSharp.sln"))) + { + current = Path.GetDirectoryName(current); + if (Path.GetPathRoot(current) == current) + { + break; + } + } + + var result = Path.Combine(current, ".msbuild"); + + return Directory.Exists(result) + ? result + : null; + } } } diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 6a588c13ab..c4e004d682 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -34,10 +34,11 @@ public override ImmutableArray GetInstances() var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); - var roslynPath = Path.Combine(toolsPath, "Roslyn"); - if (Directory.Exists(roslynPath)) + var localMSBuildPath = FindLocalMSBuildDirectory(); + if (localMSBuildPath != null) { - propertyOverrides.Add("CscToolPath", roslynPath); + var localRoslynPath = Path.Combine(localMSBuildPath, "Roslyn"); + propertyOverrides.Add("CscToolPath", localRoslynPath); propertyOverrides.Add("CscToolExe", "csc.exe"); } diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs index 73db3facf4..929bbc4781 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs @@ -18,7 +18,7 @@ public StandAloneInstanceProvider(ILoggerFactory loggerFactory, bool allowMonoPa public override ImmutableArray GetInstances() { - var path = FindMSBuildDirectory(); + var path = FindLocalMSBuildDirectory(); if (path == null) { return ImmutableArray.Empty; @@ -61,42 +61,6 @@ public override ImmutableArray GetInstances() setMSBuildExePathVariable: true)); } - private static string FindMSBuildDirectory() - { - // If OmniSharp is running normally, MSBuild is located in an 'msbuild' folder beneath OmniSharp.exe. - var msbuildDirectory = Path.Combine(AppContext.BaseDirectory, "msbuild"); - - if (!Directory.Exists(msbuildDirectory)) - { - // If the 'msbuild' folder does not exists beneath "OmniSharp.exe", this is likely a development scenario, - // such as debugging or running unit tests. In that case, we try to locate the ".msbuild" folder at the - // solution level. - msbuildDirectory = FindLocalMSBuildFromSolution(); - } - - return msbuildDirectory; - } - - private static string FindLocalMSBuildFromSolution() - { - // Try to locate the .msbuild folder by search for the OmniSharp solution relative to the current folder. - var current = AppContext.BaseDirectory; - while (!File.Exists(Path.Combine(current, "OmniSharp.sln"))) - { - current = Path.GetDirectoryName(current); - if (Path.GetPathRoot(current) == current) - { - break; - } - } - - var result = Path.Combine(current, ".msbuild"); - - return Directory.Exists(result) - ? result - : null; - } - private static bool TryGetMonoXBuildPath(out string path) { path = null; From 4305d283c68fa6a529b583596e16a42d9881e0bd Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Oct 2017 13:52:56 -0700 Subject: [PATCH 07/17] Log MSBuild property overrides --- src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs index dbb6c36ed5..62d47b3b47 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs @@ -88,7 +88,15 @@ public void RegisterInstance(MSBuildInstance instance) } } - _logger.LogInformation($"Registered MSBuild instance: {instance}"); + var builder = new StringBuilder(); + builder.Append($"Registered MSBuild instance: {instance}"); + + foreach (var kvp in instance.PropertyOverrides) + { + builder.Append($"{Environment.NewLine} {kvp.Key} = {kvp.Value}"); + } + + _logger.LogInformation(builder.ToString()); } private Assembly Resolve(object sender, ResolveEventArgs e) From ad85cd64624756bf02bd9bc582b4a5adfee5cce9 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 25 Oct 2017 14:20:41 -0700 Subject: [PATCH 08/17] Use correct path to MSBuild Roslyn folder on Mono --- .../MSBuild/Discovery/Providers/MonoInstanceProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index c4e004d682..d4f62bd7e7 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -37,7 +37,7 @@ public override ImmutableArray GetInstances() var localMSBuildPath = FindLocalMSBuildDirectory(); if (localMSBuildPath != null) { - var localRoslynPath = Path.Combine(localMSBuildPath, "Roslyn"); + var localRoslynPath = Path.Combine(localMSBuildPath, "15.0", "Bin", "Roslyn"); propertyOverrides.Add("CscToolPath", localRoslynPath); propertyOverrides.Add("CscToolExe", "csc.exe"); } From 23d32e20fd03223f6ddb7d4024b5618c6c2b3341 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 26 Oct 2017 09:53:41 -0700 Subject: [PATCH 09/17] MSBuild discovery on Mono should only succeed if Mono >= 5.2.0 is located --- .../Utilities/PlatformHelper.cs | 26 +++++++++++++++++++ .../Providers/MonoInstanceProvider.cs | 8 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/OmniSharp.Abstractions/Utilities/PlatformHelper.cs b/src/OmniSharp.Abstractions/Utilities/PlatformHelper.cs index 36f3978251..cc17979ec7 100644 --- a/src/OmniSharp.Abstractions/Utilities/PlatformHelper.cs +++ b/src/OmniSharp.Abstractions/Utilities/PlatformHelper.cs @@ -60,6 +60,32 @@ private static string RealPath(string path) return result; } + public static Version GetMonoVersion() + { + var output = ProcessHelper.RunAndCaptureOutput("mono", "--version"); + if (output == null) + { + return null; + } + + // The mono --version text contains several lines. We'll just walk through the first line, + // word by word, until we find a word that parses as a version number. Normally, this should + // be the *fifth* word. E.g. "Mono JIT compiler version 4.8.0" + + var lines = output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + var words = lines[0].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var word in words) + { + if (Version.TryParse(word, out var version)) + { + return version; + } + } + + return null; + } + public static string GetMonoRuntimePath() { if (IsWindows) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index d4f62bd7e7..d1697d73d3 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -26,6 +26,14 @@ public override ImmutableArray GetInstances() return ImmutableArray.Empty; } + // Double-check Mono version. MSBuild support in versions earlier than 5.2.0 is + // too experimental to use. + var monoVersion = PlatformHelper.GetMonoVersion(); + if (monoVersion == null || monoVersion < new Version("5.2.0")) + { + return ImmutableArray.Empty; + } + var toolsPath = Path.Combine(path, "15.0", "bin"); if (!Directory.Exists(toolsPath)) { From c9488a21ed467aa3f45eef6decef94f673b4ebfe Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 26 Oct 2017 09:59:59 -0700 Subject: [PATCH 10/17] Introduce helper for 'ImmutableArray.Empty' --- .../MSBuild/Discovery/MSBuildInstanceProvider.cs | 1 + .../Discovery/Providers/DevConsoleInstanceProvider.cs | 6 +++--- .../MSBuild/Discovery/Providers/MonoInstanceProvider.cs | 8 ++++---- .../Discovery/Providers/StandAloneInstanceProvider.cs | 2 +- .../Discovery/Providers/VisualStudioInstanceProvider.cs | 6 +++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs index 1dec63f6c9..71f3324e86 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs @@ -8,6 +8,7 @@ namespace OmniSharp.MSBuild.Discovery internal abstract class MSBuildInstanceProvider { protected readonly ILogger Logger; + protected static readonly ImmutableArray NoInstances = ImmutableArray.Empty; protected MSBuildInstanceProvider(ILoggerFactory loggerFactory) { diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs index 20c85bf463..c8da108dd2 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs @@ -17,20 +17,20 @@ public override ImmutableArray GetInstances() { if (!PlatformHelper.IsWindows) { - return ImmutableArray.Empty; + return NoInstances; } var path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); if (string.IsNullOrEmpty(path)) { - return ImmutableArray.Empty; + return NoInstances; } var toolsPath = Path.Combine(path, "MSBuild", "15.0", "Bin"); if (!Directory.Exists(toolsPath)) { - return ImmutableArray.Empty; + return NoInstances; } var versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index d1697d73d3..5be5730469 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -17,13 +17,13 @@ public override ImmutableArray GetInstances() { if (!PlatformHelper.IsMono) { - return ImmutableArray.Empty; + return NoInstances; } var path = PlatformHelper.GetMonoMSBuildDirPath(); if (path == null) { - return ImmutableArray.Empty; + return NoInstances; } // Double-check Mono version. MSBuild support in versions earlier than 5.2.0 is @@ -31,13 +31,13 @@ public override ImmutableArray GetInstances() var monoVersion = PlatformHelper.GetMonoVersion(); if (monoVersion == null || monoVersion < new Version("5.2.0")) { - return ImmutableArray.Empty; + return NoInstances; } var toolsPath = Path.Combine(path, "15.0", "bin"); if (!Directory.Exists(toolsPath)) { - return ImmutableArray.Empty; + return NoInstances; } var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs index 929bbc4781..fa16c75fea 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs @@ -21,7 +21,7 @@ public override ImmutableArray GetInstances() var path = FindLocalMSBuildDirectory(); if (path == null) { - return ImmutableArray.Empty; + return NoInstances; } var extensionsPath = path; diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs index e6daa738ca..84b12e95bd 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs @@ -19,7 +19,7 @@ public override ImmutableArray GetInstances() { if (!PlatformHelper.IsWindows) { - return ImmutableArray.Empty; + return NoInstances; } try @@ -27,7 +27,7 @@ public override ImmutableArray GetInstances() var configuration = Interop.GetSetupConfiguration(); if (configuration == null) { - return ImmutableArray.Empty; + return NoInstances; } var builder = ImmutableArray.CreateBuilder(); @@ -87,7 +87,7 @@ private ImmutableArray LogExceptionAndReturnEmpty(Exception ex) { Logger.LogDebug(ex, "An exception was thrown while retrieving Visual Studio instances."); - return ImmutableArray.Empty; + return NoInstances; } } } From 74c21f7f24871130514ebcaf8f97e72554cea3de Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 26 Oct 2017 10:04:35 -0700 Subject: [PATCH 11/17] Use nameof in a few places --- .../MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs | 2 +- .../MSBuild/Discovery/Providers/MonoInstanceProvider.cs | 2 +- .../MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs index c8da108dd2..a8c4398787 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs @@ -44,7 +44,7 @@ public override ImmutableArray GetInstances() return ImmutableArray.Create( new MSBuildInstance( - "DEVCONSOLE", + nameof(DiscoveryType.DeveloperConsole), toolsPath, version, DiscoveryType.DeveloperConsole)); diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 5be5730469..e9862f94e2 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -52,7 +52,7 @@ public override ImmutableArray GetInstances() return ImmutableArray.Create( new MSBuildInstance( - "Mono", + nameof(DiscoveryType.Mono), toolsPath, new Version(15, 0), DiscoveryType.Mono, diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs index fa16c75fea..f0cb645681 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs @@ -53,7 +53,7 @@ public override ImmutableArray GetInstances() return ImmutableArray.Create( new MSBuildInstance( - "STANDALONE", + nameof(DiscoveryType.StandAlone), toolsPath, new Version(15, 0), DiscoveryType.StandAlone, From 14a273a2dd86f57861bdaef76f1dd82e9c359396 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 26 Oct 2017 10:25:07 -0700 Subject: [PATCH 12/17] Add a bit more logging to MSBuild discovery --- src/OmniSharp.Host/CompositionHostBuilder.cs | 5 +++++ .../Discovery/Providers/MonoInstanceProvider.cs | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/CompositionHostBuilder.cs b/src/OmniSharp.Host/CompositionHostBuilder.cs index 3bb646d3f2..dc0c37db94 100644 --- a/src/OmniSharp.Host/CompositionHostBuilder.cs +++ b/src/OmniSharp.Host/CompositionHostBuilder.cs @@ -62,6 +62,11 @@ public CompositionHost Build() { msbuildLocator.RegisterInstance(instance); } + else + { + var logger = loggerFactory.CreateLogger(); + logger.LogError("Could not locate MSBuild instance to register with OmniSharp"); + } config = config .WithProvider(MefValueProvider.From(_serviceProvider)) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index e9862f94e2..69a3bf885e 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -29,14 +29,22 @@ public override ImmutableArray GetInstances() // Double-check Mono version. MSBuild support in versions earlier than 5.2.0 is // too experimental to use. var monoVersion = PlatformHelper.GetMonoVersion(); - if (monoVersion == null || monoVersion < new Version("5.2.0")) + if (monoVersion == null) { + Logger.LogDebug("Could not retrieve Mono version"); + return NoInstances; + } + + if (monoVersion < new Version("5.2.0")) + { + Logger.LogDebug($"Found Mono MSBuild but it could not be used because it is version {monoVersion} and in needs to be >= 5.2.0"); return NoInstances; } var toolsPath = Path.Combine(path, "15.0", "bin"); if (!Directory.Exists(toolsPath)) { + Logger.LogDebug($"Mono MSBuild could not be used because '{toolsPath}' does not exist."); return NoInstances; } From 7c4b7c870bde4e535a9d3659b44ddbe4a5c20ddc Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Oct 2017 03:25:22 -0700 Subject: [PATCH 13/17] Fix build to kill OmniSharp unix processes after running scripts --- scripts/runhelpers.cake | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/runhelpers.cake b/scripts/runhelpers.cake index dd9b51a42e..b965f5c964 100644 --- a/scripts/runhelpers.cake +++ b/scripts/runhelpers.cake @@ -249,6 +249,32 @@ private void KillProcessTree(Process process) } else { - process.Kill(); + foreach (var pid in GetUnixChildProcessIds(process.Id)) + { + Run("kill", pid.ToString()); + } + + Run("kill", process.Id.ToString()); } } + +int[] GetUnixChildProcessIds(int processId) +{ + var output = RunAndCaptureOutput("ps", "-A -o ppid,pid"); + var lines = output.Split(new[] { System.Environment.NewLine }, System.StringSplitOptions.RemoveEmptyEntries); + var childPIDs = new List(); + + foreach (var line in lines) + { + var pairs = line.Trim().Split(new[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries); + + int ppid; + if (int.TryParse(pairs[0].Trim(), out ppid) && ppid == processId) + { + childPIDs.Add(int.Parse(pairs[1].Trim())); + } + + } + + return childPIDs.ToArray(); +} \ No newline at end of file From f3f2b68e9ec1f065d40bb57c9dbb5ecf3e9534e0 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Oct 2017 03:26:36 -0700 Subject: [PATCH 14/17] Update Mono MSBuild discovery to only succeed if OmniSharp is launched on the installed Mono --- .../Providers/MonoInstanceProvider.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 69a3bf885e..42c3583f85 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using Microsoft.Extensions.Logging; using OmniSharp.Utilities; @@ -20,6 +21,32 @@ public override ImmutableArray GetInstances() return NoInstances; } + // Don't resolve to MSBuild assemblies under the installed Mono unless OmniSharp + // is actually running under the installed Mono. + var monoRuntimePath = PlatformHelper.GetMonoRuntimePath(); + if (monoRuntimePath == null) + { + Logger.LogDebug("Could not retrieve Mono runtime path"); + return NoInstances; + } + + string processFileName; + try + { + processFileName = Process.GetCurrentProcess().MainModule.FileName; + } + catch (Exception ex) + { + Logger.LogDebug(ex, "Failed to retrieve current process file name"); + return NoInstances; + } + + if (!string.Equals(processFileName, monoRuntimePath, StringComparison.OrdinalIgnoreCase)) + { + Logger.LogDebug("Can't use installed Mono because OmniSharp isn't running on it"); + return NoInstances; + } + var path = PlatformHelper.GetMonoMSBuildDirPath(); if (path == null) { From 1c5f21f3095eb4b79172366a9b6e6c02cf89dafb Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Oct 2017 03:42:41 -0700 Subject: [PATCH 15/17] Increase indent slightly in output --- src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs index 62d47b3b47..10d96a4d88 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs @@ -93,7 +93,7 @@ public void RegisterInstance(MSBuildInstance instance) foreach (var kvp in instance.PropertyOverrides) { - builder.Append($"{Environment.NewLine} {kvp.Key} = {kvp.Value}"); + builder.Append($"{Environment.NewLine} {kvp.Key} = {kvp.Value}"); } _logger.LogInformation(builder.ToString()); From 7f263dc41ea9ad8b72edd473eaf4c7b00d7eccac Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Oct 2017 08:47:07 -0700 Subject: [PATCH 16/17] Use IAssemblyLoader.LoadFrom(...) --- src/OmniSharp.Host/CompositionHostBuilder.cs | 5 ++++- .../MSBuild/Discovery/MSBuildLocator.cs | 15 +++++++++------ .../EndpointMiddlewareFacts.cs | 4 +--- tests/TestUtility/OmniSharpTestHost.cs | 4 +--- tests/TestUtility/TestServiceProvider.cs | 9 ++++++--- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/OmniSharp.Host/CompositionHostBuilder.cs b/src/OmniSharp.Host/CompositionHostBuilder.cs index dc0c37db94..65967b5818 100644 --- a/src/OmniSharp.Host/CompositionHostBuilder.cs +++ b/src/OmniSharp.Host/CompositionHostBuilder.cs @@ -115,7 +115,10 @@ public static IServiceProvider CreateDefaultServiceProvider(IConfiguration confi services.AddOptions(); // MSBuild - services.AddSingleton(sp => MSBuildLocator.CreateDefault(sp.GetService())); + services.AddSingleton(sp => + MSBuildLocator.CreateDefault( + loggerFactory: sp.GetService(), + assemblyLoader: sp.GetService())); // Setup the options from configuration services.Configure(configuration); diff --git a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs index 10d96a4d88..9d476fad22 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs @@ -5,6 +5,7 @@ using System.Text; using Microsoft.Extensions.Logging; using OmniSharp.MSBuild.Discovery.Providers; +using OmniSharp.Services; using OmniSharp.Utilities; namespace OmniSharp.MSBuild.Discovery @@ -18,14 +19,16 @@ internal class MSBuildLocator : DisposableObject, IMSBuildLocator "Microsoft.Build.Utilities.Core"); private readonly ILogger _logger; + private readonly IAssemblyLoader _assemblyLoader; private readonly ImmutableArray _providers; private MSBuildInstance _registeredInstance; public MSBuildInstance RegisteredInstance => _registeredInstance; - private MSBuildLocator(ILoggerFactory loggerFactory, ImmutableArray providers) + private MSBuildLocator(ILoggerFactory loggerFactory, IAssemblyLoader assemblyLoader, ImmutableArray providers) { _logger = loggerFactory.CreateLogger(); + _assemblyLoader = assemblyLoader; _providers = providers; } @@ -38,16 +41,16 @@ protected override void DisposeCore(bool disposing) } } - public static MSBuildLocator CreateDefault(ILoggerFactory loggerFactory) - => new MSBuildLocator(loggerFactory, + public static MSBuildLocator CreateDefault(ILoggerFactory loggerFactory, IAssemblyLoader assemblyLoader) + => new MSBuildLocator(loggerFactory, assemblyLoader, ImmutableArray.Create( new DevConsoleInstanceProvider(loggerFactory), new VisualStudioInstanceProvider(loggerFactory), new MonoInstanceProvider(loggerFactory), new StandAloneInstanceProvider(loggerFactory, allowMonoPaths: true))); - public static MSBuildLocator CreateStandAlone(ILoggerFactory loggerFactory, bool allowMonoPaths) - => new MSBuildLocator(loggerFactory, + public static MSBuildLocator CreateStandAlone(ILoggerFactory loggerFactory, IAssemblyLoader assemblyLoader, bool allowMonoPaths) + => new MSBuildLocator(loggerFactory, assemblyLoader, ImmutableArray.Create( new StandAloneInstanceProvider(loggerFactory, allowMonoPaths))); @@ -109,7 +112,7 @@ private Assembly Resolve(object sender, ResolveEventArgs e) { var assemblyPath = Path.Combine(_registeredInstance.MSBuildPath, assemblyName.Name + ".dll"); var result = File.Exists(assemblyPath) - ? Assembly.LoadFrom(assemblyPath) + ? _assemblyLoader.LoadFrom(assemblyPath) : null; if (result != null) diff --git a/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs b/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs index ca64394d96..d2503768b4 100644 --- a/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs +++ b/tests/OmniSharp.Http.Tests/EndpointMiddlewareFacts.cs @@ -18,7 +18,6 @@ using OmniSharp.Models.GotoDefinition; using OmniSharp.Models.UpdateBuffer; using OmniSharp.Models.WorkspaceInformation; -using OmniSharp.MSBuild.Discovery; using OmniSharp.Services; using OmniSharp.Utilities; using TestUtility; @@ -119,8 +118,7 @@ private PlugInHost CreatePlugInHost(params Assembly[] assemblies) { var environment = new OmniSharpEnvironment(); var sharedTextWriter = new TestSharedTextWriter(this.TestOutput); - var msbuildLocator = MSBuildLocator.CreateStandAlone(this.LoggerFactory, allowMonoPaths: false); - var serviceProvider = new TestServiceProvider(environment, this.LoggerFactory, sharedTextWriter, msbuildLocator, new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build()); + var serviceProvider = new TestServiceProvider(environment, this.LoggerFactory, sharedTextWriter, new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build()); var compositionHost = new CompositionHostBuilder(serviceProvider, environment, sharedTextWriter, NullEventEmitter.Instance) .WithAssemblies(assemblies) .Build(); diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 7f08e060f0..582a83091b 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -14,7 +14,6 @@ using OmniSharp.Eventing; using OmniSharp.Mef; using OmniSharp.MSBuild; -using OmniSharp.MSBuild.Discovery; using OmniSharp.Roslyn.CSharp.Services; using OmniSharp.Services; using OmniSharp.Utilities; @@ -115,9 +114,8 @@ public static OmniSharpTestHost Create(string path = null, ITestOutputHelper tes var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); var loggerFactory = new LoggerFactory().AddXunit(testOutput); var sharedTextWriter = new TestSharedTextWriter(testOutput); - var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory, allowMonoPaths: false); - var serviceProvider = new TestServiceProvider(environment, loggerFactory, sharedTextWriter, msbuildLocator, configuration); + var serviceProvider = new TestServiceProvider(environment, loggerFactory, sharedTextWriter, configuration); var compositionHost = new CompositionHostBuilder(serviceProvider, environment, sharedTextWriter, NullEventEmitter.Instance) .WithAssemblies(s_lazyAssemblies.Value) diff --git a/tests/TestUtility/TestServiceProvider.cs b/tests/TestUtility/TestServiceProvider.cs index 234c13d1af..e42dc77674 100644 --- a/tests/TestUtility/TestServiceProvider.cs +++ b/tests/TestUtility/TestServiceProvider.cs @@ -23,7 +23,6 @@ public TestServiceProvider( IOmniSharpEnvironment environment, ILoggerFactory loggerFactory, ISharedTextWriter sharedTextWriter, - IMSBuildLocator msbuildLocator, IConfiguration configuration) { _logger = loggerFactory.CreateLogger(); @@ -35,10 +34,14 @@ public TestServiceProvider( Enumerable.Empty>() ); + var assemblyLoader = new AssemblyLoader(loggerFactory); + var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory, assemblyLoader, allowMonoPaths: false); + var memoryCache = new MemoryCache(new MemoryCacheOptions()); + _services[typeof(ILoggerFactory)] = loggerFactory; _services[typeof(IOmniSharpEnvironment)] = environment; - _services[typeof(IAssemblyLoader)] = new AssemblyLoader(loggerFactory); - _services[typeof(IMemoryCache)] = new MemoryCache(new MemoryCacheOptions()); + _services[typeof(IAssemblyLoader)] = assemblyLoader; + _services[typeof(IMemoryCache)] = memoryCache; _services[typeof(ISharedTextWriter)] = sharedTextWriter; _services[typeof(IMSBuildLocator)] = msbuildLocator; } From 9bdf658de0c833a08ee49854b4eca9c101e575ec Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 27 Oct 2017 08:57:00 -0700 Subject: [PATCH 17/17] Add more detailed comment about Mono MSBuild discovery --- .../Discovery/Providers/MonoInstanceProvider.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs index 42c3583f85..6247503056 100644 --- a/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -21,8 +21,15 @@ public override ImmutableArray GetInstances() return NoInstances; } - // Don't resolve to MSBuild assemblies under the installed Mono unless OmniSharp - // is actually running under the installed Mono. + // Don't try to resolve to MSBuild assemblies under the installed Mono path unless OmniSharp + // is actually running on the installed Mono runtime. The problem is that, when running standalone + // on its own embedded Mono runtime, OmniSharp carries it's own "GAC" of dependencies. + // And, loading Microsoft.Build.* assemblies from the installed Mono location might have different + // dependencies that aren't included in OmniSharp's GAC. This can result in strange failures during + // design-time build that are difficult to diagnose. However, if OmniSharp is actually running on the + // installed Mono runtime, Mono's GAC will be used and dependencies will be properly located. So, in + // that case we can use the Microsoft.Build.* assemblies located under the installed Mono path. + var monoRuntimePath = PlatformHelper.GetMonoRuntimePath(); if (monoRuntimePath == null) {