Organize host builder patterns (#665)

This separates out the commands to separate classes to clean up what is done in Program.cs
This commit is contained in:
Taylor Southwick 2021-06-25 12:44:23 -07:00 коммит произвёл GitHub
Родитель 608b619499
Коммит 161dffef70
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 241 добавлений и 194 удалений

Просмотреть файл

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Threading;
using Microsoft.Extensions.Hosting;
namespace Microsoft.DotNet.UpgradeAssistant.Cli
{
public class ConsoleAnalyzeCommand : UpgradeAssistantCommand
{
public ConsoleAnalyzeCommand()
: base("analyze")
{
IsHidden = true;
Handler = CommandHandler.Create<ParseResult, UpgradeOptions, CancellationToken>((result, options, token) =>
Host.CreateDefaultBuilder()
.UseConsoleUpgradeAssistant<ConsoleAnalyze>(options, result)
.RunUpgradeAssistantAsync(token));
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Threading;
using Microsoft.Extensions.Hosting;
namespace Microsoft.DotNet.UpgradeAssistant.Cli
{
public class ConsoleUpgradeCommand : UpgradeAssistantCommand
{
public ConsoleUpgradeCommand()
: base("upgrade")
{
Handler = CommandHandler.Create<ParseResult, UpgradeOptions, CancellationToken>((result, options, token) =>
Host.CreateDefaultBuilder()
.UseConsoleUpgradeAssistant<ConsoleUpgrade>(options, result)
.RunUpgradeAssistantAsync(token));
AddOption(new Option<bool>(new[] { "--skip-backup" }, "Disables backing up the project. This is not recommended unless the project is in source control since this tool will make large changes to both the project and source files."));
AddOption(new Option<bool>(new[] { "--non-interactive" }, "Automatically select each first option in non-interactive mode."));
AddOption(new Option<int>(new[] { "--non-interactive-wait" }, "Wait the supplied seconds before moving on to the next option in non-interactive mode."));
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
namespace Microsoft.DotNet.UpgradeAssistant.Cli
{
public class UpgradeAssistantCommand : Command
{
public UpgradeAssistantCommand(string name)
: base(name)
{
AddArgument(new Argument<FileInfo>("project") { Arity = ArgumentArity.ExactlyOne }.ExistingOnly());
AddOption(new Option<bool>(new[] { "--verbose", "-v" }, "Enable verbose diagnostics"));
AddOption(new Option<IReadOnlyCollection<string>>(new[] { "--extension" }, "Specifies a .NET Upgrade Assistant extension package to include. This could be an ExtensionManifest.json file, a directory containing an ExtensionManifest.json file, or a zip archive containing an extension. This option can be specified multiple times."));
AddOption(new Option<IReadOnlyCollection<string>>(new[] { "--option" }, "Specifies a .NET Upgrade Assistant extension package to include. This could be an ExtensionManifest.json file, a directory containing an ExtensionManifest.json file, or a zip archive containing an extension. This option can be specified multiple times."));
AddOption(new Option<IReadOnlyCollection<string>>(new[] { "--entry-point", "-e" }, "Provides the entry-point project to start the upgrade process. This may include globbing patterns such as '*' for match."));
AddOption(new Option<UpgradeTarget>(new[] { "--target-tfm-support" }, "Select if you would like the Long Term Support (LTS), Current, or Preview TFM. See https://dotnet.microsoft.com/platform/support/policy/dotnet-core for details for what these mean."));
AddOption(new Option<bool>(new[] { "--ignore-unsupported-features" }, "Acknowledges that upgrade-assistant will not be able to completely upgrade a project. This indicates that the solution must be redesigned (e.g. consider Blazor to replace Web Forms)."));
}
}
}

Просмотреть файл

@ -2,23 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Help;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using Microsoft.DotNet.UpgradeAssistant.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Serilog;
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Console apps don't have a synchronization context")]
@ -26,8 +15,6 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli
{
public static class Program
{
private const string LogFilePath = "log.txt";
public static Task<int> Main(string[] args)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -42,13 +29,8 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli
Name = GetProcessName(),
};
var upgradeCmd = new Command("upgrade");
ConfigureUpgradeCommand(upgradeCmd);
root.AddCommand(upgradeCmd);
var analyzeCmd = new Command("analyze");
ConfigureAnalyzeCommand(analyzeCmd);
root.AddCommand(analyzeCmd);
root.AddCommand(new ConsoleAnalyzeCommand());
root.AddCommand(new ConsoleUpgradeCommand());
return new CommandLineBuilder(root)
.UseDefaults()
@ -63,140 +45,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli
}
}
public static Task<int> RunAnalysisAsync(UpgradeOptions options, Func<IHostBuilder, IHostBuilder> configure, CancellationToken token)
=> RunCommandAsync(options, host => configure(host).ConfigureServices(services =>
{
services.AddScoped<IAppCommand, ConsoleAnalyze>();
}), token);
public static Task<int> RunUpgradeAsync(UpgradeOptions options, Func<IHostBuilder, IHostBuilder> configure, CancellationToken token)
=> RunCommandAsync(options, host => configure(host).ConfigureServices(services =>
{
services.AddScoped<IAppCommand, ConsoleUpgrade>();
}), token);
private static IHostBuilder EnableLogging(UpgradeOptions options, ParseResult parseResult, IHostBuilder host)
{
var logSettings = new LogSettings(options.Verbose);
return host
.ConfigureServices(services =>
{
services.AddSingleton(logSettings);
services.AddSingleton(parseResult);
services.AddTransient<IUpgradeStartup, UsedCommandTelemetry>();
})
.UseSerilog((_, __, loggerConfiguration) => loggerConfiguration
.Enrich.FromLogContext()
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
.WriteTo.Console(levelSwitch: logSettings.Console)
.WriteTo.File(LogFilePath, levelSwitch: logSettings.File));
}
private static Task<int> RunCommandAsync(
UpgradeOptions options,
Func<IHostBuilder, IHostBuilder> configure,
CancellationToken token)
{
ConsoleUtils.Clear();
ShowHeader();
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
var hostBuilder = Host.CreateDefaultBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.UseContentRoot(AppContext.BaseDirectory)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureServices((context, services) =>
{
// Register this first so the first startup step is to check for telemetry opt-in
services.AddTransient<IUpgradeStartup, ConsoleFirstTimeUserNotifier>();
services.AddTelemetry(options =>
{
context.Configuration.GetSection("Telemetry").Bind(options);
options.ProductVersion = UpgradeVersion.Current.FullVersion;
});
services.AddHostedService<ConsoleRunner>();
services.AddSingleton<IUpgradeStateManager, FileUpgradeStateFactory>();
services.AddExtensions()
.AddDefaultExtensions(context.Configuration)
.AddFromEnvironmentVariables(context.Configuration)
.Configure(opts =>
{
opts.RetainedProperties = options.Option.ParseOptions();
opts.CurrentVersion = UpgradeVersion.Current.Version;
foreach (var path in options.Extension)
{
opts.ExtensionPaths.Add(path);
}
});
services.AddMsBuild();
services.AddSingleton(options);
// Add command handlers
if (options.NonInteractive)
{
services.AddTransient<IUserInput, NonInteractiveUserInput>();
}
else
{
services.AddTransient<IUserInput, ConsoleCollectUserInput>();
}
services.AddSingleton(new InputOutputStreams(Console.In, Console.Out));
services.AddSingleton<CommandProvider>();
services.TryAddSingleton(new LogSettings(true));
services.AddSingleton<IProcessRunner, ProcessRunner>();
services.AddSingleton<ErrorCodeAccessor>();
services.AddStepManagement(context.Configuration.GetSection("DefaultTargetFrameworks").Bind);
});
var host = configure(hostBuilder).UseConsoleLifetime(options =>
{
options.SuppressStatusMessages = true;
}).Build();
return RunAsync(host, token);
}
private static async Task<int> RunAsync(this IHost host, CancellationToken token)
{
var errorCode = host.Services.GetRequiredService<ErrorCodeAccessor>();
try
{
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
}
else
{
host.Dispose();
}
}
return errorCode.ErrorCode;
}
private static void ShowHeader()
public static void ShowHeader()
{
var title = $"- Microsoft .NET Upgrade Assistant v{UpgradeVersion.Current.FullVersion} -";
Console.WriteLine(new string('-', title.Length));
@ -222,32 +71,5 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli
WriteHeading(Title, null);
}
}
private static void ConfigureUpgradeCommand(Command command)
{
command.Handler = CommandHandler.Create<ParseResult, UpgradeOptions, CancellationToken>((result, options, token) => RunUpgradeAsync(options, host => EnableLogging(options, result, host), token));
RegisterCommonOptions(command);
command.AddOption(new Option<bool>(new[] { "--skip-backup" }, "Disables backing up the project. This is not recommended unless the project is in source control since this tool will make large changes to both the project and source files."));
command.AddOption(new Option<bool>(new[] { "--non-interactive" }, "Automatically select each first option in non-interactive mode."));
command.AddOption(new Option<int>(new[] { "--non-interactive-wait" }, "Wait the supplied seconds before moving on to the next option in non-interactive mode."));
}
private static void ConfigureAnalyzeCommand(Command command)
{
command.Handler = CommandHandler.Create<ParseResult, UpgradeOptions, CancellationToken>((result, options, token) => RunAnalysisAsync(options, host => EnableLogging(options, result, host), token));
RegisterCommonOptions(command);
command.IsHidden = true;
}
private static void RegisterCommonOptions(Command command)
{
command.AddArgument(new Argument<FileInfo>("project") { Arity = ArgumentArity.ExactlyOne }.ExistingOnly());
command.AddOption(new Option<bool>(new[] { "--verbose", "-v" }, "Enable verbose diagnostics"));
command.AddOption(new Option<IReadOnlyCollection<string>>(new[] { "--extension" }, "Specifies a .NET Upgrade Assistant extension package to include. This could be an ExtensionManifest.json file, a directory containing an ExtensionManifest.json file, or a zip archive containing an extension. This option can be specified multiple times."));
command.AddOption(new Option<IReadOnlyCollection<string>>(new[] { "--option" }, "Specifies a .NET Upgrade Assistant extension package to include. This could be an ExtensionManifest.json file, a directory containing an ExtensionManifest.json file, or a zip archive containing an extension. This option can be specified multiple times."));
command.AddOption(new Option<IReadOnlyCollection<string>>(new[] { "--entry-point", "-e" }, "Provides the entry-point project to start the upgrade process. This may include globbing patterns such as '*' for match."));
command.AddOption(new Option<UpgradeTarget>(new[] { "--target-tfm-support" }, "Select if you would like the Long Term Support (LTS), Current, or Preview TFM. See https://dotnet.microsoft.com/platform/support/policy/dotnet-core for details for what these mean."));
command.AddOption(new Option<bool>(new[] { "--ignore-unsupported-features" }, "Acknowledges that upgrade-assistant will not be able to completely upgrade a project. This indicates that the solution must be redesigned (e.g. consider Blazor to replace Web Forms)."));
}
}
}

Просмотреть файл

@ -0,0 +1,160 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.CommandLine.Parsing;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using Microsoft.DotNet.UpgradeAssistant.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace Microsoft.DotNet.UpgradeAssistant.Cli
{
public static class UpgradeAssistantHostExtensions
{
public static IHostBuilder UseUpgradeAssistant<TApp>(this IHostBuilder host, UpgradeOptions options)
where TApp : class, IAppCommand
{
if (host is null)
{
throw new ArgumentNullException(nameof(host));
}
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
return host
.UseContentRoot(AppContext.BaseDirectory)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureServices((context, services) =>
{
// Register this first so the first startup step is to check for telemetry opt-out
services.AddTransient<IUpgradeStartup, ConsoleFirstTimeUserNotifier>();
services.AddTelemetry(options =>
{
context.Configuration.GetSection("Telemetry").Bind(options);
options.ProductVersion = UpgradeVersion.Current.FullVersion;
});
services.AddHostedService<ConsoleRunner>();
services.AddSingleton<IUpgradeStateManager, FileUpgradeStateFactory>();
services.AddExtensions()
.AddDefaultExtensions(context.Configuration)
.AddFromEnvironmentVariables(context.Configuration)
.Configure(opts =>
{
opts.RetainedProperties = options.Option.ParseOptions();
opts.CurrentVersion = UpgradeVersion.Current.Version;
foreach (var path in options.Extension)
{
opts.ExtensionPaths.Add(path);
}
});
services.AddMsBuild();
services.AddSingleton(options);
// Add command handlers
if (options.NonInteractive)
{
services.AddTransient<IUserInput, NonInteractiveUserInput>();
}
else
{
services.AddTransient<IUserInput, ConsoleCollectUserInput>();
}
services.AddSingleton(new InputOutputStreams(Console.In, Console.Out));
services.AddSingleton<CommandProvider>();
services.TryAddSingleton(new LogSettings(true));
services.AddSingleton<IProcessRunner, ProcessRunner>();
services.AddSingleton<ErrorCodeAccessor>();
services.AddStepManagement(context.Configuration.GetSection("DefaultTargetFrameworks").Bind);
services.AddScoped<IAppCommand, TApp>();
})
.UseConsoleLifetime(options =>
{
options.SuppressStatusMessages = true;
});
}
public static IHostBuilder UseConsoleUpgradeAssistant<TApp>(
this IHostBuilder host,
UpgradeOptions options,
ParseResult parseResult)
where TApp : class, IAppCommand
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
ConsoleUtils.Clear();
Program.ShowHeader();
const string LogFilePath = "log.txt";
var logSettings = new LogSettings(options.Verbose);
return host
.UseUpgradeAssistant<TApp>(options)
.ConfigureServices(services =>
{
services.AddSingleton(logSettings);
services.AddSingleton(parseResult);
services.AddTransient<IUpgradeStartup, UsedCommandTelemetry>();
})
.UseSerilog((_, __, loggerConfiguration) => loggerConfiguration
.Enrich.FromLogContext()
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
.WriteTo.Console(levelSwitch: logSettings.Console)
.WriteTo.File(LogFilePath, levelSwitch: logSettings.File));
}
public static async Task<int> RunUpgradeAssistantAsync(this IHostBuilder hostBuilder, CancellationToken token)
{
if (hostBuilder is null)
{
throw new ArgumentNullException(nameof(hostBuilder));
}
var host = hostBuilder.Build();
var errorCode = host.Services.GetRequiredService<ErrorCodeAccessor>();
try
{
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
}
else
{
host.Dispose();
}
}
return errorCode.ErrorCode;
}
}
}

Просмотреть файл

@ -1,10 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.DotNet.UpgradeAssistant
{
public class UpgradeReadinessOptions

Просмотреть файл

@ -4,7 +4,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Construction;

Просмотреть файл

@ -4,7 +4,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Construction;

Просмотреть файл

@ -3,10 +3,7 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers.Common;
using CS = Microsoft.CodeAnalysis.CSharp;

Просмотреть файл

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

Просмотреть файл

@ -43,7 +43,8 @@ namespace Integration.Tests
EntryPoint = new[] { entrypoint },
};
var status = await Program.RunUpgradeAsync(options, host => host
var status = await Host.CreateDefaultBuilder()
.UseUpgradeAssistant<ConsoleUpgrade>(options)
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterInstance(MSBuildPathInstance.Locator);
@ -60,8 +61,8 @@ namespace Integration.Tests
{
logging.SetMinimumLevel(LogLevel.Trace);
logging.AddProvider(new TestOutputHelperLoggerProvider(output));
}),
cts.Token).ConfigureAwait(false);
})
.RunUpgradeAssistantAsync(cts.Token).ConfigureAwait(false);
if (cts.Token.IsCancellationRequested)
{