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

Do not crash on startup when configuration is invalid #2140

Merged
merged 6 commits into from Apr 26, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 32 additions & 32 deletions src/OmniSharp.Host/ConfigurationBuilder.cs
@@ -1,55 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using OmniSharp.Internal;
using OmniSharp.Utilities;

namespace OmniSharp
{
public class ConfigurationBuilder : IConfigurationBuilder
public class ConfigurationBuilder
{
private readonly IOmniSharpEnvironment _environment;
private readonly IConfigurationBuilder _builder;

public ConfigurationBuilder(IOmniSharpEnvironment environment)
{
_environment = environment;
_builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory);
}

public IConfigurationBuilder Add(IConfigurationSource source)
public ConfigurationResult Build(Action<IConfigurationBuilder> additionalSetup = null)
{
_builder.Add(source);
return this;
}

public IConfigurationRoot Build()
{
var configBuilder = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddEnvironmentVariables("OMNISHARP_");

if (_environment.AdditionalArguments?.Length > 0)
try
{
configBuilder.AddCommandLine(_environment.AdditionalArguments);
var configBuilder = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddEnvironmentVariables("OMNISHARP_");

if (_environment.AdditionalArguments?.Length > 0)
{
configBuilder.AddCommandLine(_environment.AdditionalArguments);
}

// Use the global omnisharp config if there's any in the shared path
configBuilder.CreateAndAddGlobalOptionsFile(_environment);

// Use the local omnisharp config if there's any in the root path
configBuilder.AddJsonFile(
new PhysicalFileProvider(_environment.TargetDirectory).WrapForPolling(),
Constants.OptionsFile,
optional: true,
reloadOnChange: true);

// bootstrap additional host configuration at the end
additionalSetup?.Invoke(configBuilder);

var config = configBuilder.Build();
return new ConfigurationResult(config);
}
catch (Exception ex)
{
return new ConfigurationResult(ex);
}

// Use the global omnisharp config if there's any in the shared path
configBuilder.CreateAndAddGlobalOptionsFile(_environment);

// Use the local omnisharp config if there's any in the root path
configBuilder.AddJsonFile(
new PhysicalFileProvider(_environment.TargetDirectory).WrapForPolling(),
Constants.OptionsFile,
optional: true,
reloadOnChange: true);

return configBuilder.Build();
}

public IDictionary<string, object> Properties => _builder.Properties;
public IList<IConfigurationSource> Sources => _builder.Sources;
}
}
25 changes: 25 additions & 0 deletions src/OmniSharp.Host/ConfigurationResult.cs
@@ -0,0 +1,25 @@
using System;
using Microsoft.Extensions.Configuration;

namespace OmniSharp
{
public class ConfigurationResult
{
public ConfigurationResult(IConfigurationRoot configuration)
{
Configuration = configuration;
}

public ConfigurationResult(Exception exception)
{
Exception = exception;
Configuration = new ConfigurationRoot(Array.Empty<IConfigurationProvider>());
}

public IConfigurationRoot Configuration { get; }

public Exception Exception { get; }

public bool HasError() => Exception != null;
}
}
10 changes: 8 additions & 2 deletions src/OmniSharp.Http/Startup.cs
Expand Up @@ -32,8 +32,8 @@ public Startup(IOmniSharpEnvironment environment, IEventEmitter eventEmitter, Pl

public IServiceProvider ConfigureServices(IServiceCollection services)
{
var configuration = new ConfigurationBuilder(_environment).Build();
var serviceProvider = CompositionHostBuilder.CreateDefaultServiceProvider(_environment, configuration, _eventEmitter, services,
var configurationResult = new ConfigurationBuilder(_environment).Build();
var serviceProvider = CompositionHostBuilder.CreateDefaultServiceProvider(_environment, configurationResult.Configuration, _eventEmitter, services,
configureLogging: builder =>
{
builder.AddConsole();
Expand All @@ -56,6 +56,12 @@ public IServiceProvider ConfigureServices(IServiceCollection services)

var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<Startup>();

if (configurationResult.HasError())
{
logger.LogError(configurationResult.Exception, "There was an error when reading the OmniSharp configuration, starting with the default options.");
}

var assemblyLoader = serviceProvider.GetRequiredService<IAssemblyLoader>();
_compositionHost = new CompositionHostBuilder(serviceProvider)
.WithOmniSharpAssemblies()
Expand Down
15 changes: 7 additions & 8 deletions src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs
Expand Up @@ -202,24 +202,23 @@ private static LogLevel GetLogLevel(InitializeTrace initializeTrace)
application.LogLevel < logLevel ? application.LogLevel : logLevel,
application.OtherArgs.ToArray());

var configurationRoot = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
.AddConfiguration(new ConfigurationBuilder(environment).Build())
.AddConfiguration(server.Configuration.GetSection("csharp"))
.AddConfiguration(server.Configuration.GetSection("omnisharp"))
.Build()
;

var configurationResult = new ConfigurationBuilder(environment).Build(b =>
b.AddConfiguration(server.Configuration.GetSection("csharp")).AddConfiguration(server.Configuration.GetSection("omnisharp")));
var eventEmitter = new LanguageServerEventEmitter(server);

services.AddSingleton(server)
.AddSingleton<ILanguageServerFacade>(server);

var serviceProvider =
CompositionHostBuilder.CreateDefaultServiceProvider(environment, configurationRoot, eventEmitter,
CompositionHostBuilder.CreateDefaultServiceProvider(environment, configurationResult.Configuration, eventEmitter,
services, GetLogBuilderAction(configureLogging, environment.LogLevel));

var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<LanguageServerHost>();
if (configurationResult.HasError())
{
logger.LogError(configurationResult.Exception, "There was an error when reading the OmniSharp configuration, starting with the default options.");
}

var options = serviceProvider.GetRequiredService<IOptionsMonitor<OmniSharpOptions>>();
var plugins = application.CreatePluginAssemblies(options.CurrentValue, environment);
Expand Down
8 changes: 6 additions & 2 deletions src/OmniSharp.Stdio.Driver/Program.cs
Expand Up @@ -51,9 +51,9 @@ internal class Program

var environment = application.CreateEnvironment();
Configuration.ZeroBasedIndices = application.ZeroBasedIndices;
var configuration = new ConfigurationBuilder(environment).Build();
var configurationResult = new ConfigurationBuilder(environment).Build();
var writer = new SharedTextWriter(output);
var serviceProvider = CompositionHostBuilder.CreateDefaultServiceProvider(environment, configuration, new StdioEventEmitter(writer),
var serviceProvider = CompositionHostBuilder.CreateDefaultServiceProvider(environment, configurationResult.Configuration, new StdioEventEmitter(writer),
configureLogging: builder => builder.AddStdio(writer));

var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
Expand All @@ -63,6 +63,10 @@ internal class Program
var plugins = application.CreatePluginAssemblies(options.CurrentValue, environment);

var logger = loggerFactory.CreateLogger<Program>();
if (configurationResult.HasError())
{
logger.LogError(configurationResult.Exception, "There was an error when reading the OmniSharp configuration, starting with the default options.");
}
var compositionHostBuilder = new CompositionHostBuilder(serviceProvider)
.WithOmniSharpAssemblies()
.WithAssemblies(assemblyLoader.LoadByAssemblyNameOrPath(logger, plugins.AssemblyNames).ToArray());
Expand Down
65 changes: 65 additions & 0 deletions tests/OmniSharp.Tests/ConfigurationBuilderFacts.cs
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using OmniSharp.Services;
using Xunit;

namespace OmniSharp.Tests
{
public class ConfigurationBuilderFacts
{
[Fact]
public void CustomConfigCanBeAdded()
{
var env = new OmniSharpEnvironment();
var builder = new ConfigurationBuilder(env);
var result = builder.Build(c => c.AddInMemoryCollection(new Dictionary<string, string> { { "key", "value" } }));

Assert.False(result.HasError());
Assert.Equal("value", result.Configuration["key"]);
}

[Fact]
public void EnvironmentVariableCanBeRead()
{
var tempValue = Guid.NewGuid().ToString("d");
try
{
Environment.SetEnvironmentVariable("OMNISHARP_testValue", tempValue);
var env = new OmniSharpEnvironment();
var builder = new ConfigurationBuilder(env);
var result = builder.Build(c => c.AddInMemoryCollection());

Assert.False(result.HasError());
Assert.Equal(tempValue, result.Configuration["testValue"]);
}
finally
{
Environment.SetEnvironmentVariable("OMNISHARP_testValue", null);
}
}

[Fact]
public void FileArgsCanBeRead()
{
var env = new OmniSharpEnvironment(additionalArguments: new string[] { "key:nestedKey=value" });
var builder = new ConfigurationBuilder(env);
var result = builder.Build();

Assert.False(result.HasError());
Assert.Equal("value", result.Configuration["key:nestedKey"]);
}

[Fact]
public void DoesNotCrashOnException()
{
var env = new OmniSharpEnvironment();
var builder = new ConfigurationBuilder(env);
var result = builder.Build(c => throw new Exception("bad thing happened"));

Assert.True(result.HasError());
Assert.NotNull(result.Configuration);
Assert.Empty(result.Configuration.AsEnumerable());
}
}
}