Skip to content

Commit

Permalink
[release/5.0] Support Source Generators in WPF projects (#3846)
Browse files Browse the repository at this point in the history
[release/5.0] Support source generators in WPF projects (#3846) [5.0.2 servicing]
  • Loading branch information
ryalanms committed Dec 11, 2020
1 parent 7ab0e58 commit 9ffd31f
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@

<_CompileTargetNameForLocalType Condition="'$(_CompileTargetNameForLocalType)' == ''">_CompileTemporaryAssembly</_CompileTargetNameForLocalType>

<!-- The updated .NET 5.0.2 GenerateTemporaryTargetAssembly behavior that supports source generators is off by default. -->
<IncludePackageReferencesDuringMarkupCompilation Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' == ''">false</IncludePackageReferencesDuringMarkupCompilation>
<_ResolveProjectReferencesTargetName Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false'">ResolveProjectReferences</_ResolveProjectReferencesTargetName>
<_CompileTemporaryAssemblyDependsOn>BuildOnlySettings;ResolveKeySource;$(_ResolveProjectReferencesTargetName);CoreCompile</_CompileTemporaryAssemblyDependsOn>

Expand Down Expand Up @@ -418,33 +420,52 @@
<_ParentProjectExtension>$([System.IO.Path]::GetExtension($(MSBuildProjectFullPath)))</_ParentProjectExtension>
<_TemporaryTargetAssemblyProjectNameNoExtension>$([System.String]::Join("_", "$(_ParentProjectName)", "$([System.IO.Path]::GetFileNameWithoutExtension($([System.IO.Path]::GetRandomFileName())))", "wpftmp"))</_TemporaryTargetAssemblyProjectNameNoExtension>
<_TemporaryTargetAssemblyProjectName>$(_TemporaryTargetAssemblyProjectNameNoExtension)$(_ParentProjectExtension)</_TemporaryTargetAssemblyProjectName>
<_TempProjectNuGetExtensionsPath>$([System.Text.RegularExpressions.Regex]::Replace($(MSBuildProjectExtensionsPath), "$(_ParentProjectName)\\$", "$(_TemporaryTargetAssemblyProjectNameNoExtension)\", System.Text.RegularExpressions.RegexOptions.IgnoreCase))</_TempProjectNuGetExtensionsPath>
</PropertyGroup>

<Error Condition="Exists('$(_TempProjectNuGetExtensionsPath)')"
Text="$(_TempProjectNuGetExtensionsPath) should not exist. This is directory name is randomly generated for each temp project compilation." />

<!-- Collect the generated NuGet files from the parent project required to support PackageReferences. -->
<ItemGroup Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false'">
<SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_ParentProjectName)$(_ParentProjectExtension).nuget.g.props"/>
<SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_ParentProjectName)$(_ParentProjectExtension).nuget.g.targets"/>
<SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_ParentProjectName)$(_ParentProjectExtension).nuget.dgspec.json"/>
<SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)project.assets.json"/>
<SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)project.nuget.cache"/>

<DestGeneratedNuGetPropsAndTargets Include="$(_TempProjectNuGetExtensionsPath)$(_TemporaryTargetAssemblyProjectName).nuget.g.props"/>
<DestGeneratedNuGetPropsAndTargets Include="$(_TempProjectNuGetExtensionsPath)$(_TemporaryTargetAssemblyProjectName).nuget.g.targets"/>
<DestGeneratedNuGetPropsAndTargets Include="$(_TempProjectNuGetExtensionsPath)$(_TemporaryTargetAssemblyProjectName).nuget.dgspec.json"/>
<DestGeneratedNuGetPropsAndTargets Include="$(_TempProjectNuGetExtensionsPath)project.assets.json"/>
<DestGeneratedNuGetPropsAndTargets Include="$(_TempProjectNuGetExtensionsPath)project.nuget.cache"/>
<_SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_ParentProjectName)$(_ParentProjectExtension).nuget.g.props"/>
<_SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_ParentProjectName)$(_ParentProjectExtension).nuget.g.targets"/>
<_SourceGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_ParentProjectName)$(_ParentProjectExtension).nuget.dgspec.json"/>

<_DestGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_TemporaryTargetAssemblyProjectName).nuget.g.props"/>
<_DestGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_TemporaryTargetAssemblyProjectName).nuget.g.targets"/>
<_DestGeneratedNuGetPropsAndTargets Include="$(MSBuildProjectExtensionsPath)$(_TemporaryTargetAssemblyProjectName).nuget.dgspec.json"/>

</ItemGroup>

<!-- Copy the renamed outer project NuGet props/targets files to the MSBuildProjectExtensionsPath used by NuGet. -->
<Copy Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false'"
SourceFiles="@(SourceGeneratedNuGetPropsAndTargets)"
DestinationFiles="@(DestGeneratedNuGetPropsAndTargets)"
SourceFiles="@(_SourceGeneratedNuGetPropsAndTargets)"
DestinationFiles="@(_DestGeneratedNuGetPropsAndTargets)"
/>

<!--
Undo TargetFramework and RID append set in Microsoft.NET.RuntimeIdentifierInference.targets to prevent
a duplicate target framework and runtime identifier in the IntermediateOutputPath when the new BuildEngine
instance runs.
'Append $(RuntimeIdentifier) to directory to output and intermediate paths to prevent bin clashes between
targets. But do not append the implicit default runtime identifier for .NET Framework apps as that would
append a RID the user never mentioned in the path and do so even in the AnyCPU case.'
-->

<PropertyGroup>
<_IntermediateOutputPathNoTargetFrameworkOrRID>$(IntermediateOutputPath)</_IntermediateOutputPathNoTargetFrameworkOrRID>
</PropertyGroup>

<PropertyGroup Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false' and
'$(AppendRuntimeIdentifierToOutputPath)' == 'true' and '$(RuntimeIdentifier)' != '' and
'$(_UsingDefaultRuntimeIdentifier)' != 'true'">
<_IntermediateOutputPathNoTargetFrameworkOrRID>$([System.Text.RegularExpressions.Regex]::Replace($(_IntermediateOutputPathNoTargetFrameworkOrRID), "$(RuntimeIdentifier)\\$",, System.Text.RegularExpressions.RegexOptions.IgnoreCase))</_IntermediateOutputPathNoTargetFrameworkOrRID>
</PropertyGroup>

<PropertyGroup Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false' and
'$(AppendTargetFrameworkToOutputPath)' == 'true' and '$(TargetFramework)' != '' and
'$(_UnsupportedTargetFrameworkError)' != 'true'">
<_IntermediateOutputPathNoTargetFrameworkOrRID>$([System.Text.RegularExpressions.Regex]::Replace($(_IntermediateOutputPathNoTargetFrameworkOrRID), "$(TargetFramework)\\$",, System.Text.RegularExpressions.RegexOptions.IgnoreCase))</_IntermediateOutputPathNoTargetFrameworkOrRID>
</PropertyGroup>

<!-- Use the legacy .NET Framework/.NET Core 3.0 GenerateTemporaryTargetAssembly path if 'IncludePackageReferencesDuringMarkupCompilation' is 'false',. -->
<GenerateTemporaryTargetAssembly
CurrentProject="$(MSBuildProjectFullPath)"
Expand All @@ -453,7 +474,8 @@
CompileTypeName="Compile"
GeneratedCodeFiles="@(_GeneratedCodeFiles)"
ReferencePath="@(ReferencePath)"
IntermediateOutputPath="$(IntermediateOutputPath)"
BaseIntermediateOutputPath="$(BaseIntermediateOutputPath)"
IntermediateOutputPath="$(_IntermediateOutputPathNoTargetFrameworkOrRID)"
AssemblyName="$(AssemblyName)"
CompileTargetName="$(_CompileTargetNameForLocalType)"
GenerateTemporaryTargetAssemblyDebuggingInformation="$(GenerateTemporaryTargetAssemblyDebuggingInformation)"
Expand All @@ -468,18 +490,13 @@
<Output TaskParameter="Include" ItemName="AssemblyForLocalTypeReference" />
</CreateItem>

<!-- Remove generated NuGet props/targets files and temp project directory. -->
<Error
Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false'
and '!$(_TempProjectNuGetExtensionsPath.Contains(&quot;_wpftmp&quot;))'"
Text="$(_TempProjectNuGetExtensionsPath) is not valid. This directory name is randomly generated for each temp project compilation and must contain a random component and '_wpftmp'."/>

<RemoveDir
<!-- Remove generated NuGet props/targets files. -->
<Delete
Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false'
and '$(GenerateTemporaryTargetAssemblyDebuggingInformation)' != 'true'"
Directories="$(_TempProjectNuGetExtensionsPath)" />

</Target>
Files="@(_DestGeneratedNuGetPropsAndTargets)" />
</Target>

<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,32 +244,39 @@ private bool ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport()
RemoveItemsByName(xmlProjectDoc, MARKUPRESOURCENAME);
RemoveItemsByName(xmlProjectDoc, RESOURCENAME);

// Replace the Reference Item list with ReferencePath
RemoveItemsByName(xmlProjectDoc, REFERENCETYPENAME);
AddNewItems(xmlProjectDoc, ReferencePathTypeName, ReferencePath);

// Add GeneratedCodeFiles to Compile item list.
AddNewItems(xmlProjectDoc, CompileTypeName, GeneratedCodeFiles);

// Replace implicit SDK imports with explicit SDK imports
ReplaceImplicitImports(xmlProjectDoc);

// Add properties required for temporary assembly compilation
var properties = new List<(string PropertyName, string PropertyValue)>
{
( nameof(AssemblyName), AssemblyName ),
( nameof(IntermediateOutputPath), IntermediateOutputPath ),
( "AppendTargetFrameworkToOutputPath", "false"),
( nameof(BaseIntermediateOutputPath), BaseIntermediateOutputPath ),
( "_TargetAssemblyProjectName", Path.GetFileNameWithoutExtension(CurrentProject)),
( nameof(Analyzers), Analyzers)
( nameof(Analyzers), Analyzers )
};

AddNewProperties(xmlProjectDoc, properties);

// Replace the Reference Item list with ReferencePath
RemoveItemsByName(xmlProjectDoc, REFERENCETYPENAME);
AddNewItems(xmlProjectDoc, ReferencePathTypeName, ReferencePath);

// Add GeneratedCodeFiles to Compile item list.
AddNewItems(xmlProjectDoc, CompileTypeName, GeneratedCodeFiles);

// Save the xmlDocument content into the temporary project file.
xmlProjectDoc.Save(TemporaryTargetAssemblyProjectName);

// Disable conflicting Arcade SDK workaround that imports NuGet props/targets
Hashtable globalProperties = new Hashtable(1);
globalProperties["_WpfTempProjectNuGetFilePathNoExt"] = "";

//
// Compile the temporary target assembly project
//
retValue = BuildEngine.BuildProjectFile(TemporaryTargetAssemblyProjectName, new string[] { CompileTargetName }, null, null);
retValue = BuildEngine.BuildProjectFile(TemporaryTargetAssemblyProjectName, new string[] { CompileTargetName }, globalProperties, null);

// Delete the temporary project file and generated files unless diagnostic mode has been requested
if (!GenerateTemporaryTargetAssemblyDebuggingInformation)
Expand Down Expand Up @@ -457,6 +464,17 @@ public bool GenerateTemporaryTargetAssemblyDebuggingInformation
public string Analyzers
{ get; set; }

/// <summary>
/// BaseIntermediateOutputPath
///
/// Required for Source Generator support. May be null.
///
/// </summary>
public string BaseIntermediateOutputPath
{
get; set;
}

/// <summary>
/// IncludePackageReferencesDuringMarkupCompilation
///
Expand Down Expand Up @@ -665,7 +683,7 @@ private void AddNewProperties(XmlDocument xmlProjectDoc, List<(string PropertyNa

// Create a new PropertyGroup element
XmlNode nodeItemGroup = xmlProjectDoc.CreateElement("PropertyGroup", root.NamespaceURI);
root.InsertAfter(nodeItemGroup, root.FirstChild);
root.PrependChild(nodeItemGroup);

// Append this new ItemGroup item into the list of children of the document root.
foreach(var property in properties)
Expand All @@ -683,6 +701,64 @@ private void AddNewProperties(XmlDocument xmlProjectDoc, List<(string PropertyNa
}
}

//
// Replace implicit SDK imports with explicit imports
//
static private void ReplaceImplicitImports(XmlDocument xmlProjectDoc)
{
if (xmlProjectDoc == null)
{
// When the parameters are not valid, simply return it, instead of throwing exceptions.
return;
}

XmlNode root = xmlProjectDoc.DocumentElement;

for (int i = 0; i < root.Attributes.Count; i++)
{
XmlAttribute xmlAttribute = root.Attributes[i] as XmlAttribute;

if (xmlAttribute.Name.Equals("Sdk", StringComparison.OrdinalIgnoreCase))
{
// <Project Sdk="Microsoft.NET.Sdk">

// Remove Sdk attribute
var sdkValue = xmlAttribute.Value;
root.Attributes.Remove(xmlAttribute);

//
// Add explicit top import
//
// <Import Project = "Sdk.props" Sdk="Microsoft.NET.Sdk" />
//
XmlNode nodeImportProps = xmlProjectDoc.CreateElement("Import", root.NamespaceURI);
XmlAttribute projectAttribute = xmlProjectDoc.CreateAttribute("Project", root.NamespaceURI);
projectAttribute.Value = "Sdk.props";
nodeImportProps.Attributes.Append(projectAttribute);
nodeImportProps.Attributes.Append(xmlAttribute);

// Prepend this Import to the root of the XML document
root.PrependChild(nodeImportProps);

//
// Add explicit bottom import
//
// <Import Project = "Sdk.targets" Sdk="Microsoft.NET.Sdk"
//
XmlNode nodeImportTargets = xmlProjectDoc.CreateElement("Import", root.NamespaceURI);
XmlAttribute projectAttribute2 = xmlProjectDoc.CreateAttribute("Project", root.NamespaceURI);
projectAttribute2.Value = "Sdk.targets";
XmlAttribute projectAttribute3 = xmlProjectDoc.CreateAttribute("Sdk", root.NamespaceURI);
projectAttribute3.Value = sdkValue;
nodeImportTargets.Attributes.Append(projectAttribute2);
nodeImportTargets.Attributes.Append(projectAttribute3);

// Append this Import to the end of the XML document
root.AppendChild(nodeImportTargets);
}
}
}

#endregion Private Methods


Expand Down Expand Up @@ -733,3 +809,4 @@ private void AddNewProperties(XmlDocument xmlProjectDoc, List<(string PropertyNa

#endregion GenerateProjectForLocalTypeReference Task class
}

0 comments on commit 9ffd31f

Please sign in to comment.