This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-toolbuilder-cil/src/MfGames.ToolBuilder/ToolBuilder.cs

194 lines
6.6 KiB
C#

using System;
using System.IO;
using System.Threading.Tasks;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using MfGames.ToolBuilder.Globals;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace MfGames.ToolBuilder
{
/// <summary>
/// A builder pattern for creating the tool. This wraps much of the hosting
/// infrastructure with some opinionated decisions and reduces the amount of
/// boilerplate needed to configure the tool.
/// </summary>
public class ToolBuilder
{
private readonly string[] arguments;
private readonly ConfigToolGlobalService configService;
private readonly IHostBuilder hostBuilder;
private readonly LoggingToolGlobalService loggingService;
public ToolBuilder(
string applicationName,
string internalName,
string[] arguments)
{
// Create our various services.
this.arguments = arguments;
this.ApplicationName = applicationName;
this.InternalName = internalName;
this.configService = new ConfigToolGlobalService()
.WithInternalName(this.InternalName);
this.loggingService = new LoggingToolGlobalService();
// Set up logging first so we can report the loading process. This
// sets up the Serilog.Log.Logger which means we can use that for
// everything beyond this point.
this.loggingService.Configure(arguments);
// Start up the basic configuration.
//
// Normally, we would use Host.CreateDefaultBuilder(args) to create
// this, but when run as a stand-alone application in someone's
// $HOME on Linux, this causes an inotify watcher to be registered
// on the entire directory tree (which takes a really long time).
//
// We also don't need most of the default features.
this.hostBuilder =
new HostBuilder()
.UseDefaultServiceProvider(
(context, options) =>
{
bool isDevelopment =
context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
})
.UseConsoleLifetime()
.ConfigureAppConfiguration(this.ConfigureAppConfiguration)
.UseSerilog()
.UseServiceProviderFactory(
new AutofacServiceProviderFactory())
.ConfigureServices(this.ConfigureServices)
.ConfigureContainer<ContainerBuilder>(
this.ConfigureContainer);
}
/// <summary>
/// Gets the human-readable name of the application.
/// </summary>
public string ApplicationName { get; }
/// <summary>
/// Gets the internal name of the application.
/// </summary>
public string InternalName { get; }
public static ToolBuilder Create(
string applicationName,
string internalName,
string[] arguments)
{
return new ToolBuilder(applicationName, internalName, arguments);
}
public ToolBuilder ConfigureContainer(
Action<ContainerBuilder> configure)
{
this.hostBuilder.ConfigureContainer(configure);
return this;
}
public ToolBuilder ConfigureServices(
Action<IServiceCollection> configure)
{
this.hostBuilder.ConfigureServices(configure);
return this;
}
/// <summary>
/// Finishes building the tool, parses the arguments, and runs the
/// command.
/// </summary>
/// <returns>An error code, 0 for successful, otherwise false.</returns>
public async Task<int> RunAsync()
{
try
{
await this.hostBuilder
.RunConsoleAsync()
.ConfigureAwait(false);
}
catch (Exception exception)
{
Log.Fatal(
exception,
"There was a problem running the command: {Arguments}",
this.arguments);
return Environment.ExitCode == 0 ? 1 : Environment.ExitCode;
}
// Get the exit code and return it.
return Environment.ExitCode;
}
private void ConfigureAppConfiguration(
HostBuilderContext context,
IConfigurationBuilder builder)
{
builder.SetBasePath(Directory.GetCurrentDirectory());
this.configService.Configure(builder, this.arguments);
}
private void ConfigureContainer(
HostBuilderContext context,
ContainerBuilder builder)
{
// We want to get logging up and running as soon as possible. We
// also hook up the logging to the process exit in an attempt to
// make sure the logger is properly flushed before exiting.
builder.RegisterInstance(Log.Logger).As<ILogger>().SingleInstance();
AppDomain.CurrentDomain.ProcessExit +=
(_, _) => Log.CloseAndFlush();
// Register the global services as singletons.
builder
.RegisterInstance(this.configService)
.AsSelf()
.SingleInstance();
builder
.RegisterInstance(this.loggingService)
.AsSelf()
.SingleInstance();
// Register the tool service since we have to use the factory to
// use it.
builder
.Register(this.CreateToolService)
.SingleInstance();
// Register the components required to make the CLI work.
builder.RegisterModule<ToolBuilderModule>();
}
private void ConfigureServices(
HostBuilderContext context,
IServiceCollection services)
{
services.AddAutofac();
}
private ToolService CreateToolService(IComponentContext context)
{
var factory = context.Resolve<ToolService.Factory>();
var service = factory(this.InternalName);
return service;
}
}
}