Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/MSBuildLocator.Tests/QueryOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -76,4 +79,4 @@ private void VerifyQueryResults(IEnumerable<VisualStudioInstance> instances, Dis
}
}
}
}
}
9 changes: 7 additions & 2 deletions src/MSBuildLocator/DiscoveryType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public enum DiscoveryType
/// <summary>
/// Discovery via dotnet --info.
/// </summary>
DotNetSdk = 4
DotNetSdk = 4,

/// <summary>
/// Discovery via Mono path testing.
/// </summary>
Mono = 8
}
}
}
130 changes: 130 additions & 0 deletions src/MSBuildLocator/MSBuildLocator.Mono.cs
Original file line number Diff line number Diff line change
@@ -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<VisualStudioInstance> 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether this is better or it's better to not return it. Users could find it separately and register it anyway. @rainersigwald, thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok to merge? Or are we waiting for this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to wait for @rainersigwald's signoff. Also, would you mind adding the if-not-mono check discussed here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, and fixed a bug introduced in the nuget PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably ok, let's just listen for feedback.

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;

/// <summary>
/// Gets a flag indicating if we are running under MONO
/// </summary>
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;
}
}
}
}
29 changes: 22 additions & 7 deletions src/MSBuildLocator/MSBuildLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace Microsoft.Build.Locator
{
public static class MSBuildLocator
public static partial class MSBuildLocator
{
private const string MSBuildPublicKeyToken = "b03f5f7f11d50a3a";

Expand Down Expand Up @@ -139,13 +139,20 @@ public static void RegisterInstance(VisualStudioInstance instance)
/// </param>
public static void RegisterMSBuildPath(string msbuildPath)
{
RegisterMSBuildPath(new string[] {
var searchPaths = new List<string>
{
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());
}

/// <summary>
Expand Down Expand Up @@ -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<string> paths = msbuildSearchPaths.Where(path => !Directory.Exists(path));
if (paths.FirstOrDefault() == null)
IEnumerable<string> 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)
Expand Down Expand Up @@ -328,6 +335,14 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName)

private static IEnumerable<VisualStudioInstance> 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)
Expand Down
1 change: 1 addition & 0 deletions src/MSBuildLocator/Microsoft.Build.Locator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is unfortunate. I told you to do this! But . . . it's really nice that the Locator package has no dependencies so you can use it basically anywhere. Unfortunately, this type wasn't added to .NET Framework until 4.7.1, so this reference is required to call it in a straightforward way, which makes the package slightly less easy to use.

I think we can call it via reflection instead to avoid that hard requirement--if the type/method is not available, it's not macOS!

<PackageReference Include="MicroBuild.Core" Version="0.3.0" PrivateAssets="all" />
<Content Include="build\Microsoft.Build.Locator.props">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
Expand Down
10 changes: 9 additions & 1 deletion src/MSBuildLocator/VisualStudioInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -60,4 +68,4 @@ internal VisualStudioInstance(string name, string path, Version version, Discove
/// </summary>
public DiscoveryType DiscoveryType { get; }
}
}
}
2 changes: 1 addition & 1 deletion src/MSBuildLocator/VisualStudioInstanceQueryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down