diff --git a/src/MSBuildLocator.Tests/QueryOptionsTests.cs b/src/MSBuildLocator.Tests/QueryOptionsTests.cs index c9fb62ea..97918345 100644 --- a/src/MSBuildLocator.Tests/QueryOptionsTests.cs +++ b/src/MSBuildLocator.Tests/QueryOptionsTests.cs @@ -19,11 +19,13 @@ public void CombinationTest() new VisualStudioInstance("A7D13212839F4997AF65F7F74618EBAB", "none", new Version(1, 0), DiscoveryType.DeveloperConsole), new VisualStudioInstance("DBF404629ED2408182263033F0358A1E", "none", new Version(1, 0), DiscoveryType.VisualStudioSetup), new VisualStudioInstance("98B38291074547D89A86758A26621EF3", "none", new Version(1, 0), DiscoveryType.DotNetSdk), + new VisualStudioInstance("98B38291074547D89A86758A26621ABC", "none", new Version(1, 0), DiscoveryType.Mono) }; VerifyQueryResults(instances, DiscoveryType.DeveloperConsole, "A7D13212839F4997AF65F7F74618EBAB"); VerifyQueryResults(instances, DiscoveryType.VisualStudioSetup, "DBF404629ED2408182263033F0358A1E"); VerifyQueryResults(instances, DiscoveryType.DotNetSdk, "98B38291074547D89A86758A26621EF3"); + VerifyQueryResults(instances, DiscoveryType.Mono, "98B38291074547D89A86758A26621ABC"); VerifyQueryResults(instances, DiscoveryType.DeveloperConsole | DiscoveryType.DotNetSdk, "A7D13212839F4997AF65F7F74618EBAB", "98B38291074547D89A86758A26621EF3"); VerifyQueryResults(instances, DiscoveryType.DeveloperConsole | DiscoveryType.VisualStudioSetup | DiscoveryType.DotNetSdk, "A7D13212839F4997AF65F7F74618EBAB", "DBF404629ED2408182263033F0358A1E", "98B38291074547D89A86758A26621EF3"); VerifyQueryResults(instances, DiscoveryType.VisualStudioSetup | DiscoveryType.DotNetSdk, "DBF404629ED2408182263033F0358A1E", "98B38291074547D89A86758A26621EF3"); @@ -38,6 +40,7 @@ public void MultipleResultsTest() new VisualStudioInstance("EFE7BA53882F4214BBD8447EC0683FC8", "none", new Version(1, 0), DiscoveryType.VisualStudioSetup), new VisualStudioInstance("5F932E55D1B84DCB82EE97B47EB531EB", "none", new Version(1, 0), DiscoveryType.VisualStudioSetup), new VisualStudioInstance("78C4AEBB58AE44ACA0FA1E78B8306E2A", "none", new Version(1, 0), DiscoveryType.VisualStudioSetup), + new VisualStudioInstance("98B38291074547D89A86758A26621ABC", "none", new Version(1, 0), DiscoveryType.Mono) }; VerifyQueryResults(instances, DiscoveryType.VisualStudioSetup, "EFE7BA53882F4214BBD8447EC0683FC8", "5F932E55D1B84DCB82EE97B47EB531EB", "78C4AEBB58AE44ACA0FA1E78B8306E2A"); @@ -76,4 +79,4 @@ private void VerifyQueryResults(IEnumerable instances, Dis } } } -} \ No newline at end of file +} diff --git a/src/MSBuildLocator/DiscoveryType.cs b/src/MSBuildLocator/DiscoveryType.cs index 7a4ad585..b50b070e 100644 --- a/src/MSBuildLocator/DiscoveryType.cs +++ b/src/MSBuildLocator/DiscoveryType.cs @@ -25,6 +25,11 @@ public enum DiscoveryType /// /// Discovery via dotnet --info. /// - DotNetSdk = 4 + DotNetSdk = 4, + + /// + /// Discovery via Mono path testing. + /// + Mono = 8 } -} \ No newline at end of file +} diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs new file mode 100644 index 00000000..8fddd9b1 --- /dev/null +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Build.Locator +{ + public static partial class MSBuildLocator + { + private static readonly string s_monoMSBuildDll_Current_RelativePath = Path.Combine ("lib", "mono", "msbuild", "Current", "bin", "MSBuild.dll"); + private static readonly string s_monoMSBuildDll_15_0_RelativePath = Path.Combine ("lib", "mono", "msbuild", "15.0", "bin", "MSBuild.dll"); + private static readonly string s_monoOSXBasePath = "/Library/Frameworks/Mono.framework/Versions"; + + internal static IEnumerable GetMonoMSBuildInstances() + { + // $prefix/lib/mono/4.5/mscorlib.dll + var runningMonoFullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof (object).Assembly.Location), "..", "..", "..")); + if (TryGetValidMonoVersion(runningMonoFullPath, out var version)) + { + yield return new VisualStudioInstance("Mono", runningMonoFullPath, version, DiscoveryType.Mono); + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // Returning just one instance on !osx, because we cannot determine + // where other mono installations might be + yield break; + } + + foreach(var dirPath in Directory.EnumerateDirectories(s_monoOSXBasePath)) + { + if (string.Equals(Path.GetFileName(dirPath), "Current") || // skip the `Current` symlink + string.Equals(Path.GetFullPath(dirPath), runningMonoFullPath)) // and the running mono version returned above + { + continue; + } + + if (TryGetValidMonoVersion(dirPath, out version)) + { + yield return new VisualStudioInstance("Mono", dirPath + '/', version, DiscoveryType.Mono); + } + } + + bool TryGetValidMonoVersion(string path, out Version ver) + { + ver = null; + if (!File.Exists(Path.Combine(path, s_monoMSBuildDll_Current_RelativePath)) && + !File.Exists(Path.Combine(path, s_monoMSBuildDll_15_0_RelativePath))) + { + return false; + } + + if (TryGetMonoVersionFromMonoBinary(path, out ver) || Version.TryParse(Path.GetFileName(path), out ver)) + { + return true; + } + + // The path has a valid mono, but we can't find the version + // so, let's return the instance at least but with version 0.0.0 + ver = new Version(0, 0, 0); + return true; + } + + bool TryGetMonoVersionFromMonoBinary(string monoPrefixPath, out Version ver) + { + ver = null; + try + { + using (var p = new Process()) + { + p.StartInfo = new ProcessStartInfo + { + FileName = Path.Combine(monoPrefixPath, "bin", "mono"), + Arguments = "--version=number", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + // Don't pollute caller's console + p.OutputDataReceived += (s, e) => {}; + p.ErrorDataReceived += (s, e) => {}; + + p.Start(); + p.WaitForExit(); + + var stdout_str = p.StandardOutput.ReadToEnd(); + return Version.TryParse(stdout_str, out ver); + } + } + catch (Win32Exception) + { } + + return false; + } + } + + // Taken from MSBuild/NativeMethodsShared + private static readonly object IsRunningOnMonoLock = new object(); + + private static bool? _isRunningOnMono; + + /// + /// Gets a flag indicating if we are running under MONO + /// + internal static bool IsRunningOnMono + { + get + { + if (_isRunningOnMono.HasValue) return _isRunningOnMono.Value; + + lock (IsRunningOnMonoLock) + { + if (_isRunningOnMono == null) + { + // There could be potentially expensive TypeResolve events, so cache IsMono. + _isRunningOnMono = Type.GetType("Mono.Runtime") != null; + } + } + + return _isRunningOnMono.Value; + } + } + } +} diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index e2fbab52..5f29795b 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -15,7 +15,7 @@ namespace Microsoft.Build.Locator { - public static class MSBuildLocator + public static partial class MSBuildLocator { private const string MSBuildPublicKeyToken = "b03f5f7f11d50a3a"; @@ -139,13 +139,20 @@ public static void RegisterInstance(VisualStudioInstance instance) /// public static void RegisterMSBuildPath(string msbuildPath) { - RegisterMSBuildPath(new string[] { + var searchPaths = new List + { msbuildPath + }; + #if NET46 + if (!IsRunningOnMono) + { // Finds and loads NuGet assemblies if msbuildPath is in a VS installation - , Path.GetFullPath(Path.Combine(msbuildPath, "..", "..", "..", "Common7", "IDE", "CommonExtensions", "Microsoft", "NuGet")) + searchPaths.Add(Path.GetFullPath(Path.Combine(msbuildPath, "..", "..", "..", "Common7", "IDE", "CommonExtensions", "Microsoft", "NuGet"))); + } #endif - }); + + RegisterMSBuildPath(searchPaths.ToArray()); } /// @@ -179,10 +186,10 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) throw new AggregateException("Search paths for MSBuild assemblies cannot be null and must contain non-whitespace characters.", nullOrWhiteSpaceExceptions); } - IEnumerable paths = msbuildSearchPaths.Where(path => !Directory.Exists(path)); - if (paths.FirstOrDefault() == null) + IEnumerable nonExistantPaths = msbuildSearchPaths.Where(path => !Directory.Exists(path)); + if (nonExistantPaths.Any()) { - throw new AggregateException($"A directory or directories in \"{nameof(msbuildSearchPaths)}\" do not exist", paths.Select(path => new ArgumentException($"Directory \"{path}\" does not exist", nameof(msbuildSearchPaths)))); + throw new AggregateException($"A directory or directories in \"{nameof(msbuildSearchPaths)}\" do not exist", nonExistantPaths.Select(path => new ArgumentException($"Directory \"{path}\" does not exist", nameof(msbuildSearchPaths)))); } if (!CanRegister) @@ -328,6 +335,14 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) private static IEnumerable GetInstances(VisualStudioInstanceQueryOptions options) { + if (options.DiscoveryTypes.HasFlag(DiscoveryType.Mono) && IsRunningOnMono) + { + foreach(var instance in GetMonoMSBuildInstances()) + yield return instance; + + yield break; + } + #if NET46 var devConsole = GetDevConsoleInstance(); if (devConsole != null) diff --git a/src/MSBuildLocator/Microsoft.Build.Locator.csproj b/src/MSBuildLocator/Microsoft.Build.Locator.csproj index 712f67c2..5fe34191 100644 --- a/src/MSBuildLocator/Microsoft.Build.Locator.csproj +++ b/src/MSBuildLocator/Microsoft.Build.Locator.csproj @@ -26,6 +26,7 @@ + Never diff --git a/src/MSBuildLocator/VisualStudioInstance.cs b/src/MSBuildLocator/VisualStudioInstance.cs index e8185dec..9e40c22f 100644 --- a/src/MSBuildLocator/VisualStudioInstance.cs +++ b/src/MSBuildLocator/VisualStudioInstance.cs @@ -30,6 +30,14 @@ internal VisualStudioInstance(string name, string path, Version version, Discove case DiscoveryType.DotNetSdk: MSBuildPath = VisualStudioRootPath; break; + case DiscoveryType.Mono: + { + var pathCurrent = Path.Combine(VisualStudioRootPath, "lib", "mono", "msbuild", "Current", "bin"); + var path15_0 = Path.Combine(VisualStudioRootPath, "lib", "mono", "msbuild", "15.0", "bin"); + + MSBuildPath = File.Exists(pathCurrent) ? pathCurrent : path15_0; + break; + } default: throw new ArgumentOutOfRangeException(nameof(discoveryType), discoveryType, null); } @@ -60,4 +68,4 @@ internal VisualStudioInstance(string name, string path, Version version, Discove /// public DiscoveryType DiscoveryType { get; } } -} \ No newline at end of file +} diff --git a/src/MSBuildLocator/VisualStudioInstanceQueryOptions.cs b/src/MSBuildLocator/VisualStudioInstanceQueryOptions.cs index 3c257d85..0f3d0d81 100644 --- a/src/MSBuildLocator/VisualStudioInstanceQueryOptions.cs +++ b/src/MSBuildLocator/VisualStudioInstanceQueryOptions.cs @@ -17,7 +17,7 @@ public class VisualStudioInstanceQueryOptions { DiscoveryTypes = #if FEATURE_VISUALSTUDIOSETUP - DiscoveryType.DeveloperConsole | DiscoveryType.VisualStudioSetup + DiscoveryType.DeveloperConsole | DiscoveryType.VisualStudioSetup | DiscoveryType.Mono #endif #if NETCOREAPP DiscoveryType.DotNetSdk