Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 67c833c

Browse files
committed
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 `<MSBuild />` - 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 `<ItemDefinitionGroup />`; 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
1 parent 792d5b4 commit 67c833c

9 files changed

+383
-168
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.Build.Framework;
5+
using Microsoft.Build.Utilities;
6+
7+
namespace Microsoft.Extensions.ApiDescription.Client
8+
{
9+
/// <summary>
10+
/// Restore <see cref="ITaskItem"/>s from given property value.
11+
/// </summary>
12+
public class GetCurrentItems : Task
13+
{
14+
/// <summary>
15+
/// The property value to deserialize.
16+
/// </summary>
17+
[Required]
18+
public string Input { get; set; }
19+
20+
/// <summary>
21+
/// The restored <see cref="ITaskItem"/>s. Will never contain more than one item.
22+
/// </summary>
23+
[Output]
24+
public ITaskItem[] Outputs { get; set; }
25+
26+
/// <inheritdoc />
27+
public override bool Execute()
28+
{
29+
Outputs = new[] { MetadataSerializer.DeserializeMetadata(Input) };
30+
31+
return true;
32+
}
33+
}
34+
}

src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,21 @@ public override bool Execute()
6161
if (string.IsNullOrEmpty(@namespace))
6262
{
6363
@namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace;
64-
newItem.SetMetadata("Namespace", @namespace);
64+
MetadataSerializer.SetMetadata(newItem, "Namespace", @namespace);
6565
}
6666

6767
var outputPath = item.GetMetadata("OutputPath");
6868
if (string.IsNullOrEmpty(outputPath))
6969
{
7070
var className = item.GetMetadata("ClassName");
71-
outputPath = className + (isTypeScript ? ".ts" : ".cs");
71+
outputPath = $"{className}{(isTypeScript ? ".ts" : ".cs")}";
7272
}
7373

7474
outputPath = GetFullPath(outputPath);
75-
newItem.SetMetadata("OutputPath", outputPath);
75+
MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath);
76+
77+
// Add metadata which may be used as a property and passed to an inner build.
78+
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
7679
}
7780

7881
Outputs = outputs.ToArray();

src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public override bool Execute()
5252
outputPath = className + (isTypeScript ? ".ts" : ".cs");
5353
}
5454

55+
// Add metadata which may be used as a property and passed to an inner build.
56+
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
5557
outputPath = GetFullPath(outputPath);
5658
newItem.SetMetadata("OutputPath", outputPath);
5759
}

src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public override bool Execute()
9595
}
9696

9797
documentPath = GetFullPath(documentPath);
98-
newItem.SetMetadata("DocumentPath", documentPath);
98+
MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath);
9999
}
100100

101101
Outputs = outputs.ToArray();
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Text;
6+
using Microsoft.Build.Framework;
7+
using Microsoft.Build.Utilities;
8+
9+
namespace Microsoft.Extensions.ApiDescription.Client
10+
{
11+
/// <summary>
12+
/// Utility methods to serialize and deserialize <see cref="ITaskItem"/> metadata.
13+
/// </summary>
14+
/// <remarks>
15+
/// Based on and uses the same escaping as
16+
/// https://github.com/Microsoft/msbuild/blob/e70a3159d64f9ed6ec3b60253ef863fa883a99b1/src/Shared/EscapingUtilities.cs
17+
/// </remarks>
18+
public static class MetadataSerializer
19+
{
20+
private static readonly char[] CharsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' };
21+
private static readonly HashSet<char> CharsToEscapeHash = new HashSet<char>(CharsToEscape);
22+
23+
/// <summary>
24+
/// Add the given <paramref name="key"/> and <paramref name="value"/> to the <paramref name="item"/>. Or,
25+
/// modify existing value to be <paramref name="value"/>.
26+
/// </summary>
27+
/// <param name="item">The <see cref="ITaskItem"/> to update.</param>
28+
/// <param name="key">The name of the new metadata.</param>
29+
/// <param name="value">The value of the new metadata. Assumed to be unescaped.</param>
30+
/// <remarks>Uses same hex-encoded format as MSBuild's EscapeUtilities.</remarks>
31+
public static void SetMetadata(ITaskItem item, string key, string value)
32+
{
33+
if (item is ITaskItem2 item2)
34+
{
35+
item2.SetMetadataValueLiteral(key, value);
36+
return;
37+
}
38+
39+
if (value.IndexOfAny(CharsToEscape) == -1)
40+
{
41+
item.SetMetadata(key, value);
42+
return;
43+
}
44+
45+
var builder = new StringBuilder();
46+
EscapeValue(value, builder);
47+
item.SetMetadata(key, builder.ToString());
48+
}
49+
50+
/// <summary>
51+
/// Serialize metadata for use as a property value passed into an inner build.
52+
/// </summary>
53+
/// <param name="item">The item to serialize.</param>
54+
/// <returns>A <see cref="string"/> containing the serialized metadata.</returns>
55+
/// <remarks>Uses same hex-encoded format as MSBuild's EscapeUtilities.</remarks>
56+
public static string SerializeMetadata(ITaskItem item)
57+
{
58+
var builder = new StringBuilder();
59+
if (item is ITaskItem2 item2)
60+
{
61+
builder.Append($"Identity={item2.EvaluatedIncludeEscaped}");
62+
var metadata = item2.CloneCustomMetadataEscaped();
63+
foreach (var key in metadata.Keys)
64+
{
65+
var value = metadata[key];
66+
builder.Append($"|{key.ToString()}={value.ToString()}");
67+
}
68+
}
69+
else
70+
{
71+
builder.Append($"Identity=");
72+
EscapeValue(item.ItemSpec, builder);
73+
74+
var metadata = item.CloneCustomMetadata();
75+
foreach (var key in metadata.Keys)
76+
{
77+
builder.Append($"|{key.ToString()}=");
78+
79+
var value = metadata[key];
80+
EscapeValue(value.ToString(), builder);
81+
}
82+
}
83+
84+
return builder.ToString();
85+
}
86+
87+
/// <summary>
88+
/// Recreate an <see cref="ITaskItem"/> with metadata encoded in given <paramref name="value"/>.
89+
/// </summary>
90+
/// <param name="value">The serialized metadata.</param>
91+
/// <returns>The deserialized <see cref="ITaskItem"/>.</returns>
92+
public static ITaskItem DeserializeMetadata(string value)
93+
{
94+
var metadata = value.Split('|');
95+
var item = new TaskItem();
96+
97+
// TaskItem implements ITaskITem2 explicitly and ITaskItem implicitly.
98+
var item2 = (ITaskItem2)item;
99+
foreach (var segment in metadata)
100+
{
101+
var keyAndValue = segment.Split(new[] { '=' }, count: 2);
102+
if (string.Equals("Identity", keyAndValue[0]))
103+
{
104+
item2.EvaluatedIncludeEscaped = keyAndValue[1];
105+
continue;
106+
}
107+
108+
item2.SetMetadata(keyAndValue[0], keyAndValue[1]);
109+
}
110+
111+
return item;
112+
}
113+
114+
private static void EscapeValue(string value, StringBuilder builder)
115+
{
116+
if (string.IsNullOrEmpty(value))
117+
{
118+
builder.Append(value);
119+
return;
120+
}
121+
122+
if (value.IndexOfAny(CharsToEscape) == -1)
123+
{
124+
builder.Append(value);
125+
return;
126+
}
127+
128+
foreach (var @char in value)
129+
{
130+
if (CharsToEscapeHash.Contains(@char))
131+
{
132+
builder.Append('%');
133+
builder.Append(HexDigitChar(@char / 0x10));
134+
builder.Append(HexDigitChar(@char & 0x0F));
135+
continue;
136+
}
137+
138+
builder.Append(@char);
139+
}
140+
}
141+
142+
private static char HexDigitChar(int x)
143+
{
144+
return (char)(x + (x < 10 ? '0' : ('a' - 10)));
145+
}
146+
}
147+
}

src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,32 @@
66
<_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Client.dll</_ApiDescriptionTasksAssemblyPath>
77
<_ApiDescriptionTasksAssemblyTarget />
88
</PropertyGroup>
9+
<UsingTask TaskName="GetCurrentItems" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
910
<UsingTask TaskName="GetFileReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
1011
<UsingTask TaskName="GetProjectReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
1112
<UsingTask TaskName="GetUriReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
1213
<UsingTask TaskName="Microsoft.Extensions.ApiDescription.Client.DownloadFile" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
1314

15+
<!-- Settings users may update as they see fit. -->
1416
<PropertyGroup>
15-
<ServiceProjectReferenceCheckIfNewer Condition="'$(ServiceProjectReferenceCheckIfNewer)' == ''">true</ServiceProjectReferenceCheckIfNewer>
17+
<ServiceProjectReferenceCheckIfNewer
18+
Condition="'$(ServiceProjectReferenceCheckIfNewer)' == ''">true</ServiceProjectReferenceCheckIfNewer>
1619
<ServiceProjectReferenceDirectory
17-
Condition="'$(ServiceProjectReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)'))</ServiceProjectReferenceDirectory>
20+
Condition="'$(ServiceProjectReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)'))</ServiceProjectReferenceDirectory>
1821

19-
<ServiceUriReferenceCheckIfNewer Condition="'$(ServiceUriReferenceCheckIfNewer)' == ''">true</ServiceUriReferenceCheckIfNewer>
22+
<ServiceUriReferenceCheckIfNewer
23+
Condition="'$(ServiceUriReferenceCheckIfNewer)' == ''">true</ServiceUriReferenceCheckIfNewer>
2024
<ServiceUriReferenceDirectory
21-
Condition="'$(ServiceUriReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)'))</ServiceUriReferenceDirectory>
25+
Condition="'$(ServiceUriReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)'))</ServiceUriReferenceDirectory>
2226

23-
<ServiceFileReferenceCheckIfNewer Condition="'$(ServiceFileReferenceCheckIfNewer)' == ''">true</ServiceFileReferenceCheckIfNewer>
27+
<ServiceFileReferenceCheckIfNewer
28+
Condition="'$(ServiceFileReferenceCheckIfNewer)' == ''">true</ServiceFileReferenceCheckIfNewer>
2429
<ServiceFileReferenceDirectory
25-
Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)'))</ServiceFileReferenceDirectory>
26-
<ServiceFileReferenceCSharpNamespace Condition="'$(ServiceFileReferenceCSharpNamespace)' == ''">$(RootNamespace)</ServiceFileReferenceCSharpNamespace>
27-
<ServiceFileReferenceTypeScriptNamespace Condition="'$(ServiceFileReferenceTypeScriptNamespace)' == ''">$(RootNamespace)</ServiceFileReferenceTypeScriptNamespace>
28-
29-
<DefaultDocumentGeneratorDependsOn>
30-
_DefaultDocumentGenerator_GetMetadata;
31-
_DefaultDocumentGenerator_Core;
32-
_DefaultDocumentGenerator_SetMetadata
33-
</DefaultDocumentGeneratorDependsOn>
34-
<ServiceProjectReferenceGeneratorDependsOn>
35-
_ServiceProjectReferenceGenerator_GetTargetFramework;
36-
_ServiceProjectReferenceGenerator_GetProjectTargetPath;
37-
_ServiceProjectReferenceGenerator_Restore;
38-
_ServiceProjectReferenceGenerator_Build;
39-
_ServiceProjectReferenceGenerator_Core
40-
</ServiceProjectReferenceGeneratorDependsOn>
41-
<ServiceUriReferenceGeneratorDependsOn>
42-
_ServiceUriReferenceGenerator_GetMetadata;
43-
_ServiceUriReferenceGenerator_Core
44-
</ServiceUriReferenceGeneratorDependsOn>
45-
<ServiceFileReferenceGeneratorDependsOn>
46-
_CheckServiceReferences;
47-
ServiceProjectReferenceGenerator;
48-
ServiceUriReferenceGenerator;
49-
_ServiceFileReferenceGenerator_GetMetadata;
50-
_ServiceFileReferenceGenerator_Core
51-
</ServiceFileReferenceGeneratorDependsOn>
30+
Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)'))</ServiceFileReferenceDirectory>
31+
<ServiceFileReferenceCSharpNamespace
32+
Condition="'$(ServiceFileReferenceCSharpNamespace)' == ''">$(RootNamespace)</ServiceFileReferenceCSharpNamespace>
33+
<ServiceFileReferenceTypeScriptNamespace
34+
Condition="'$(ServiceFileReferenceTypeScriptNamespace)' == ''">$(RootNamespace)</ServiceFileReferenceTypeScriptNamespace>
5235
</PropertyGroup>
5336

5437
<!--
@@ -59,6 +42,33 @@
5942
<ServiceProjectReference>
6043
<!-- Name of the API description document generator. -->
6144
<DocumentGenerator>Default</DocumentGenerator>
45+
46+
<!-- Server project metadata which is likely applicable to all document generators. -->
47+
48+
<!--
49+
Full path of the project's generated assembly. Corresponds to $(TargetPath). Because common code builds server
50+
projects, file exists prior to document generator invocation.
51+
-->
52+
<ProjectAssemblyPath />
53+
<!-- Server project's chosen configuration. Likely matches client project's configuration. -->
54+
<ProjectConfiguration />
55+
<!--
56+
Server project's extensions path. Corresponds to $(MSBuildProjectExtensionsPath). User must set this if
57+
server project's value is not 'obj/'.
58+
-->
59+
<ProjectExtensionsPath />
60+
<!-- Runtime identifier to use when building the server project. -->
61+
<ProjectRuntimeIdentifier />
62+
<!-- Server project's target framework. Defaults to $(TargetFramewok) or first of $(TargetFrameworks). -->
63+
<ProjectTargetFramework />
64+
65+
<!-- Metadata specific to the Default document generator (though other document generators are free to use it). -->
66+
67+
<!--
68+
Options added to Default document generator tool's command line. Defaults to
69+
$(DefaultDocumentGeneratorDefaultOptions) if that is set in the client project.
70+
-->
71+
<DefaultDocumentGeneratorOptions />
6272
<!--
6373
Name of the document to generate. Passed to the %(Method) when using Default document generator. Default is set
6474
in server project, falling back to "v1".
@@ -84,36 +94,19 @@
8494
the %(Service) fails. Default is set in server project and metadata has no further fallback.
8595
-->
8696
<Uri />
87-
88-
<!--
89-
Full path of the project's generated assembly. Corresponds to $(TargetPath). Because common code builds server
90-
projects, file exists prior to document generator invocation.
91-
-->
92-
<ProjectAssemblyPath />
93-
<!-- Server project's chosen configuration. Likely matches client project's configuration. -->
94-
<ProjectConfiguration />
95-
<!--
96-
Server project's extensions path. Corresponds to $(MSBuildProjectExtensionsPath). Must set this if project's
97-
value is not 'obj/'.
98-
-->
99-
<ProjectExtensionsPath />
100-
<!-- Runtime identifier to use when building the server project. -->
101-
<ProjectRuntimeIdentifier />
102-
<!-- Server project's target framework. Defaults to $(TargetFramewok) or first of $(TargetFrameworks). -->
103-
<ProjectTargetFramework />
10497
</ServiceProjectReference>
10598

10699
<ServiceUriReference>
107100
<!--
108101
Full path where the API description document is placed. Default filename is based on %(Identity).
109-
Filenames and relative paths (if explicitly set) are combined with $(ServiceProjectReferenceDirectory).
102+
Filenames and relative paths (if explicitly set) are combined with $(ServiceUriReferenceDirectory).
110103
-->
111104
<DocumentPath />
112105
</ServiceUriReference>
113106

114107
<ServiceFileReference>
115-
<!-- Name of the class to generate. -->
116-
<ClassName>%(Filename)Client</ClassName>
108+
<!-- Name of the class to generate. Defaults to %(Filename)Client but with an uppercase first letter. -->
109+
<ClassName />
117110
<!-- Code generator to use. Required. -->
118111
<CodeGenerator />
119112
<!--
@@ -124,7 +117,7 @@
124117
<!--
125118
Path to place generated code. Code generator may interpret path as a filename or directory. Default filename or
126119
folder name is %(ClassName).[cs|ts]. Filenames and relative paths (if explicitly set) are combined with
127-
$(ServiceProjectReferenceDirectory).
120+
$(ServiceFileReferenceDirectory).
128121
-->
129122
<OutputPath />
130123
</ServiceFileReference>

0 commit comments

Comments
 (0)