Rewrite dotnet-getdocument and GetDocument.Insider to retrieve all documents in one go (#10667)

- #8242 1 of 2
- save a cache file listing all retrieved documents
- remove .NET Core App 2.0 support
- remove ServiceProjectReferenceMetadata.targets, related `Project` class, and searches for a project
  - tools will run within a project and get needed information from project on command line
- roll framework forward in both tools to expand their applicability when using .NET Core
- use Microsoft.Extensions.HostFactoryResolver.Sources (part of #4923)
  - remove Microsoft.AspNetCore.Hosting.Abstractions dependency

nits:
- refactor methods in `GetDocumentCommandWorker`
- reorder option addition for consistency and to place `--help` at the top of help output
- consolidate information about method signatures at top of `GetDocumentCommandWorker`
- consolidate `try` / `catch` blocks in `GetDocumentCommandWorker`
- shorten the lifespan of a `Task`
- ensure GetDocument.Insider exit codes are unique
- make a few more `string`s `const`
- fold a few expressions over fewer lines
This commit is contained in:
Doug Bunting 2019-06-02 21:58:08 -07:00 committed by GitHub
parent 91e6839c8d
commit 4c8ca0b080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 601 additions and 990 deletions

View File

@ -32,7 +32,6 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
}
protected virtual int Execute()
=> 0;
protected virtual int Execute() => 0;
}
}

View File

@ -5,7 +5,7 @@ using System;
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,45 +14,30 @@ 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 <Name>",
Resources.FormatDocumentDescription(FallbackDocumentName));
_method = command.Option("--method <Name>", Resources.FormatMethodDescription(FallbackMethod));
_output = command.Option("--output <Path>", Resources.OutputDescription);
_service = command.Option("--service <QualifiedName>", Resources.FormatServiceDescription(FallbackService));
_fileListPath = command.Option("--file-list <Path>", Resources.FileListDescription);
_output = command.Option("--output <Directory>", Resources.OutputDescription);
}
protected override void Validate()
{
base.Validate();
if (!_fileListPath.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_fileListPath.LongName));
}
if (!_output.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_output.LongName));
}
if (_method.HasValue() && !_service.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_service.LongName));
}
if (_service.HasValue() && !_method.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_method.LongName));
}
}
protected override int Execute()
@ -79,7 +64,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
}
}
#if NETCOREAPP2_0
#if NETCOREAPP2_1
AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) =>
{
var name = assemblyName.Name;
@ -125,7 +110,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
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 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
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 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
return 1;
return 2;
}
}

View File

@ -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 FileListPath { get; set; }
public string Method { get; set; }
public string OutputDirectory { get; set; }
public string OutputPath { get; set; }
public string Service { get; set; }
public string ProjectName { get; set; }
}
}

View File

@ -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<object>();
private static readonly Type[] GetDocumentsParameterTypes = Type.EmptyTypes;
private static readonly Type GetDocumentsReturnType = typeof(IEnumerable<string>);
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 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
if (entryPointType == null)
{
Reporter.WriteError(Resources.FormatMissingEntryPoint(context.AssemblyPath));
return 2;
}
var services = GetServices(entryPointType, context.AssemblyPath, context.AssemblyName);
if (services == null)
{
return 3;
}
var success = TryProcess(context, services);
if (!success)
try
{
// As part of the aspnet/Mvc#8425 fix, return 4 here.
return 0;
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<string>());
if (services == null)
{
Reporter.WriteError(Resources.FormatServiceProviderNotFound(
typeof(IServiceProvider),
HostFactoryResolver.BuildWebHost,
HostFactoryResolver.CreateHostBuilder,
HostFactoryResolver.CreateWebHostBuilder,
entryPointType));
return 5;
}
var success = GetDocuments(context, services);
if (!success)
{
return 6;
}
}
catch (Exception ex)
{
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;
Type serviceType = null;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
serviceType = assembly.GetType(DocumentService, throwOnError: false);
if (serviceType != null)
{
break;
}
}
Reporter.WriteInformation(Resources.FormatUsingDocument(documentName));
Reporter.WriteInformation(Resources.FormatUsingMethod(methodName));
Reporter.WriteInformation(Resources.FormatUsingService(serviceName));
if (serviceType == null)
{
Reporter.WriteError(Resources.FormatServiceTypeNotFound(DocumentService));
return false;
}
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<string>)InvokeMethod(getDocumentsMethod, service, GetDocumentsArguments);
if (documentNames == null)
{
return false;
}
// Write out the documents.
Directory.CreateDirectory(context.OutputDirectory);
var filePathList = new List<string>();
foreach (var documentName in documentNames)
{
var filePath = GetDocument(
documentName,
context.ProjectName,
context.OutputDirectory,
generateMethod,
service);
if (filePath == null)
{
return false;
}
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)
{
return null;
}
var finished = resultTask.Wait(TimeSpan.FromMinutes(1));
if (!finished)
{
Reporter.WriteError(Resources.FormatMethodTimedOut(GenerateMethodName, DocumentService, 1));
return null;
}
}
if (stream.Length == 0L)
{
Reporter.WriteError(
Resources.FormatMethodWroteNoContent(GenerateMethodName, DocumentService, documentName));
return null;
}
var filePath = GetDocumentPath(documentName, projectName, outputDirectory);
Reporter.WriteInformation(Resources.FormatWritingDocument(documentName, filePath));
try
{
Type serviceType = null;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
serviceType = assembly.GetType(serviceName, throwOnError: false);
if (serviceType != null)
{
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;
}
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))
{
Reporter.WriteWarning(Resources.FormatMethodReturnTypeUnsupported(
methodName,
serviceName,
method.ReturnType,
typeof(Task)));
return false;
}
var service = services.GetService(serviceType);
if (service == null)
{
Reporter.WriteWarning(Resources.FormatServiceNotFound(serviceName));
return false;
}
stream.Position = 0L;
// 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 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();
}
}
}
return true;
using var outStream = File.Create(filePath);
stream.CopyTo(outStream);
}
catch (AggregateException ex) when (ex.InnerException != null)
catch
{
foreach (var innerException in ex.Flatten().InnerExceptions)
{
Reporter.WriteWarning(FormatException(innerException));
}
}
catch (Exception ex)
{
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<string>() };
var methodInfo = entryPointType.GetMethod("BuildWebHost");
if (methodInfo != null)
string path;
if (string.Equals(DefaultDocumentName, documentName, StringComparison.Ordinal))
{
// 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.");
// 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));
return null;
while (sanitizedDocumentName.Contains(InvalidFilenameString))
{
sanitizedDocumentName = sanitizedDocumentName.Replace(InvalidFilenameString, DotString);
}
try
{
var webHost = (IWebHost)methodInfo.Invoke(obj: null, parameters: args);
return webHost.Services;
}
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.");
return null;
}
try
{
var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args);
return builder.Build().Services;
}
catch (Exception ex)
{
Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}");
return null;
}
path = Path.Combine(outputDirectory, path);
}
return null;
return path;
}
private static string FormatException(Exception exception)
private static MethodInfo GetMethod(string methodName, Type type, Type[] parameterTypes, Type returnType)
{
return $"{exception.GetType().FullName}: {exception.Message}";
var method = type.GetMethod(methodName, parameterTypes);
if (method == null)
{
Reporter.WriteError(Resources.FormatMethodNotFound(methodName, type));
return null;
}
if (method.IsStatic)
{
Reporter.WriteError(Resources.FormatMethodIsStatic(methodName, type));
return null;
}
if (!returnType.IsAssignableFrom(method.ReturnType))
{
Reporter.WriteError(
Resources.FormatMethodReturnTypeUnsupported(methodName, type, method.ReturnType, returnType));
return null;
}
return method;
}
private static object InvokeMethod(MethodInfo method, object instance, object[] arguments)
{
var result = method.Invoke(instance, arguments);
if (result == null)
{
Reporter.WriteError(
Resources.FormatMethodReturnedNull(method.Name, method.DeclaringType, method.ReturnType));
}
return result;
}
}
}

View File

@ -9,9 +9,9 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
public override void Configure(CommandLineApplication command)
{
base.Configure(command);
command.HelpOption("-h|--help");
command.VersionOption("--version", ProductInfo.GetVersion);
base.Configure(command);
}
}
}

View File

@ -9,13 +9,16 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
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 <PATH>", Resources.AssemblyDescription);
AssemblyPath = command.Option("--assembly <PATH>", Resources.AssemblyDescription);
ProjectName = command.Option("--project <Name>", Resources.ProjectDescription);
ToolsDirectory = command.Option("--tools-directory <PATH>", Resources.ToolsDirectoryDescription);
}
@ -28,6 +31,11 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
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));

View File

@ -5,7 +5,7 @@
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
<VerifyVersion>false</VerifyVersion>
<VersionPrefix>$(ExperimentalVersionPrefix)</VersionPrefix>
<VersionSuffix>$(ExperimentalVersionSuffix)</VersionSuffix>
@ -17,10 +17,7 @@
</ItemGroup>
<ItemGroup>
<!-- Hard-coded to 2.0 because this project currently targets netcoreapp2.0 -->
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.0">
<AllowExplicitReference>true</AllowExplicitReference>
</PackageReference>
<Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" />
</ItemGroup>
<Target Name="BuildX86" AfterTargets="Build" Condition=" '$(TargetFramework)' == 'net461' And '$(Platform)' != 'x86' ">

View File

@ -17,9 +17,10 @@ namespace Microsoft.Extensions.ApiDescription.Tool
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);

View File

@ -11,7 +11,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The assembly to use.
/// The assembly path to use. Required.
/// </summary>
internal static string AssemblyDescription
{
@ -19,7 +19,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// The assembly to use.
/// The assembly path to use. Required.
/// </summary>
internal static string FormatAssemblyDescription()
=> GetString("AssemblyDescription");
@ -39,7 +39,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0);
/// <summary>
/// Do not colorize output.
/// Do not colorize console output.
/// </summary>
internal static string NoColorDescription
{
@ -47,13 +47,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// Do not colorize output.
/// Do not colorize console output.
/// </summary>
internal static string FormatNoColorDescription()
=> GetString("NoColorDescription");
/// <summary>
/// The file to write the result to.
/// The directory where the document files should be written. Required.
/// </summary>
internal static string OutputDescription
{
@ -61,7 +61,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// The file to write the result to.
/// The directory where the document files should be written. Required.
/// </summary>
internal static string FormatOutputDescription()
=> GetString("OutputDescription");
@ -81,7 +81,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
=> GetString("PrefixDescription");
/// <summary>
/// Show verbose output.
/// Show verbose console output.
/// </summary>
internal static string VerboseDescription
{
@ -89,13 +89,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// Show verbose output.
/// Show verbose console output.
/// </summary>
internal static string FormatVerboseDescription()
=> GetString("VerboseDescription");
/// <summary>
/// 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.
/// </summary>
internal static string ToolsDirectoryDescription
{
@ -103,108 +103,24 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// 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.
/// </summary>
internal static string FormatToolsDirectoryDescription()
=> GetString("ToolsDirectoryDescription");
/// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// Generating document named '{0}'.
/// </summary>
internal static string MethodDescription
internal static string GeneratingDocument
{
get => GetString("MethodDescription");
get => GetString("GeneratingDocument");
}
/// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// Generating document named '{0}'.
/// </summary>
internal static string FormatMethodDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodDescription"), p0);
/// <summary>
/// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.
/// </summary>
internal static string ServiceDescription
{
get => GetString("ServiceDescription");
}
/// <summary>
/// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.
/// </summary>
internal static string FormatServiceDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0);
/// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary>
internal static string DocumentDescription
{
get => GetString("DocumentDescription");
}
/// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary>
internal static string FormatDocumentDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DocumentDescription"), p0);
/// <summary>
/// Using document name '{0}'.
/// </summary>
internal static string UsingDocument
{
get => GetString("UsingDocument");
}
/// <summary>
/// Using document name '{0}'.
/// </summary>
internal static string FormatUsingDocument(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingDocument"), p0);
/// <summary>
/// Using method '{0}'.
/// </summary>
internal static string UsingMethod
{
get => GetString("UsingMethod");
}
/// <summary>
/// Using method '{0}'.
/// </summary>
internal static string FormatUsingMethod(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingMethod"), p0);
/// <summary>
/// Using service '{0}'.
/// </summary>
internal static string UsingService
{
get => GetString("UsingService");
}
/// <summary>
/// Using service '{0}'.
/// </summary>
internal static string FormatUsingService(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0);
/// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary>
internal static string MethodInvocationFailed
{
get => GetString("MethodInvocationFailed");
}
/// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary>
internal static string FormatMethodInvocationFailed(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodInvocationFailed"), p0, p1, p2);
internal static string FormatGeneratingDocument(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("GeneratingDocument"), p0);
/// <summary>
/// Assembly '{0}' does not contain an entry point.
@ -235,7 +151,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
=> string.Format(CultureInfo.CurrentCulture, GetString("ServiceTypeNotFound"), p0);
/// <summary>
/// Unable to find method named '{0}' in '{1}' implementation.
/// Method '{0}' not found in type '{1}' with expected signature.
/// </summary>
internal static string MethodNotFound
{
@ -243,13 +159,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// Unable to find method named '{0}' in '{1}' implementation.
/// Method '{0}' not found in type '{1}' with expected signature.
/// </summary>
internal static string FormatMethodNotFound(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodNotFound"), p0, p1);
/// <summary>
/// Unable to find service of type '{0}' in dependency injection container.
/// Unable to find service type '{0}' in dependency injection container.
/// </summary>
internal static string ServiceNotFound
{
@ -257,13 +173,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// Unable to find service of type '{0}' in dependency injection container.
/// Unable to find service type '{0}' in dependency injection container.
/// </summary>
internal static string FormatServiceNotFound(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ServiceNotFound"), p0);
/// <summary>
/// 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}'.
/// </summary>
internal static string MethodReturnedNull
{
@ -271,13 +187,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// 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}'.
/// </summary>
internal static string FormatMethodReturnedNull(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnedNull"), p0, p1, p2);
/// <summary>
/// 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}'.
/// </summary>
internal static string MethodReturnTypeUnsupported
{
@ -285,13 +201,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// 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}'.
/// </summary>
internal static string FormatMethodReturnTypeUnsupported(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnTypeUnsupported"), p0, p1, p2, p3);
/// <summary>
/// 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.
/// </summary>
internal static string MethodTimedOut
{
@ -299,11 +215,123 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// 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.
/// </summary>
internal static string FormatMethodTimedOut(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodTimedOut"), p0, p1, p2);
/// <summary>
/// Method '{0}' of type '{1}' is static. Must be an instance method.
/// </summary>
internal static string MethodIsStatic
{
get => GetString("MethodIsStatic");
}
/// <summary>
/// Method '{0}' of type '{1}' is static. Must be an instance method.
/// </summary>
internal static string FormatMethodIsStatic(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodIsStatic"), p0, p1);
/// <summary>
/// No method '{0}', '{1}' or '{2}' found in type '{3}' with expected signatures.
/// </summary>
internal static string MethodsNotFound
{
get => GetString("MethodsNotFound");
}
/// <summary>
/// No method '{0}', '{1}' or '{2}' found in type '{3}' with expected signatures.
/// </summary>
internal static string FormatMethodsNotFound(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodsNotFound"), p0, p1, p2, p3);
/// <summary>
/// Writing document named '{0}' to '{1}'.
/// </summary>
internal static string WritingDocument
{
get => GetString("WritingDocument");
}
/// <summary>
/// Writing document named '{0}' to '{1}'.
/// </summary>
internal static string FormatWritingDocument(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("WritingDocument"), p0, p1);
/// <summary>
/// Method '{0}' of type '{1}' wrote no content for document named '{2}'.
/// </summary>
internal static string MethodWroteNoContent
{
get => GetString("MethodWroteNoContent");
}
/// <summary>
/// Method '{0}' of type '{1}' wrote no content for document named '{2}'.
/// </summary>
internal static string FormatMethodWroteNoContent(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodWroteNoContent"), p0, p1, p2);
/// <summary>
/// GetDocument.Insider
/// </summary>
internal static string CommandFullName
{
get => GetString("CommandFullName");
}
/// <summary>
/// GetDocument.Insider
/// </summary>
internal static string FormatCommandFullName()
=> GetString("CommandFullName");
/// <summary>
/// The path where the list of document files should be written. Required.
/// </summary>
internal static string FileListDescription
{
get => GetString("FileListDescription");
}
/// <summary>
/// The path where the list of document files should be written. Required.
/// </summary>
internal static string FormatFileListDescription()
=> GetString("FileListDescription");
/// <summary>
/// The project name to use. Required.
/// </summary>
internal static string ProjectDescription
{
get => GetString("ProjectDescription");
}
/// <summary>
/// The project name to use. Required.
/// </summary>
internal static string FormatProjectDescription()
=> GetString("ProjectDescription");
/// <summary>
/// Unable to resolve a non-null '{0}' implementation using method '{1}', '{2}' or '{3}' of type '{4}'.
/// </summary>
internal static string ServiceProviderNotFound
{
get => GetString("ServiceProviderNotFound");
}
/// <summary>
/// Unable to resolve a non-null '{0}' implementation using method '{1}', '{2}' or '{3}' of type '{4}'.
/// </summary>
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)
{
var value = _resourceManager.GetString(name);

View File

@ -118,46 +118,28 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AssemblyDescription" xml:space="preserve">
<value>The assembly to use.</value>
<value>The assembly path to use. Required.</value>
</data>
<data name="MissingOption" xml:space="preserve">
<value>Missing required option '--{0}'.</value>
</data>
<data name="NoColorDescription" xml:space="preserve">
<value>Do not colorize output.</value>
<value>Do not colorize console output.</value>
</data>
<data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value>
<value>The directory where the document files should be written. Required.</value>
</data>
<data name="PrefixDescription" xml:space="preserve">
<value>Prefix console output with logging level.</value>
</data>
<data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value>
<value>Show verbose console output.</value>
</data>
<data name="ToolsDirectoryDescription" xml:space="preserve">
<value>Location from which inside man was copied (in the .NET Framework case) or loaded.</value>
<value>Location from which inside man was copied (in the .NET Framework case) or loaded. Required.</value>
</data>
<data name="MethodDescription" xml:space="preserve">
<value>The name of the method to invoke on the '--service' instance. Default value '{0}'.</value>
</data>
<data name="ServiceDescription" xml:space="preserve">
<value>The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.</value>
</data>
<data name="DocumentDescription" xml:space="preserve">
<value>The name of the document to pass to the '--method' method. Default value '{0}'.</value>
</data>
<data name="UsingDocument" xml:space="preserve">
<value>Using document name '{0}'.</value>
</data>
<data name="UsingMethod" xml:space="preserve">
<value>Using method '{0}'.</value>
</data>
<data name="UsingService" xml:space="preserve">
<value>Using service '{0}'.</value>
</data>
<data name="MethodInvocationFailed" xml:space="preserve">
<value>Method '{0}' of service '{1}' failed to generate document '{2}'.</value>
<data name="GeneratingDocument" xml:space="preserve">
<value>Generating document named '{0}'.</value>
</data>
<data name="MissingEntryPoint" xml:space="preserve">
<value>Assembly '{0}' does not contain an entry point.</value>
@ -166,18 +148,42 @@
<value>Unable to find service type '{0}' in loaded assemblies.</value>
</data>
<data name="MethodNotFound" xml:space="preserve">
<value>Unable to find method named '{0}' in '{1}' implementation.</value>
<value>Method '{0}' not found in type '{1}' with expected signature.</value>
</data>
<data name="ServiceNotFound" xml:space="preserve">
<value>Unable to find service of type '{0}' in dependency injection container.</value>
<value>Unable to find service type '{0}' in dependency injection container.</value>
</data>
<data name="MethodReturnedNull" xml:space="preserve">
<value>Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'.</value>
<value>Method '{0}' of type '{1}' returned null. Must return a non-null '{2}'.</value>
</data>
<data name="MethodReturnTypeUnsupported" xml:space="preserve">
<value>Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'.</value>
<value>Method '{0}' of type '{1}' has unsupported return type '{2}'. Must return '{3}'.</value>
</data>
<data name="MethodTimedOut" xml:space="preserve">
<value>Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute.</value>
<value>Method '{0}' of type '{1}' timed out. Must complete execution within {2} minute.</value>
</data>
<data name="MethodIsStatic" xml:space="preserve">
<value>Method '{0}' of type '{1}' is static. Must be an instance method.</value>
</data>
<data name="MethodsNotFound" xml:space="preserve">
<value>No method '{0}', '{1}' or '{2}' found in type '{3}' with expected signatures.</value>
</data>
<data name="WritingDocument" xml:space="preserve">
<value>Writing document named '{0}' to '{1}'.</value>
</data>
<data name="MethodWroteNoContent" xml:space="preserve">
<value>Method '{0}' of type '{1}' wrote no content for document named '{2}'.</value>
</data>
<data name="CommandFullName" xml:space="preserve">
<value>GetDocument.Insider</value>
</data>
<data name="FileListDescription" xml:space="preserve">
<value>The path where the list of document files should be written. Required.</value>
</data>
<data name="ProjectDescription" xml:space="preserve">
<value>The project name to use. Required.</value>
</data>
<data name="ServiceProviderNotFound" xml:space="preserve">
<value>Unable to resolve a non-null '{0}' implementation using method '{1}', '{2}' or '{3}' of type '{4}'.</value>
</data>
</root>

View File

@ -0,0 +1,4 @@
{
"applyPatches": true,
"rollForwardOnNoCandidateFx": 2
}

View File

@ -16,155 +16,123 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
private const string InsideManName = "GetDocument.Insider";
private readonly ProjectOptions _projectOptions = new ProjectOptions();
private IList<string> _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;
_output = command.Option("--output <Path>", Resources.OutputDescription);
command.VersionOption("--version", ProductInfo.GetVersion);
_args = command.RemainingArguments;
base.Configure(command);
_projectOptions.Configure(command);
_args = command.RemainingArguments;
}
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<string>();
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)))
{
var projectAssets = JToken.ReadFrom(reader);
var packageFolders = projectAssets["packageFolders"]
.Children<JProperty>()
.Select(p => p.Name);
using var reader = new JsonTextReader(File.OpenText(projectAssetsFile));
var projectAssets = JToken.ReadFrom(reader);
var packageFolders = projectAssets["packageFolders"]
.Children<JProperty>()
.Select(p => p.Name);
foreach (var packageFolder in packageFolders)
{
args.Add("--additionalProbingPath");
args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
}
foreach (var packageFolder in packageFolders)
{
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 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
args.Add("--prefix-output");
}
return Exe.Run(executable, args, project.ProjectDirectory);
return Exe.Run(executable, args);
}
finally
{
@ -205,44 +173,5 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands
}
}
}
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];
}
}
}

View File

@ -79,37 +79,37 @@ namespace Microsoft.Extensions.ApiDescription.Tool
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 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
}
if (pendingBackslashs != 0)
if (pendingBackslashes != 0)
{
builder.Append('\\', pendingBackslashs * 2);
builder.Append('\\', pendingBackslashes * 2);
}
builder.Append("\"");

View File

@ -14,6 +14,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
{
FullName = Resources.CommandFullName,
Name = Resources.CommandFullName,
};
new InvokeCommand().Configure(app);

View File

@ -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<string, string> metadata;
var metadataPath = Path.GetTempFileName();
try
{
var args = new List<string>
{
"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;
}
}
}

View File

@ -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 <CONFIGURATION>", Resources.ConfigurationDescription);
Project = command.Option("-p|--project <PROJECT>", Resources.ProjectDescription);
ProjectExtensionsPath = command.Option(
"--projectExtensionsPath <PATH>",
Resources.ProjectExtensionsPathDescription);
Runtime = command.Option("--runtime <RUNTIME_IDENTIFIER>", Resources.RuntimeDescription);
AssemblyPath = command.Option("--assembly <Path>", Resources.AssemblyDescription);
AssetsFile = command.Option("--assets-file <Path>", Resources.AssetsFileDescription);
TargetFramework = command.Option("--framework <FRAMEWORK>", Resources.TargetFrameworkDescription);
Platform = command.Option("--platform <Target>", Resources.PlatformDescription);
ProjectName = command.Option("--project <Name>", Resources.ProjectDescription);
RuntimeFrameworkVersion = command.Option("--runtime <RUNTIME_IDENTIFIER>", 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));
}
}
}
}

View File

@ -10,20 +10,6 @@ namespace Microsoft.Extensions.ApiDescription.Tool
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The configuration to use.
/// </summary>
internal static string ConfigurationDescription
{
get => GetString("ConfigurationDescription");
}
/// <summary>
/// The configuration to use.
/// </summary>
internal static string FormatConfigurationDescription()
=> GetString("ConfigurationDescription");
/// <summary>
/// dotnet-getdocument
/// </summary>
@ -39,7 +25,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool
=> GetString("CommandFullName");
/// <summary>
/// The target framework.
/// The target framework to use. Required.
/// </summary>
internal static string TargetFrameworkDescription
{
@ -47,69 +33,27 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// The target framework.
/// The target framework to use. Required.
/// </summary>
internal static string FormatTargetFrameworkDescription()
=> GetString("TargetFrameworkDescription");
/// <summary>
/// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.1 or higher.
/// </summary>
internal static string GetMetadataFailed
internal static string OldNETCoreAppProject
{
get => GetString("GetMetadataFailed");
get => GetString("OldNETCoreAppProject");
}
/// <summary>
/// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.1 or higher.
/// </summary>
internal static string FormatGetMetadataFailed()
=> GetString("GetMetadataFailed");
internal static string FormatOldNETCoreAppProject(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("OldNETCoreAppProject"), p0, p1);
/// <summary>
/// More than one project was found in the current working directory. Use the --project option.
/// </summary>
internal static string MultipleProjects
{
get => GetString("MultipleProjects");
}
/// <summary>
/// More than one project was found in the current working directory. Use the --project option.
/// </summary>
internal static string FormatMultipleProjects()
=> GetString("MultipleProjects");
/// <summary>
/// More than one project was found in directory '{0}'. Specify one using its file name.
/// </summary>
internal static string MultipleProjectsInDirectory
{
get => GetString("MultipleProjectsInDirectory");
}
/// <summary>
/// More than one project was found in directory '{0}'. Specify one using its file name.
/// </summary>
internal static string FormatMultipleProjectsInDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MultipleProjectsInDirectory"), p0);
/// <summary>
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.
/// </summary>
internal static string NETCoreApp1Project
{
get => GetString("NETCoreApp1Project");
}
/// <summary>
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.
/// </summary>
internal static string FormatNETCoreApp1Project(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("NETCoreApp1Project"), p0, p1);
/// <summary>
/// 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.
/// </summary>
internal static string NETStandardProject
{
@ -117,13 +61,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// 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.
/// </summary>
internal static string FormatNETStandardProject(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NETStandardProject"), p0);
/// <summary>
/// Do not colorize output.
/// Do not colorize console output.
/// </summary>
internal static string NoColorDescription
{
@ -131,41 +75,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// Do not colorize output.
/// Do not colorize console output.
/// </summary>
internal static string FormatNoColorDescription()
=> GetString("NoColorDescription");
/// <summary>
/// No project was found. Change the current working directory or use the --project option.
/// </summary>
internal static string NoProject
{
get => GetString("NoProject");
}
/// <summary>
/// No project was found. Change the current working directory or use the --project option.
/// </summary>
internal static string FormatNoProject()
=> GetString("NoProject");
/// <summary>
/// No project was found in directory '{0}'.
/// </summary>
internal static string NoProjectInDirectory
{
get => GetString("NoProjectInDirectory");
}
/// <summary>
/// No project was found in directory '{0}'.
/// </summary>
internal static string FormatNoProjectInDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NoProjectInDirectory"), p0);
/// <summary>
/// Prefix output with level.
/// Prefix console output with logging level.
/// </summary>
internal static string PrefixDescription
{
@ -173,13 +89,13 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// Prefix output with level.
/// Prefix console output with logging level.
/// </summary>
internal static string FormatPrefixDescription()
=> GetString("PrefixDescription");
/// <summary>
/// The project to use.
/// The project name. Required.
/// </summary>
internal static string ProjectDescription
{
@ -187,25 +103,11 @@ namespace Microsoft.Extensions.ApiDescription.Tool
}
/// <summary>
/// The project to use.
/// The project name. Required.
/// </summary>
internal static string FormatProjectDescription()
=> GetString("ProjectDescription");
/// <summary>
/// The MSBuild project extensions path. Defaults to "obj".
/// </summary>
internal static string ProjectExtensionsPathDescription
{
get => GetString("ProjectExtensionsPathDescription");
}
/// <summary>
/// The MSBuild project extensions path. Defaults to "obj".
/// </summary>
internal static string FormatProjectExtensionsPathDescription()
=> GetString("ProjectExtensionsPathDescription");
/// <summary>
/// The runtime identifier to use.
/// </summary>
@ -234,20 +136,6 @@ namespace Microsoft.Extensions.ApiDescription.Tool
internal static string FormatUnsupportedFramework(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedFramework"), p0, p1);
/// <summary>
/// Using project '{0}'.
/// </summary>
internal static string UsingProject
{
get => GetString("UsingProject");
}
/// <summary>
/// Using project '{0}'.
/// </summary>
internal static string FormatUsingProject(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingProject"), p0);
/// <summary>
/// Show verbose output.
/// </summary>
@ -263,60 +151,60 @@ namespace Microsoft.Extensions.ApiDescription.Tool
=> GetString("VerboseDescription");
/// <summary>
/// Writing '{0}'...
/// The project assets file to use.
/// </summary>
internal static string WritingFile
internal static string AssetsFileDescription
{
get => GetString("WritingFile");
get => GetString("AssetsFileDescription");
}
/// <summary>
/// Writing '{0}'...
/// The project assets file to use.
/// </summary>
internal static string FormatWritingFile(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0);
internal static string FormatAssetsFileDescription()
=> GetString("AssetsFileDescription");
/// <summary>
/// Project output not found. Project must be up-to-date when using this tool.
/// Missing required option '--{0}'.
/// </summary>
internal static string MustBuild
internal static string MissingOption
{
get => GetString("MustBuild");
get => GetString("MissingOption");
}
/// <summary>
/// Project output not found. Project must be up-to-date when using this tool.
/// Missing required option '--{0}'.
/// </summary>
internal static string FormatMustBuild()
=> GetString("MustBuild");
internal static string FormatMissingOption(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0);
/// <summary>
/// The file to write the result to.
/// The platform to use.
/// </summary>
internal static string OutputDescription
internal static string PlatformDescription
{
get => GetString("OutputDescription");
get => GetString("PlatformDescription");
}
/// <summary>
/// The file to write the result to.
/// The platform to use.
/// </summary>
internal static string FormatOutputDescription()
=> GetString("OutputDescription");
internal static string FormatPlatformDescription()
=> GetString("PlatformDescription");
/// <summary>
/// Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set.
/// The assembly path to use. Required.
/// </summary>
internal static string GetMetadataValueFailed
internal static string AssemblyDescription
{
get => GetString("GetMetadataValueFailed");
get => GetString("AssemblyDescription");
}
/// <summary>
/// Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set.
/// The assembly path to use. Required.
/// </summary>
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)
{

View File

@ -117,47 +117,26 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConfigurationDescription" xml:space="preserve">
<value>The configuration to use.</value>
</data>
<data name="CommandFullName" xml:space="preserve">
<value>dotnet-getdocument</value>
</data>
<data name="TargetFrameworkDescription" xml:space="preserve">
<value>The target framework.</value>
<value>The target framework to use. Required.</value>
</data>
<data name="GetMetadataFailed" xml:space="preserve">
<value>Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.</value>
</data>
<data name="MultipleProjects" xml:space="preserve">
<value>More than one project was found in the current working directory. Use the --project option.</value>
</data>
<data name="MultipleProjectsInDirectory" xml:space="preserve">
<value>More than one project was found in directory '{0}'. Specify one using its file name.</value>
</data>
<data name="NETCoreApp1Project" xml:space="preserve">
<value>Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.</value>
<data name="OldNETCoreAppProject" xml:space="preserve">
<value>Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.1 or higher.</value>
</data>
<data name="NETStandardProject" xml:space="preserve">
<value>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.</value>
<value>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.</value>
</data>
<data name="NoColorDescription" xml:space="preserve">
<value>Do not colorize output.</value>
</data>
<data name="NoProject" xml:space="preserve">
<value>No project was found. Change the current working directory or use the --project option.</value>
</data>
<data name="NoProjectInDirectory" xml:space="preserve">
<value>No project was found in directory '{0}'.</value>
<value>Do not colorize console output.</value>
</data>
<data name="PrefixDescription" xml:space="preserve">
<value>Prefix output with level.</value>
<value>Prefix console output with logging level.</value>
</data>
<data name="ProjectDescription" xml:space="preserve">
<value>The project to use.</value>
</data>
<data name="ProjectExtensionsPathDescription" xml:space="preserve">
<value>The MSBuild project extensions path. Defaults to "obj".</value>
<value>The project name. Required.</value>
</data>
<data name="RuntimeDescription" xml:space="preserve">
<value>The runtime identifier to use.</value>
@ -165,22 +144,19 @@
<data name="UnsupportedFramework" xml:space="preserve">
<value>Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.</value>
</data>
<data name="UsingProject" xml:space="preserve">
<value>Using project '{0}'.</value>
</data>
<data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value>
</data>
<data name="WritingFile" xml:space="preserve">
<value>Writing '{0}'...</value>
<data name="AssetsFileDescription" xml:space="preserve">
<value>The project assets file to use.</value>
</data>
<data name="MustBuild" xml:space="preserve">
<value>Project output not found. Project must be up-to-date when using this tool.</value>
<data name="MissingOption" xml:space="preserve">
<value>Missing required option '--{0}'.</value>
</data>
<data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value>
<data name="PlatformDescription" xml:space="preserve">
<value>The platform to use.</value>
</data>
<data name="GetMetadataValueFailed" xml:space="preserve">
<value>Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set.</value>
<data name="AssemblyDescription" xml:space="preserve">
<value>The assembly path to use. Required.</value>
</data>
</root>
</root>

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Collect properties only in inner build. Execute unconditionally before WriteServiceProjectReferenceMetadata. -->
<Target Name="GetServiceProjectReferenceMetadata"
BeforeTargets="WriteServiceProjectReferenceMetadata"
Condition="'$(TargetFramework)' != ''"
Returns="@(ServiceProjectReferenceMetadata)">
<ItemGroup Condition="'$(TargetFramework)' != ''">
<ServiceProjectReferenceMetadata Include="DefaultDocumentName: $(DefaultServiceProjectDocumentName)" />
<ServiceProjectReferenceMetadata Include="DefaultMethod: $(DefaultServiceProjectMethod)" />
<ServiceProjectReferenceMetadata Include="DefaultService: $(DefaultServiceProjectService)" />
<ServiceProjectReferenceMetadata Include="AssemblyName: $(AssemblyName)" />
<ServiceProjectReferenceMetadata Include="Configuration: $(Configuration)" />
<ServiceProjectReferenceMetadata Include="OutputPath: $(OutputPath)" />
<ServiceProjectReferenceMetadata Include="Platform: $(Platform)" />
<ServiceProjectReferenceMetadata Include="PlatformTarget: $(PlatformTarget)" />
<ServiceProjectReferenceMetadata Include="ProjectAssetsFile: $(ProjectAssetsFile)" />
<ServiceProjectReferenceMetadata Include="ProjectDepsFilePath: $(ProjectDepsFilePath)" />
<ServiceProjectReferenceMetadata Include="ProjectDirectory: $(MSBuildProjectDirectory)" />
<ServiceProjectReferenceMetadata Include="ProjectExtensionsPath: $(MSBuildProjectExtensionsPath)" />
<ServiceProjectReferenceMetadata Include="ProjectName: $(MSBuildProjectName)" />
<ServiceProjectReferenceMetadata Include="ProjectRuntimeConfigFilePath: $(ProjectRuntimeConfigFilePath)" />
<ServiceProjectReferenceMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" />
<ServiceProjectReferenceMetadata Include="RuntimeIdentifier: $(RuntimeIdentifier)" />
<ServiceProjectReferenceMetadata Include="TargetFramework: $(TargetFramework)" />
<ServiceProjectReferenceMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" />
<ServiceProjectReferenceMetadata Include="TargetPath: $(TargetPath)" />
</ItemGroup>
</Target>
<!-- Write information only in inner build. -->
<Target Name="WriteServiceProjectReferenceMetadata" Returns="@(ServiceProjectReferenceMetadata)">
<MSBuild Condition="'$(TargetFramework)' == ''"
Projects="$(MSBuildProjectFile)"
Targets="WriteServiceProjectReferenceMetadata"
Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);ServiceProjectReferenceMetadataFile=$(ServiceProjectReferenceMetadataFile)" />
<WriteLinesToFile Condition="'$(TargetFramework)' != ''"
File="$(ServiceProjectReferenceMetadataPath)"
Lines="@(ServiceProjectReferenceMetadata)" />
</Target>
</Project>

View File

@ -22,8 +22,6 @@
<Compile Include="../../GetDocumentInsider/src/ProductInfo.cs" />
<Compile Include="../../GetDocumentInsider/src/Reporter.cs" />
<EmbeddedResource Include="ServiceProjectReferenceMetadata.targets" />
<Reference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,4 @@
{
"applyPatches": true,
"rollForwardOnNoCandidateFx": 2
}