From 376fa254f848bcb22b559f747327ad1047028827 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Thu, 16 Jan 2020 20:08:32 -0500 Subject: [PATCH 01/16] Add support for Mono - on OSX, we get the version from the path - we return the version of currently running mono, as the first result - on linux/windows, we try to get the version from `mono --version` - if that fails, then we return the instance with `0.0.0.0` --- src/MSBuildLocator.Tests/QueryOptionsTests.cs | 5 +- src/MSBuildLocator/DiscoveryType.cs | 4 +- src/MSBuildLocator/MSBuildLocator.cs | 145 ++++++++++++++++++ src/MSBuildLocator/VisualStudioInstance.cs | 10 +- .../VisualStudioInstanceQueryOptions.cs | 2 +- 5 files changed, 162 insertions(+), 4 deletions(-) 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..c298981b 100644 --- a/src/MSBuildLocator/DiscoveryType.cs +++ b/src/MSBuildLocator/DiscoveryType.cs @@ -25,6 +25,8 @@ public enum DiscoveryType /// /// Discovery via dotnet --info. /// - DotNetSdk = 4 + DotNetSdk = 4, + + Mono = 8 } } \ No newline at end of file diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index e050ee79..0f5f8da3 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -3,10 +3,13 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading; #if NETCOREAPP @@ -27,6 +30,11 @@ public static class MSBuildLocator "Microsoft.Build.Utilities.Core" }; + 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"; + private static Lazy s_monoVersionRegex = new Lazy(() => new Regex("^Mono.*compiler version ([0-9\\.]*)")); + #if NET46 private static ResolveEventHandler s_registeredHandler; #else @@ -305,6 +313,14 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) private static IEnumerable GetInstances(VisualStudioInstanceQueryOptions options) { + if (IsRunningOnMono) + { + foreach(var instance in GetMonoMSBuildInstances()) + yield return instance; + + yield break; + } + #if NET46 var devConsole = GetDevConsoleInstance(); if (devConsole != null) @@ -323,6 +339,87 @@ private static IEnumerable GetInstances(VisualStudioInstan #endif } + static IEnumerable GetMonoMSBuildInstances () + { + // $prefix/lib/mono/4.5/mscorlib.dll + var runningMonoPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof (object).Assembly.Location), "..", "..", "..")); + if (TryGetValidMonoVersion (runningMonoPath, out var version)) + { + yield return new VisualStudioInstance("Mono", runningMonoPath, version, DiscoveryType.Mono); + } + + if (!IsOSX) + { + // Returning just one instance on !osx + yield break; + } + + foreach(var dirPath in Directory.EnumerateDirectories(s_monoOSXBasePath)) + { + if (string.Compare(Path.GetFileName (dirPath), "Current") == 0 || // skip the `Current` symlink + string.Compare(dirPath, runningMonoPath) == 0) // and the running mono version + { + 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 (Version.TryParse(Path.GetFileName(path), out ver) || TryGetMonoVersionFromMonoBinary(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 + { + var p = new Process (); + p.StartInfo.FileName = Path.Combine (monoPrefixPath, "bin", "mono"); + p.StartInfo.Arguments = "--version"; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + + // Don't pollute caller's console + p.OutputDataReceived += (s, e) => {}; + + p.Start (); + p.WaitForExit (); + + var stdout_str = p.StandardOutput.ReadToEnd (); + var match = s_monoVersionRegex.Value.Match(stdout_str); + if (match.Success) + { + return Version.TryParse(match.Groups[1].ToString(), out ver); + } + } catch (Win32Exception) { + } + + return false; + } + } + + + #if NET46 private static VisualStudioInstance GetDevConsoleInstance() { @@ -354,5 +451,53 @@ private static VisualStudioInstance GetDevConsoleInstance() return null; } #endif + + // 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 != null) return _isRunningOnMono.Value; + + lock (IsRunningOnMonoLock) + { + if (_isRunningOnMono == null) + { + // There could be potentially expensive TypeResolve events, so cache IsMono. + // Also, VS does not host Mono runtimes, so turn IsMono off when msbuild is running under VS + _isRunningOnMono = Type.GetType("Mono.Runtime") != null; + } + } + + return _isRunningOnMono.Value; + } + } + + // Taken from MSBuild/NativeMethodsShared + private static bool? _isOSX; + + /// + /// Gets a flag indicating if we are running under Mac OSX + /// + internal static bool IsOSX + { + get + { + if (!_isOSX.HasValue) + { + _isOSX = File.Exists("/usr/lib/libc.dylib"); + } + + return _isOSX.Value; + } + } + } } 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 From 3ec1c846b171bb4ba3ec3baef0e7f9526edd003a Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 17 Jan 2020 12:32:12 -0500 Subject: [PATCH 02/16] Address review comments --- src/MSBuildLocator/MSBuildLocator.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 0f5f8da3..91433304 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -33,7 +33,6 @@ public static 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"; - private static Lazy s_monoVersionRegex = new Lazy(() => new Regex("^Mono.*compiler version ([0-9\\.]*)")); #if NET46 private static ResolveEventHandler s_registeredHandler; @@ -313,7 +312,7 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) private static IEnumerable GetInstances(VisualStudioInstanceQueryOptions options) { - if (IsRunningOnMono) + if (options.DiscoveryTypes.HasFlag(DiscoveryType.Mono) && IsRunningOnMono) { foreach(var instance in GetMonoMSBuildInstances()) yield return instance; @@ -377,7 +376,7 @@ bool TryGetValidMonoVersion (string path, out Version ver) return false; } - if (Version.TryParse(Path.GetFileName(path), out ver) || TryGetMonoVersionFromMonoBinary(path, out ver)) + if (TryGetMonoVersionFromMonoBinary(path, out ver) || Version.TryParse(Path.GetFileName(path), out ver)) { return true; } @@ -395,22 +394,20 @@ bool TryGetMonoVersionFromMonoBinary(string monoPrefixPath, out Version ver) { var p = new Process (); p.StartInfo.FileName = Path.Combine (monoPrefixPath, "bin", "mono"); - p.StartInfo.Arguments = "--version"; + p.StartInfo.Arguments = "--version=number"; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.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 (); - var match = s_monoVersionRegex.Value.Match(stdout_str); - if (match.Success) - { - return Version.TryParse(match.Groups[1].ToString(), out ver); - } + return Version.TryParse(stdout_str, out ver); } catch (Win32Exception) { } @@ -464,14 +461,13 @@ internal static bool IsRunningOnMono { get { - if (_isRunningOnMono != null) return _isRunningOnMono.Value; + if (_isRunningOnMono.HasValue) return _isRunningOnMono.Value; lock (IsRunningOnMonoLock) { if (_isRunningOnMono == null) { // There could be potentially expensive TypeResolve events, so cache IsMono. - // Also, VS does not host Mono runtimes, so turn IsMono off when msbuild is running under VS _isRunningOnMono = Type.GetType("Mono.Runtime") != null; } } From 483787235decef7767bfe35b2ba438760b4fd9bd Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Thu, 10 Dec 2020 13:52:03 -0500 Subject: [PATCH 03/16] Update src/MSBuildLocator/MSBuildLocator.cs Co-authored-by: Joey Robichaud --- src/MSBuildLocator/MSBuildLocator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 91433304..f118b143 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -494,6 +494,5 @@ internal static bool IsOSX return _isOSX.Value; } } - } } From e51852c656993f3e956539ba6c85f15a4a3d2f1b Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Thu, 10 Dec 2020 13:52:14 -0500 Subject: [PATCH 04/16] Update src/MSBuildLocator/DiscoveryType.cs Co-authored-by: Joey Robichaud --- src/MSBuildLocator/DiscoveryType.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MSBuildLocator/DiscoveryType.cs b/src/MSBuildLocator/DiscoveryType.cs index c298981b..b50b070e 100644 --- a/src/MSBuildLocator/DiscoveryType.cs +++ b/src/MSBuildLocator/DiscoveryType.cs @@ -27,6 +27,9 @@ public enum DiscoveryType /// DotNetSdk = 4, + /// + /// Discovery via Mono path testing. + /// Mono = 8 } -} \ No newline at end of file +} From f9b158f22cc4af76d365ded71ba2e58d1876c8e8 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Thu, 10 Dec 2020 13:53:18 -0500 Subject: [PATCH 05/16] Remove unnecessary blank lines --- src/MSBuildLocator/MSBuildLocator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index f118b143..03059b6d 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -415,8 +415,6 @@ bool TryGetMonoVersionFromMonoBinary(string monoPrefixPath, out Version ver) } } - - #if NET46 private static VisualStudioInstance GetDevConsoleInstance() { From 1c5c7bd40f1f2019f1286c9c36b4337c9a40fdca Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 14:25:16 -0500 Subject: [PATCH 06/16] Address review feedback - move mono specific stuff to `MSBuildLocator.Mono.cs` --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 139 ++++++++++++++++++++++ src/MSBuildLocator/MSBuildLocator.cs | 128 +------------------- 2 files changed, 140 insertions(+), 127 deletions(-) create mode 100644 src/MSBuildLocator/MSBuildLocator.Mono.cs diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs new file mode 100644 index 00000000..6e322c61 --- /dev/null +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; + +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 runningMonoPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof (object).Assembly.Location), "..", "..", "..")); + if (TryGetValidMonoVersion (runningMonoPath, out var version)) + { + yield return new VisualStudioInstance("Mono", runningMonoPath, version, DiscoveryType.Mono); + } + + if (!IsOSX) + { + // Returning just one instance on !osx + yield break; + } + + foreach(var dirPath in Directory.EnumerateDirectories(s_monoOSXBasePath)) + { + if (string.Equals(Path.GetFileName(dirPath), "Current") || // skip the `Current` symlink + string.Equals(dirPath, runningMonoPath)) // and the running mono version + { + 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 + { + var p = new Process(); + p.StartInfo.FileName = Path.Combine(monoPrefixPath, "bin", "mono"); + p.StartInfo.Arguments = "--version=number"; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.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; + } + } + + // Taken from MSBuild/NativeMethodsShared + private static bool? _isOSX; + + /// + /// Gets a flag indicating if we are running under Mac OSX + /// + internal static bool IsOSX + { + get + { + if (!_isOSX.HasValue) + { + _isOSX = File.Exists("/usr/lib/libc.dylib"); + } + + return _isOSX.Value; + } + } + + } +} diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index c0cc0f80..4386908e 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -18,7 +18,7 @@ namespace Microsoft.Build.Locator { - public static class MSBuildLocator + public static partial class MSBuildLocator { private const string MSBuildPublicKeyToken = "b03f5f7f11d50a3a"; @@ -31,10 +31,6 @@ public static class MSBuildLocator "Microsoft.Build.Utilities.Core", }; - 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"; - #if NET46 private static ResolveEventHandler s_registeredHandler; #else @@ -321,82 +317,6 @@ private static IEnumerable GetInstances(VisualStudioInstan #endif } - static IEnumerable GetMonoMSBuildInstances () - { - // $prefix/lib/mono/4.5/mscorlib.dll - var runningMonoPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof (object).Assembly.Location), "..", "..", "..")); - if (TryGetValidMonoVersion (runningMonoPath, out var version)) - { - yield return new VisualStudioInstance("Mono", runningMonoPath, version, DiscoveryType.Mono); - } - - if (!IsOSX) - { - // Returning just one instance on !osx - yield break; - } - - foreach(var dirPath in Directory.EnumerateDirectories(s_monoOSXBasePath)) - { - if (string.Compare(Path.GetFileName (dirPath), "Current") == 0 || // skip the `Current` symlink - string.Compare(dirPath, runningMonoPath) == 0) // and the running mono version - { - 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 - { - var p = new Process (); - p.StartInfo.FileName = Path.Combine (monoPrefixPath, "bin", "mono"); - p.StartInfo.Arguments = "--version=number"; - p.StartInfo.UseShellExecute = false; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.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; - } - } #if NET46 private static VisualStudioInstance GetDevConsoleInstance() @@ -429,51 +349,5 @@ private static VisualStudioInstance GetDevConsoleInstance() return null; } #endif - - // 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; - } - } - - // Taken from MSBuild/NativeMethodsShared - private static bool? _isOSX; - - /// - /// Gets a flag indicating if we are running under Mac OSX - /// - internal static bool IsOSX - { - get - { - if (!_isOSX.HasValue) - { - _isOSX = File.Exists("/usr/lib/libc.dylib"); - } - - return _isOSX.Value; - } - } } } From c5ba85c15069533e02c6314a995c6027d473ee8b Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 14:26:59 -0500 Subject: [PATCH 07/16] [mono] Handle `Directory.EnumerateDirectories` returning names with .. trailing slash, addressing feedback in https://github.com/microsoft/MSBuildLocator/pull/82#discussion_r552076470 --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs index 6e322c61..95c8c804 100644 --- a/src/MSBuildLocator/MSBuildLocator.Mono.cs +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -15,10 +15,10 @@ public static partial class MSBuildLocator internal static IEnumerable GetMonoMSBuildInstances() { // $prefix/lib/mono/4.5/mscorlib.dll - var runningMonoPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof (object).Assembly.Location), "..", "..", "..")); - if (TryGetValidMonoVersion (runningMonoPath, out var version)) + var runningMonoFullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof (object).Assembly.Location), "..", "..", "..")); + if (TryGetValidMonoVersion (runningMonoFullPath, out var version)) { - yield return new VisualStudioInstance("Mono", runningMonoPath, version, DiscoveryType.Mono); + yield return new VisualStudioInstance("Mono", runningMonoFullPath, version, DiscoveryType.Mono); } if (!IsOSX) @@ -30,7 +30,7 @@ internal static IEnumerable GetMonoMSBuildInstances() foreach(var dirPath in Directory.EnumerateDirectories(s_monoOSXBasePath)) { if (string.Equals(Path.GetFileName(dirPath), "Current") || // skip the `Current` symlink - string.Equals(dirPath, runningMonoPath)) // and the running mono version + string.Equals(Path.GetFullPath(dirPath), runningMonoFullPath)) // and the running mono version { continue; } From 5f95f03847bf1534cc05ff542c75177148e5e724 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 14:47:57 -0500 Subject: [PATCH 08/16] little cleanup --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 42 +++++++++++++---------- src/MSBuildLocator/MSBuildLocator.cs | 1 - 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs index 95c8c804..0c0b05fd 100644 --- a/src/MSBuildLocator/MSBuildLocator.Mono.cs +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -16,7 +16,7 @@ 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)) + if (TryGetValidMonoVersion(runningMonoFullPath, out var version)) { yield return new VisualStudioInstance("Mono", runningMonoFullPath, version, DiscoveryType.Mono); } @@ -66,24 +66,30 @@ bool TryGetMonoVersionFromMonoBinary(string monoPrefixPath, out Version ver) ver = null; try { - var p = new Process(); - p.StartInfo.FileName = Path.Combine(monoPrefixPath, "bin", "mono"); - p.StartInfo.Arguments = "--version=number"; - p.StartInfo.UseShellExecute = false; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.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) { + 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; } diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 4386908e..53bcc341 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -317,7 +317,6 @@ private static IEnumerable GetInstances(VisualStudioInstan #endif } - #if NET46 private static VisualStudioInstance GetDevConsoleInstance() { From ebe52265d70c65e42dea32e9d335420520927ad8 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 14:52:01 -0500 Subject: [PATCH 09/16] remove blank newline before last closing brace --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs index 0c0b05fd..110061fd 100644 --- a/src/MSBuildLocator/MSBuildLocator.Mono.cs +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -140,6 +140,5 @@ internal static bool IsOSX return _isOSX.Value; } } - } } From 3108a59e0a97077e7d4aa97e6a9fe918405c20f2 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 14:54:34 -0500 Subject: [PATCH 10/16] remove unused usings --- src/MSBuildLocator/MSBuildLocator.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 53bcc341..68c59c18 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; -using System.Text.RegularExpressions; using System.Threading; #if NETCOREAPP From e02e0edc1dc6516fb39afa6f01525b9375c381fe Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 23:16:19 -0500 Subject: [PATCH 11/16] address review feedback --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs index 110061fd..796b2d04 100644 --- a/src/MSBuildLocator/MSBuildLocator.Mono.cs +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -1,3 +1,6 @@ +// 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; @@ -45,7 +48,7 @@ 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))) + !File.Exists(Path.Combine(path, s_monoMSBuildDll_15_0_RelativePath))) { return false; } From 241758ef9e7ea2bb203420d66363048e208efc66 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 5 Jan 2021 23:16:51 -0500 Subject: [PATCH 12/16] Update src/MSBuildLocator/MSBuildLocator.Mono.cs Co-authored-by: Forgind --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs index 796b2d04..cf3c9e03 100644 --- a/src/MSBuildLocator/MSBuildLocator.Mono.cs +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -40,7 +40,7 @@ internal static IEnumerable GetMonoMSBuildInstances() if (TryGetValidMonoVersion(dirPath, out version)) { - yield return new VisualStudioInstance("Mono", dirPath, version, DiscoveryType.Mono); + yield return new VisualStudioInstance("Mono", dirPath + '/', version, DiscoveryType.Mono); } } From 788358623d1486ec3597d5bd5cc3dc21a14a3a57 Mon Sep 17 00:00:00 2001 From: Forgind Date: Wed, 6 Jan 2021 09:54:44 -0800 Subject: [PATCH 13/16] Load NuGet assemblies (#115) Adds an overload to permit supplying additional paths in which to search for MSBuild assemblies beyond the default. Uses this new overload to add the search path for NuGet assemblies for when in a VS install. The latter fixes #86. --- src/MSBuildLocator/MSBuildLocator.cs | 57 +++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 68c59c18..933d2741 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -139,14 +139,50 @@ public static void RegisterInstance(VisualStudioInstance instance) /// public static void RegisterMSBuildPath(string msbuildPath) { - if (string.IsNullOrWhiteSpace(msbuildPath)) + RegisterMSBuildPath(new string[] { + msbuildPath +#if NET46 + // Finds and loads NuGet assemblies if msbuildPath is in a VS installation + , Path.GetFullPath(Path.Combine(msbuildPath, "..", "..", "..", "Common7", "IDE", "CommonExtensions", "Microsoft", "NuGet")) +#endif + }); + } + + /// + /// Add assembly resolution for Microsoft.Build core dlls in the current AppDomain from the specified + /// path. + /// + /// + /// Paths to directories containing a deployment of MSBuild binaries. + /// A minimal MSBuild deployment would be the publish result of the Microsoft.Build.Runtime package. + /// + /// In order to restore and build real projects, one needs a deployment that contains the rest of the toolchain (nuget, compilers, etc.). + /// Such deployments can be found in installations such as Visual Studio or dotnet CLI. + /// + public static void RegisterMSBuildPath(string[] msbuildSearchPaths) + { + if (msbuildSearchPaths.Length < 1) { - throw new ArgumentException("Value may not be null or whitespace", nameof(msbuildPath)); + throw new ArgumentException("Must provide at least one search path to RegisterMSBuildPath."); } - if (!Directory.Exists(msbuildPath)) + List nullOrWhiteSpaceExceptions = new List(); + for (int i = 0; i < msbuildSearchPaths.Length; i++) { - throw new ArgumentException($"Directory \"{msbuildPath}\" does not exist", nameof(msbuildPath)); + if (string.IsNullOrWhiteSpace(msbuildSearchPaths[i])) + { + nullOrWhiteSpaceExceptions.Add(new ArgumentException($"Value at position {i+1} may not be null or whitespace", nameof(msbuildSearchPaths))); + } + } + if (nullOrWhiteSpaceExceptions.Count > 0) + { + 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) + { + 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)))); } if (!CanRegister) @@ -202,12 +238,15 @@ Assembly TryLoadAssembly(AssemblyName assemblyName) // Look in the MSBuild folder for any unresolved reference. It may be a dependency // of MSBuild or a task. - string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll"); - if (File.Exists(targetAssembly)) + foreach (string msbuildPath in msbuildSearchPaths) { - assembly = Assembly.LoadFrom(targetAssembly); - loadedAssemblies.Add(assemblyName.FullName, assembly); - return assembly; + string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll"); + if (File.Exists(targetAssembly)) + { + assembly = Assembly.LoadFrom(targetAssembly); + loadedAssemblies.Add(assemblyName.FullName, assembly); + return assembly; + } } return null; From f8eb71fa8126bcb3f91200de081f2680adbb08cb Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 6 Jan 2021 13:17:58 -0500 Subject: [PATCH 14/16] Fix check for any non existant search paths - it was inverted --- src/MSBuildLocator/MSBuildLocator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 933d2741..08a939bd 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -179,10 +179,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) From 04a7fc1a21057a2a1223b0e0e188593ef4fc0b84 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 6 Jan 2021 13:19:08 -0500 Subject: [PATCH 15/16] [mono] Don't include .net framework specific path --- src/MSBuildLocator/MSBuildLocator.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 08a939bd..5f29795b 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -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()); } /// From 9794b5a1d01b6213c7e1f0a2b2b1f9ef6d1fd122 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 8 Jan 2021 15:21:49 -0500 Subject: [PATCH 16/16] address review feedback - use RuntimeInformation.IsOSPlatform to detect OSX --- src/MSBuildLocator/MSBuildLocator.Mono.cs | 27 ++++--------------- .../Microsoft.Build.Locator.csproj | 1 + 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/MSBuildLocator/MSBuildLocator.Mono.cs b/src/MSBuildLocator/MSBuildLocator.Mono.cs index cf3c9e03..8fddd9b1 100644 --- a/src/MSBuildLocator/MSBuildLocator.Mono.cs +++ b/src/MSBuildLocator/MSBuildLocator.Mono.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; namespace Microsoft.Build.Locator { @@ -24,16 +25,17 @@ internal static IEnumerable GetMonoMSBuildInstances() yield return new VisualStudioInstance("Mono", runningMonoFullPath, version, DiscoveryType.Mono); } - if (!IsOSX) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - // Returning just one instance on !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 + string.Equals(Path.GetFullPath(dirPath), runningMonoFullPath)) // and the running mono version returned above { continue; } @@ -124,24 +126,5 @@ internal static bool IsRunningOnMono return _isRunningOnMono.Value; } } - - // Taken from MSBuild/NativeMethodsShared - private static bool? _isOSX; - - /// - /// Gets a flag indicating if we are running under Mac OSX - /// - internal static bool IsOSX - { - get - { - if (!_isOSX.HasValue) - { - _isOSX = File.Exists("/usr/lib/libc.dylib"); - } - - return _isOSX.Value; - } - } } } 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