Create a single Microsoft.Extensions.ApiDescription.Client package

- #8428
- add signing-related and PackageVerifier configuration for new package
- remove packaging configuration from dotnet-getdocument project
- adjust `dotnet-getdocument` invocation to its new location
- remove use of nonexistent (ignored) `dotnet-getdocument --no-build` option

Remove `--uri` feature from `dotnet-getdocument`
- reduce dependencies from Microsoft.AspNetCore.TestHost to Microsoft.AspNetCore.Hosting.Abstractions
  - assume web site depends on that
- merge `DownloadFileCore` into `DownloadFile`
- remove other unecessary code e.g. `WrappedException` was never `throw`n

Correct issues in `DownloadFile`
- e.g. dispose of `responseStream`, use `await` more, support FIPS-compliant machines

nits:
- clean up `Project` and the metadata it fetches
- remove unnecessary `.props` and `.targets` files
This commit is contained in:
Doug Bunting 2018-09-11 16:20:07 -07:00
parent 5a58f81d8d
commit d3442f3590
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
29 changed files with 261 additions and 809 deletions

View File

@ -1,7 +1,16 @@
{ {
"Default": { "Default": {
"rules": [ "rules": [
"DefaultCompositeRule" "DefaultCompositeRule"
] ],
"packages": {
"Microsoft.Extensions.ApiDescription.Client": {
"Exclusions": {
"BUILD_ITEMS_FRAMEWORK": {
"*": "Package includes tool with different target frameworks."
}
}
}
} }
} }
}

View File

@ -7,7 +7,7 @@
is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product
dependencies. Do not use these properties elsewhere. dependencies. Do not use these properties elsewhere.
--> -->
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion> <AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion> <BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>2.1.1</BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion> <BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>2.1.1</BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>
@ -32,6 +32,7 @@
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion> <MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsPackageVersion> <MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingAbstractionsPackageVersion> <MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractions20PackageVersion>2.0.0</MicrosoftAspNetCoreHostingAbstractions20PackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingPackageVersion> <MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion> <MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpExtensionsPackageVersion> <MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
@ -54,7 +55,6 @@
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreSessionPackageVersion> <MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreSessionPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreStaticFilesPackageVersion> <MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestHostPackageVersion> <MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreTestHost20PackageVersion>2.0.0</MicrosoftAspNetCoreTestHost20PackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestingPackageVersion> <MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreWebUtilitiesPackageVersion> <MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion> <MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion>

View File

@ -17,13 +17,11 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
internal const string FallbackDocumentName = "v1"; internal const string FallbackDocumentName = "v1";
internal const string FallbackMethod = "Generate"; internal const string FallbackMethod = "Generate";
internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider"; internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider";
private const string WorkerType = "Microsoft.Extensions.ApiDescription.Client.Commands.GetDocumentCommandWorker";
private CommandOption _documentName; private CommandOption _documentName;
private CommandOption _method; private CommandOption _method;
private CommandOption _output; private CommandOption _output;
private CommandOption _service; private CommandOption _service;
private CommandOption _uri;
public override void Configure(CommandLineApplication command) public override void Configure(CommandLineApplication command)
{ {
@ -35,7 +33,6 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
_method = command.Option("--method <Name>", Resources.FormatMethodDescription(FallbackMethod)); _method = command.Option("--method <Name>", Resources.FormatMethodDescription(FallbackMethod));
_output = command.Option("--output <Path>", Resources.OutputDescription); _output = command.Option("--output <Path>", Resources.OutputDescription);
_service = command.Option("--service <QualifiedName>", Resources.FormatServiceDescription(FallbackService)); _service = command.Option("--service <QualifiedName>", Resources.FormatServiceDescription(FallbackService));
_uri = command.Option("--uri <URI>", Resources.UriDescription);
} }
protected override void Validate() protected override void Validate()
@ -131,12 +128,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
#error target frameworks need to be updated. #error target frameworks need to be updated.
#endif #endif
// Now safe to reference TestHost type. // Now safe to reference the application's code.
try try
{ {
var workerType = thisAssembly.GetType(WorkerType, throwOnError: true);
var methodInfo = workerType.GetMethod("Process", BindingFlags.Public | BindingFlags.Static);
var assemblyPath = AssemblyPath.Value(); var assemblyPath = AssemblyPath.Value();
var context = new GetDocumentCommandContext var context = new GetDocumentCommandContext
{ {
@ -147,10 +141,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
Method = _method.Value(), Method = _method.Value(),
Output = _output.Value(), Output = _output.Value(),
Service = _service.Value(), Service = _service.Value(),
Uri = _uri.Value(),
}; };
return (int)methodInfo.Invoke(obj: null, parameters: new[] { context }); return GetDocumentCommandWorker.Process(context);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -21,7 +21,5 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
public string Output { get; set; } public string Output { get; set; }
public string Service { get; set; } public string Service { get; set; }
public string Uri { get; set; }
} }
} }

View File

@ -2,15 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.ApiDescription.Client.Commands namespace Microsoft.Extensions.ApiDescription.Client.Commands
{ {
@ -34,25 +29,10 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
} }
var success = TryProcess(context, services); var success = TryProcess(context, services);
if (!success && string.IsNullOrEmpty(context.Uri)) if (!success)
{ {
return 4; // As part of the aspnet/Mvc#8425 fix, return 4 here.
} return 0;
var builder = GetBuilder(entryPointType, context.AssemblyPath, context.AssemblyName);
if (builder == null)
{
return 5;
}
// Mute the HttpsRedirectionMiddleware warning about HTTPS configuration.
builder.ConfigureLogging(loggingBuilder => loggingBuilder.AddFilter(
"Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware",
LogLevel.Error));
using (var server = new TestServer(builder))
{
ProcessAsync(context, server).Wait();
} }
return 0; return 0;
@ -95,15 +75,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
if (!success) if (!success)
{ {
// As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName); var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName);
if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output)) Reporter.WriteWarning(message);
{
Reporter.WriteError(message);
}
else
{
Reporter.WriteWarning(message);
}
} }
return success; return success;
@ -111,35 +85,14 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
catch (Exception ex) catch (Exception ex)
{ {
var message = FormatException(ex); var message = FormatException(ex);
if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output))
{ // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
Reporter.WriteError(message); Reporter.WriteWarning(message);
}
else
{
Reporter.WriteWarning(message);
}
return false; return false;
} }
} }
public static async Task ProcessAsync(GetDocumentCommandContext context, TestServer server)
{
Debug.Assert(!string.IsNullOrEmpty(context.Uri));
Reporter.WriteInformation(Resources.FormatUsingUri(context.Uri));
var httpClient = server.CreateClient();
await DownloadFileCore.DownloadAsync(
context.Uri,
context.Output,
httpClient,
new LogWrapper(),
CancellationToken.None,
timeoutSeconds: 60);
}
// TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. // 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 IServiceProvider GetServices(Type entryPointType, string assemblyPath, string assemblyName)
{ {
@ -203,54 +156,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
} }
} }
// Startup return null;
return new WebHostBuilder().UseStartup(assemblyName).Build().Services;
}
// TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available.
private static IWebHostBuilder GetBuilder(Type entryPointType, string assemblyPath, string assemblyName)
{
var methodInfo = entryPointType.GetMethod("BuildWebHost");
if (methodInfo != null)
{
// BuildWebHost cannot be used. Fall through, most likely to Startup fallback.
Reporter.WriteWarning(
"BuildWebHost method cannot be used. Falling back to minimal Startup configuration.");
}
methodInfo = entryPointType.GetMethod("CreateWebHostBuilder");
if (methodInfo != null)
{
// 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 args = new[] { Array.Empty<string>() };
var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args);
return builder;
}
catch (Exception ex)
{
Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}");
return null;
}
}
// Startup
return new WebHostBuilder().UseStartup(assemblyName);
} }
private static string FormatException(Exception exception) private static string FormatException(Exception exception)

View File

@ -1,121 +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.IO;
using System.Net.Http;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.ApiDescription.Client
{
internal static class DownloadFileCore
{
public static async Task DownloadAsync(
string uri,
string destinationPath,
HttpClient httpClient,
ILogWrapper log,
CancellationToken cancellationToken,
int timeoutSeconds)
{
// Timeout if the response has not begun within 1 minute
httpClient.Timeout = TimeSpan.FromMinutes(1);
var destinationExists = File.Exists(destinationPath);
var reachedCopy = false;
try
{
using (var response = await httpClient.GetAsync(uri, cancellationToken))
{
response.EnsureSuccessStatusCode();
cancellationToken.ThrowIfCancellationRequested();
using (var responseStreamTask = response.Content.ReadAsStreamAsync())
{
var finished = await Task.WhenAny(
responseStreamTask,
Task.Delay(TimeSpan.FromSeconds(timeoutSeconds)));
if (!ReferenceEquals(responseStreamTask, finished))
{
throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds.");
}
var responseStream = await responseStreamTask;
if (destinationExists)
{
// Check hashes before using the downloaded information.
var downloadHash = GetHash(responseStream);
responseStream.Position = 0L;
byte[] destinationHash;
using (var destinationStream = File.OpenRead(destinationPath))
{
destinationHash = GetHash(destinationStream);
}
var sameHashes = downloadHash.LongLength == destinationHash.LongLength;
for (var i = 0L; sameHashes && i < downloadHash.LongLength; i++)
{
sameHashes = downloadHash[i] == destinationHash[i];
}
if (sameHashes)
{
log.LogInformational($"Not overwriting existing and matching file '{destinationPath}'.");
return;
}
}
else
{
// May need to create directory to hold the file.
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!(string.IsNullOrEmpty(destinationDirectory) || Directory.Exists(destinationDirectory)))
{
Directory.CreateDirectory(destinationDirectory);
}
}
// Create or overwrite the destination file.
reachedCopy = true;
using (var outStream = File.Create(destinationPath))
{
responseStream.CopyTo(outStream);
}
}
}
}
catch (HttpRequestException ex) when (destinationExists)
{
if (ex.InnerException is SocketException socketException)
{
log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'.");
}
else
{
log.LogWarning($"Unable to download {uri}: {ex.Message}");
}
}
catch (Exception ex)
{
log.LogError($"Downloading '{uri}' failed.");
log.LogError(ex, showStackTrace: true);
if (reachedCopy)
{
File.Delete(destinationPath);
}
}
}
private static byte[] GetHash(Stream stream)
{
using (var algorithm = SHA256.Create())
{
return algorithm.ComputeHash(stream);
}
}
}
}

View File

@ -13,6 +13,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(MicrosoftAspNetCoreTestHost20PackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractions20PackageVersion)" />
</ItemGroup> </ItemGroup>
<Target Name="BuildX86" AfterTargets="Build" Condition=" '$(TargetFramework)' == 'net461' And '$(Platform)' != 'x86' ">
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="TargetFramework=$(TargetFramework);Platform=x86" Targets="Build" />
</Target>
</Project> </Project>

View File

@ -1,53 +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;
namespace Microsoft.Extensions.ApiDescription.Client
{
internal interface ILogWrapper
{
/// <summary>
/// Logs specified informational <paramref name="message"/>. Implementations should be thread safe.
/// </summary>
/// <param name="message">The message to log.</param>
/// <param name="messageArgs">Optional arguments for formatting the <paramref name="message"/> string.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="message"/> is <see langword="null"/>.
/// </exception>
void LogInformational(string message, params object[] messageArgs);
/// <summary>
/// Logs a warning with the specified <paramref name="message"/>. Implementations should be thread safe.
/// </summary>
/// <param name="message">The message to log.</param>
/// <param name="messageArgs">Optional arguments for formatting the <paramref name="message"/> string.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="message"/> is <see langword="null"/>.
/// </exception>
void LogWarning(string message, params object[] messageArgs);
/// <summary>
/// Logs an error with the specified <paramref name="message"/>. Implementations should be thread safe.
/// </summary>
/// <param name="message">The message to log.</param>
/// <param name="messageArgs">Optional arguments for formatting the <paramref name="message"/> string.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="message"/> is <see langword="null"/>.
/// </exception>
void LogError(string message, params object[] messageArgs);
/// <summary>
/// Logs an error with the message and (optionally) the stack trace of the given <paramref name="exception"/>.
/// Implementations should be thread safe.
/// </summary>
/// <param name="exception">The <see cref="Exception"/> to log.</param>
/// <param name="showStackTrace">
/// If <see langword="true"/>, append stack trace to <paramref name="exception"/>'s message.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="exception"/> is <see langword="null"/>.
/// </exception>
void LogError(Exception exception, bool showStackTrace);
}
}

View File

@ -1,18 +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 Microsoft.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Client
{
internal static class Json
{
public static CommandOption ConfigureOption(CommandLineApplication command)
=> command.Option("--json", Resources.JsonDescription);
public static string Literal(string text)
=> text != null
? "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""
: "null";
}
}

View File

@ -1,31 +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;
namespace Microsoft.Extensions.ApiDescription.Client
{
public class LogWrapper : ILogWrapper
{
public void LogError(string message, params object[] messageArgs)
{
Reporter.WriteError(string.Format(message, messageArgs));
}
public void LogError(Exception exception, bool showStackTrace)
{
var message = showStackTrace ? exception.ToString() : exception.Message;
Reporter.WriteError(message);
}
public void LogInformational(string message, params object[] messageArgs)
{
Reporter.WriteInformation(string.Format(message, messageArgs));
}
public void LogWarning(string message, params object[] messageArgs)
{
Reporter.WriteWarning(string.Format(message, messageArgs));
}
}
}

View File

@ -30,10 +30,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
} }
catch (Exception ex) catch (Exception ex)
{ {
if (ex is CommandException if (ex is CommandException || ex is CommandParsingException)
|| ex is CommandParsingException
|| (ex is WrappedException wrappedException
&& wrappedException.Type == "Microsoft.Extensions.ApiDescription.Client.Design.OperationException"))
{ {
Reporter.WriteVerbose(ex.ToString()); Reporter.WriteVerbose(ex.ToString());
} }

View File

@ -24,20 +24,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
internal static string FormatAssemblyDescription() internal static string FormatAssemblyDescription()
=> GetString("AssemblyDescription"); => GetString("AssemblyDescription");
/// <summary>
/// Show JSON output.
/// </summary>
internal static string JsonDescription
{
get => GetString("JsonDescription");
}
/// <summary>
/// Show JSON output.
/// </summary>
internal static string FormatJsonDescription()
=> GetString("JsonDescription");
/// <summary> /// <summary>
/// Missing required option '--{0}'. /// Missing required option '--{0}'.
/// </summary> /// </summary>
@ -94,48 +80,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
internal static string FormatPrefixDescription() internal static string FormatPrefixDescription()
=> GetString("PrefixDescription"); => GetString("PrefixDescription");
/// <summary>
/// Using application base '{0}'.
/// </summary>
internal static string UsingApplicationBase
{
get => GetString("UsingApplicationBase");
}
/// <summary>
/// Using application base '{0}'.
/// </summary>
internal static string FormatUsingApplicationBase(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingApplicationBase"), p0);
/// <summary>
/// Using assembly '{0}'.
/// </summary>
internal static string UsingAssembly
{
get => GetString("UsingAssembly");
}
/// <summary>
/// Using assembly '{0}'.
/// </summary>
internal static string FormatUsingAssembly(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingAssembly"), p0);
/// <summary>
/// Using configuration file '{0}'.
/// </summary>
internal static string UsingConfigurationFile
{
get => GetString("UsingConfigurationFile");
}
/// <summary>
/// Using configuration file '{0}'.
/// </summary>
internal static string FormatUsingConfigurationFile(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingConfigurationFile"), p0);
/// <summary> /// <summary>
/// Show verbose output. /// Show verbose output.
/// </summary> /// </summary>
@ -150,34 +94,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
internal static string FormatVerboseDescription() internal static string FormatVerboseDescription()
=> GetString("VerboseDescription"); => GetString("VerboseDescription");
/// <summary>
/// Writing '{0}'...
/// </summary>
internal static string WritingFile
{
get => GetString("WritingFile");
}
/// <summary>
/// Writing '{0}'...
/// </summary>
internal static string FormatWritingFile(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0);
/// <summary>
/// Using working directory '{0}'.
/// </summary>
internal static string UsingWorkingDirectory
{
get => GetString("UsingWorkingDirectory");
}
/// <summary>
/// Using working directory '{0}'.
/// </summary>
internal static string FormatUsingWorkingDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingWorkingDirectory"), p0);
/// <summary> /// <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.
/// </summary> /// </summary>
@ -192,20 +108,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
internal static string FormatToolsDirectoryDescription() internal static string FormatToolsDirectoryDescription()
=> GetString("ToolsDirectoryDescription"); => GetString("ToolsDirectoryDescription");
/// <summary>
/// The URI to download the document from.
/// </summary>
internal static string UriDescription
{
get => GetString("UriDescription");
}
/// <summary>
/// The URI to download the document from.
/// </summary>
internal static string FormatUriDescription()
=> GetString("UriDescription");
/// <summary> /// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'. /// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// </summary> /// </summary>
@ -234,20 +136,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
internal static string FormatServiceDescription(object p0) internal static string FormatServiceDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0);
/// <summary>
/// Missing required option '--{0}' or '--{1}'.
/// </summary>
internal static string MissingOptions
{
get => GetString("MissingOptions");
}
/// <summary>
/// Missing required option '--{0}' or '--{1}'.
/// </summary>
internal static string FormatMissingOptions(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingOptions"), p0, p1);
/// <summary> /// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'. /// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary> /// </summary>
@ -304,20 +192,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
internal static string FormatUsingService(object p0) internal static string FormatUsingService(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0);
/// <summary>
/// Using URI '{0}'.
/// </summary>
internal static string UsingUri
{
get => GetString("UsingUri");
}
/// <summary>
/// Using URI '{0}'.
/// </summary>
internal static string FormatUsingUri(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingUri"), p0);
/// <summary> /// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'. /// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary> /// </summary>

View File

@ -120,9 +120,6 @@
<data name="AssemblyDescription" xml:space="preserve"> <data name="AssemblyDescription" xml:space="preserve">
<value>The assembly to use.</value> <value>The assembly to use.</value>
</data> </data>
<data name="JsonDescription" xml:space="preserve">
<value>Show JSON output.</value>
</data>
<data name="MissingOption" xml:space="preserve"> <data name="MissingOption" xml:space="preserve">
<value>Missing required option '--{0}'.</value> <value>Missing required option '--{0}'.</value>
</data> </data>
@ -135,39 +132,18 @@
<data name="PrefixDescription" xml:space="preserve"> <data name="PrefixDescription" xml:space="preserve">
<value>Prefix console output with logging level.</value> <value>Prefix console output with logging level.</value>
</data> </data>
<data name="UsingApplicationBase" xml:space="preserve">
<value>Using application base '{0}'.</value>
</data>
<data name="UsingAssembly" xml:space="preserve">
<value>Using assembly '{0}'.</value>
</data>
<data name="UsingConfigurationFile" xml:space="preserve">
<value>Using configuration file '{0}'.</value>
</data>
<data name="VerboseDescription" xml:space="preserve"> <data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value> <value>Show verbose output.</value>
</data> </data>
<data name="WritingFile" xml:space="preserve">
<value>Writing '{0}'...</value>
</data>
<data name="UsingWorkingDirectory" xml:space="preserve">
<value>Using working directory '{0}'.</value>
</data>
<data name="ToolsDirectoryDescription" xml:space="preserve"> <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.</value>
</data> </data>
<data name="UriDescription" xml:space="preserve">
<value>The URI to download the document from.</value>
</data>
<data name="MethodDescription" xml:space="preserve"> <data name="MethodDescription" xml:space="preserve">
<value>The name of the method to invoke on the '--service' instance. Default value '{0}'.</value> <value>The name of the method to invoke on the '--service' instance. Default value '{0}'.</value>
</data> </data>
<data name="ServiceDescription" xml:space="preserve"> <data name="ServiceDescription" xml:space="preserve">
<value>The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.</value> <value>The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.</value>
</data> </data>
<data name="MissingOptions" xml:space="preserve">
<value>Missing required option '--{0}' or '--{1}'.</value>
</data>
<data name="DocumentDescription" xml:space="preserve"> <data name="DocumentDescription" xml:space="preserve">
<value>The name of the document to pass to the '--method' method. Default value '{0}'.</value> <value>The name of the document to pass to the '--method' method. Default value '{0}'.</value>
</data> </data>
@ -180,13 +156,10 @@
<data name="UsingService" xml:space="preserve"> <data name="UsingService" xml:space="preserve">
<value>Using service '{0}'.</value> <value>Using service '{0}'.</value>
</data> </data>
<data name="UsingUri" xml:space="preserve">
<value>Using URI '{0}'.</value>
</data>
<data name="MethodInvocationFailed" xml:space="preserve"> <data name="MethodInvocationFailed" xml:space="preserve">
<value>Method '{0}' of service '{1}' failed to generate document '{2}'.</value> <value>Method '{0}' of service '{1}' failed to generate document '{2}'.</value>
</data> </data>
<data name="MissingEntryPoint" xml:space="preserve"> <data name="MissingEntryPoint" xml:space="preserve">
<value>Assembly '{0}' does not contain an entry point.</value> <value>Assembly '{0}' does not contain an entry point.</value>
</data> </data>
</root> </root>

View File

@ -1,24 +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;
namespace Microsoft.Extensions.ApiDescription.Client
{
internal class WrappedException : Exception
{
private readonly string _stackTrace;
public WrappedException(string type, string message, string stackTrace)
: base(message)
{
Type = type;
_stackTrace = stackTrace;
}
public string Type { get; }
public override string ToString()
=> _stackTrace;
}
}

View File

@ -4,6 +4,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
@ -94,19 +97,130 @@ namespace Microsoft.Extensions.ApiDescription.Client
return; return;
} }
log.LogMessage($"Downloading '{uri}' to '{destinationPath}'."); log.LogMessage(MessageImportance.High, $"Downloading '{uri}' to '{destinationPath}'.");
using (var httpClient = new HttpClient using (var httpClient = new HttpClient())
{ {
}) await DownloadAsync(uri, destinationPath, httpClient, cancellationToken, log, timeoutSeconds);
}
}
public static async Task DownloadAsync(
string uri,
string destinationPath,
HttpClient httpClient,
CancellationToken cancellationToken,
TaskLoggingHelper log,
int timeoutSeconds)
{
// Timeout if the response has not begun within 1 minute
httpClient.Timeout = TimeSpan.FromMinutes(1);
var destinationExists = File.Exists(destinationPath);
var reachedCopy = false;
try
{ {
await DownloadFileCore.DownloadAsync( using (var response = await httpClient.GetAsync(uri, cancellationToken))
uri, {
destinationPath, response.EnsureSuccessStatusCode();
httpClient, cancellationToken.ThrowIfCancellationRequested();
new LogWrapper(log),
cancellationToken, using (var responseStreamTask = response.Content.ReadAsStreamAsync())
timeoutSeconds); {
var finished = await Task.WhenAny(
responseStreamTask,
Task.Delay(TimeSpan.FromSeconds(timeoutSeconds)));
if (!ReferenceEquals(responseStreamTask, finished))
{
throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds.");
}
using (var responseStream = await responseStreamTask)
{
if (destinationExists)
{
// Check hashes before using the downloaded information.
var downloadHash = GetHash(responseStream);
responseStream.Position = 0L;
byte[] destinationHash;
using (var destinationStream = File.OpenRead(destinationPath))
{
destinationHash = GetHash(destinationStream);
}
var sameHashes = downloadHash.Length == destinationHash.Length;
for (var i = 0; sameHashes && i < downloadHash.Length; i++)
{
sameHashes = downloadHash[i] == destinationHash[i];
}
if (sameHashes)
{
log.LogMessage($"Not overwriting existing and matching file '{destinationPath}'.");
return;
}
}
else
{
// May need to create directory to hold the file.
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
}
// Create or overwrite the destination file.
reachedCopy = true;
using (var outStream = File.Create(destinationPath))
{
await responseStream.CopyToAsync(outStream);
}
}
}
}
}
catch (HttpRequestException ex) when (destinationExists)
{
if (ex.InnerException is SocketException socketException)
{
log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'.");
}
else
{
log.LogWarning($"Unable to download {uri}: {ex.Message}");
}
}
catch (Exception ex)
{
log.LogError($"Downloading '{uri}' failed.");
log.LogErrorFromException(ex, showStackTrace: true);
if (reachedCopy)
{
File.Delete(destinationPath);
}
}
}
private static byte[] GetHash(Stream stream)
{
SHA256 algorithm;
try
{
algorithm = SHA256.Create();
}
catch (TargetInvocationException)
{
// SHA256.Create is documented to throw this exception on FIPS-compliant machines. See
// https://msdn.microsoft.com/en-us/library/z08hz7ad Fall back to a FIPS-compliant SHA256 algorithm.
algorithm = new SHA256CryptoServiceProvider();
}
using (algorithm)
{
return algorithm.ComputeHash(stream);
} }
} }
} }

View File

@ -1,38 +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 Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Client
{
internal class LogWrapper : ILogWrapper
{
private readonly TaskLoggingHelper _log;
public LogWrapper(TaskLoggingHelper log)
{
_log = log;
}
public void LogError(string message, params object[] messageArgs)
{
_log.LogError(message, messageArgs);
}
public void LogError(Exception exception, bool showStackTrace)
{
_log.LogErrorFromException(exception, showStackTrace);
}
public void LogInformational(string message, params object[] messageArgs)
{
_log.LogMessage(message, messageArgs);
}
public void LogWarning(string message, params object[] messageArgs)
{
_log.LogWarning(message, messageArgs);
}
}
}

View File

@ -8,21 +8,52 @@
<Description>MSBuild tasks and targets for code generation</Description> <Description>MSBuild tasks and targets for code generation</Description>
<EnableApiCheck>false</EnableApiCheck> <EnableApiCheck>false</EnableApiCheck>
<IsPackable>false</IsPackable> <IncludeBuildOutput>false</IncludeBuildOutput>
<IncludeSource>false</IncludeSource>
<IncludeSymbols>false</IncludeSymbols>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile> <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
<PackageTags>Build Tasks;msbuild;DownloadFile;GetFilenameFromUri;code generation</PackageTags> <PackageTags>Build Tasks;MSBuild;Swagger;Open API;code generation; Web API client</PackageTags>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks> <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="../GetDocumentInsider/DownloadFileCore.cs" /> <PackageReference Include="Microsoft.Build.Utilities.Core"
<Compile Include="../GetDocumentInsider/ILogWrapper.cs" /> Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
<EmbeddedResource Include="ServiceProjectReferenceMetadata.targets" /> <PackageReference Include="System.Net.Http"
Condition="'$(TargetFramework)' == 'net461'"
Version="$(SystemNetHttpPackageVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFramework)' != ''">
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" /> <SignedPackageFile Include="$(TargetPath)">
<PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" /> <Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tasks/$(TargetFramework)/$(TargetFileName)</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
</ItemGroup>
<!-- Add other signed files in a single inner build, avoiding duplications in this multi-TFM project. -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<SignedPackageFile Include="../dotnet-getdocument/bin/$(Configuration)/netcoreapp2.1/dotnet-getdocument.dll">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/dotnet-getdocument.dll</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/$(Configuration)/net461/GetDocument.Insider.exe">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/net461/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/x86/$(Configuration)/net461/GetDocument.Insider.exe">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/net461-x86/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/$(Configuration)/netcoreapp2.0/GetDocument.Insider.dll">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/netcoreapp2.0/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
</ItemGroup> </ItemGroup>
<Target Name="PopulateNuspec"> <Target Name="PopulateNuspec">

View File

@ -12,17 +12,19 @@
<owners>$owners$</owners> <owners>$owners$</owners>
<projectUrl>$projectUrl$</projectUrl> <projectUrl>$projectUrl$</projectUrl>
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" /> <repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>true</requireLicenseAcceptance>
<tags>$tags$</tags> <tags>$tags$</tags>
<version>$version$</version> <version>$version$</version>
<dependencies>
<dependency id="NSwag.MSBuild" version="11.18.6" include="runtime,build,native,contentfiles,analyzers" />
</dependencies>
</metadata> </metadata>
<files> <files>
<file src="bin\$configuration$\**" target="tasks" exclude="**\Microsoft.Build.*.dll" /> <file src="build\*" target="build" />
<file src="build\*.*" target="build" /> <file src="buildMultiTargeting\*" target="buildMultiTargeting" />
<file src="buildMultiTargeting\*.*" target="buildMultiTargeting" /> <file src="bin\$configuration$\net461\Microsoft.Extensions.ApiDescription.Client.*" target="tasks\net461" />
<file src="bin\$configuration$\netstandard2.0\Microsoft.Extensions.ApiDescription.Client.*" target="tasks\netstandard2.0" />
<file src="..\dotnet-getdocument\bin\$configuration$\netcoreapp2.1\dotnet-getdocument.*" target="tools" />
<file src="..\GetDocumentInsider\bin\$configuration$\net461\GetDocument.Insider.*" target="tools\net461" />
<file src="..\GetDocumentInsider\bin\x86\$configuration$\net461\GetDocument.Insider.*" target="tools\net461-x86" />
<file src="..\GetDocumentInsider\bin\$configuration$\netcoreapp2.0\GetDocument.Insider.*" target="tools\netcoreapp2.0" />
</files> </files>
</package> </package>

View File

@ -1,32 +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="WriteServiceProjectReferenceMetadataCore"
Condition="'$(TargetFramework)' != ''"
Returns="@(ServiceProjectReferenceMetadata)">
<ItemGroup Condition="'$(TargetFramework)' != ''">
<ServiceProjectReferenceMetadata Include="DefaultDocumentName: $(DefaultServiceProjectDocumentName)" />
<ServiceProjectReferenceMetadata Include="DefaultMethod: $(DefaultServiceProjectMethod)" />
<ServiceProjectReferenceMetadata Include="DefaultService: $(DefaultServiceProjectService)" />
<ServiceProjectReferenceMetadata Include="DefaultUri: $(DefaultServiceProjectUri)" />
<ServiceProjectReferenceMetadata Include="AssemblyPath: $(TargetPath)" />
<ServiceProjectReferenceMetadata Include="Configuration: $(Configuration)" />
<ServiceProjectReferenceMetadata Include="ExtensionsPath: $(MSBuildProjectExtensionsPath)" />
<ServiceProjectReferenceMetadata Include="RuntimeIdentifier: $(RuntimeIdentifier)" />
<ServiceProjectReferenceMetadata Include="TargetFramework: $(TargetFramework)" />
</ItemGroup>
</Target>
<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

@ -93,11 +93,6 @@
Default is set in server project, falling back to "Microsoft.Extensions.ApiDescription.IDocumentProvider". Default is set in server project, falling back to "Microsoft.Extensions.ApiDescription.IDocumentProvider".
--> -->
<Service /> <Service />
<!--
URI from which Default document generator should download the document. Used only if invoking the %(Method) on
the %(Service) fails. Default is set in server project and metadata has no further fallback.
-->
<Uri />
</ServiceProjectReference> </ServiceProjectReference>
<ServiceUriReference> <ServiceUriReference>

View File

@ -179,7 +179,7 @@
<ItemGroup> <ItemGroup>
<!-- @(CurrentServiceProjectReference) item group will never contain more than one item. --> <!-- @(CurrentServiceProjectReference) item group will never contain more than one item. -->
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)"> <CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>dotnet getdocument --no-build --project %(FullPath) --output %(DocumentPath)</Command> <Command>dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project %(FullPath) --output %(DocumentPath)</Command>
<DefaultDocumentGeneratorOptions <DefaultDocumentGeneratorOptions
Condition="'%(DefaultDocumentGeneratorOptions)' == ''">$(DefaultDocumentGeneratorDefaultOptions)</DefaultDocumentGeneratorOptions> Condition="'%(DefaultDocumentGeneratorOptions)' == ''">$(DefaultDocumentGeneratorDefaultOptions)</DefaultDocumentGeneratorOptions>
<ProjectConfiguration <ProjectConfiguration
@ -194,9 +194,6 @@
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)"> <CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Service)' != ''">%(Command) --service %(Service)</Command> <Command Condition="'%(Service)' != ''">%(Command) --service %(Service)</Command>
</CurrentServiceProjectReference> </CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Uri)' != ''">%(Command) --uri %(Uri)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)"> <CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --configuration %(ProjectConfiguration) %(DefaultDocumentGeneratorOptions)</Command> <Command>%(Command) --configuration %(ProjectConfiguration) %(DefaultDocumentGeneratorOptions)</Command>
</CurrentServiceProjectReference> </CurrentServiceProjectReference>

View File

@ -159,12 +159,6 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands
args.Add(project.DefaultService); args.Add(project.DefaultService);
} }
if (!(args.Contains("--uri") || string.IsNullOrEmpty(project.DefaultUri)))
{
args.Add("--uri");
args.Add(project.DefaultUri);
}
if (_output.HasValue()) if (_output.HasValue())
{ {
args.Add("--output"); args.Add("--output");

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -11,7 +12,8 @@ namespace Microsoft.Extensions.ApiDescription.Client
{ {
internal class Project internal class Project
{ {
private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Client.ServiceProjectReferenceMetadata"; private const string ResourceFilename = "ServiceProjectReferenceMetadata.targets";
private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Client." + ResourceFilename;
private Project() private Project()
{ {
@ -33,8 +35,6 @@ namespace Microsoft.Extensions.ApiDescription.Client
public string DefaultService { get; private set; } public string DefaultService { get; private set; }
public string DefaultUri { get; private set; }
public string DepsPath { get; private set; } public string DepsPath { get; private set; }
public string Directory { get; private set; } public string Directory { get; private set; }
@ -53,41 +53,36 @@ namespace Microsoft.Extensions.ApiDescription.Client
public string RuntimeFrameworkVersion { get; private set; } public string RuntimeFrameworkVersion { get; private set; }
public string RuntimeIdentifier { get; private set; }
public string TargetFramework { get; private set; } public string TargetFramework { get; private set; }
public string TargetFrameworkMoniker { get; private set; } public string TargetFrameworkMoniker { get; private set; }
public static Project FromFile( public static Project FromFile(
string file, string projectFile,
string buildExtensionsDirectory, string buildExtensionsDirectory,
string framework = null, string framework = null,
string configuration = null, string configuration = null,
string runtime = null) string runtime = null)
{ {
Debug.Assert(!string.IsNullOrEmpty(file), "file is null or empty."); if (string.IsNullOrEmpty(projectFile))
{
throw new ArgumentNullException(nameof(projectFile));
}
if (string.IsNullOrEmpty(buildExtensionsDirectory)) if (string.IsNullOrEmpty(buildExtensionsDirectory))
{ {
buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(file), "obj"); buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(projectFile), "obj");
} }
IODirectory.CreateDirectory(buildExtensionsDirectory); IODirectory.CreateDirectory(buildExtensionsDirectory);
var assembly = typeof(Project).Assembly; var assembly = typeof(Project).Assembly;
var propsPath = Path.Combine( var targetsPath = Path.Combine(
buildExtensionsDirectory, buildExtensionsDirectory,
Path.GetFileName(file) + ".ServiceProjectReferenceMetadata.props"); $"{Path.GetFileName(projectFile)}.{ResourceFilename}");
using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.props")) using (var input = assembly.GetManifestResourceStream(MSBuildResourceName))
{
using (var output = File.OpenWrite(propsPath))
{
Reporter.WriteVerbose(Resources.FormatWritingFile(propsPath));
input.CopyTo(output);
}
}
var targetsPath = Path.ChangeExtension(propsPath, ".targets");
using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.targets"))
{ {
using (var output = File.OpenWrite(targetsPath)) using (var output = File.OpenWrite(targetsPath))
{ {
@ -101,32 +96,29 @@ namespace Microsoft.Extensions.ApiDescription.Client
var metadataPath = Path.GetTempFileName(); var metadataPath = Path.GetTempFileName();
try try
{ {
var propertyArg = "/property:ServiceProjectReferenceMetadataPath=" + metadataPath;
if (!string.IsNullOrEmpty(framework))
{
propertyArg += ";TargetFramework=" + framework;
}
if (!string.IsNullOrEmpty(configuration))
{
propertyArg += ";Configuration=" + configuration;
}
if (!string.IsNullOrEmpty(runtime))
{
propertyArg += ";RuntimeIdentifier=" + runtime;
}
var args = new List<string> var args = new List<string>
{ {
"msbuild", "msbuild",
"/target:WriteServiceProjectReferenceMetadata", "/target:WriteServiceProjectReferenceMetadata",
propertyArg,
"/verbosity:quiet", "/verbosity:quiet",
"/nologo" "/nologo",
$"/property:ServiceProjectReferenceMetadataPath={metadataPath}",
projectFile,
}; };
if (!string.IsNullOrEmpty(file)) if (!string.IsNullOrEmpty(framework))
{ {
args.Add(file); 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); var exitCode = Exe.Run("dotnet", args);
@ -140,21 +132,20 @@ namespace Microsoft.Extensions.ApiDescription.Client
} }
finally finally
{ {
File.Delete(propsPath);
File.Delete(metadataPath); File.Delete(metadataPath);
File.Delete(targetsPath); File.Delete(targetsPath);
} }
var project = new Project var project = new Project
{ {
DefaultDocumentName = metadata[nameof(DefaultDocumentName)],
DefaultMethod = metadata[nameof(DefaultMethod)],
DefaultService = metadata[nameof(DefaultService)],
AssemblyName = metadata[nameof(AssemblyName)], AssemblyName = metadata[nameof(AssemblyName)],
AssemblyPath = metadata[nameof(AssemblyPath)], AssemblyPath = metadata[nameof(AssemblyPath)],
AssetsPath = metadata[nameof(AssetsPath)], AssetsPath = metadata[nameof(AssetsPath)],
Configuration = metadata[nameof(Configuration)], Configuration = metadata[nameof(Configuration)],
DefaultDocumentName = metadata[nameof(DefaultDocumentName)],
DefaultMethod = metadata[nameof(DefaultMethod)],
DefaultService = metadata[nameof(DefaultService)],
DefaultUri = metadata[nameof(DefaultUri)],
DepsPath = metadata[nameof(DepsPath)], DepsPath = metadata[nameof(DepsPath)],
Directory = metadata[nameof(Directory)], Directory = metadata[nameof(Directory)],
ExtensionsPath = metadata[nameof(ExtensionsPath)], ExtensionsPath = metadata[nameof(ExtensionsPath)],
@ -164,6 +155,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)], PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)],
RuntimeConfigPath = metadata[nameof(RuntimeConfigPath)], RuntimeConfigPath = metadata[nameof(RuntimeConfigPath)],
RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)], RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)],
RuntimeIdentifier = metadata[nameof(RuntimeIdentifier)],
TargetFramework = metadata[nameof(TargetFramework)], TargetFramework = metadata[nameof(TargetFramework)],
TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)], TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)],
}; };
@ -193,6 +185,11 @@ namespace Microsoft.Extensions.ApiDescription.Client
project.AssemblyPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssemblyPath)); project.AssemblyPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssemblyPath));
} }
if (!Path.IsPathRooted(project.ExtensionsPath))
{
project.ExtensionsPath = Path.GetFullPath(Path.Combine(project.Directory, project.ExtensionsPath));
}
if (!Path.IsPathRooted(project.OutputPath)) if (!Path.IsPathRooted(project.OutputPath))
{ {
project.OutputPath = Path.GetFullPath(Path.Combine(project.Directory, project.OutputPath)); project.OutputPath = Path.GetFullPath(Path.Combine(project.Directory, project.OutputPath));

View File

@ -277,7 +277,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
=> string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0);
/// <summary> /// <summary>
/// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. /// Project output not found. Project must be up-to-date when using this tool.
/// </summary> /// </summary>
internal static string MustBuild internal static string MustBuild
{ {
@ -285,7 +285,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
} }
/// <summary> /// <summary>
/// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. /// Project output not found. Project must be up-to-date when using this tool.
/// </summary> /// </summary>
internal static string FormatMustBuild() internal static string FormatMustBuild()
=> GetString("MustBuild"); => GetString("MustBuild");

View File

@ -175,7 +175,7 @@
<value>Writing '{0}'...</value> <value>Writing '{0}'...</value>
</data> </data>
<data name="MustBuild" xml:space="preserve"> <data name="MustBuild" xml:space="preserve">
<value>Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option.</value> <value>Project output not found. Project must be up-to-date when using this tool.</value>
</data> </data>
<data name="OutputDescription" xml:space="preserve"> <data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value> <value>The file to write the result to.</value>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- No need for any additional work in outer build. -->
<WriteServiceProjectReferenceMetadataDependsOn Condition="'$(TargetFramework)' != '' AND '$(WriteServiceProjectReferenceMetadataDependsOn)' == ''">
$(WriteServiceProjectReferenceMetadataDependsOn)
</WriteServiceProjectReferenceMetadataDependsOn>
<!-- Execute WriteServiceProjectReferenceMetadata support targets after WriteServiceProjectReferenceMetadataDependsOn targets by default. -->
<GetServiceProjectReferenceMetadataDependsOn Condition="'$(GetServiceProjectReferenceMetadataDependsOn)' == ''">
$(WriteServiceProjectReferenceMetadataDependsOn)
</GetServiceProjectReferenceMetadataDependsOn>
<WriteServiceProjectReferenceMetadataCoreDependsOn Condition="'$(WriteServiceProjectReferenceMetadataCoreDependsOn)' == ''">
$(WriteServiceProjectReferenceMetadataDependsOn)
</WriteServiceProjectReferenceMetadataCoreDependsOn>
</PropertyGroup>
</Project>

View File

@ -1,20 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="WriteServiceProjectReferenceMetadata" <!-- Collect properties only in inner build. Execute unconditionally before WriteServiceProjectReferenceMetadata. -->
DependsOnTargets="$(WriteServiceProjectReferenceMetadataDependsOn)"
Returns="@(ServiceProjectReferenceMetadata)" />
<!-- Collect properties only in inner build. Execute unconditionally before WriteServiceProjectReferenceMetadataCore. -->
<Target Name="GetServiceProjectReferenceMetadata" <Target Name="GetServiceProjectReferenceMetadata"
BeforeTargets="WriteServiceProjectReferenceMetadataCore" BeforeTargets="WriteServiceProjectReferenceMetadata"
Condition="'$(TargetFramework)' != ''" Condition="'$(TargetFramework)' != ''"
DependsOnTargets="$(GetServiceProjectReferenceMetadataDependsOn)"
Returns="@(ServiceProjectReferenceMetadata)"> Returns="@(ServiceProjectReferenceMetadata)">
<ItemGroup Condition="'$(TargetFramework)' != ''"> <ItemGroup Condition="'$(TargetFramework)' != ''">
<ServiceProjectReferenceMetadata Include="DefaultDocumentName: $(DefaultServiceProjectDocumentName)" /> <ServiceProjectReferenceMetadata Include="DefaultDocumentName: $(DefaultServiceProjectDocumentName)" />
<ServiceProjectReferenceMetadata Include="DefaultMethod: $(DefaultServiceProjectMethod)"></ServiceProjectReferenceMetadata> <ServiceProjectReferenceMetadata Include="DefaultMethod: $(DefaultServiceProjectMethod)" />
<ServiceProjectReferenceMetadata Include="DefaultService: $(DefaultServiceProjectService)"></ServiceProjectReferenceMetadata> <ServiceProjectReferenceMetadata Include="DefaultService: $(DefaultServiceProjectService)" />
<ServiceProjectReferenceMetadata Include="DefaultUri: $(DefaultServiceProjectUri)"></ServiceProjectReferenceMetadata>
<ServiceProjectReferenceMetadata Include="AssemblyName: $(AssemblyName)" /> <ServiceProjectReferenceMetadata Include="AssemblyName: $(AssemblyName)" />
<ServiceProjectReferenceMetadata Include="AssemblyPath: $(TargetPath)" /> <ServiceProjectReferenceMetadata Include="AssemblyPath: $(TargetPath)" />
@ -29,21 +23,21 @@
<ServiceProjectReferenceMetadata Include="PlatformTarget: $(PlatformTarget)" /> <ServiceProjectReferenceMetadata Include="PlatformTarget: $(PlatformTarget)" />
<ServiceProjectReferenceMetadata Include="RuntimeConfigPath: $(ProjectRuntimeConfigFilePath)" /> <ServiceProjectReferenceMetadata Include="RuntimeConfigPath: $(ProjectRuntimeConfigFilePath)" />
<ServiceProjectReferenceMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" /> <ServiceProjectReferenceMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" />
<ServiceProjectReferenceMetadata Include="RuntimeIdentifier: $(RuntimeIdentifier)" />
<ServiceProjectReferenceMetadata Include="TargetFramework: $(TargetFramework)" /> <ServiceProjectReferenceMetadata Include="TargetFramework: $(TargetFramework)" />
<ServiceProjectReferenceMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" /> <ServiceProjectReferenceMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" />
</ItemGroup> </ItemGroup>
</Target> </Target>
<!-- Write information only in inner build. Execute unconditionally before WriteServiceProjectReferenceMetadata. --> <!-- Write information only in inner build. -->
<Target Name="WriteServiceProjectReferenceMetadataCore" <Target Name="WriteServiceProjectReferenceMetadata" Returns="@(ServiceProjectReferenceMetadata)">
BeforeTargets="WriteServiceProjectReferenceMetadata"
DependsOnTargets="$(WriteServiceProjectReferenceMetadataCoreDependsOn)"
Returns="@(ServiceProjectReferenceMetadata)">
<MSBuild Condition="'$(TargetFramework)' == ''" <MSBuild Condition="'$(TargetFramework)' == ''"
Projects="$(MSBuildProjectFile)" Projects="$(MSBuildProjectFile)"
Targets="WriteServiceProjectReferenceMetadata" Targets="WriteServiceProjectReferenceMetadata"
Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);ServiceProjectReferenceMetadataFile=$(ServiceProjectReferenceMetadataFile)" /> Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);ServiceProjectReferenceMetadataFile=$(ServiceProjectReferenceMetadataFile)" />
<WriteLinesToFile Condition="'$(TargetFramework)' != ''" File="$(ServiceProjectReferenceMetadataPath)" Lines="@(ServiceProjectReferenceMetadata)" /> <WriteLinesToFile Condition="'$(TargetFramework)' != ''"
File="$(ServiceProjectReferenceMetadataPath)"
Lines="@(ServiceProjectReferenceMetadata)" />
</Target> </Target>
</Project> </Project>

View File

@ -1,89 +1,25 @@
<Project Sdk="Internal.AspNetCore.Sdk"> <Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup> <PropertyGroup>
<!-- Execute PopulateNuspec fairly late. -->
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);PopulateNuspec</GenerateNuspecDependsOn>
<AssemblyName>dotnet-getdocument</AssemblyName> <AssemblyName>dotnet-getdocument</AssemblyName>
<Description>GetDocument Command-line Tool outside man</Description> <Description>GetDocument Command-line Tool outside man</Description>
<EnableApiCheck>false</EnableApiCheck> <EnableApiCheck>false</EnableApiCheck>
<GenerateToolShims>true</GenerateToolShims>
<IncludeSource>false</IncludeSource>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool>
<PackageTags>GetDocument;command line;command-line;tool</PackageTags>
<RootNamespace>Microsoft.Extensions.ApiDescription.Client</RootNamespace> <RootNamespace>Microsoft.Extensions.ApiDescription.Client</RootNamespace>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="../GetDocumentInsider/AnsiConsole.cs" /> <Compile Include="../GetDocumentInsider/Ansi*.cs" />
<Compile Include="../GetDocumentInsider/AnsiConstants.cs" />
<Compile Include="../GetDocumentInsider/AnsiTextWriter.cs" />
<Compile Include="../GetDocumentInsider/CommandException.cs" /> <Compile Include="../GetDocumentInsider/CommandException.cs" />
<Compile Include="../GetDocumentInsider/CommandLineUtils/*.cs" LinkBase="CommandLineUtils" /> <Compile Include="../GetDocumentInsider/CommandLineUtils/*.cs" LinkBase="CommandLineUtils" />
<Compile Include="../GetDocumentInsider/Commands/CommandBase.cs" Link="Commands/CommandBase.cs" /> <Compile Include="../GetDocumentInsider/Commands/CommandBase.cs" Link="Commands/CommandBase.cs" />
<Compile Include="../GetDocumentInsider/Commands/HelpCommandBase.cs" Link="Commands/HelpCommandBase.cs" /> <Compile Include="../GetDocumentInsider/Commands/HelpCommandBase.cs" Link="Commands/HelpCommandBase.cs" />
<Compile Include="../GetDocumentInsider/ProductInfo.cs" /> <Compile Include="../GetDocumentInsider/ProductInfo.cs" />
<Compile Include="../GetDocumentInsider/Reporter.cs" /> <Compile Include="../GetDocumentInsider/Reporter.cs" />
</ItemGroup>
<ItemGroup> <EmbeddedResource Include="ServiceProjectReferenceMetadata.targets" />
<EmbeddedResource Include="ServiceProjectReferenceMetadata.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" /> <PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<!-- Additional files to be code signed -->
<SignedPackageFile Include="tools/netcoreapp2.1/any/tools/net461/any/GetDocument.Insider.exe" Certificate="$(AssemblySigningCertName)" />
<SignedPackageFile Include="tools/netcoreapp2.1/any/tools/net461/win-x86/GetDocument.Insider.exe" Certificate="$(AssemblySigningCertName)" />
<SignedPackageFile Include="tools/netcoreapp2.1/any/tools/netcoreapp2.0/any/GetDocument.Insider.dll" Certificate="$(AssemblySigningCertName)" />
<!-- Third-party assemblies should be signed with the 3PartyDual certificate -->
<SignedPackageFile Include="tools/netcoreapp2.1/any/Newtonsoft.Json.dll" Certificate="3PartyDual" />
</ItemGroup>
<Target Name="PopulateNuspec" DependsOnTargets="Build">
<ItemGroup>
<_Temporary Remove="@(Temporary)" />
<_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461" />
<_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461;Platform=x86" />
<_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=netcoreapp2.0" />
</ItemGroup>
<MSBuild Projects="../GetDocumentInsider/GetDocumentInsider.csproj" RemoveProperties="RuntimeIdentifier;TargetFramework" Targets="Restore" />
<MSBuild Projects="@(_Temporary)" BuildInParallel="$(BuildInParallel)" RemoveProperties="RuntimeIdentifier" Targets="Publish" />
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<NuspecProperties>
authors=$(Authors);
copyright=$(Copyright);
description=$(Description);
iconUrl=$(PackageIconUrl);
id=$(PackageId);
InsiderNet461Output=..\GetDocumentInsider\bin\$(Configuration)\net461\publish\*;
InsiderNet461X86Output=..\GetDocumentInsider\bin\x86\$(Configuration)\net461\publish\*;
InsiderNetCoreOutput=..\GetDocumentInsider\bin\$(Configuration)\netcoreapp2.0\publish\*;
licenseUrl=$(PackageLicenseUrl);
Output=$(PublishDir)**\*;
OutputShims=$(IntermediateOutputPath)shims\**\*;
packageType=$(PackageType);
projectUrl=$(PackageProjectUrl);
repositoryCommit=$(RepositoryCommit);
repositoryUrl=$(RepositoryUrl);
SettingsFile=$(_ToolsSettingsFilePath);
tags=$(PackageTags.Replace(';', ' '));
targetFramework=$(TargetFramework);
version=$(PackageVersion);
</NuspecProperties>
</PropertyGroup>
</Target>
</Project> </Project>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>$id$</id>
<version>$version$</version>
<authors>$authors$</authors>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<licenseUrl>$licenseUrl$</licenseUrl>
<projectUrl>$projectUrl$</projectUrl>
<iconUrl>$iconUrl$</iconUrl>
<description>$description$</description>
<copyright>$copyright$</copyright>
<tags>$tags$</tags>
<packageTypes>
<packageType name="$packageType$" />
</packageTypes>
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
</metadata>
<files>
<file src="$InsiderNet461Output$" target="tools\$targetFramework$\any\net461" />
<file src="$InsiderNet461X86Output$" target="tools\$targetFramework$\any\net461-x86" />
<file src="$InsiderNetCoreOutput$" target="tools\$targetFramework$\any\netcoreapp2.0" />
<file src="$Output$" target="tools\$targetFramework$\any" />
<!-- file src="$OutputShims$" target="tools\$targetFramework$\any\shims" / -->
<file src="$SettingsFile$" target="tools\$targetFramework$\any" />
</files>
</package>