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 @@
+
+
+
+
+
+