Skip to content
Merged
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
2 changes: 1 addition & 1 deletion eng/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
</ItemGroup>

<ItemGroup>
<PackageVersion Include="Microsoft.Build.Locator" Version="1.6.10" />
<PackageVersion Include="Microsoft.Build.Locator" Version="1.8.1" />

<!--
Visual Studio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool
var isFileBasedProgram = VirtualProjectXmlProvider.IsFileBasedProgram(documentPath, textAndVersion.Text);

const BuildHostProcessKind buildHostKind = BuildHostProcessKind.NetCore;
var buildHost = await buildHostProcessManager.GetBuildHostAsync(buildHostKind, cancellationToken);
var buildHost = await buildHostProcessManager.GetBuildHostAsync(buildHostKind, virtualProjectPath, dotnetPath: null, cancellationToken);
var loadedFile = await buildHost.LoadProjectAsync(virtualProjectPath, virtualProjectContent, languageName: LanguageNames.CSharp, cancellationToken);

return new RemoteProjectLoadResult(
Expand Down
92 changes: 55 additions & 37 deletions src/Workspaces/MSBuild/BuildHost/BuildHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,62 +42,75 @@ private bool TryEnsureMSBuildLoaded(string projectOrSolutionFilePath)
return true;
}

if (!PlatformInformation.IsRunningOnMono)
var instance = FindMSBuild(projectOrSolutionFilePath, includeUnloadableInstances: false);
if (instance is null)
{
return false;
}

VisualStudioInstance? instance;

#if NETFRAMEWORK
MSBuildLocator.RegisterMSBuildPath(instance.Path);
_logger.LogInformation($"Registered MSBuild {instance.Version} instance at {instance.Path}");
return true;
}
}

// In this case, we're just going to pick the highest VS install on the machine, in case the projects are using some newer
// MSBuild features. Since we don't have something like a global.json we can't really know what the minimum version is.
private MSBuildLocation? FindMSBuild(string projectOrSolutionFilePath, bool includeUnloadableInstances)
{
if (!PlatformInformation.IsRunningOnMono)
{
VisualStudioInstance? instance;

// TODO: we should also check that the managed tools are actually installed
instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(vs => vs.Version).FirstOrDefault();
#if NETFRAMEWORK
// In this case, we're just going to pick the highest VS install on the machine, in case the projects are using some newer
// MSBuild features. Since we don't have something like a global.json we can't really know what the minimum version is.

// TODO: we should also check that the managed tools are actually installed
instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(vs => vs.Version).FirstOrDefault();
#else
// Locate the right SDK for this particular project; MSBuildLocator ensures in this case the first one is the preferred one.
// The includeUnloadableInstance parameter additionally locates SDKs from all installations regardless of whether they are
// loadable by the BuildHost process.
var options = new VisualStudioInstanceQueryOptions
{
DiscoveryTypes = DiscoveryType.DotNetSdk,
WorkingDirectory = Path.GetDirectoryName(projectOrSolutionFilePath),
AllowAllDotnetLocations = includeUnloadableInstances,
AllowAllRuntimeVersions = includeUnloadableInstances,
};

// Locate the right SDK for this particular project; MSBuildLocator ensures in this case the first one is the preferred one.
// TODO: we should pick the appropriate instance back in the main process and just use the one chosen here.
var options = new VisualStudioInstanceQueryOptions { DiscoveryTypes = DiscoveryType.DotNetSdk, WorkingDirectory = Path.GetDirectoryName(projectOrSolutionFilePath) };
instance = MSBuildLocator.QueryVisualStudioInstances(options).FirstOrDefault();

instance = MSBuildLocator.QueryVisualStudioInstances(options).FirstOrDefault();
#endif

if (instance != null)
{
MSBuildLocator.RegisterInstance(instance);
_logger.LogInformation($"Registered MSBuild instance at {instance.MSBuildPath}");
}
else
{
_logger.LogCritical("No compatible MSBuild instance could be found.");
}
}
else
if (instance != null)
{
#if NETFRAMEWORK
return new(instance.MSBuildPath, instance.Version.ToString());
}

// We're running on Mono, but not all Mono installations have a usable MSBuild installation, so let's see if we have one that we can use.
var monoMSBuildDirectory = MonoMSBuildDiscovery.GetMonoMSBuildDirectory();
_logger.LogCritical("No compatible MSBuild instance could be found.");
}
else
{

if (monoMSBuildDirectory != null)
{
MSBuildLocator.RegisterMSBuildPath(monoMSBuildDirectory);
_logger.LogInformation($"Registered MSBuild instance at {monoMSBuildDirectory}");
}
else
#if NETFRAMEWORK
// We're running on Mono, but not all Mono installations have a usable MSBuild installation, so let's see if we have one that we can use.
var monoMSBuildDirectory = MonoMSBuildDiscovery.GetMonoMSBuildDirectory();
if (monoMSBuildDirectory != null)
{
var monoMSBuildVersion = MonoMSBuildDiscovery.GetMonoMSBuildVersion();
if (monoMSBuildVersion != null)
{
_logger.LogCritical("No Mono MSBuild installation could be found; see https://www.mono-project.com/ for installation instructions.");
return new(monoMSBuildDirectory, monoMSBuildVersion);
}
}

_logger.LogCritical("No Mono MSBuild installation could be found; see https://www.mono-project.com/ for installation instructions.");
#else
_logger.LogCritical("Trying to run the .NET Core BuildHost on Mono is unsupported.");
_logger.LogCritical("Trying to run the .NET Core BuildHost on Mono is unsupported.");
#endif
}

return MSBuildLocator.IsRegistered;
}

return null;
}

[MemberNotNull(nameof(_buildManager))]
Expand All @@ -122,6 +135,11 @@ private void CreateBuildManager()
}
}

public MSBuildLocation? FindBestMSBuild(string projectOrSolutionFilePath)
{
return FindMSBuild(projectOrSolutionFilePath, includeUnloadableInstances: true);
}

public bool HasUsableMSBuild(string projectOrSolutionFilePath)
{
return TryEnsureMSBuildLoaded(projectOrSolutionFilePath);
Expand Down
19 changes: 18 additions & 1 deletion src/Workspaces/MSBuild/BuildHost/Rpc/Contracts/IBuildHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,27 @@ namespace Microsoft.CodeAnalysis.MSBuild;
/// </summary>
internal interface IBuildHost
{
/// <summary>
/// Finds the best MSBuild instance installed for loading the given project or solution.
/// </summary>
/// <remarks>
/// This may return MSBuild instances that are not loadable by the BuildHost process.
/// </remarks>
MSBuildLocation? FindBestMSBuild(string projectOrSolutionFilePath);

/// <summary>
/// Determines whether there is a MSBuild instance that is loadable by the BuildHost process.
/// </summary>
/// <remarks>
/// This may return true even if the project or solution require a newer version of MSBuild.
/// </remarks>
bool HasUsableMSBuild(string projectOrSolutionFilePath);

Task<int> LoadProjectFileAsync(string projectFilePath, string languageName, CancellationToken cancellationToken);

/// <summary>Permits loading a project file which only exists in-memory, for example, for file-based program scenarios.</summary>
/// <summary>
/// Permits loading a project file which only exists in-memory, for example, for file-based program scenarios.
/// </summary>
/// <param name="projectFilePath">A path to a project file which may or may not exist on disk. Note that an extension that is known by MSBuild, such as .csproj or .vbproj, should be used here.</param>
/// <param name="projectContent">The project file XML content.</param>
int LoadProject(string projectFilePath, string projectContent, string languageName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.Serialization;

namespace Microsoft.CodeAnalysis.MSBuild;

[DataContract]
internal sealed class MSBuildLocation(string path, string version)
{
/// <summary>
/// This is the path to the directory containing the MSBuild binaries.
/// </summary>
/// <remarks>
/// When running on .NET this will be the path to the SDK required for loading projects.
/// </remarks>
[DataMember(Order = 0)]
public string Path { get; } = path;

/// <summary>
/// This is the version of MSBuild at this location.
/// </summary>
[DataMember(Order = 1)]
public string Version { get; } = version;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
Expand All @@ -17,6 +18,7 @@ internal static class MonoMSBuildDiscovery
private static string? s_monoRuntimeExecutablePath;
private static string? s_monoLibDirPath;
private static string? s_monoMSBuildDirectory;
private static string? s_monoVersionString;

private static IEnumerable<string> GetSearchPaths()
{
Expand Down Expand Up @@ -143,7 +145,7 @@ private static IEnumerable<string> GetSearchPaths()
if (!monoMSBuildDir.Exists)
return null;

// Inside this is either a Current directory or a 15.0 directory, so find it; the previous code at
// Inside this is either a Current directory or a 15.0 directory, so find it; the previous code at
// https://github.com/OmniSharp/omnisharp-roslyn/blob/dde8119c40f4e3920eb5ea894cbca047033bd9aa/src/OmniSharp.Host/MSBuild/Discovery/MSBuildInstanceProvider.cs#L48-L58
// ensured we had a correctly normalized path in case the underlying file system might have been case insensitive.
var versionDirectory =
Expand All @@ -159,4 +161,32 @@ private static IEnumerable<string> GetSearchPaths()

return s_monoMSBuildDirectory;
}

public static string? GetMonoMSBuildVersion()
{
Contract.ThrowIfTrue(PlatformInformation.IsWindows);

if (s_monoVersionString == null)
{
var monoMSBuildDirectory = GetMonoMSBuildDirectory();
if (monoMSBuildDirectory == null)
{
return null;
}

// Look for Microsoft.Build.dll in the tools path. If it isn't there, this is likely a Mono layout on Linux
// where the 'msbuild' package has not been installed.
var monoMSBuildPath = Path.Combine(monoMSBuildDirectory, "Microsoft.Build.dll");
try
{
var msbuildVersionInfo = FileVersionInfo.GetVersionInfo(monoMSBuildPath);
s_monoVersionString = msbuildVersionInfo.ProductVersion;
}
catch (FileNotFoundException)
{
}
}

return s_monoVersionString;
}
}
Loading
Loading