Skip to content

Commit

Permalink
feat: Upri resources trimming
Browse files Browse the repository at this point in the history
  • Loading branch information
ebariche committed Apr 9, 2024
1 parent 690fa21 commit 6c0935a
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 64 deletions.
101 changes: 75 additions & 26 deletions src/SourceGenerators/Uno.UI.Tasks/Content/Uno.UI.Tasks.targets
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<Import Project="uno.ui.tasks.assets.targets"/>

<UsingTask AssemblyFile="$(UnoUIMSBuildTasksPath)\Uno.UI.Tasks.v0.dll" TaskName="Uno.UI.Tasks.ResourcesGenerator.ResourcesGenerationTask_v0" />
<UsingTask AssemblyFile="$(UnoUIMSBuildTasksPath)\Uno.UI.Tasks.v0.dll" TaskName="Uno.UI.Tasks.ResourcesGenerator.UpriFeaturesGeneratorTask_v0" />
<UsingTask AssemblyFile="$(UnoUIMSBuildTasksPath)\Uno.UI.Tasks.v0.dll" TaskName="Uno.UI.Tasks.ResourcesGenerator.UpriSubstitutionsGeneratorTask_v0" />
<UsingTask AssemblyFile="$(UnoUIMSBuildTasksPath)\Uno.UI.Tasks.v0.dll" TaskName="Uno.UI.Tasks.Assets.RetargetAssets_v0" />
<UsingTask AssemblyFile="$(UnoUIMSBuildTasksPath)\Uno.UI.Tasks.v0.dll" TaskName="Uno.UI.Tasks.LinkerHintsGenerator.LinkerHintGeneratorTask_v0" TaskFactory="TaskHostFactory" />
<UsingTask AssemblyFile="$(UnoUIMSBuildTasksPath)\Uno.UI.Tasks.v0.dll" TaskName="Uno.UI.Tasks.LinkerHintsGenerator.LinkerDefinitionMergerTask_v0" TaskFactory="TaskHostFactory" />
Expand Down Expand Up @@ -45,7 +47,7 @@
<Target Name="UnoResourcesGeneration"
BeforeTargets="_GetLibraryImports;PrepareForBuild;_CheckForContent;_CollectBundleResources;_ComputeAndroidResourcePaths"
DependsOnTargets="ResolveProjectReferences;_UnoLangSetup"
Condition="'$(DesignTimeBuild)' != 'true' and '$(BuildingInsideUnoSourceGenerator)' == ''">
Condition="'$(DesignTimeBuild)' != 'true'">

<CheckForDevenv Condition="'$(SolutionFileName)'=='Uno.UI.sln' and '$(MSBuildRuntimeType)'!='Core'" />

Expand Down Expand Up @@ -178,66 +180,113 @@
</Target>

<!--
XAML Resources Trimming
Upri Resources Trimming
-->

<Target Name="_UnoLinkerUpriSubstitutions"
Condition="'$(DesignTimeBuild)' != 'true'"
AfterTargets="UnoResourcesGeneration">

<ItemGroup>
<_UnoUpriResource Condition="'%(GeneratedFiles.UnoResourceTarget)' == 'Uno'" Include="@(GeneratedFiles->'%(LogicalName)')">
<Language>%(GeneratedFiles.Language)</Language>
</_UnoUpriResource>
</ItemGroup>

<UpriSubstitutionsGeneratorTask_v0
AssemblyName="$(AssemblyName)"
Resources="@(_UnoUpriResource)"
OutputFile="$(IntermediateOutputPath)\Substitutions\UpriResources.Substitutions.xml" />

<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)\Substitutions\UpriResources.Substitutions.xml" />
</ItemGroup>

</Target>

<Target Name="_UnoLinkerUpriFeatures"
Condition="'$(IsUnoHead)'=='true'"
BeforeTargets="BuildDist">

<UpriFeaturesGeneratorTask_v0
Languages="@(UnoSupportedLanguage)">
<Output TaskParameter="OutputFeatures" ItemName="_UpriOutputFeatures" />
</UpriFeaturesGeneratorTask_v0>

<ItemGroup>
<RuntimeHostConfigurationOption Include="@(_UpriOutputFeatures)" Trim="true" />
</ItemGroup>

</Target>

<!--
Linker Substitutions Merge
-->

<Target Name="_UnoLinkerHintsSubstitutionsMerge"
Condition="
'$(DesignTimeBuild)' != 'true'
and '$(BuildingInsideUnoSourceGenerator)' == ''
and '$(SkipCompilerExecution)' == ''
and '$(UnoXamlResourcesTrimming)'=='true'
and $(_IsXamlTrimmingAvailable)"
<Target Name="_UnoLinkerCollectSubstitutions"
Condition="'$(_UnoRequiresEmbeddedResourcesInjection)' == 'true'"
AfterTargets="CoreCompile">

<ItemGroup>
<_SubstitutionFiles Include="@(UnoLinkerSubstitution)" />
<_SubstitutionFiles Include="$(IntermediateOutputPath)\Substitutions\*.Substitutions.xml" />
</ItemGroup>

</Target>

<Target Name="_UnoLinkerSubstitutionsMerge"
Condition="'$(_UnoRequiresEmbeddedResourcesInjection)' == 'true' and @(_SubstitutionFiles->Count()) > 0"
DependsOnTargets="_UnoLinkerCollectSubstitutions"
BeforeTargets="_UnoEmbeddedResourcesInjection">

<LinkerDefinitionMergerTask_v0
DefinitionFiles="@(_SubstitutionFiles)"
TargetDefinitionFile="$(IntermediateOutputPath)ILLink.Substitutions.xml"
/>
TargetDefinitionFile="$(IntermediateOutputPath)ILLink.Substitutions.xml" />

<ItemGroup>
<EmbeddedResource Include="$(IntermediateOutputPath)ILLink.Substitutions.xml" LogicalName="ILLink.Substitutions.xml" />

<FileWrites Include="$(IntermediateOutputPath)ILLink.Substitutions.xml" />
</ItemGroup>

</Target>

<PropertyGroup>
<TargetsTriggeredByCompilation>$(TargetsTriggeredByCompilation);_UnoSetupEmbeddedResourcesInjection</TargetsTriggeredByCompilation>
</PropertyGroup>

<Target Name="_UnoSetupEmbeddedResourcesInjection">

<PropertyGroup Condition="'$(DesignTimeBuild)' != 'true' and '$(SkipCompilerExecution)' == ''">
<_UnoRequiresEmbeddedResourcesInjection>true</_UnoRequiresEmbeddedResourcesInjection>
</PropertyGroup>

</Target>

<ItemGroup>
<_UnoEmbeddedResourcesInjectionAfterTargets Include="CoreCompile" />
<_UnoEmbeddedResourcesInjectionAfterTargets Include="_UnoLinkerHintsSubstitutionsMerge" />
</ItemGroup>

<Target Name="_UnoEmbeddedResourcesInjection"
Inputs="@(ReferencePath);@(EmbeddedResource)"
Outputs="$(IntermediateOutputPath)$(TargetFileName)"
Condition="
'$(DesignTimeBuild)' != 'true'
and '$(BuildingInsideUnoSourceGenerator)' == ''
and '$(SkipCompilerExecution)' == ''
and ('$(UnoXamlResourcesTrimming)'=='true' or '$(UnoRewriteEmbeddedResources)'=='true')
and $(_IsXamlTrimmingAvailable)"
Condition="'$(_UnoRequiresEmbeddedResourcesInjection)' == 'true'"
AfterTargets="@(_UnoEmbeddedResourcesInjectionAfterTargets)">

<ItemGroup>
<!-- Filter ReferenceCopyLocalPaths as it may contain pdbs as well -->
<_UnoEmbeddedResourcesInjectionAssembliesForReferencePaths
Include="@(ReferencePath)"
Condition="'%(Extension)' == '.dll'" />
<_UnoEmbeddedResourcesInjectionAssembliesForReferencePaths Include="@(ReferencePath)" Condition="'%(Extension)' == '.dll'" />
</ItemGroup>

<EmbeddedResourceInjectorTask_v0
EmbeddedResources="@(EmbeddedResource)"
TargetAssembly="$(IntermediateOutputPath)$(TargetFileName)"
ReferencePath="@(_UnoEmbeddedResourcesInjectionAssembliesForReferencePaths)"
/>
ReferencePath="@(_UnoEmbeddedResourcesInjectionAssembliesForReferencePaths)" />

</Target>

<!--
XAML Resources Trimming
-->

<Target Name="_UnoLinkerHintsPass1"
Condition="
'$(IsUnoHead)'=='true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,39 +64,10 @@ public override bool Execute()
linkerNode.InnerXml += defDoc.DocumentElement.InnerXml;
}

string? existingContent;
try
{
existingContent = File.ReadAllText(TargetDefinitionFile);
}
catch (FileNotFoundException)
{
// We use catch instead of File.Exists in case it could get deleted in between.
existingContent = null;
}

using (var writer = new Utf8StringWriter())
{
doc.Save(writer);
var output = writer.ToString();
if (existingContent != output)
{
// Make sure to only write the file if there is a change.
// This has a large effect on the whole build as writing the file here unnecessarily would cause
// _UnoEmbeddedResourcesInjection to run unnecessarily becase:
// Input file "obj\Uno.UI.Skia\Debug\net7.0\ILLink.Substitutions.xml" is newer than output file "obj\Uno.UI.Skia\Debug\net7.0\Uno.UI.dll".
// which will in turn cause many other things to be considered NOT up-to-date even when they should be up-to-date.
File.WriteAllText(TargetDefinitionFile, output, writer.Encoding);
}
}
doc.Save(TargetDefinitionFile);
}

return true;
}

private class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ string buildBasePath()
new Dictionary<string, string>()
{
{ "UnoResourceTarget", "Uno" },
{ "LogicalName", logicalTargetPath.Replace(Path.DirectorySeparatorChar, '.') }
{ "LogicalName", logicalTargetPath.Replace(Path.DirectorySeparatorChar, '.') },
{ "Language", language }
}
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Uno.UI.Tasks.ResourcesGenerator
{
public class UpriFeaturesGeneratorTask_v0 : Task
{
public ITaskItem[]? Languages { get; set; }

[Output]
public ITaskItem[]? OutputFeatures { get; set; }

public override bool Execute()
{
// Debugger.Launch();

if (Languages != null && Languages.Length > 0)
{
// Get all cultures except Invariant and legacy Chinese (superseded by zh-Hans and zh-Hant)
var allCultures =
CultureInfo
.GetCultures(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures)
.Where(c => c.IetfLanguageTag != string.Empty && c.Name != "zh-CHS" && c.Name != "zh-CHT")
.ToDictionary(c => c.IetfLanguageTag, c => "false");

foreach (var language in Languages)
{
var culture = new CultureInfo(language.ItemSpec);

while (culture.IetfLanguageTag != string.Empty)
{
allCultures[culture.IetfLanguageTag] = "true";

culture = culture.Parent;
}
}

OutputFeatures =
allCultures
.Select(kvp => new TaskItem($"UPRI_{kvp.Key.Replace('-', '_').ToLowerInvariant()}", new Dictionary<string, string>() { ["Value"] = kvp.Value }))
.ToArray();
}
else
{
OutputFeatures = Array.Empty<ITaskItem>();
}

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#nullable enable

using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Uno.UI.Tasks.ResourcesGenerator
{
/// <summary>
/// Generates a linker substitution file for UPRI resources.
/// This allows the linker to trim unused localization resources.
/// </summary>
public class UpriSubstitutionsGeneratorTask_v0 : Task
{
[Required]
public string? AssemblyName { get; set; }

[Required]
public ITaskItem[]? Resources { get; set; }

[Required]
public string? OutputFile { get; set; }

public override bool Execute()
{
// Debugger.Launch();

if (!string.IsNullOrEmpty(AssemblyName) && Resources != null && !string.IsNullOrEmpty(OutputFile))
{
var xml = new XmlDocument();

var linkerNode = xml.CreateElement(string.Empty, "linker", string.Empty);

xml.AppendChild(linkerNode);

foreach (var resourceGroup in Resources.GroupBy(r => r.GetMetadata("Language").Replace('-', '_').ToLowerInvariant()))
{
var assemblyNode = xml.CreateElement(string.Empty, "assembly", string.Empty);
assemblyNode.SetAttribute("fullname", AssemblyName);
assemblyNode.SetAttribute("feature", $"UPRI_{resourceGroup.Key}");
assemblyNode.SetAttribute("featurevalue", "false");

linkerNode.AppendChild(assemblyNode);

foreach (var resource in resourceGroup)
{
var resourceNode = xml.CreateElement(string.Empty, "resource", string.Empty);
resourceNode.SetAttribute("name", resource.ItemSpec);
resourceNode.SetAttribute("action", "remove");

assemblyNode.AppendChild(resourceNode);
}
}

Directory.CreateDirectory(Path.GetDirectoryName(OutputFile));

using var sw = new StreamWriter(OutputFile, append: false, Encoding.UTF8);

xml.Save(sw);
}

return true;
}
}
}
7 changes: 0 additions & 7 deletions src/Uno.UI.Wasm.Tests/Uno.UI.Wasm.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
<TSBindingsPath>$(MSBuildThisFileDirectory)tsbindings</TSBindingsPath>
<DefineConstants>$(DefineConstants);__WASM__</DefineConstants>

<!-- Required for TSBinding tests -->
<UnoRewriteEmbeddedResources>true</UnoRewriteEmbeddedResources>

<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

<EnableDefaultTypeScriptItems>false</EnableDefaultTypeScriptItems>
Expand Down Expand Up @@ -83,10 +80,6 @@
</ItemGroup>
</Target>

<ItemGroup>
<_UnoEmbeddedResourcesInjectionAfterTargets Include="CompileTypeScriptWithTSConfig" />
</ItemGroup>

<Import Project="..\SourceGenerators\Uno.UI.Tasks\Content\Uno.UI.Tasks.targets" Condition="'$(SkipUnoResourceGeneration)' == '' " />
<Import Project="..\SourceGenerators\Uno.UI.SourceGenerators\Content\Uno.UI.SourceGenerators.props" />
</Project>

0 comments on commit 6c0935a

Please sign in to comment.