From 87e304334db5bfa8dcedc593c4ebf328fb23c735 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 6 Sep 2018 15:48:55 -0700 Subject: [PATCH] Remove batching requirements placed on code and document generator providers - #8419 - perform batching and `@(ServiceFileReference)` and `@(Compile)` additions in common code - take advantage of new simplicity in `DefaultDocumentGenerator` target - add metadata serialization / deserialization in support of passing items into `` - also ensure metadata values are escaped before calling `ITaskItem.SetMetadata(...)` - correct typos in Microsoft.Extensions.ApiDescription.Client.* e.g. in comments and metadata names - move last remaining `GenerationTasks` file nits: - combine `_ServiceProjectReferenceGenerator_Restore` and `_ServiceProjectReferenceGenerator_Build` targets - only build web sites projects once - remove unused `buildMultiTargeting` targets - remove qualification of metadata listed in an ``; will always exist - add / remove a few `Condition`s that were missing / redundant - move properties users won't normally set to Microsoft.Extensions.ApiDescription.Client.targets - shorten lines in MSBuild files --- .../GetCurrentItems.cs | 34 +++ .../GetFileReferenceMetadata.cs | 9 +- .../GetProjectReferenceMetadata.cs | 2 + .../GetUriReferenceMetadata.cs | 2 +- .../MetadataSerializer.cs | 147 ++++++++++++ ...oft.Extensions.ApiDescription.Client.props | 99 ++++---- ...t.Extensions.ApiDescription.Client.targets | 220 +++++++++++------- .../GenerationTasks.targets | 29 --- ...t.Extensions.ApiDescription.Client.targets | 9 + 9 files changed, 377 insertions(+), 174 deletions(-) create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs delete mode 100644 src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs new file mode 100644 index 0000000000..975e716d64 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs @@ -0,0 +1,34 @@ +// 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.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Extensions.ApiDescription.Client +{ + /// + /// Restore s from given property value. + /// + public class GetCurrentItems : Task + { + /// + /// The property value to deserialize. + /// + [Required] + public string Input { get; set; } + + /// + /// The restored s. Will never contain more than one item. + /// + [Output] + public ITaskItem[] Outputs { get; set; } + + /// + public override bool Execute() + { + Outputs = new[] { MetadataSerializer.DeserializeMetadata(Input) }; + + return true; + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs index 68751b7aa5..f41d061974 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -61,18 +61,21 @@ namespace Microsoft.Extensions.ApiDescription.Client if (string.IsNullOrEmpty(@namespace)) { @namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace; - newItem.SetMetadata("Namespace", @namespace); + MetadataSerializer.SetMetadata(newItem, "Namespace", @namespace); } var outputPath = item.GetMetadata("OutputPath"); if (string.IsNullOrEmpty(outputPath)) { var className = item.GetMetadata("ClassName"); - outputPath = className + (isTypeScript ? ".ts" : ".cs"); + outputPath = $"{className}{(isTypeScript ? ".ts" : ".cs")}"; } outputPath = GetFullPath(outputPath); - newItem.SetMetadata("OutputPath", outputPath); + MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath); + + // Add metadata which may be used as a property and passed to an inner build. + newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); } Outputs = outputs.ToArray(); diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs index 3fd84cf2e0..635863d417 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs @@ -52,6 +52,8 @@ namespace Microsoft.Extensions.ApiDescription.Client outputPath = className + (isTypeScript ? ".ts" : ".cs"); } + // Add metadata which may be used as a property and passed to an inner build. + newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); outputPath = GetFullPath(outputPath); newItem.SetMetadata("OutputPath", outputPath); } diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs index d63e2eb684..873ef57066 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs @@ -95,7 +95,7 @@ namespace Microsoft.Extensions.ApiDescription.Client } documentPath = GetFullPath(documentPath); - newItem.SetMetadata("DocumentPath", documentPath); + MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath); } Outputs = outputs.ToArray(); diff --git a/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs b/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs new file mode 100644 index 0000000000..3f430380a0 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs @@ -0,0 +1,147 @@ +// 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.Collections.Generic; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Extensions.ApiDescription.Client +{ + /// + /// Utility methods to serialize and deserialize metadata. + /// + /// + /// Based on and uses the same escaping as + /// https://github.com/Microsoft/msbuild/blob/e70a3159d64f9ed6ec3b60253ef863fa883a99b1/src/Shared/EscapingUtilities.cs + /// + public static class MetadataSerializer + { + private static readonly char[] CharsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' }; + private static readonly HashSet CharsToEscapeHash = new HashSet(CharsToEscape); + + /// + /// Add the given and to the . Or, + /// modify existing value to be . + /// + /// The to update. + /// The name of the new metadata. + /// The value of the new metadata. Assumed to be unescaped. + /// Uses same hex-encoded format as MSBuild's EscapeUtilities. + public static void SetMetadata(ITaskItem item, string key, string value) + { + if (item is ITaskItem2 item2) + { + item2.SetMetadataValueLiteral(key, value); + return; + } + + if (value.IndexOfAny(CharsToEscape) == -1) + { + item.SetMetadata(key, value); + return; + } + + var builder = new StringBuilder(); + EscapeValue(value, builder); + item.SetMetadata(key, builder.ToString()); + } + + /// + /// Serialize metadata for use as a property value passed into an inner build. + /// + /// The item to serialize. + /// A containing the serialized metadata. + /// Uses same hex-encoded format as MSBuild's EscapeUtilities. + public static string SerializeMetadata(ITaskItem item) + { + var builder = new StringBuilder(); + if (item is ITaskItem2 item2) + { + builder.Append($"Identity={item2.EvaluatedIncludeEscaped}"); + var metadata = item2.CloneCustomMetadataEscaped(); + foreach (var key in metadata.Keys) + { + var value = metadata[key]; + builder.Append($"|{key.ToString()}={value.ToString()}"); + } + } + else + { + builder.Append($"Identity="); + EscapeValue(item.ItemSpec, builder); + + var metadata = item.CloneCustomMetadata(); + foreach (var key in metadata.Keys) + { + builder.Append($"|{key.ToString()}="); + + var value = metadata[key]; + EscapeValue(value.ToString(), builder); + } + } + + return builder.ToString(); + } + + /// + /// Recreate an with metadata encoded in given . + /// + /// The serialized metadata. + /// The deserialized . + public static ITaskItem DeserializeMetadata(string value) + { + var metadata = value.Split('|'); + var item = new TaskItem(); + + // TaskItem implements ITaskITem2 explicitly and ITaskItem implicitly. + var item2 = (ITaskItem2)item; + foreach (var segment in metadata) + { + var keyAndValue = segment.Split(new[] { '=' }, count: 2); + if (string.Equals("Identity", keyAndValue[0])) + { + item2.EvaluatedIncludeEscaped = keyAndValue[1]; + continue; + } + + item2.SetMetadata(keyAndValue[0], keyAndValue[1]); + } + + return item; + } + + private static void EscapeValue(string value, StringBuilder builder) + { + if (string.IsNullOrEmpty(value)) + { + builder.Append(value); + return; + } + + if (value.IndexOfAny(CharsToEscape) == -1) + { + builder.Append(value); + return; + } + + foreach (var @char in value) + { + if (CharsToEscapeHash.Contains(@char)) + { + builder.Append('%'); + builder.Append(HexDigitChar(@char / 0x10)); + builder.Append(HexDigitChar(@char & 0x0F)); + continue; + } + + builder.Append(@char); + } + } + + private static char HexDigitChar(int x) + { + return (char)(x + (x < 10 ? '0' : ('a' - 10))); + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props index 45384b7d3e..2db402f022 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props @@ -6,49 +6,32 @@ <_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Client.dll <_ApiDescriptionTasksAssemblyTarget /> + + - true + true $([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)')) + Condition="'$(ServiceProjectReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)')) - true + true $([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)')) + Condition="'$(ServiceUriReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)')) - true + true $([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)')) - $(RootNamespace) - $(RootNamespace) - - - _DefaultDocumentGenerator_GetMetadata; - _DefaultDocumentGenerator_Core; - _DefaultDocumentGenerator_SetMetadata - - - _ServiceProjectReferenceGenerator_GetTargetFramework; - _ServiceProjectReferenceGenerator_GetProjectTargetPath; - _ServiceProjectReferenceGenerator_Restore; - _ServiceProjectReferenceGenerator_Build; - _ServiceProjectReferenceGenerator_Core - - - _ServiceUriReferenceGenerator_GetMetadata; - _ServiceUriReferenceGenerator_Core - - - _CheckServiceReferences; - ServiceProjectReferenceGenerator; - ServiceUriReferenceGenerator; - _ServiceFileReferenceGenerator_GetMetadata; - _ServiceFileReferenceGenerator_Core - + Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)')) + $(RootNamespace) + $(RootNamespace) Default + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - %(Filename)Client + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets index 6a68aba48b..bd25080826 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets @@ -1,11 +1,34 @@  + + + + _ServiceProjectReferenceGenerator_GetTargetFramework; + _ServiceProjectReferenceGenerator_GetProjectTargetPath; + _ServiceProjectReferenceGenerator_Build; + _ServiceProjectReferenceGenerator_Core; + _ServiceProjectReferenceGenerator_SetMetadata + + + _ServiceUriReferenceGenerator_GetMetadata; + _ServiceUriReferenceGenerator_Core + + + _CheckServiceReferences; + ServiceProjectReferenceGenerator; + ServiceUriReferenceGenerator; + _ServiceFileReferenceGenerator_GetMetadata; + _ServiceFileReferenceGenerator_Core; + _ServiceFileReferenceGenerator_SetMetadata + + + - - - @@ -38,7 +61,7 @@ - $(_TargetFramework) + $(_TargetFramework) <_Temporary Remove="@(_Temporary)" /> @@ -53,17 +76,19 @@ + Inputs="%(ServiceProjectReference.TargetFramework)%(FullPath)')" + Outputs="<not-a-file !>"> <_FullPath>%(ServiceProjectReference.FullPath) - <_TargetFramework>%(ServiceProjectReference.TargetFramework) + <_TargetFramework>%(ServiceProjectReference.ProjectTargetFramework) <_Temporary Remove="@(_Temporary)" /> - + + Condition="'%(ServiceProjectReference.FullPath)' == '$(_FullPath)' AND '%(ProjectTargetFramework)' == '$(_TargetFramework)'"> $(_ProjectTargetPath) <_Temporary Remove="@(_Temporary)" /> @@ -91,99 +116,85 @@ - - - - - + + RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier" + Targets="Restore;Build" /> - + + + + + - + + + + + + + + + + + + + + + + + + + - + + + + - <_Temporary Remove="@(_Temporary)" /> - <_Temporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'Default'))" /> + + + dotnet getdocument --no-build --project %(FullPath) --output %(DocumentPath) + $(DefaultDocumentGeneratorDefaultOptions) + + + %(Command) --framework %(ProjectTargetFramework) + %(Command) --configuration $(Configuration) + %(Command) --configuration %(ProjectConfiguration) + %(Command) --method %(Method) + %(Command) --service %(Service) + %(Command) --uri %(Uri) + %(Command) %(DefaultDocumentGeneratorOptions) + - - + + - - - - <_Command>dotnet getdocument --configuration $(Configuration) --no-build - - - <_Temporary Update="@(_Temporary)"> - $(DefaultDocumentGeneratorDefaultOptions) - $(_Command) --project %(FullPath) --output %(DocumentPath) --framework %(TargetFramework) - - <_Temporary Update="@(_Temporary)"> - %(Command) --uri %(_Temporary.Uri) - - <_Temporary Update="@(_Temporary)"> - %(Command) --service %(_Temporary.Service) --method %(_Temporary.Method) - - <_Temporary Update="@(_Temporary)"> - %(Command) %(_Temporary.Options) - - - - - - - - - - - - - - <_Temporary Remove="@(_Temporary)" /> - - - - - - + <_Temporary Remove="@(_Temporary)" /> - + @@ -199,9 +210,10 @@ DestinationPath="%(DocumentPath)" Overwrite="$(ServiceUriReferenceCheckIfNewer)" /> + - + @@ -209,12 +221,15 @@ - + <_Temporary Remove="@(_Temporary)" /> - + @@ -225,9 +240,38 @@ - + + + + + - + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets deleted file mode 100644 index 5e73fd66e1..0000000000 --- a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets new file mode 100644 index 0000000000..a9c3d53836 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets @@ -0,0 +1,9 @@ + + + + + +