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/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/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 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.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/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..65967b5818 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,24 @@ 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); + } + else + { + var logger = loggerFactory.CreateLogger(); + logger.LogError("Could not locate MSBuild instance to register with OmniSharp"); + } + config = config .WithProvider(MefValueProvider.From(_serviceProvider)) .WithProvider(MefValueProvider.From(fileSystemWatcher)) @@ -69,11 +79,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 +114,12 @@ public static IServiceProvider CreateDefaultServiceProvider(IConfiguration confi services.AddSingleton(); services.AddOptions(); + // MSBuild + services.AddSingleton(sp => + MSBuildLocator.CreateDefault( + loggerFactory: sp.GetService(), + assemblyLoader: 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..71f3324e86 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.Extensions.Logging; + +namespace OmniSharp.MSBuild.Discovery +{ + internal abstract class MSBuildInstanceProvider + { + protected readonly ILogger Logger; + protected static readonly ImmutableArray NoInstances = ImmutableArray.Empty; + + protected MSBuildInstanceProvider(ILoggerFactory loggerFactory) + { + Logger = loggerFactory.CreateLogger(this.GetType()); + } + + 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/MSBuildLocator.cs b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs new file mode 100644 index 0000000000..9d476fad22 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/MSBuildLocator.cs @@ -0,0 +1,161 @@ +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.Services; +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 IAssemblyLoader _assemblyLoader; + private readonly ImmutableArray _providers; + private MSBuildInstance _registeredInstance; + + public MSBuildInstance RegisteredInstance => _registeredInstance; + + private MSBuildLocator(ILoggerFactory loggerFactory, IAssemblyLoader assemblyLoader, ImmutableArray providers) + { + _logger = loggerFactory.CreateLogger(); + _assemblyLoader = assemblyLoader; + _providers = providers; + } + + protected override void DisposeCore(bool disposing) + { + if (_registeredInstance != null) + { + AppDomain.CurrentDomain.AssemblyResolve -= Resolve; + _registeredInstance = null; + } + } + + 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, IAssemblyLoader assemblyLoader, bool allowMonoPaths) + => new MSBuildLocator(loggerFactory, assemblyLoader, + ImmutableArray.Create( + new StandAloneInstanceProvider(loggerFactory, allowMonoPaths))); + + 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."); + } + } + + 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) + { + 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) + ? _assemblyLoader.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..a8c4398787 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/DevConsoleInstanceProvider.cs @@ -0,0 +1,53 @@ +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 NoInstances; + } + + var path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); + + if (string.IsNullOrEmpty(path)) + { + return NoInstances; + } + + var toolsPath = Path.Combine(path, "MSBuild", "15.0", "Bin"); + if (!Directory.Exists(toolsPath)) + { + return NoInstances; + } + + 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( + 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 new file mode 100644 index 0000000000..6247503056 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/MonoInstanceProvider.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics; +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 NoInstances; + } + + // 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) + { + 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) + { + return NoInstances; + } + + // 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) + { + 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; + } + + var propertyOverrides = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); + + var localMSBuildPath = FindLocalMSBuildDirectory(); + if (localMSBuildPath != null) + { + var localRoslynPath = Path.Combine(localMSBuildPath, "15.0", "Bin", "Roslyn"); + propertyOverrides.Add("CscToolPath", localRoslynPath); + propertyOverrides.Add("CscToolExe", "csc.exe"); + } + + return ImmutableArray.Create( + new MSBuildInstance( + nameof(DiscoveryType.Mono), + toolsPath, + new Version(15, 0), + DiscoveryType.Mono, + propertyOverrides.ToImmutable())); + } + } +} 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..f0cb645681 --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/StandAloneInstanceProvider.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.Extensions.Logging; +using OmniSharp.Utilities; + +namespace OmniSharp.MSBuild.Discovery.Providers +{ + internal class StandAloneInstanceProvider : MSBuildInstanceProvider + { + private readonly bool _allowMonoPaths; + + public StandAloneInstanceProvider(ILoggerFactory loggerFactory, bool allowMonoPaths) + : base(loggerFactory) + { + _allowMonoPaths = allowMonoPaths; + } + + public override ImmutableArray GetInstances() + { + var path = FindLocalMSBuildDirectory(); + if (path == null) + { + return NoInstances; + } + + var extensionsPath = path; + var toolsPath = Path.Combine(extensionsPath, "15.0", "Bin"); + 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); + propertyOverrides.Add("CscToolExe", "csc.exe"); + + return ImmutableArray.Create( + new MSBuildInstance( + nameof(DiscoveryType.StandAlone), + toolsPath, + new Version(15, 0), + DiscoveryType.StandAlone, + propertyOverrides.ToImmutable(), + setMSBuildExePathVariable: true)); + } + + 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 new file mode 100644 index 0000000000..84b12e95bd --- /dev/null +++ b/src/OmniSharp.Host/MSBuild/Discovery/Providers/VisualStudioInstanceProvider.cs @@ -0,0 +1,93 @@ +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 NoInstances; + } + + try + { + var configuration = Interop.GetSetupConfiguration(); + if (configuration == null) + { + return NoInstances; + } + + 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. + 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); + + 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 NoInstances; + } + } +} 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..c5df0a5bb7 100644 --- a/src/OmniSharp.MSBuild/Extensions.cs +++ b/src/OmniSharp.MSBuild/Extensions.cs @@ -1,26 +1,31 @@ using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.Extensions.Logging; namespace OmniSharp.MSBuild { internal static class Extensions { - public static void AddPropertyIfNeeded(this Dictionary properties, ILogger logger, string name, string userOptionValue, string environmentValue) + public static void AddPropertyOverride( + this Dictionary properties, + string propertyName, + string userOverrideValue, + ImmutableDictionary propertyOverrides, + ILogger logger) { - if (!string.IsNullOrWhiteSpace(userOptionValue)) + var overrideValue = propertyOverrides.GetValueOrDefault(propertyName); + + if (!string.IsNullOrEmpty(userOverrideValue)) { // If the user set the option, we should use that. - properties.Add(name, userOptionValue); + properties.Add(propertyName, userOverrideValue); + logger.LogDebug($"'{propertyName}' set to '{userOverrideValue}' (user override)"); } - else if (!string.IsNullOrWhiteSpace(environmentValue)) + else if (!string.IsNullOrEmpty(overrideValue)) { // 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}"); + properties.Add(propertyName, overrideValue); + logger.LogDebug($"'{propertyName}' set to '{overrideValue}'"); } } } 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/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/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/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.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..582a83091b 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -111,17 +111,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 serviceProvider = new TestServiceProvider(environment, loggerFactory, sharedTextWriter, configuration); - if (!MSBuildEnvironment.IsInitialized) - { - MSBuildEnvironment.InitializeForTest(loggerFactory.CreateLogger()); - } - var compositionHost = new CompositionHostBuilder(serviceProvider, environment, sharedTextWriter, NullEventEmitter.Instance) .WithAssemblies(s_lazyAssemblies.Value) .Build(); diff --git a/tests/TestUtility/TestServiceProvider.cs b/tests/TestUtility/TestServiceProvider.cs index f4edfef31b..e42dc77674 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,11 @@ 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, + IConfiguration configuration) { _logger = loggerFactory.CreateLogger(); @@ -31,11 +34,16 @@ public TestServiceProvider(IOmniSharpEnvironment environment, ILoggerFactory log 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; } ~TestServiceProvider() diff --git a/tools/packages.config b/tools/packages.config index a062d1ffc8..00999c515d 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,7 +1,11 @@ + + + +