Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Custom .NET CLI support to OmniSharp #2227

Merged
merged 2 commits into from
Sep 28, 2021
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
13 changes: 6 additions & 7 deletions src/OmniSharp.Host/CompositionHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ public static IServiceProvider CreateDefaultServiceProvider(
services.AddSingleton<IAnalyzerAssemblyLoader, AnalyzerAssemblyLoader>();
services.AddOptions();

// Setup the options from configuration
services.Configure<OmniSharpOptions>(configuration)
.PostConfigure<OmniSharpOptions>(OmniSharpOptions.PostConfigure);
services.AddSingleton(configuration);
services.AddSingleton<IConfiguration>(configuration);

services.AddSingleton<IDotNetCliService, DotNetCliService>();

// MSBuild
Expand All @@ -149,13 +155,6 @@ public static IServiceProvider CreateDefaultServiceProvider(
assemblyLoader: sp.GetService<IAssemblyLoader>(),
msbuildConfiguration: configuration.GetSection("msbuild")));


// Setup the options from configuration
services.Configure<OmniSharpOptions>(configuration)
.PostConfigure<OmniSharpOptions>(OmniSharpOptions.PostConfigure);
services.AddSingleton(configuration);
services.AddSingleton<IConfiguration>(configuration);

services.AddLogging(builder =>
{
var workspaceInformationServiceName = typeof(WorkspaceInformationService).FullName;
Expand Down
40 changes: 38 additions & 2 deletions src/OmniSharp.Host/Services/DotNetCliService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NuGet.Versioning;
using OmniSharp.Eventing;
using OmniSharp.Options;
using OmniSharp.Utilities;

namespace OmniSharp.Services
Expand All @@ -20,14 +25,45 @@ internal class DotNetCliService : IDotNetCliService

public string DotNetPath { get; }

public DotNetCliService(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, string dotnetPath = null)
public DotNetCliService(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, IOptions<DotNetCliOptions> dotNetCliOptions, IOmniSharpEnvironment environment)
{
_logger = loggerFactory.CreateLogger<DotNetCliService>();
_eventEmitter = eventEmitter;
_locks = new ConcurrentDictionary<string, object>();
_semaphore = new SemaphoreSlim(Environment.ProcessorCount / 2);

DotNetPath = dotnetPath ?? "dotnet";
// Check if any of the provided paths have a dotnet executable.
string executableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
foreach (var path in dotNetCliOptions.Value.GetNormalizedLocationPaths(environment))
{
if (File.Exists(Path.Combine(path, $"dotnet{executableExtension}")))
{
// We'll take the first path that has a dotnet executable.
DotNetPath = Path.Combine(path, "dotnet");

Choose a reason for hiding this comment

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

I believe we need a break; here. Currently, it will continue looping the location paths and take the last path that has a dotnet executable. If you agree, I can push a PR for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you're right, so a PR is probably worthwhile.

}
else
{
_logger.LogInformation($"Provided dotnet CLI path does not contain the dotnet executable: '{path}'.");
}
}

// If we still haven't found a dotnet CLI, check the DOTNET_ROOT environment variable.
if (DotNetPath is null)
{
_logger.LogInformation("Checking the 'DOTNET_ROOT' environment variable to find a .NET SDK");
string dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT");
if (!string.IsNullOrEmpty(dotnetRoot) && File.Exists(Path.Combine(dotnetRoot, $"dotnet{executableExtension}")))
{
DotNetPath = Path.Combine(dotnetRoot, "dotnet");
}
}

// If we still haven't found the CLI, use the one on the PATH.
if (DotNetPath is null)
{
_logger.LogInformation("Using the 'dotnet' on the PATH.");
DotNetPath = "dotnet";
}

_logger.LogInformation($"DotNetPath set to {DotNetPath}");
}
Expand Down
12 changes: 12 additions & 0 deletions src/OmniSharp.Shared/Options/DotNetCliOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OmniSharp.Options
{
public class DotNetCliOptions : OmniSharpExtensionsOptions
{
}
}
3 changes: 3 additions & 0 deletions src/OmniSharp.Shared/Options/OmniSharpOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class OmniSharpOptions

public ImplementTypeOptions ImplementTypeOptions { get; set; } = new ImplementTypeOptions();

public DotNetCliOptions DotNetCliOptions { get; set; } = new DotNetCliOptions();

public OmniSharpExtensionsOptions Plugins { get; set; } = new OmniSharpExtensionsOptions();

public override string ToString() => JsonConvert.SerializeObject(this);
Expand All @@ -26,6 +28,7 @@ public static void PostConfigure(OmniSharpOptions options)
options.FileOptions ??= new FileOptions();
options.RenameOptions ??= new RenameOptions();
options.ImplementTypeOptions ??= new ImplementTypeOptions();
options.DotNetCliOptions ??= new DotNetCliOptions();
options.Plugins ??= new OmniSharpExtensionsOptions();
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ public async Task Given_a_restored_project_the_references_are_emitted()
var emitter = new ProjectLoadTestEventEmitter();

using var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld");
var dotnetCliService = new DotNetCliService(LoggerFactory, emitter);
await dotnetCliService.RestoreAsync(testProject.Directory);
using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory));
var dotnetCliService = host.GetExport<IDotNetCliService>();
await dotnetCliService.RestoreAsync(testProject.Directory);
Assert.Single(emitter.ReceivedMessages);
Assert.NotEmpty(emitter.ReceivedMessages[0].References.Where(reference => reference == GetHashedReference("system.core")));
}
Expand Down
10 changes: 9 additions & 1 deletion tests/OmniSharp.MSBuild.Tests/ProjectWithAnalyzersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using OmniSharp.FileWatching;
using OmniSharp.Models.Diagnostics;
using OmniSharp.Models.FilesChanged;
using OmniSharp.Options;
using OmniSharp.Services;
using TestUtility;
using Xunit;
Expand Down Expand Up @@ -348,7 +349,14 @@ await host.GetFilesChangedService().Handle(new[] {

private static async Task RestoreProject(ITestProject testProject)
{
await new DotNetCliService(new LoggerFactory(), NullEventEmitter.Instance).RestoreAsync(testProject.Directory);
DotNetCliOptions options = new DotNetCliOptions
{
LocationPaths = new[]
{
Path.Combine(TestAssets.Instance.RootFolder, DotNetCliVersion.Current.GetFolderName())
}
};
await new DotNetCliService(new LoggerFactory(), NullEventEmitter.Instance, Microsoft.Extensions.Options.Options.Create(options), new OmniSharpEnvironment(testProject.Directory)).RestoreAsync(testProject.Directory);
}
}
}
25 changes: 13 additions & 12 deletions tests/TestUtility/TestServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces;
using OmniSharp;
using OmniSharp.Eventing;
using OmniSharp.FileWatching;
Expand Down Expand Up @@ -81,7 +83,7 @@ public static IServiceProvider Create(
eventEmitter = eventEmitter ?? NullEventEmitter.Instance;

var assemblyLoader = CreateAssemblyLoader(loggerFactory);
var dotNetCliService = CreateDotNetCliService(dotNetCliVersion, loggerFactory, eventEmitter);
var dotNetCliService = CreateDotNetCliService(dotNetCliVersion, loggerFactory, environment, eventEmitter);
var configuration = CreateConfiguration(configurationData);
var msbuildLocator = CreateMSBuildLocator(loggerFactory, assemblyLoader);
var sharedTextWriter = CreateSharedTextWriter(testOutput);
Expand All @@ -105,7 +107,7 @@ public static IServiceProvider Create(
{
eventEmitter = eventEmitter ?? NullEventEmitter.Instance;

var dotNetCliService = CreateDotNetCliService(dotNetCliVersion, loggerFactory, eventEmitter);
var dotNetCliService = CreateDotNetCliService(dotNetCliVersion, loggerFactory, environment, eventEmitter);
var configuration = CreateConfiguration(configurationData);
var sharedTextWriter = CreateSharedTextWriter(testOutput);

Expand Down Expand Up @@ -142,26 +144,25 @@ private static IConfigurationRoot CreateConfiguration(IConfiguration configurati
return builder.Build();
}

private static IDotNetCliService CreateDotNetCliService(DotNetCliVersion dotNetCliVersion,
ILoggerFactory loggerFactory, IEventEmitter eventEmitter)
private static IDotNetCliService CreateDotNetCliService(
DotNetCliVersion dotNetCliVersion,
ILoggerFactory loggerFactory,
IOmniSharpEnvironment environment,
IEventEmitter eventEmitter)
{
var dotnetPath = Path.Combine(
TestAssets.Instance.RootFolder,
dotNetCliVersion.GetFolderName(),
"dotnet");
dotNetCliVersion.GetFolderName());

if (!File.Exists(dotnetPath))
{
dotnetPath = Path.ChangeExtension(dotnetPath, ".exe");
}
var options = new DotNetCliOptions { LocationPaths = new[] { dotnetPath } };

if (!File.Exists(dotnetPath))
if (!Directory.Exists(dotnetPath))
{
throw new InvalidOperationException(
$"Local .NET CLI path does not exist. Did you run build.(ps1|sh) from the command line?");
}

return new DotNetCliService(loggerFactory, NullEventEmitter.Instance, dotnetPath);
return new DotNetCliService(loggerFactory, NullEventEmitter.Instance, Options.Create(options), environment);
}

private static IMSBuildLocator CreateMSBuildLocator(ILoggerFactory loggerFactory,
Expand Down