diff --git a/src/Mvc/GetDocumentInsider/src/Commands/CommandBase.cs b/src/Mvc/GetDocumentInsider/src/Commands/CommandBase.cs index ac9a4b1a3738..b5eddd978c88 100644 --- a/src/Mvc/GetDocumentInsider/src/Commands/CommandBase.cs +++ b/src/Mvc/GetDocumentInsider/src/Commands/CommandBase.cs @@ -32,7 +32,6 @@ protected virtual void Validate() { } - protected virtual int Execute() - => 0; + protected virtual int Execute() => 0; } } diff --git a/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommand.cs b/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommand.cs index 11c381aac6d6..145db50da6e0 100644 --- a/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommand.cs +++ b/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommand.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; -#if NETCOREAPP2_0 +#if NETCOREAPP2_1 using System.Runtime.Loader; #endif using Microsoft.DotNet.Cli.CommandLine; @@ -14,44 +14,29 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal class GetDocumentCommand : ProjectCommandBase { - internal const string FallbackDocumentName = "v1"; - internal const string FallbackMethod = "GenerateAsync"; - internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider"; - - private CommandOption _documentName; - private CommandOption _method; + private CommandOption _fileListPath; private CommandOption _output; - private CommandOption _service; public override void Configure(CommandLineApplication command) { base.Configure(command); - _documentName = command.Option( - "--documentName ", - Resources.FormatDocumentDescription(FallbackDocumentName)); - _method = command.Option("--method ", Resources.FormatMethodDescription(FallbackMethod)); - _output = command.Option("--output ", Resources.OutputDescription); - _service = command.Option("--service ", Resources.FormatServiceDescription(FallbackService)); + _fileListPath = command.Option("--file-list ", Resources.FileListDescription); + _output = command.Option("--output ", Resources.OutputDescription); } protected override void Validate() { base.Validate(); - if (!_output.HasValue()) - { - throw new CommandException(Resources.FormatMissingOption(_output.LongName)); - } - - if (_method.HasValue() && !_service.HasValue()) + if (!_fileListPath.HasValue()) { - throw new CommandException(Resources.FormatMissingOption(_service.LongName)); + throw new CommandException(Resources.FormatMissingOption(_fileListPath.LongName)); } - if (_service.HasValue() && !_method.HasValue()) + if (!_output.HasValue()) { - throw new CommandException(Resources.FormatMissingOption(_method.LongName)); + throw new CommandException(Resources.FormatMissingOption(_output.LongName)); } } @@ -79,7 +64,7 @@ protected override int Execute() } } -#if NETCOREAPP2_0 +#if NETCOREAPP2_1 AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) => { var name = assemblyName.Name; @@ -125,7 +110,7 @@ protected override int Execute() return Assembly.LoadFile(assemblyPath); }; #else -#error target frameworks need to be updated. +#error Target frameworks need to be updated. #endif // Now safe to reference the application's code. @@ -135,12 +120,10 @@ protected override int Execute() var context = new GetDocumentCommandContext { AssemblyPath = assemblyPath, - AssemblyDirectory = Path.GetDirectoryName(assemblyPath), AssemblyName = Path.GetFileNameWithoutExtension(assemblyPath), - DocumentName = _documentName.Value(), - Method = _method.Value(), - OutputPath = _output.Value(), - Service = _service.Value(), + FileListPath = _fileListPath.Value(), + OutputDirectory = _output.Value(), + ProjectName = ProjectName.Value(), }; return GetDocumentCommandWorker.Process(context); @@ -148,7 +131,7 @@ protected override int Execute() catch (Exception ex) { Console.Error.WriteLine(ex.ToString()); - return 1; + return 2; } } diff --git a/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandContext.cs b/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandContext.cs index 0cd0bd7f572a..3cf1da50d192 100644 --- a/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandContext.cs +++ b/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandContext.cs @@ -8,18 +8,14 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands [Serializable] public class GetDocumentCommandContext { - public string AssemblyDirectory { get; set; } - public string AssemblyName { get; set; } public string AssemblyPath { get; set; } - public string DocumentName { get; set; } - - public string Method { get; set; } + public string FileListPath { get; set; } - public string OutputPath { get; set; } + public string OutputDirectory { get; set; } - public string Service { get; set; } + public string ProjectName { get; set; } } } diff --git a/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs b/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs index fa2ee803dd73..2f9a61def0ea 100644 --- a/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs +++ b/src/Mvc/GetDocumentInsider/src/Commands/GetDocumentCommandWorker.cs @@ -2,15 +2,36 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Text; using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal class GetDocumentCommandWorker { + private const string DefaultDocumentName = "v1"; + private const string DocumentService = "Microsoft.Extensions.ApiDescriptions.IDocumentProvider"; + private const string DotString = "."; + private const string InvalidFilenameString = ".."; + private const string JsonExtension = ".json"; + private const string UnderscoreString = "_"; + private static readonly char[] InvalidFilenameCharacters = Path.GetInvalidFileNameChars(); + private static readonly Encoding UTF8EncodingWithoutBOM + = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + private const string GetDocumentsMethodName = "GetDocumentNames"; + private static readonly object[] GetDocumentsArguments = Array.Empty(); + private static readonly Type[] GetDocumentsParameterTypes = Type.EmptyTypes; + private static readonly Type GetDocumentsReturnType = typeof(IEnumerable); + + private const string GenerateMethodName = "GenerateAsync"; + private static readonly Type[] GenerateMethodParameterTypes = new[] { typeof(string), typeof(TextWriter) }; + private static readonly Type GenerateMethodReturnType = typeof(Task); + public static int Process(GetDocumentCommandContext context) { var assemblyName = new AssemblyName(context.AssemblyName); @@ -19,203 +40,251 @@ public static int Process(GetDocumentCommandContext context) if (entryPointType == null) { Reporter.WriteError(Resources.FormatMissingEntryPoint(context.AssemblyPath)); - return 2; + return 3; } - var services = GetServices(entryPointType, context.AssemblyPath, context.AssemblyName); - if (services == null) + try { - return 3; - } + var serviceFactory = HostFactoryResolver.ResolveServiceProviderFactory(assembly); + if (serviceFactory == null) + { + Reporter.WriteError(Resources.FormatMethodsNotFound( + HostFactoryResolver.BuildWebHost, + HostFactoryResolver.CreateHostBuilder, + HostFactoryResolver.CreateWebHostBuilder, + entryPointType)); + + return 4; + } + + var services = serviceFactory(Array.Empty()); + if (services == null) + { + Reporter.WriteError(Resources.FormatServiceProviderNotFound( + typeof(IServiceProvider), + HostFactoryResolver.BuildWebHost, + HostFactoryResolver.CreateHostBuilder, + HostFactoryResolver.CreateWebHostBuilder, + entryPointType)); - var success = TryProcess(context, services); - if (!success) + return 5; + } + + var success = GetDocuments(context, services); + if (!success) + { + return 6; + } + } + catch (Exception ex) { - // As part of the aspnet/Mvc#8425 fix, return 4 here. - return 0; + Reporter.WriteError(ex.ToString()); + return 7; } return 0; } - public static bool TryProcess(GetDocumentCommandContext context, IServiceProvider services) + private static bool GetDocuments(GetDocumentCommandContext context, IServiceProvider services) { - var documentName = string.IsNullOrEmpty(context.DocumentName) ? - GetDocumentCommand.FallbackDocumentName : - context.DocumentName; - var methodName = string.IsNullOrEmpty(context.Method) ? - GetDocumentCommand.FallbackMethod : - context.Method; - var serviceName = string.IsNullOrEmpty(context.Service) ? - GetDocumentCommand.FallbackService : - context.Service; - - Reporter.WriteInformation(Resources.FormatUsingDocument(documentName)); - Reporter.WriteInformation(Resources.FormatUsingMethod(methodName)); - Reporter.WriteInformation(Resources.FormatUsingService(serviceName)); - - try + Type serviceType = null; + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - Type serviceType = null; - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + serviceType = assembly.GetType(DocumentService, throwOnError: false); + if (serviceType != null) { - serviceType = assembly.GetType(serviceName, throwOnError: false); - if (serviceType != null) - { - break; - } + break; } + } - // As part of the aspnet/Mvc#8425 fix, make all warnings in this method errors unless the file already - // exists. - if (serviceType == null) - { - Reporter.WriteWarning(Resources.FormatServiceTypeNotFound(serviceName)); - return false; - } + if (serviceType == null) + { + Reporter.WriteError(Resources.FormatServiceTypeNotFound(DocumentService)); + return false; + } - var method = serviceType.GetMethod(methodName, new[] { typeof(string), typeof(TextWriter) }); - if (method == null) - { - Reporter.WriteWarning(Resources.FormatMethodNotFound(methodName, serviceName)); - return false; - } - else if (!typeof(Task).IsAssignableFrom(method.ReturnType)) + var getDocumentsMethod = GetMethod( + GetDocumentsMethodName, + serviceType, + GetDocumentsParameterTypes, + GetDocumentsReturnType); + if (getDocumentsMethod == null) + { + return false; + } + + var generateMethod = GetMethod( + GenerateMethodName, + serviceType, + GenerateMethodParameterTypes, + GenerateMethodReturnType); + if (generateMethod == null) + { + return false; + } + + var service = services.GetService(serviceType); + if (service == null) + { + Reporter.WriteError(Resources.FormatServiceNotFound(DocumentService)); + return false; + } + + var documentNames = (IEnumerable)InvokeMethod(getDocumentsMethod, service, GetDocumentsArguments); + if (documentNames == null) + { + return false; + } + + // Write out the documents. + Directory.CreateDirectory(context.OutputDirectory); + var filePathList = new List(); + foreach (var documentName in documentNames) + { + var filePath = GetDocument( + documentName, + context.ProjectName, + context.OutputDirectory, + generateMethod, + service); + if (filePath == null) { - Reporter.WriteWarning(Resources.FormatMethodReturnTypeUnsupported( - methodName, - serviceName, - method.ReturnType, - typeof(Task))); return false; } - var service = services.GetService(serviceType); - if (service == null) + filePathList.Add(filePath); + } + + // Write out the cache file. + var stream = File.Create(context.FileListPath); + using var writer = new StreamWriter(stream); + writer.WriteLine(string.Join(Environment.NewLine, filePathList)); + + return true; + } + + private static string GetDocument( + string documentName, + string projectName, + string outputDirectory, + MethodInfo generateMethod, + object service) + { + Reporter.WriteInformation(Resources.FormatGeneratingDocument(documentName)); + + using var stream = new MemoryStream(); + using (var writer = new StreamWriter(stream, UTF8EncodingWithoutBOM, bufferSize: 1024, leaveOpen: true)) + { + var arguments = new object[] { documentName, writer }; + using var resultTask = (Task)InvokeMethod(generateMethod, service, arguments); + if (resultTask == null) { - Reporter.WriteWarning(Resources.FormatServiceNotFound(serviceName)); - return false; + return null; } - // Create the output FileStream last to avoid corrupting an existing file or writing partial data. - var stream = new MemoryStream(); - using (var writer = new StreamWriter(stream)) + var finished = resultTask.Wait(TimeSpan.FromMinutes(1)); + if (!finished) { - var resultTask = (Task)method.Invoke(service, new object[] { documentName, writer }); - if (resultTask == null) - { - Reporter.WriteWarning( - Resources.FormatMethodReturnedNull(methodName, serviceName, nameof(Task))); - return false; - } - - var finishedIndex = Task.WaitAny(resultTask, Task.Delay(TimeSpan.FromMinutes(1))); - if (finishedIndex != 0) - { - Reporter.WriteWarning(Resources.FormatMethodTimedOut(methodName, serviceName, 1)); - return false; - } - - writer.Flush(); - if (stream.Length > 0L) - { - stream.Position = 0L; - using (var outStream = File.Create(context.OutputPath)) - { - stream.CopyTo(outStream); - outStream.Flush(); - } - } + Reporter.WriteError(Resources.FormatMethodTimedOut(GenerateMethodName, DocumentService, 1)); + return null; } + } + + if (stream.Length == 0L) + { + Reporter.WriteError( + Resources.FormatMethodWroteNoContent(GenerateMethodName, DocumentService, documentName)); - return true; + return null; } - catch (AggregateException ex) when (ex.InnerException != null) + + var filePath = GetDocumentPath(documentName, projectName, outputDirectory); + Reporter.WriteInformation(Resources.FormatWritingDocument(documentName, filePath)); + try { - foreach (var innerException in ex.Flatten().InnerExceptions) - { - Reporter.WriteWarning(FormatException(innerException)); - } + stream.Position = 0L; + + // Create the output FileStream last to avoid corrupting an existing file or writing partial data. + using var outStream = File.Create(filePath); + stream.CopyTo(outStream); } - catch (Exception ex) + catch { - Reporter.WriteWarning(FormatException(ex)); + File.Delete(filePath); + throw; } - File.Delete(context.OutputPath); - - return false; + return filePath; } - // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. - private static IServiceProvider GetServices(Type entryPointType, string assemblyPath, string assemblyName) + private static string GetDocumentPath(string documentName, string projectName, string outputDirectory) { - var args = new[] { Array.Empty() }; - var methodInfo = entryPointType.GetMethod("BuildWebHost"); - if (methodInfo != null) - { - // BuildWebHost (old style has highest priority) - var parameters = methodInfo.GetParameters(); - if (!methodInfo.IsStatic || - parameters.Length != 1 || - typeof(string[]) != parameters[0].ParameterType || - typeof(IWebHost) != methodInfo.ReturnType) - { - Reporter.WriteError( - "BuildWebHost method found in {assemblyPath} does not have expected signature."); - - return null; - } + string path; + if (string.Equals(DefaultDocumentName, documentName, StringComparison.Ordinal)) + { + // Leave default document name out of the filename. + path = projectName + JsonExtension; + } + else + { + // Sanitize the document name because it may contain almost any character, including illegal filename + // characters such as '/' and '?' and the string "..". Do not treat slashes as folder separators. + var sanitizedDocumentName = string.Join( + UnderscoreString, + documentName.Split(InvalidFilenameCharacters)); - try + while (sanitizedDocumentName.Contains(InvalidFilenameString)) { - var webHost = (IWebHost)methodInfo.Invoke(obj: null, parameters: args); - - return webHost.Services; + sanitizedDocumentName = sanitizedDocumentName.Replace(InvalidFilenameString, DotString); } - catch (Exception ex) - { - Reporter.WriteError($"BuildWebHost method threw: {FormatException(ex)}"); - return null; - } + path = $"{projectName}_{documentName}{JsonExtension}"; } - if ((methodInfo = entryPointType.GetMethod("CreateWebHostBuilder")) != null) + if (!string.IsNullOrEmpty(outputDirectory)) { - // CreateWebHostBuilder - var parameters = methodInfo.GetParameters(); - if (!methodInfo.IsStatic || - parameters.Length != 1 || - typeof(string[]) != parameters[0].ParameterType || - typeof(IWebHostBuilder) != methodInfo.ReturnType) - { - Reporter.WriteError( - "CreateWebHostBuilder method found in {assemblyPath} does not have expected signature."); + path = Path.Combine(outputDirectory, path); + } - return null; - } + return path; + } - try - { - var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args); + private static MethodInfo GetMethod(string methodName, Type type, Type[] parameterTypes, Type returnType) + { + var method = type.GetMethod(methodName, parameterTypes); + if (method == null) + { + Reporter.WriteError(Resources.FormatMethodNotFound(methodName, type)); + return null; + } - return builder.Build().Services; - } - catch (Exception ex) - { - Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}"); + if (method.IsStatic) + { + Reporter.WriteError(Resources.FormatMethodIsStatic(methodName, type)); + return null; + } - return null; - } + if (!returnType.IsAssignableFrom(method.ReturnType)) + { + Reporter.WriteError( + Resources.FormatMethodReturnTypeUnsupported(methodName, type, method.ReturnType, returnType)); + + return null; } - return null; + return method; } - private static string FormatException(Exception exception) + private static object InvokeMethod(MethodInfo method, object instance, object[] arguments) { - return $"{exception.GetType().FullName}: {exception.Message}"; + var result = method.Invoke(instance, arguments); + if (result == null) + { + Reporter.WriteError( + Resources.FormatMethodReturnedNull(method.Name, method.DeclaringType, method.ReturnType)); + } + + return result; } } } diff --git a/src/Mvc/GetDocumentInsider/src/Commands/HelpCommandBase.cs b/src/Mvc/GetDocumentInsider/src/Commands/HelpCommandBase.cs index 55e84272ac98..bab529d82709 100644 --- a/src/Mvc/GetDocumentInsider/src/Commands/HelpCommandBase.cs +++ b/src/Mvc/GetDocumentInsider/src/Commands/HelpCommandBase.cs @@ -9,9 +9,9 @@ internal class HelpCommandBase : CommandBase { public override void Configure(CommandLineApplication command) { - base.Configure(command); - command.HelpOption("-h|--help"); + command.VersionOption("--version", ProductInfo.GetVersion); + base.Configure(command); } } } diff --git a/src/Mvc/GetDocumentInsider/src/Commands/ProjectCommandBase.cs b/src/Mvc/GetDocumentInsider/src/Commands/ProjectCommandBase.cs index 8e60d9603faf..f6f3b2094575 100644 --- a/src/Mvc/GetDocumentInsider/src/Commands/ProjectCommandBase.cs +++ b/src/Mvc/GetDocumentInsider/src/Commands/ProjectCommandBase.cs @@ -9,13 +9,16 @@ internal abstract class ProjectCommandBase : HelpCommandBase { public CommandOption AssemblyPath { get; private set; } + public CommandOption ProjectName { get; private set; } + public CommandOption ToolsDirectory { get; private set; } public override void Configure(CommandLineApplication command) { base.Configure(command); - AssemblyPath = command.Option("-a|--assembly ", Resources.AssemblyDescription); + AssemblyPath = command.Option("--assembly ", Resources.AssemblyDescription); + ProjectName = command.Option("--project ", Resources.ProjectDescription); ToolsDirectory = command.Option("--tools-directory ", Resources.ToolsDirectoryDescription); } @@ -28,6 +31,11 @@ protected override void Validate() throw new CommandException(Resources.FormatMissingOption(AssemblyPath.LongName)); } + if (!ProjectName.HasValue()) + { + throw new CommandException(Resources.FormatMissingOption(ProjectName.LongName)); + } + if (!ToolsDirectory.HasValue()) { throw new CommandException(Resources.FormatMissingOption(ToolsDirectory.LongName)); diff --git a/src/Mvc/GetDocumentInsider/src/GetDocumentInsider.csproj b/src/Mvc/GetDocumentInsider/src/GetDocumentInsider.csproj index 3b09c3a0db37..1622b5076cd2 100644 --- a/src/Mvc/GetDocumentInsider/src/GetDocumentInsider.csproj +++ b/src/Mvc/GetDocumentInsider/src/GetDocumentInsider.csproj @@ -5,7 +5,7 @@ false Exe Microsoft.Extensions.ApiDescription.Tool - netcoreapp2.0;net461 + netcoreapp2.1;net461 false $(ExperimentalVersionPrefix) $(ExperimentalVersionSuffix) @@ -17,10 +17,7 @@ - - - true - + diff --git a/src/Mvc/GetDocumentInsider/src/Program.cs b/src/Mvc/GetDocumentInsider/src/Program.cs index 6d144dc5d5f1..8f39235b70ab 100644 --- a/src/Mvc/GetDocumentInsider/src/Program.cs +++ b/src/Mvc/GetDocumentInsider/src/Program.cs @@ -17,9 +17,10 @@ private static int Main(string[] args) Console.OutputEncoding = Encoding.UTF8; } - var app = new CommandLineApplication(throwOnUnexpectedArg: false) + var app = new CommandLineApplication() { - Name = "GetDocument.Insider" + FullName = Resources.CommandFullName, + Name = Resources.CommandFullName, }; new GetDocumentCommand().Configure(app); diff --git a/src/Mvc/GetDocumentInsider/src/Properties/Resources.Designer.cs b/src/Mvc/GetDocumentInsider/src/Properties/Resources.Designer.cs index 488d4ae93c5a..9d45bcb911d5 100644 --- a/src/Mvc/GetDocumentInsider/src/Properties/Resources.Designer.cs +++ b/src/Mvc/GetDocumentInsider/src/Properties/Resources.Designer.cs @@ -11,7 +11,7 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly); /// - /// The assembly to use. + /// The assembly path to use. Required. /// internal static string AssemblyDescription { @@ -19,7 +19,7 @@ internal static string AssemblyDescription } /// - /// The assembly to use. + /// The assembly path to use. Required. /// internal static string FormatAssemblyDescription() => GetString("AssemblyDescription"); @@ -39,7 +39,7 @@ internal static string FormatMissingOption(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0); /// - /// Do not colorize output. + /// Do not colorize console output. /// internal static string NoColorDescription { @@ -47,13 +47,13 @@ internal static string NoColorDescription } /// - /// Do not colorize output. + /// Do not colorize console output. /// internal static string FormatNoColorDescription() => GetString("NoColorDescription"); /// - /// The file to write the result to. + /// The directory where the document files should be written. Required. /// internal static string OutputDescription { @@ -61,7 +61,7 @@ internal static string OutputDescription } /// - /// The file to write the result to. + /// The directory where the document files should be written. Required. /// internal static string FormatOutputDescription() => GetString("OutputDescription"); @@ -81,7 +81,7 @@ internal static string FormatPrefixDescription() => GetString("PrefixDescription"); /// - /// Show verbose output. + /// Show verbose console output. /// internal static string VerboseDescription { @@ -89,13 +89,13 @@ internal static string VerboseDescription } /// - /// Show verbose output. + /// Show verbose console output. /// internal static string FormatVerboseDescription() => GetString("VerboseDescription"); /// - /// Location from which inside man was copied (in the .NET Framework case) or loaded. + /// Location from which inside man was copied (in the .NET Framework case) or loaded. Required. /// internal static string ToolsDirectoryDescription { @@ -103,206 +103,234 @@ internal static string ToolsDirectoryDescription } /// - /// Location from which inside man was copied (in the .NET Framework case) or loaded. + /// Location from which inside man was copied (in the .NET Framework case) or loaded. Required. /// internal static string FormatToolsDirectoryDescription() => GetString("ToolsDirectoryDescription"); /// - /// The name of the method to invoke on the '--service' instance. Default value '{0}'. + /// Generating document named '{0}'. /// - internal static string MethodDescription + internal static string GeneratingDocument { - get => GetString("MethodDescription"); + get => GetString("GeneratingDocument"); } /// - /// The name of the method to invoke on the '--service' instance. Default value '{0}'. + /// Generating document named '{0}'. /// - internal static string FormatMethodDescription(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("MethodDescription"), p0); + internal static string FormatGeneratingDocument(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("GeneratingDocument"), p0); /// - /// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. + /// Assembly '{0}' does not contain an entry point. /// - internal static string ServiceDescription + internal static string MissingEntryPoint { - get => GetString("ServiceDescription"); + get => GetString("MissingEntryPoint"); } /// - /// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. + /// Assembly '{0}' does not contain an entry point. /// - internal static string FormatServiceDescription(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0); + internal static string FormatMissingEntryPoint(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0); /// - /// The name of the document to pass to the '--method' method. Default value '{0}'. + /// Unable to find service type '{0}' in loaded assemblies. /// - internal static string DocumentDescription + internal static string ServiceTypeNotFound { - get => GetString("DocumentDescription"); + get => GetString("ServiceTypeNotFound"); } /// - /// The name of the document to pass to the '--method' method. Default value '{0}'. + /// Unable to find service type '{0}' in loaded assemblies. /// - internal static string FormatDocumentDescription(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("DocumentDescription"), p0); + internal static string FormatServiceTypeNotFound(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ServiceTypeNotFound"), p0); /// - /// Using document name '{0}'. + /// Method '{0}' not found in type '{1}' with expected signature. /// - internal static string UsingDocument + internal static string MethodNotFound { - get => GetString("UsingDocument"); + get => GetString("MethodNotFound"); } /// - /// Using document name '{0}'. + /// Method '{0}' not found in type '{1}' with expected signature. /// - internal static string FormatUsingDocument(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingDocument"), p0); + internal static string FormatMethodNotFound(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodNotFound"), p0, p1); /// - /// Using method '{0}'. + /// Unable to find service type '{0}' in dependency injection container. /// - internal static string UsingMethod + internal static string ServiceNotFound { - get => GetString("UsingMethod"); + get => GetString("ServiceNotFound"); } /// - /// Using method '{0}'. + /// Unable to find service type '{0}' in dependency injection container. /// - internal static string FormatUsingMethod(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingMethod"), p0); + internal static string FormatServiceNotFound(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ServiceNotFound"), p0); /// - /// Using service '{0}'. + /// Method '{0}' of type '{1}' returned null. Must return a non-null '{2}'. /// - internal static string UsingService + internal static string MethodReturnedNull { - get => GetString("UsingService"); + get => GetString("MethodReturnedNull"); } /// - /// Using service '{0}'. + /// Method '{0}' of type '{1}' returned null. Must return a non-null '{2}'. /// - internal static string FormatUsingService(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0); + internal static string FormatMethodReturnedNull(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnedNull"), p0, p1, p2); /// - /// Method '{0}' of service '{1}' failed to generate document '{2}'. + /// Method '{0}' of type '{1}' has unsupported return type '{2}'. Must return '{3}'. /// - internal static string MethodInvocationFailed + internal static string MethodReturnTypeUnsupported { - get => GetString("MethodInvocationFailed"); + get => GetString("MethodReturnTypeUnsupported"); } /// - /// Method '{0}' of service '{1}' failed to generate document '{2}'. + /// Method '{0}' of type '{1}' has unsupported return type '{2}'. Must return '{3}'. /// - internal static string FormatMethodInvocationFailed(object p0, object p1, object p2) - => string.Format(CultureInfo.CurrentCulture, GetString("MethodInvocationFailed"), p0, p1, p2); + internal static string FormatMethodReturnTypeUnsupported(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnTypeUnsupported"), p0, p1, p2, p3); /// - /// Assembly '{0}' does not contain an entry point. + /// Method '{0}' of type '{1}' timed out. Must complete execution within {2} minute. /// - internal static string MissingEntryPoint + internal static string MethodTimedOut { - get => GetString("MissingEntryPoint"); + get => GetString("MethodTimedOut"); } /// - /// Assembly '{0}' does not contain an entry point. + /// Method '{0}' of type '{1}' timed out. Must complete execution within {2} minute. /// - internal static string FormatMissingEntryPoint(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0); + internal static string FormatMethodTimedOut(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodTimedOut"), p0, p1, p2); /// - /// Unable to find service type '{0}' in loaded assemblies. + /// Method '{0}' of type '{1}' is static. Must be an instance method. /// - internal static string ServiceTypeNotFound + internal static string MethodIsStatic { - get => GetString("ServiceTypeNotFound"); + get => GetString("MethodIsStatic"); } /// - /// Unable to find service type '{0}' in loaded assemblies. + /// Method '{0}' of type '{1}' is static. Must be an instance method. /// - internal static string FormatServiceTypeNotFound(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("ServiceTypeNotFound"), p0); + internal static string FormatMethodIsStatic(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodIsStatic"), p0, p1); /// - /// Unable to find method named '{0}' in '{1}' implementation. + /// No method '{0}', '{1}' or '{2}' found in type '{3}' with expected signatures. /// - internal static string MethodNotFound + internal static string MethodsNotFound { - get => GetString("MethodNotFound"); + get => GetString("MethodsNotFound"); } /// - /// Unable to find method named '{0}' in '{1}' implementation. + /// No method '{0}', '{1}' or '{2}' found in type '{3}' with expected signatures. /// - internal static string FormatMethodNotFound(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("MethodNotFound"), p0, p1); + internal static string FormatMethodsNotFound(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodsNotFound"), p0, p1, p2, p3); /// - /// Unable to find service of type '{0}' in dependency injection container. + /// Writing document named '{0}' to '{1}'. /// - internal static string ServiceNotFound + internal static string WritingDocument { - get => GetString("ServiceNotFound"); + get => GetString("WritingDocument"); } /// - /// Unable to find service of type '{0}' in dependency injection container. + /// Writing document named '{0}' to '{1}'. /// - internal static string FormatServiceNotFound(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("ServiceNotFound"), p0); + internal static string FormatWritingDocument(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("WritingDocument"), p0, p1); /// - /// Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'. + /// Method '{0}' of type '{1}' wrote no content for document named '{2}'. /// - internal static string MethodReturnedNull + internal static string MethodWroteNoContent { - get => GetString("MethodReturnedNull"); + get => GetString("MethodWroteNoContent"); } /// - /// Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'. + /// Method '{0}' of type '{1}' wrote no content for document named '{2}'. /// - internal static string FormatMethodReturnedNull(object p0, object p1, object p2) - => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnedNull"), p0, p1, p2); + internal static string FormatMethodWroteNoContent(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodWroteNoContent"), p0, p1, p2); /// - /// Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'. + /// GetDocument.Insider /// - internal static string MethodReturnTypeUnsupported + internal static string CommandFullName { - get => GetString("MethodReturnTypeUnsupported"); + get => GetString("CommandFullName"); } /// - /// Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'. + /// GetDocument.Insider /// - internal static string FormatMethodReturnTypeUnsupported(object p0, object p1, object p2, object p3) - => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnTypeUnsupported"), p0, p1, p2, p3); + internal static string FormatCommandFullName() + => GetString("CommandFullName"); /// - /// Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute. + /// The path where the list of document files should be written. Required. /// - internal static string MethodTimedOut + internal static string FileListDescription { - get => GetString("MethodTimedOut"); + get => GetString("FileListDescription"); } /// - /// Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute. + /// The path where the list of document files should be written. Required. /// - internal static string FormatMethodTimedOut(object p0, object p1, object p2) - => string.Format(CultureInfo.CurrentCulture, GetString("MethodTimedOut"), p0, p1, p2); + internal static string FormatFileListDescription() + => GetString("FileListDescription"); + + /// + /// The project name to use. Required. + /// + internal static string ProjectDescription + { + get => GetString("ProjectDescription"); + } + + /// + /// The project name to use. Required. + /// + internal static string FormatProjectDescription() + => GetString("ProjectDescription"); + + /// + /// Unable to resolve a non-null '{0}' implementation using method '{1}', '{2}' or '{3}' of type '{4}'. + /// + internal static string ServiceProviderNotFound + { + get => GetString("ServiceProviderNotFound"); + } + + /// + /// Unable to resolve a non-null '{0}' implementation using method '{1}', '{2}' or '{3}' of type '{4}'. + /// + internal static string FormatServiceProviderNotFound(object p0, object p1, object p2, object p3, object p4) + => string.Format(CultureInfo.CurrentCulture, GetString("ServiceProviderNotFound"), p0, p1, p2, p3, p4); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Mvc/GetDocumentInsider/src/Resources.resx b/src/Mvc/GetDocumentInsider/src/Resources.resx index facc64415430..a27b1be2b00b 100644 --- a/src/Mvc/GetDocumentInsider/src/Resources.resx +++ b/src/Mvc/GetDocumentInsider/src/Resources.resx @@ -118,46 +118,28 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The assembly to use. + The assembly path to use. Required. Missing required option '--{0}'. - Do not colorize output. + Do not colorize console output. - The file to write the result to. + The directory where the document files should be written. Required. Prefix console output with logging level. - Show verbose output. + Show verbose console output. - Location from which inside man was copied (in the .NET Framework case) or loaded. + Location from which inside man was copied (in the .NET Framework case) or loaded. Required. - - The name of the method to invoke on the '--service' instance. Default value '{0}'. - - - The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. - - - The name of the document to pass to the '--method' method. Default value '{0}'. - - - Using document name '{0}'. - - - Using method '{0}'. - - - Using service '{0}'. - - - Method '{0}' of service '{1}' failed to generate document '{2}'. + + Generating document named '{0}'. Assembly '{0}' does not contain an entry point. @@ -166,18 +148,42 @@ Unable to find service type '{0}' in loaded assemblies. - Unable to find method named '{0}' in '{1}' implementation. + Method '{0}' not found in type '{1}' with expected signature. - Unable to find service of type '{0}' in dependency injection container. + Unable to find service type '{0}' in dependency injection container. - Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'. + Method '{0}' of type '{1}' returned null. Must return a non-null '{2}'. - Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'. + Method '{0}' of type '{1}' has unsupported return type '{2}'. Must return '{3}'. - Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute. + Method '{0}' of type '{1}' timed out. Must complete execution within {2} minute. + + + Method '{0}' of type '{1}' is static. Must be an instance method. + + + No method '{0}', '{1}' or '{2}' found in type '{3}' with expected signatures. + + + Writing document named '{0}' to '{1}'. + + + Method '{0}' of type '{1}' wrote no content for document named '{2}'. + + + GetDocument.Insider + + + The path where the list of document files should be written. Required. + + + The project name to use. Required. + + + Unable to resolve a non-null '{0}' implementation using method '{1}', '{2}' or '{3}' of type '{4}'. \ No newline at end of file diff --git a/src/Mvc/GetDocumentInsider/src/runtimeconfig.template.json b/src/Mvc/GetDocumentInsider/src/runtimeconfig.template.json new file mode 100644 index 000000000000..516c80a38878 --- /dev/null +++ b/src/Mvc/GetDocumentInsider/src/runtimeconfig.template.json @@ -0,0 +1,4 @@ +{ + "applyPatches": true, + "rollForwardOnNoCandidateFx": 2 +} diff --git a/src/Mvc/dotnet-getdocument/src/Commands/InvokeCommand.cs b/src/Mvc/dotnet-getdocument/src/Commands/InvokeCommand.cs index 359b1732352f..52df8b1ae508 100644 --- a/src/Mvc/dotnet-getdocument/src/Commands/InvokeCommand.cs +++ b/src/Mvc/dotnet-getdocument/src/Commands/InvokeCommand.cs @@ -16,155 +16,123 @@ internal class InvokeCommand : HelpCommandBase { private const string InsideManName = "GetDocument.Insider"; + private readonly ProjectOptions _projectOptions = new ProjectOptions(); private IList _args; - private CommandOption _configuration; - private CommandOption _output; - private CommandOption _project; - private CommandOption _projectExtensionsPath; - private CommandOption _runtime; - private CommandOption _targetFramework; public override void Configure(CommandLineApplication command) { - var options = new ProjectOptions(); - options.Configure(command); - - _configuration = options.Configuration; - _project = options.Project; - _projectExtensionsPath = options.ProjectExtensionsPath; - _runtime = options.Runtime; - _targetFramework = options.TargetFramework; + base.Configure(command); - _output = command.Option("--output ", Resources.OutputDescription); - command.VersionOption("--version", ProductInfo.GetVersion); + _projectOptions.Configure(command); _args = command.RemainingArguments; + } - base.Configure(command); + protected override void Validate() + { + base.Validate(); + _projectOptions.Validate(); } protected override int Execute() { - var projectFile = FindProjects( - _project.Value(), - Resources.NoProject, - Resources.MultipleProjects); - Reporter.WriteVerbose(Resources.FormatUsingProject(projectFile)); - - var project = Project.FromFile( - projectFile, - _projectExtensionsPath.Value(), - _targetFramework.Value(), - _configuration.Value(), - _runtime.Value()); - if (!File.Exists(project.TargetPath)) - { - throw new CommandException(Resources.MustBuild); - } - var thisPath = Path.GetFullPath(Path.GetDirectoryName(typeof(InvokeCommand).Assembly.Location)); + var projectName = _projectOptions.ProjectName.Value(); + var assemblyPath = _projectOptions.AssemblyPath.Value(); + var targetDirectory = Path.GetDirectoryName(assemblyPath); + string executable = null; var cleanupExecutable = false; try { string toolsDirectory; var args = new List(); - var targetFramework = new FrameworkName(project.TargetFrameworkMoniker); + var targetFramework = new FrameworkName(_projectOptions.TargetFramework.Value()); switch (targetFramework.Identifier) { case ".NETFramework": cleanupExecutable = true; - executable = Path.Combine(project.OutputPath, InsideManName + ".exe"); toolsDirectory = Path.Combine( thisPath, - project.PlatformTarget == "x86" ? "net461-x86" : "net461"); + _projectOptions.Platform.Value() == "x86" ? "net461-x86" : "net461"); var executableSource = Path.Combine(toolsDirectory, InsideManName + ".exe"); + executable = Path.Combine(targetDirectory, InsideManName + ".exe"); File.Copy(executableSource, executable, overwrite: true); - if (!string.IsNullOrEmpty(project.ConfigPath)) + var configPath = assemblyPath + ".config"; + if (File.Exists(configPath)) { - File.Copy(project.ConfigPath, executable + ".config", overwrite: true); + File.Copy(configPath, executable + ".config", overwrite: true); } break; case ".NETCoreApp": - executable = "dotnet"; - toolsDirectory = Path.Combine(thisPath, "netcoreapp2.0"); - - if (targetFramework.Version < new Version(2, 0)) + if (targetFramework.Version < new Version(2, 1)) { - throw new CommandException( - Resources.FormatNETCoreApp1Project(project.ProjectName, targetFramework.Version)); + throw new CommandException(Resources.FormatOldNETCoreAppProject( + projectName, + targetFramework.Version)); } + executable = "dotnet"; + toolsDirectory = Path.Combine(thisPath, "netcoreapp2.1"); + args.Add("exec"); args.Add("--depsFile"); - args.Add(project.ProjectDepsFilePath); + args.Add(Path.ChangeExtension(assemblyPath, ".deps.json")); - if (!string.IsNullOrEmpty(project.ProjectAssetsFile)) + var projectAssetsFile = _projectOptions.AssetsFile.Value(); + if (!string.IsNullOrEmpty(projectAssetsFile) && File.Exists(projectAssetsFile)) { - using (var reader = new JsonTextReader(File.OpenText(project.ProjectAssetsFile))) + using var reader = new JsonTextReader(File.OpenText(projectAssetsFile)); + var projectAssets = JToken.ReadFrom(reader); + var packageFolders = projectAssets["packageFolders"] + .Children() + .Select(p => p.Name); + + foreach (var packageFolder in packageFolders) { - var projectAssets = JToken.ReadFrom(reader); - var packageFolders = projectAssets["packageFolders"] - .Children() - .Select(p => p.Name); - - foreach (var packageFolder in packageFolders) - { - args.Add("--additionalProbingPath"); - args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar)); - } + args.Add("--additionalProbingPath"); + args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar)); } } - if (File.Exists(project.ProjectRuntimeConfigFilePath)) + var runtimeConfigPath = Path.ChangeExtension(assemblyPath, ".runtimeconfig.json"); + if (File.Exists(runtimeConfigPath)) { args.Add("--runtimeConfig"); - args.Add(project.ProjectRuntimeConfigFilePath); + args.Add(runtimeConfigPath); } - else if (!string.IsNullOrEmpty(project.RuntimeFrameworkVersion)) + else { - args.Add("--fx-version"); - args.Add(project.RuntimeFrameworkVersion); + var runtimeFrameworkVersion = _projectOptions.RuntimeFrameworkVersion.Value(); + if (!string.IsNullOrEmpty(runtimeFrameworkVersion)) + { + args.Add("--fx-version"); + args.Add(runtimeFrameworkVersion); + } } args.Add(Path.Combine(toolsDirectory, InsideManName + ".dll")); break; case ".NETStandard": - throw new CommandException(Resources.FormatNETStandardProject(project.ProjectName)); + throw new CommandException(Resources.FormatNETStandardProject(projectName)); default: throw new CommandException( - Resources.FormatUnsupportedFramework(project.ProjectName, targetFramework.Identifier)); + Resources.FormatUnsupportedFramework(projectName, targetFramework.Identifier)); } args.AddRange(_args); args.Add("--assembly"); - args.Add(project.TargetPath); + args.Add(assemblyPath); + args.Add("--project"); + args.Add(projectName); args.Add("--tools-directory"); args.Add(toolsDirectory); - if (!(args.Contains("--method") || string.IsNullOrEmpty(project.DefaultMethod))) - { - args.Add("--method"); - args.Add(project.DefaultMethod); - } - - if (!(args.Contains("--service") || string.IsNullOrEmpty(project.DefaultService))) - { - args.Add("--service"); - args.Add(project.DefaultService); - } - - if (_output.HasValue()) - { - args.Add("--output"); - args.Add(Path.GetFullPath(_output.Value())); - } - if (Reporter.IsVerbose) { args.Add("--verbose"); @@ -180,7 +148,7 @@ protected override int Execute() args.Add("--prefix-output"); } - return Exe.Run(executable, args, project.ProjectDirectory); + return Exe.Run(executable, args); } finally { @@ -205,44 +173,5 @@ protected override int Execute() } } } - - private static string FindProjects( - string path, - string errorWhenNoProject, - string errorWhenMultipleProjects) - { - var specified = true; - if (path == null) - { - specified = false; - path = Directory.GetCurrentDirectory(); - } - else if (!Directory.Exists(path)) // It's not a directory - { - return path; - } - - var projectFiles = Directory - .EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly) - .Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase)) - .Take(2) - .ToList(); - if (projectFiles.Count == 0) - { - throw new CommandException( - specified - ? Resources.FormatNoProjectInDirectory(path) - : errorWhenNoProject); - } - if (projectFiles.Count != 1) - { - throw new CommandException( - specified - ? Resources.FormatMultipleProjectsInDirectory(path) - : errorWhenMultipleProjects); - } - - return projectFiles[0]; - } } } diff --git a/src/Mvc/dotnet-getdocument/src/Exe.cs b/src/Mvc/dotnet-getdocument/src/Exe.cs index bc17eee13036..68df26cc318a 100644 --- a/src/Mvc/dotnet-getdocument/src/Exe.cs +++ b/src/Mvc/dotnet-getdocument/src/Exe.cs @@ -79,37 +79,37 @@ private static string ToArguments(IReadOnlyList args) builder.Append("\""); - var pendingBackslashs = 0; + var pendingBackslashes = 0; for (var j = 0; j < args[i].Length; j++) { switch (args[i][j]) { case '\"': - if (pendingBackslashs != 0) + if (pendingBackslashes != 0) { - builder.Append('\\', pendingBackslashs * 2); - pendingBackslashs = 0; + builder.Append('\\', pendingBackslashes * 2); + pendingBackslashes = 0; } builder.Append("\\\""); break; case '\\': - pendingBackslashs++; + pendingBackslashes++; break; default: - if (pendingBackslashs != 0) + if (pendingBackslashes != 0) { - if (pendingBackslashs == 1) + if (pendingBackslashes == 1) { builder.Append("\\"); } else { - builder.Append('\\', pendingBackslashs * 2); + builder.Append('\\', pendingBackslashes * 2); } - pendingBackslashs = 0; + pendingBackslashes = 0; } builder.Append(args[i][j]); @@ -117,9 +117,9 @@ private static string ToArguments(IReadOnlyList args) } } - if (pendingBackslashs != 0) + if (pendingBackslashes != 0) { - builder.Append('\\', pendingBackslashs * 2); + builder.Append('\\', pendingBackslashes * 2); } builder.Append("\""); diff --git a/src/Mvc/dotnet-getdocument/src/Program.cs b/src/Mvc/dotnet-getdocument/src/Program.cs index 94958f2840e1..cef1648689de 100644 --- a/src/Mvc/dotnet-getdocument/src/Program.cs +++ b/src/Mvc/dotnet-getdocument/src/Program.cs @@ -14,6 +14,7 @@ private static int Main(string[] args) var app = new CommandLineApplication(throwOnUnexpectedArg: false) { FullName = Resources.CommandFullName, + Name = Resources.CommandFullName, }; new InvokeCommand().Configure(app); diff --git a/src/Mvc/dotnet-getdocument/src/Project.cs b/src/Mvc/dotnet-getdocument/src/Project.cs deleted file mode 100644 index db9734e532a6..000000000000 --- a/src/Mvc/dotnet-getdocument/src/Project.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using IODirectory = System.IO.Directory; - -namespace Microsoft.Extensions.ApiDescription.Tool -{ - internal class Project - { - private const string ResourceFilename = "ServiceProjectReferenceMetadata.targets"; - private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Tool." + ResourceFilename; - - private Project() - { - } - - public string AssemblyName { get; private set; } - - public string ConfigPath { get; private set; } - - public string Configuration { get; private set; } - - public string DefaultDocumentName { get; private set; } - - public string DefaultMethod { get; private set; } - - public string DefaultService { get; private set; } - - public string OutputPath { get; private set; } - - public string Platform { get; private set; } - - public string PlatformTarget { get; private set; } - - public string ProjectAssetsFile { get; private set; } - - public string ProjectDepsFilePath { get; private set; } - - public string ProjectDirectory { get; private set; } - - public string ProjectExtensionsPath { get; private set; } - - public string ProjectName { get; private set; } - - public string ProjectRuntimeConfigFilePath { get; private set; } - - public string RuntimeFrameworkVersion { get; private set; } - - public string RuntimeIdentifier { get; private set; } - - public string TargetFramework { get; private set; } - - public string TargetFrameworkMoniker { get; private set; } - - public string TargetPath { get; private set; } - - public static Project FromFile( - string projectFile, - string buildExtensionsDirectory, - string framework = null, - string configuration = null, - string runtime = null) - { - if (string.IsNullOrEmpty(projectFile)) - { - throw new ArgumentNullException(nameof(projectFile)); - } - - if (string.IsNullOrEmpty(buildExtensionsDirectory)) - { - buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(projectFile), "obj"); - } - - IODirectory.CreateDirectory(buildExtensionsDirectory); - - var assembly = typeof(Project).Assembly; - var targetsPath = Path.Combine( - buildExtensionsDirectory, - $"{Path.GetFileName(projectFile)}.{ResourceFilename}"); - using (var input = assembly.GetManifestResourceStream(MSBuildResourceName)) - { - using (var output = File.OpenWrite(targetsPath)) - { - // NB: Copy always in case it changes - Reporter.WriteVerbose(Resources.FormatWritingFile(targetsPath)); - input.CopyTo(output); - - output.Flush(); - } - } - - IDictionary metadata; - var metadataPath = Path.GetTempFileName(); - try - { - var args = new List - { - "msbuild", - "/target:WriteServiceProjectReferenceMetadata", - "/verbosity:quiet", - "/nologo", - "/nodeReuse:false", - $"/property:ServiceProjectReferenceMetadataPath={metadataPath}", - projectFile, - }; - - if (!string.IsNullOrEmpty(framework)) - { - args.Add($"/property:TargetFramework={framework}"); - } - - if (!string.IsNullOrEmpty(configuration)) - { - args.Add($"/property:Configuration={configuration}"); - } - - if (!string.IsNullOrEmpty(runtime)) - { - args.Add($"/property:RuntimeIdentifier={runtime}"); - } - - var exitCode = Exe.Run("dotnet", args); - if (exitCode != 0) - { - throw new CommandException(Resources.GetMetadataFailed); - } - - metadata = File - .ReadLines(metadataPath) - .Select(l => l.Split(new[] { ':' }, 2)) - .ToDictionary(s => s[0], s => s[1].TrimStart()); - } - finally - { - // Ignore errors about in-use files. Should still be marked for delete after process cleanup. - try - { - File.Delete(metadataPath); - } - catch (UnauthorizedAccessException) - { - } - - try - { - File.Delete(targetsPath); - } - catch (UnauthorizedAccessException) - { - } - } - - var project = new Project - { - DefaultDocumentName = metadata[nameof(DefaultDocumentName)], - DefaultMethod = metadata[nameof(DefaultMethod)], - DefaultService = metadata[nameof(DefaultService)], - - AssemblyName = metadata[nameof(AssemblyName)], - Configuration = metadata[nameof(Configuration)], - OutputPath = metadata[nameof(OutputPath)], - Platform = metadata[nameof(Platform)], - PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)], - ProjectAssetsFile = metadata[nameof(ProjectAssetsFile)], - ProjectDepsFilePath = metadata[nameof(ProjectDepsFilePath)], - ProjectDirectory = metadata[nameof(ProjectDirectory)], - ProjectExtensionsPath = metadata[nameof(ProjectExtensionsPath)], - ProjectName = metadata[nameof(ProjectName)], - ProjectRuntimeConfigFilePath = metadata[nameof(ProjectRuntimeConfigFilePath)], - RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)], - RuntimeIdentifier = metadata[nameof(RuntimeIdentifier)], - TargetFramework = metadata[nameof(TargetFramework)], - TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)], - TargetPath = metadata[nameof(TargetPath)], - }; - - if (string.IsNullOrEmpty(project.OutputPath)) - { - throw new CommandException( - Resources.FormatGetMetadataValueFailed(nameof(OutputPath), nameof(OutputPath))); - } - - if (string.IsNullOrEmpty(project.ProjectDirectory)) - { - throw new CommandException( - Resources.FormatGetMetadataValueFailed(nameof(ProjectDirectory), "MSBuildProjectDirectory")); - } - - if (string.IsNullOrEmpty(project.TargetPath)) - { - throw new CommandException( - Resources.FormatGetMetadataValueFailed(nameof(TargetPath), nameof(TargetPath))); - } - - if (!Path.IsPathRooted(project.ProjectDirectory)) - { - project.OutputPath = Path.GetFullPath( - Path.Combine(IODirectory.GetCurrentDirectory(), project.ProjectDirectory)); - } - - if (!Path.IsPathRooted(project.OutputPath)) - { - project.OutputPath = Path.GetFullPath(Path.Combine(project.ProjectDirectory, project.OutputPath)); - } - - if (!Path.IsPathRooted(project.ProjectExtensionsPath)) - { - project.ProjectExtensionsPath = Path.GetFullPath( - Path.Combine(project.ProjectDirectory, project.ProjectExtensionsPath)); - } - - if (!Path.IsPathRooted(project.TargetPath)) - { - project.TargetPath = Path.GetFullPath(Path.Combine(project.OutputPath, project.TargetPath)); - } - - // Some document generation tools support non-ASP.NET Core projects. Any of the remaining properties may - // thus be null empty. - var configPath = $"{project.TargetPath}.config"; - if (File.Exists(configPath)) - { - project.ConfigPath = configPath; - } - - if (!(string.IsNullOrEmpty(project.ProjectAssetsFile) || Path.IsPathRooted(project.ProjectAssetsFile))) - { - project.ProjectAssetsFile = Path.GetFullPath( - Path.Combine(project.ProjectDirectory, project.ProjectAssetsFile)); - } - - if (!(string.IsNullOrEmpty(project.ProjectDepsFilePath) || Path.IsPathRooted(project.ProjectDepsFilePath))) - { - project.ProjectDepsFilePath = Path.GetFullPath( - Path.Combine(project.ProjectDirectory, project.ProjectDepsFilePath)); - } - - if (!(string.IsNullOrEmpty(project.ProjectRuntimeConfigFilePath) || - Path.IsPathRooted(project.ProjectRuntimeConfigFilePath))) - { - project.ProjectRuntimeConfigFilePath = Path.GetFullPath( - Path.Combine(project.OutputPath, project.ProjectRuntimeConfigFilePath)); - } - - return project; - } - } -} diff --git a/src/Mvc/dotnet-getdocument/src/ProjectOptions.cs b/src/Mvc/dotnet-getdocument/src/ProjectOptions.cs index c235c2191a92..84b32968e992 100644 --- a/src/Mvc/dotnet-getdocument/src/ProjectOptions.cs +++ b/src/Mvc/dotnet-getdocument/src/ProjectOptions.cs @@ -7,25 +7,44 @@ namespace Microsoft.Extensions.ApiDescription.Tool { internal class ProjectOptions { - public CommandOption Configuration { get; private set; } + public CommandOption AssemblyPath { get; private set; } - public CommandOption Project { get; private set; } + public CommandOption AssetsFile { get; private set; } - public CommandOption ProjectExtensionsPath { get; private set; } + public CommandOption Platform { get; private set; } - public CommandOption Runtime { get; private set; } + public CommandOption ProjectName { get; private set; } + + public CommandOption RuntimeFrameworkVersion { get; private set; } public CommandOption TargetFramework { get; private set; } public void Configure(CommandLineApplication command) { - Configuration = command.Option("--configuration ", Resources.ConfigurationDescription); - Project = command.Option("-p|--project ", Resources.ProjectDescription); - ProjectExtensionsPath = command.Option( - "--projectExtensionsPath ", - Resources.ProjectExtensionsPathDescription); - Runtime = command.Option("--runtime ", Resources.RuntimeDescription); + AssemblyPath = command.Option("--assembly ", Resources.AssemblyDescription); + AssetsFile = command.Option("--assets-file ", Resources.AssetsFileDescription); TargetFramework = command.Option("--framework ", Resources.TargetFrameworkDescription); + Platform = command.Option("--platform ", Resources.PlatformDescription); + ProjectName = command.Option("--project ", Resources.ProjectDescription); + RuntimeFrameworkVersion = command.Option("--runtime ", Resources.RuntimeDescription); + } + + public void Validate() + { + if (!AssemblyPath.HasValue()) + { + throw new CommandException(Resources.FormatMissingOption(AssemblyPath.LongName)); + } + + if (!ProjectName.HasValue()) + { + throw new CommandException(Resources.FormatMissingOption(ProjectName.LongName)); + } + + if (!TargetFramework.HasValue()) + { + throw new CommandException(Resources.FormatMissingOption(TargetFramework.LongName)); + } } } } diff --git a/src/Mvc/dotnet-getdocument/src/Properties/Resources.Designer.cs b/src/Mvc/dotnet-getdocument/src/Properties/Resources.Designer.cs index 28501e78806d..49baad030bbc 100644 --- a/src/Mvc/dotnet-getdocument/src/Properties/Resources.Designer.cs +++ b/src/Mvc/dotnet-getdocument/src/Properties/Resources.Designer.cs @@ -10,20 +10,6 @@ internal static class Resources private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly); - /// - /// The configuration to use. - /// - internal static string ConfigurationDescription - { - get => GetString("ConfigurationDescription"); - } - - /// - /// The configuration to use. - /// - internal static string FormatConfigurationDescription() - => GetString("ConfigurationDescription"); - /// /// dotnet-getdocument /// @@ -39,7 +25,7 @@ internal static string FormatCommandFullName() => GetString("CommandFullName"); /// - /// The target framework. + /// The target framework to use. Required. /// internal static string TargetFrameworkDescription { @@ -47,69 +33,27 @@ internal static string TargetFrameworkDescription } /// - /// The target framework. + /// The target framework to use. Required. /// internal static string FormatTargetFrameworkDescription() => GetString("TargetFrameworkDescription"); /// - /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option. - /// - internal static string GetMetadataFailed - { - get => GetString("GetMetadataFailed"); - } - - /// - /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option. - /// - internal static string FormatGetMetadataFailed() - => GetString("GetMetadataFailed"); - - /// - /// More than one project was found in the current working directory. Use the --project option. - /// - internal static string MultipleProjects - { - get => GetString("MultipleProjects"); - } - - /// - /// More than one project was found in the current working directory. Use the --project option. - /// - internal static string FormatMultipleProjects() - => GetString("MultipleProjects"); - - /// - /// More than one project was found in directory '{0}'. Specify one using its file name. - /// - internal static string MultipleProjectsInDirectory - { - get => GetString("MultipleProjectsInDirectory"); - } - - /// - /// More than one project was found in directory '{0}'. Specify one using its file name. - /// - internal static string FormatMultipleProjectsInDirectory(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("MultipleProjectsInDirectory"), p0); - - /// - /// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. + /// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.1 or higher. /// - internal static string NETCoreApp1Project + internal static string OldNETCoreAppProject { - get => GetString("NETCoreApp1Project"); + get => GetString("OldNETCoreAppProject"); } /// - /// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. + /// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.1 or higher. /// - internal static string FormatNETCoreApp1Project(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("NETCoreApp1Project"), p0, p1); + internal static string FormatOldNETCoreAppProject(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("OldNETCoreAppProject"), p0, p1); /// - /// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, update this project to target .NET Core and / or .NET Framework. /// internal static string NETStandardProject { @@ -117,13 +61,13 @@ internal static string NETStandardProject } /// - /// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, update this project to target .NET Core and / or .NET Framework. /// internal static string FormatNETStandardProject(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("NETStandardProject"), p0); /// - /// Do not colorize output. + /// Do not colorize console output. /// internal static string NoColorDescription { @@ -131,41 +75,13 @@ internal static string NoColorDescription } /// - /// Do not colorize output. + /// Do not colorize console output. /// internal static string FormatNoColorDescription() => GetString("NoColorDescription"); /// - /// No project was found. Change the current working directory or use the --project option. - /// - internal static string NoProject - { - get => GetString("NoProject"); - } - - /// - /// No project was found. Change the current working directory or use the --project option. - /// - internal static string FormatNoProject() - => GetString("NoProject"); - - /// - /// No project was found in directory '{0}'. - /// - internal static string NoProjectInDirectory - { - get => GetString("NoProjectInDirectory"); - } - - /// - /// No project was found in directory '{0}'. - /// - internal static string FormatNoProjectInDirectory(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("NoProjectInDirectory"), p0); - - /// - /// Prefix output with level. + /// Prefix console output with logging level. /// internal static string PrefixDescription { @@ -173,13 +89,13 @@ internal static string PrefixDescription } /// - /// Prefix output with level. + /// Prefix console output with logging level. /// internal static string FormatPrefixDescription() => GetString("PrefixDescription"); /// - /// The project to use. + /// The project name. Required. /// internal static string ProjectDescription { @@ -187,25 +103,11 @@ internal static string ProjectDescription } /// - /// The project to use. + /// The project name. Required. /// internal static string FormatProjectDescription() => GetString("ProjectDescription"); - /// - /// The MSBuild project extensions path. Defaults to "obj". - /// - internal static string ProjectExtensionsPathDescription - { - get => GetString("ProjectExtensionsPathDescription"); - } - - /// - /// The MSBuild project extensions path. Defaults to "obj". - /// - internal static string FormatProjectExtensionsPathDescription() - => GetString("ProjectExtensionsPathDescription"); - /// /// The runtime identifier to use. /// @@ -234,20 +136,6 @@ internal static string UnsupportedFramework internal static string FormatUnsupportedFramework(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedFramework"), p0, p1); - /// - /// Using project '{0}'. - /// - internal static string UsingProject - { - get => GetString("UsingProject"); - } - - /// - /// Using project '{0}'. - /// - internal static string FormatUsingProject(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingProject"), p0); - /// /// Show verbose output. /// @@ -263,60 +151,60 @@ internal static string FormatVerboseDescription() => GetString("VerboseDescription"); /// - /// Writing '{0}'... + /// The project assets file to use. /// - internal static string WritingFile + internal static string AssetsFileDescription { - get => GetString("WritingFile"); + get => GetString("AssetsFileDescription"); } /// - /// Writing '{0}'... + /// The project assets file to use. /// - internal static string FormatWritingFile(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0); + internal static string FormatAssetsFileDescription() + => GetString("AssetsFileDescription"); /// - /// Project output not found. Project must be up-to-date when using this tool. + /// Missing required option '--{0}'. /// - internal static string MustBuild + internal static string MissingOption { - get => GetString("MustBuild"); + get => GetString("MissingOption"); } /// - /// Project output not found. Project must be up-to-date when using this tool. + /// Missing required option '--{0}'. /// - internal static string FormatMustBuild() - => GetString("MustBuild"); + internal static string FormatMissingOption(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0); /// - /// The file to write the result to. + /// The platform to use. /// - internal static string OutputDescription + internal static string PlatformDescription { - get => GetString("OutputDescription"); + get => GetString("PlatformDescription"); } /// - /// The file to write the result to. + /// The platform to use. /// - internal static string FormatOutputDescription() - => GetString("OutputDescription"); + internal static string FormatPlatformDescription() + => GetString("PlatformDescription"); /// - /// Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set. + /// The assembly path to use. Required. /// - internal static string GetMetadataValueFailed + internal static string AssemblyDescription { - get => GetString("GetMetadataValueFailed"); + get => GetString("AssemblyDescription"); } /// - /// Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set. + /// The assembly path to use. Required. /// - internal static string FormatGetMetadataValueFailed(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("GetMetadataValueFailed"), p0, p1); + internal static string FormatAssemblyDescription() + => GetString("AssemblyDescription"); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Mvc/dotnet-getdocument/src/Resources.resx b/src/Mvc/dotnet-getdocument/src/Resources.resx index d87157fff24f..08f92d039730 100644 --- a/src/Mvc/dotnet-getdocument/src/Resources.resx +++ b/src/Mvc/dotnet-getdocument/src/Resources.resx @@ -117,47 +117,26 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The configuration to use. - dotnet-getdocument - The target framework. - - - Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option. - - - More than one project was found in the current working directory. Use the --project option. - - - More than one project was found in directory '{0}'. Specify one using its file name. + The target framework to use. Required. - - Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. + + Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.1 or higher. - Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, update this project to target .NET Core and / or .NET Framework. - Do not colorize output. - - - No project was found. Change the current working directory or use the --project option. - - - No project was found in directory '{0}'. + Do not colorize console output. - Prefix output with level. + Prefix console output with logging level. - The project to use. - - - The MSBuild project extensions path. Defaults to "obj". + The project name. Required. The runtime identifier to use. @@ -165,22 +144,19 @@ Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework. - - Using project '{0}'. - Show verbose output. - - Writing '{0}'... + + The project assets file to use. - - Project output not found. Project must be up-to-date when using this tool. + + Missing required option '--{0}'. - - The file to write the result to. + + The platform to use. - - Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set. + + The assembly path to use. Required. - + \ No newline at end of file diff --git a/src/Mvc/dotnet-getdocument/src/ServiceProjectReferenceMetadata.targets b/src/Mvc/dotnet-getdocument/src/ServiceProjectReferenceMetadata.targets deleted file mode 100644 index 177d950c9e91..000000000000 --- a/src/Mvc/dotnet-getdocument/src/ServiceProjectReferenceMetadata.targets +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mvc/dotnet-getdocument/src/dotnet-getdocument.csproj b/src/Mvc/dotnet-getdocument/src/dotnet-getdocument.csproj index 1991b3e2df87..6a2f52617982 100644 --- a/src/Mvc/dotnet-getdocument/src/dotnet-getdocument.csproj +++ b/src/Mvc/dotnet-getdocument/src/dotnet-getdocument.csproj @@ -22,8 +22,6 @@ - - diff --git a/src/Mvc/dotnet-getdocument/src/runtimeconfig.template.json b/src/Mvc/dotnet-getdocument/src/runtimeconfig.template.json new file mode 100644 index 000000000000..516c80a38878 --- /dev/null +++ b/src/Mvc/dotnet-getdocument/src/runtimeconfig.template.json @@ -0,0 +1,4 @@ +{ + "applyPatches": true, + "rollForwardOnNoCandidateFx": 2 +}