From cdc815a985806cf4e458ed2475ca7c7b085cf2d4 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 1 Sep 2022 12:48:36 -0700 Subject: [PATCH 1/2] Update dotnet-getdocument to support minimal APIs --- .../src/Commands/GetDocumentCommand.cs | 4 +- .../src/Commands/GetDocumentCommandWorker.cs | 107 ++++++++++++++++++ .../src/GetDocument.Insider.csproj | 6 +- .../src/Commands/InvokeCommand.cs | 9 +- .../src/dotnet-getdocument.csproj | 2 +- 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs index 599fbd9941ee..a6a195a30ea5 100644 --- a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs +++ b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; -#if NETCOREAPP2_1 +#if NETCOREAPP2_1 || NET7_0_OR_GREATER using System.Runtime.Loader; #endif using Microsoft.Extensions.CommandLineUtils; @@ -69,7 +69,7 @@ protected override int Execute() } } -#if NETCOREAPP2_1 +#if NETCOREAPP2_1 || NET7_0_OR_GREATER AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) => { var name = assemblyName.Name; diff --git a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs index 13d3faab7bd9..13432ef196d5 100644 --- a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs +++ b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs @@ -2,13 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Tools.Internal; +#if NET7_0_OR_GREATER +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Http.Features; +#endif namespace Microsoft.Extensions.ApiDescription.Tool.Commands; @@ -53,6 +60,88 @@ public int Process() return 3; } +#if NET7_0_OR_GREATER + // Register no-op implementations of IServer and IHostLifetime + // to prevent the application server from actually launching after build. + void ConfigureHostBuilder(object hostBuilder) + { + ((IHostBuilder)hostBuilder).ConfigureServices((context, services) => + { + services.AddSingleton(); + services.AddSingleton(); + }); + } + + // Register a TCS to be invoked when the entrypoint (aka Program.Main) + // has finished running. For minimal APIs, this means that all app.X + // calls about the host has been built have been executed. + var waitForStartTcs = new TaskCompletionSource(); + void OnEntryPointExit(Exception exception) + { + // If the entry point exited, we'll try to complete the wait + if (exception != null) + { + waitForStartTcs.TrySetException(exception); + } + else + { + waitForStartTcs.TrySetResult(null); + } + } + + // Resolve the host factory, ensuring that we don't stop the + // application after the host has been built. + var factory = HostFactoryResolver.ResolveHostFactory(assembly, + stopApplication: false, + configureHostBuilder: ConfigureHostBuilder, + entrypointCompleted: OnEntryPointExit); + + if (factory == null) + { + _reporter.WriteError(Resources.FormatMethodsNotFound( + HostFactoryResolver.BuildWebHost, + HostFactoryResolver.CreateHostBuilder, + HostFactoryResolver.CreateWebHostBuilder, + entryPointType)); + + return 4; + } + + try + { + // Retrieve the service provider from the target host. + var services = ((IHost)factory(new[] { $"--{HostDefaults.ApplicationKey}={assemblyName}" })).Services; + if (services == null) + { + _reporter.WriteError(Resources.FormatServiceProviderNotFound( + typeof(IServiceProvider), + HostFactoryResolver.BuildWebHost, + HostFactoryResolver.CreateHostBuilder, + HostFactoryResolver.CreateWebHostBuilder, + entryPointType)); + + return 5; + } + + // Wait for th application to start to ensure that all configurations + // on the WebApplicationBuilder have been processed. + var applicationLifetime = services.GetRequiredService(); + using (var registration = applicationLifetime.ApplicationStarted.Register(() => waitForStartTcs.TrySetResult(null))) + { + waitForStartTcs.Task.Wait(); + var success = GetDocuments(services); + if (!success) + { + return 6; + } + } + } + catch (Exception ex) + { + _reporter.WriteError(ex.ToString()); + return 7; + } +#else try { var serviceFactory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); @@ -91,6 +180,7 @@ public int Process() _reporter.WriteError(ex.ToString()); return 7; } +#endif return 0; } @@ -303,4 +393,21 @@ private object InvokeMethod(MethodInfo method, object instance, object[] argumen return result; } + +#if NET7_0_OR_GREATER + private sealed class NoopHostLifetime : IHostLifetime + { + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task WaitForStartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + private sealed class NoopServer : IServer + { + public IFeatureCollection Features { get; } = new FeatureCollection(); + public void Dispose() { } + public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) => Task.CompletedTask; + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + } +#endif } diff --git a/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj b/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj index 93a7fac7821c..3961d1bcc98d 100644 --- a/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj +++ b/src/Tools/GetDocumentInsider/src/GetDocument.Insider.csproj @@ -5,7 +5,7 @@ false Exe Microsoft.Extensions.ApiDescription.Tool - netcoreapp2.1;$(DefaultNetFxTargetFramework) + netcoreapp2.1;$(DefaultNetCoreTargetFramework);$(DefaultNetFxTargetFramework) false disable $(NoWarn);nullable @@ -15,6 +15,10 @@ + + + + true diff --git a/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs b/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs index 7c7b8fb568b7..17d15ea0e641 100644 --- a/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs +++ b/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs @@ -79,9 +79,16 @@ protected override int Execute() projectName, targetFramework.Version)); } + else if (targetFramework.Version > new Version(7, 0)) + { + toolsDirectory = Path.Combine(thisPath, "net7.0"); + } + else + { + toolsDirectory = Path.Combine(thisPath, "netcoreapp2.1"); + } executable = DotNetMuxer.MuxerPathOrDefault(); - toolsDirectory = Path.Combine(thisPath, "netcoreapp2.1"); args.Add("exec"); args.Add("--depsFile"); diff --git a/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj b/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj index 7bcdcbcd7ace..3b37a5ea7841 100644 --- a/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj +++ b/src/Tools/dotnet-getdocument/src/dotnet-getdocument.csproj @@ -5,7 +5,7 @@ false Exe Microsoft.Extensions.ApiDescription.Tool - netcoreapp2.1 + netcoreapp2.1;$(DefaultNetCoreTargetFramework) false false From d01f69661926899a66f774dd6a027c6cddf0e970 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 7 Sep 2022 10:25:28 -0700 Subject: [PATCH 2/2] Address feedback from peer review --- .../src/Commands/GetDocumentCommand.cs | 4 ++-- .../src/Commands/GetDocumentCommandWorker.cs | 12 ++++++------ .../dotnet-getdocument/src/Commands/InvokeCommand.cs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs index a6a195a30ea5..26ef4958c37f 100644 --- a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs +++ b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommand.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; -#if NETCOREAPP2_1 || NET7_0_OR_GREATER +#if NETCOREAPP using System.Runtime.Loader; #endif using Microsoft.Extensions.CommandLineUtils; @@ -69,7 +69,7 @@ protected override int Execute() } } -#if NETCOREAPP2_1 || NET7_0_OR_GREATER +#if NETCOREAPP AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) => { var name = assemblyName.Name; diff --git a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs index 13432ef196d5..b5f63acd8416 100644 --- a/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs +++ b/src/Tools/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs @@ -75,7 +75,7 @@ void ConfigureHostBuilder(object hostBuilder) // Register a TCS to be invoked when the entrypoint (aka Program.Main) // has finished running. For minimal APIs, this means that all app.X // calls about the host has been built have been executed. - var waitForStartTcs = new TaskCompletionSource(); + var waitForStartTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); void OnEntryPointExit(Exception exception) { // If the entry point exited, we'll try to complete the wait @@ -104,7 +104,7 @@ void OnEntryPointExit(Exception exception) HostFactoryResolver.CreateWebHostBuilder, entryPointType)); - return 4; + return 8; } try @@ -120,10 +120,10 @@ void OnEntryPointExit(Exception exception) HostFactoryResolver.CreateWebHostBuilder, entryPointType)); - return 5; + return 9; } - // Wait for th application to start to ensure that all configurations + // Wait for the application to start to ensure that all configurations // on the WebApplicationBuilder have been processed. var applicationLifetime = services.GetRequiredService(); using (var registration = applicationLifetime.ApplicationStarted.Register(() => waitForStartTcs.TrySetResult(null))) @@ -132,14 +132,14 @@ void OnEntryPointExit(Exception exception) var success = GetDocuments(services); if (!success) { - return 6; + return 10; } } } catch (Exception ex) { _reporter.WriteError(ex.ToString()); - return 7; + return 11; } #else try diff --git a/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs b/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs index 17d15ea0e641..593719792974 100644 --- a/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs +++ b/src/Tools/dotnet-getdocument/src/Commands/InvokeCommand.cs @@ -79,9 +79,9 @@ protected override int Execute() projectName, targetFramework.Version)); } - else if (targetFramework.Version > new Version(7, 0)) + else if (targetFramework.Version >= new Version(7, 0)) { - toolsDirectory = Path.Combine(thisPath, "net7.0"); + toolsDirectory = Path.Combine(thisPath, $"net{targetFramework.Version}"); } else {