Fix a few issues with Microsoft.Extensions.ApiDescription.Client targets

- follow-ups to 1646345955 and 9d109f5956
- fix `%(Command)` updates in `DefaultDocumentGenerator` target
  - later references to metadata values set within an item are not up-to-date
- qualify values for `%(SourceProject)`, `%(SourceUri)` and `%(SourceDocument)` when setting that metadata
  - MSBuild can't distinguish unqualified metadata references unless using `<X Update="@(X)">`
- fix `@(CurrentServiceFileReference)` items
  - was a copy 'n paste error in `_ServiceFileReferenceGenerator_Core` target
- remove per-language default namespace values
- do not add TypeScript files to `@(Compile)`; generally enhance final item additions
  - use `$(DefaultLanguageSourceExtension)` to help here
  - exclude generated source files with `%(OutputPath)` that does not match `$(DefaultLanguageSourceExtension)`
  - really support `%(OutputPath)` directories
- stick with current `$(TargetFramework)` when building `...ReferenceGenerator_Inner` targets
  - `%(ProjectTargetFramework)` will not exist for all `@(ServiceFileReference)` items
  - building the current project, not a service project; `%(ProjectTargetFramework)` may not be supported

nits:
- shorten a few more long lines in Microsoft.Extensions.ApiDescription.Client.targets
- reduce logging from that file
- do not include `%(SerializedMetadata)` in `%(SerializedMetadata)`
  - caused extra-long serialization of items that were originally `@(ServiceProjectReference)`s
- add more info to various comments
- always use element syntax for metadata additions
This commit is contained in:
Doug Bunting 2018-09-25 15:23:50 -07:00
parent fb9393febf
commit ea80199e53
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
3 changed files with 128 additions and 86 deletions

View File

@ -15,23 +15,25 @@ namespace Microsoft.Extensions.ApiDescription.Client
/// </summary>
public class GetFileReferenceMetadata : Task
{
private const string TypeScriptLanguageName = "TypeScript";
/// <summary>
/// Default Namespace metadata value for C# output.
/// Extension to use in default OutputPath metadata value. Ignored when generating TypeScript.
/// </summary>
[Required]
public string CSharpNamespace { get; set; }
public string Extension { get; set; }
/// <summary>
/// Default Namespace metadata value.
/// </summary>
[Required]
public string Namespace { get; set; }
/// <summary>
/// Default directory for OutputPath values.
/// </summary>
public string OutputDirectory { get; set; }
/// <summary>
/// Default Namespace metadata value for TypeScript output.
/// </summary>
[Required]
public string TypeScriptNamespace { get; set; }
/// <summary>
/// The ServiceFileReference items to update.
/// </summary>
@ -39,8 +41,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata
/// will contain full paths.
/// The updated ServiceFileReference items. Will include ClassName, Namespace and OutputPath metadata.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
@ -50,6 +51,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var newItem = new TaskItem(item);
@ -89,22 +91,24 @@ namespace Microsoft.Extensions.ApiDescription.Client
MetadataSerializer.SetMetadata(newItem, "ClassName", className);
}
var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase);
var @namespace = item.GetMetadata("Namespace");
if (string.IsNullOrEmpty(@namespace))
{
@namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace;
MetadataSerializer.SetMetadata(newItem, "Namespace", @namespace);
MetadataSerializer.SetMetadata(newItem, "Namespace", Namespace);
}
var outputPath = item.GetMetadata("OutputPath");
if (string.IsNullOrEmpty(outputPath))
{
outputPath = $"{className}{(isTypeScript ? ".ts" : ".cs")}";
var isTypeScript = codeGenerator.EndsWith(TypeScriptLanguageName, StringComparison.OrdinalIgnoreCase);
outputPath = $"{className}{(isTypeScript ? ".ts" : Extension)}";
}
outputPath = GetFullPath(outputPath);
MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath);
// Place output file in correct directory (relative to project directory).
if (!Path.IsPathRooted(outputPath) && !string.IsNullOrEmpty(OutputDirectory))
{
outputPath = Path.Combine(OutputDirectory, outputPath);
}
if (!destinations.Add(outputPath))
{
@ -113,7 +117,10 @@ namespace Microsoft.Extensions.ApiDescription.Client
Log.LogError(Resources.FormatDuplicateFileOutputPaths(outputPath));
}
MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath);
// Add metadata which may be used as a property and passed to an inner build.
newItem.RemoveMetadata("SerializedMetadata");
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
}
@ -121,20 +128,5 @@ namespace Microsoft.Extensions.ApiDescription.Client
return !Log.HasLoggedErrors;
}
private string GetFullPath(string path)
{
if (!Path.IsPathRooted(path))
{
if (!string.IsNullOrEmpty(OutputDirectory))
{
path = Path.Combine(OutputDirectory, path);
}
path = Path.GetFullPath(path);
}
return path;
}
}
}

View File

@ -12,7 +12,10 @@
<UsingTask TaskName="GetUriReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="Microsoft.Extensions.ApiDescription.Client.DownloadFile" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<!-- Settings users may update as they see fit. -->
<!--
Settings users may update as they see fit. All $(...Directory) values are interpreted relative to the client
project folder, unless already an absolute path.
-->
<PropertyGroup>
<ServiceProjectReferenceCheckIfNewer
Condition="'$(ServiceProjectReferenceCheckIfNewer)' == ''">true</ServiceProjectReferenceCheckIfNewer>
@ -28,10 +31,8 @@
Condition="'$(ServiceFileReferenceCheckIfNewer)' == ''">true</ServiceFileReferenceCheckIfNewer>
<ServiceFileReferenceDirectory
Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)'))</ServiceFileReferenceDirectory>
<ServiceFileReferenceCSharpNamespace
Condition="'$(ServiceFileReferenceCSharpNamespace)' == ''">$(RootNamespace)</ServiceFileReferenceCSharpNamespace>
<ServiceFileReferenceTypeScriptNamespace
Condition="'$(ServiceFileReferenceTypeScriptNamespace)' == ''">$(RootNamespace)</ServiceFileReferenceTypeScriptNamespace>
<DefaultDocumentGeneratorDefaultOptions Condition="'$(DefaultDocumentGeneratorDefaultOptions)' == ''" />
</PropertyGroup>
<!--
@ -40,7 +41,10 @@
-->
<ItemDefinitionGroup>
<ServiceProjectReference>
<!-- Name of the API description document generator. -->
<!--
Name of the API description document generator. Builds will invoke a target named
"%(DocumentGenerator)DocumentGenerator" to do actual document retrieval / generation.
-->
<DocumentGenerator>Default</DocumentGenerator>
<!-- Server project metadata which is likely applicable to all document generators. -->
@ -107,17 +111,20 @@
<ServiceFileReference>
<!-- Name of the class to generate. Defaults to %(Filename)Client but with an uppercase first letter. -->
<ClassName />
<!-- Code generator to use. Required. -->
<!--
Code generator to use. Required and must end with "CSharp" or "TypeScript" (the currently-supported target
languages). Builds will invoke a target named "%(CodeGenerator)CodeGenerator" to do actual code generation.
-->
<CodeGenerator />
<!--
Namespace to contain generated class. Default is either $(ServiceFileReferenceCSharpNamespace) or
$(ServiceFileReferenceTypeScriptNamespace), depending on target language.
Namespace to contain generated class. Default is $(RootNamespace).
-->
<Namespace />
<!--
Path to place generated code. Code generator may interpret path as a filename or directory. Default filename or
folder name is %(ClassName).[cs|ts]. Filenames and relative paths (if explicitly set) are combined with
$(ServiceFileReferenceDirectory).
$(ServiceFileReferenceDirectory). Final value (depending on $(ServiceFileReferenceDirectory)) is likely to be
a path relative to the client project.
-->
<OutputPath />
</ServiceFileReference>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<!-- Internal settings for Microsoft.Extensions.ApiDescription.Client.targets use. Not intended for customization. -->
<!-- Internal settings. Not intended for customization. -->
<PropertyGroup>
<ServiceProjectReferenceGeneratorDependsOn>
_ServiceProjectReferenceGenerator_GetTargetFramework;
@ -25,8 +25,10 @@
<!-- ServiceProjectReference support -->
<!-- Metadata setup phase 1: Ensure items have TargetFramework metadata. Call GetTargetFrameworks in the target project. -->
<!-- Inputs and outputs cause MSBuild to run target unconditionally and to batch it (run once per project). -->
<!--
Metadata setup phase 1: Ensure items have TargetFramework metadata. Call GetTargetFrameworks in the target project.
Inputs and outputs cause MSBuild to run target unconditionally and to batch it (run once per project).
-->
<Target Name="_ServiceProjectReferenceGenerator_GetTargetFramework"
Inputs="%(ServiceProjectReference.FullPath)"
Outputs="&lt;not-a-file !&gt;">
@ -45,14 +47,17 @@
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<!-- Please excuse the mess necessary to extract information from _Temporary and use it in ServiceProjectReference. -->
<!--
Please excuse the mess necessary to extract information from _Temporary and use it in ServiceProjectReference.
-->
<PropertyGroup>
<_TargetFrameworks>%(_Temporary.TargetFrameworks)</_TargetFrameworks>
<_TargetFramework>$(_TargetFrameworks.Split(';')[0])</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)" Condition="'%(FullPath)' == '$(_FullPath)'">
<ProjectTargetFramework Condition="'%(ProjectTargetFramework)' == ''">$(_TargetFramework)</ProjectTargetFramework>
<ProjectTargetFramework
Condition="'%(ProjectTargetFramework)' == ''">$(_TargetFramework)</ProjectTargetFramework>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
@ -64,8 +69,11 @@
</PropertyGroup>
</Target>
<!-- Metadata setup phase 2: Ensure items have ProjectTargetPath metadata. Call GetTargetPath in the target project. -->
<!-- Inputs and outputs cause MSBuild to run target unconditionally and batch it (run once per TargetFramework x project combination). -->
<!--
Metadata setup phase 2: Ensure items have ProjectTargetPath metadata. Call GetTargetPath in the target project.
Inputs and outputs cause MSBuild to run target unconditionally and batch it (run once per TargetFramework x
project combination).
-->
<Target Name="_ServiceProjectReferenceGenerator_GetProjectTargetPath"
Inputs="%(ServiceProjectReference.TargetFramework)%(FullPath)')"
Outputs="&lt;not-a-file !&gt;">
@ -77,9 +85,6 @@
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<Message
Importance="high"
Text="%0A_ServiceProjectReferenceGenerator_GetProjectTargetPath: '$(_FullPath)' '$(_TargetFramework)'" />
<MSBuild Projects="$(_FullPath)"
Properties="TargetFramework=$(_TargetFramework)"
RebaseOutputs="true"
@ -94,7 +99,7 @@
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)"
Condition="'%(ServiceProjectReference.FullPath)' == '$(_FullPath)' AND '%(ProjectTargetFramework)' == '$(_TargetFramework)'">
Condition="'%(FullPath)' == '$(_FullPath)' AND '%(ProjectTargetFramework)' == '$(_TargetFramework)'">
<ProjectTargetPath>$(_ProjectTargetPath)</ProjectTargetPath>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
@ -113,7 +118,8 @@
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetProjectReferenceMetadata Inputs="@(ServiceProjectReference)" DocumentDirectory="$(ServiceProjectReferenceDirectory)">
<GetProjectReferenceMetadata Inputs="@(ServiceProjectReference)"
DocumentDirectory="$(ServiceProjectReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetProjectReferenceMetadata>
@ -124,9 +130,6 @@
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<Message Importance="high" Text="%0A_ServiceProjectReferenceGenerator_GetMetadata:" />
<Message Importance="high" Text=" @(ServiceProjectReference): %(DocumentPath)" />
</Target>
<Target Name="_ServiceProjectReferenceGenerator_Build"
@ -145,16 +148,14 @@
</GetCurrentItems>
</Target>
<Target Name="_ServiceProjectReferenceGenerator_Inner" DependsOnTargets="_ServiceProjectReferenceGenerator_GetItems;$(GeneratorTarget)" />
<Target Name="_ServiceProjectReferenceGenerator_Inner"
DependsOnTargets="_ServiceProjectReferenceGenerator_GetItems;$(GeneratorTarget)" />
<Target Name="_ServiceProjectReferenceGenerator_Core" Inputs="@(ServiceProjectReference)" Outputs="%(DocumentPath)">
<Message Importance="high" Text="%0A_ServiceProjectReferenceGenerator_Core:" />
<Message Importance="high" Text=" @(ServiceProjectReference): %(DocumentPath)" />
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceProjectReference.DocumentPath);GeneratorTarget=%(DocumentGenerator)DocumentGenerator;GeneratorMetadata=%(SerializedMetadata);TargetFramework=%(ProjectTargetFramework)"
RemoveProperties="TargetFrameworks;RuntimeIdentifier"
Properties="GeneratorTargetPath=%(ServiceProjectReference.DocumentPath);GeneratorTarget=%(DocumentGenerator)DocumentGenerator;GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_ServiceProjectReferenceGenerator_Inner" />
</Target>
@ -164,8 +165,9 @@
<ServiceFileReference Remove="@(ServiceProjectReference -> '%(DocumentPath)')" />
<!-- Condition here is temporary. Useful while DefaultDocumentGenerator fails. -->
<ServiceFileReference Include="@(ServiceProjectReference -> '%(DocumentPath)')"
Condition="Exists('%(DocumentPath)')"
SourceProject="%(FullPath)" />
Condition="Exists('%(ServiceProjectReference.DocumentPath)')">
<SourceProject>%(ServiceProjectReference.FullPath)</SourceProject>
</ServiceFileReference>
</ItemGroup>
</Target>
@ -174,30 +176,36 @@
<!-- DefaultDocumentGenerator -->
<Target Name="DefaultDocumentGenerator">
<Message Importance="high" Text="%0ADefaultDocumentGenerator:" />
<Message Importance="high" Text=" @(CurrentServiceProjectReference): %(DocumentPath)" />
<ItemGroup>
<!-- @(CurrentServiceProjectReference) item group will never contain more than one item. -->
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>dotnet getdocument --no-build --project %(FullPath) --output %(DocumentPath)</Command>
<DefaultDocumentGeneratorOptions
Condition="'%(DefaultDocumentGeneratorOptions)' == ''">$(DefaultDocumentGeneratorDefaultOptions)</DefaultDocumentGeneratorOptions>
<ProjectConfiguration
Condition="'%(ProjectConfiguration)' == ''">$(Configuration)</ProjectConfiguration>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(_Temporary)">
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --framework %(ProjectTargetFramework)</Command>
<Command Condition="'%(ProjectConfiguration)' == ''">%(Command) --configuration $(Configuration)</Command>
<Command Condition="'%(ProjectConfiguration)' != ''">%(Command) --configuration %(ProjectConfiguration)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Method)' != ''">%(Command) --method %(Method)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Service)' != ''">%(Command) --service %(Service)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Uri)' != ''">%(Command) --uri %(Uri)</Command>
<Command
Condition="'%(DefaultDocumentGeneratorOptions)' != ''">%(Command) %(DefaultDocumentGeneratorOptions)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --configuration %(ProjectConfiguration) %(DefaultDocumentGeneratorOptions)</Command>
</CurrentServiceProjectReference>
</ItemGroup>
<Message Importance="high" Text="%0A%(CurrentServiceProjectReference.Command)" />
<Exec IgnoreExitCode="$([System.IO.File]::Exists('%(DocumentPath)'))" Command="%(CurrentServiceProjectReference.Command)" />
<Message Importance="high" Text="%0ADefaultDocumentGenerator:" />
<Message Importance="high" Text=" %(CurrentServiceProjectReference.Command)" />
<Exec Command="%(CurrentServiceProjectReference.Command)"
IgnoreExitCode="$([System.IO.File]::Exists('%(DocumentPath)'))" />
</Target>
<!-- ServiceUriReference support -->
@ -226,7 +234,9 @@
<!-- _ServiceUriReferenceGenerator_GetMetadata guarantees %(DocumentPath) values are unique. -->
<ItemGroup>
<ServiceFileReference Remove="@(ServiceUriReference -> '%(DocumentPath)')" />
<ServiceFileReference Include="@(ServiceUriReference -> '%(DocumentPath)')" SourceUri="%(Identity)" />
<ServiceFileReference Include="@(ServiceUriReference -> '%(DocumentPath)')">
<SourceUri>%(ServiceUriReference.Identity)</SourceUri>
</ServiceFileReference>
</ItemGroup>
</Target>
@ -240,9 +250,9 @@
</ItemGroup>
<GetFileReferenceMetadata Inputs="@(ServiceFileReference)"
CSharpNamespace="$(ServiceFileReferenceCSharpNamespace)"
OutputDirectory="$(ServiceFileReferenceDirectory)"
TypeScriptNamespace="$(ServiceFileReferenceTypeScriptNamespace)">
Extension="$(DefaultLanguageSourceExtension)"
Namespace="$(RootNamespace)"
OutputDirectory="$(ServiceFileReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetFileReferenceMetadata>
@ -259,16 +269,14 @@
</GetCurrentItems>
</Target>
<Target Name="_ServiceFileReferenceGenerator_Inner" DependsOnTargets="_ServiceFileReferenceGenerator_GetItems;$(GeneratorTarget)" />
<Target Name="_ServiceFileReferenceGenerator_Inner"
DependsOnTargets="_ServiceFileReferenceGenerator_GetItems;$(GeneratorTarget)" />
<Target Name="_ServiceFileReferenceGenerator_Core" Inputs="@(ServiceFileReference)" Outputs="%(OutputPath)">
<Message Importance="high" Text="%0A_ServiceFileReferenceGenerator_Core:" />
<Message Importance="high" Text=" @(ServiceFileReference): %(DocumentPath)" />
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceProjectReference.OutputPath);GeneratorTarget=%(CodeGenerator)CodeGenerator;GeneratorMetadata=%(SerializedMetadata);TargetFramework=%(ProjectTargetFramework)"
RemoveProperties="TargetFrameworks;RuntimeIdentifier"
Properties="GeneratorTargetPath=%(ServiceFileReference.OutputPath);GeneratorTarget=%(CodeGenerator)CodeGenerator;GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_ServiceFileReferenceGenerator_Inner" />
</Target>
@ -279,8 +287,43 @@
unique.
-->
<ItemGroup>
<Compile Remove="@(ServiceFileReference -> '%(OutputPath)')" />
<Compile Include="@(ServiceFileReference -> '%(OutputPath)')" SourceDocument="%(FullPath)" />
<_Files Remove="@(_Files)" />
<_Files Include="@(ServiceFileReference -> '%(OutputPath)')"
Condition="$([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))">
<OutputPathExtension>$([System.IO.Path]::GetExtension('%(ServiceFileReference.OutputPath)'))</OutputPathExtension>
</_Files>
<_Directories Remove="@(_Directories)" />
<_Directories Include="@(ServiceFileReference -> '%(OutputPath)')"
Condition="Exists('%(ServiceFileReference.OutputPath)') AND ! $([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))" />
<!-- If OutputPath is a file, add it directly to relevant items. -->
<TypeScriptCompile Remove="@(_Files)" Condition="'%(_Files.OutputPathExtension)' == '.ts'" />
<TypeScriptCompile Include="@(_Files)" Condition="'%(_Files.OutputPathExtension)' == '.ts'">
<SourceDocument>%(_Files.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Files)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'" />
<Compile Include="@(_Files)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'">
<SourceDocument>%(ServiceFileReference.FullPath)</SourceDocument>
</Compile>
<!-- Otherwise, add all descendent files with the expected extension. -->
<TypeScriptCompile Remove="@(_Directories -> '%(Identity)/**/*.ts')" />
<TypeScriptCompile Include="@(_Directories -> '%(Identity)/**/*.ts')">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'" />
<Compile Include="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</Compile>
<_Files Remove="@(_Files)" />
<_Directories Remove="@(_Directories)" />
</ItemGroup>
</Target>