Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
70f7f30
[illink] Move FixLegacyResourceDesignerStep to post-trim pipeline
sbomer Mar 30, 2026
77de75d
[targets] Fix NativeAOT designer assembly trimming
sbomer Apr 1, 2026
e9edefd
Merge remote-tracking branch 'origin/main' into fix-resource-designer
sbomer Apr 1, 2026
f0059fd
Fix merge conflict: remove FixAbstractMethodsStep from ILLink csproj
sbomer Apr 2, 2026
64d4ed1
Root designer assembly to prevent ILLink from trimming resource prope…
sbomer Apr 2, 2026
924649e
Use TrimmerRootDescriptor instead of TrimmerRootAssembly for designer…
sbomer Apr 3, 2026
8be3817
Update SkiaSharp test to expect XA8000 instead of IL8000 for release …
sbomer Apr 3, 2026
f12d747
Merge remote-tracking branch 'origin/main' into fix-resource-designer
sbomer Apr 3, 2026
83a8f64
Fix incremental build: use WriteOnlyWhenDifferent for linker descriptor
sbomer Apr 6, 2026
6e870ef
Add TODO to simplify TrimmerRootDescriptor once dotnet/runtime#126518…
sbomer Apr 6, 2026
4c4c091
Move FixLegacyResourceDesignerStep to run before ILLink trimming
sbomer Apr 7, 2026
5e3ba1f
Fix SkiaSharp NativeAOT test to expect XA8000 build failure
sbomer Apr 7, 2026
432b1b2
Filter framework/main assemblies in PreTrimmingFixLegacyDesigner for …
sbomer Apr 7, 2026
548ba35
Merge branch 'main' into dev/sbomer/fix-legacy-before-trim
sbomer Apr 10, 2026
7b72611
Filter pre/post trimming assemblies by PostprocessAssembly metadata
sbomer Apr 10, 2026
0dbbbee
Open assemblies read-only in PreTrimmingFixLegacyDesigner
sbomer Apr 10, 2026
6420522
Use InMemory=true to avoid file locking in PreTrimmingFixLegacyDesigner
sbomer Apr 13, 2026
3629ccf
Write modified assemblies to intermediate directory instead of in-place
sbomer Apr 13, 2026
db70715
Add FileWrites for incremental clean support
sbomer Apr 13, 2026
77e20ff
Add Inputs/Outputs for incremental build support
sbomer Apr 13, 2026
c1f3ceb
Scope Inputs to PostprocessAssembly assemblies only
sbomer Apr 13, 2026
bcac9af
Merge remote-tracking branch 'origin/main' into fix-legacy-before-trim
sbomer Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,10 @@ Copyright (C) 2016 Xamarin. All rights reserved.
is processed by the ILLink step. If we do not do this then the reference to
`netstandard.dll` is not replaced with `System.Private.CoreLib` and the app crashes.

We use a TrimmerRootDescriptor (not TrimmerRootAssembly) to prevent ILLink from
trimming the designer's resource properties. FixLegacyResourceDesignerStep runs
after ILLink and needs all properties present when rewriting library assemblies.
A descriptor (-x) preserves types without making the assembly an entry point,
which avoids pulling netstandard.dll into the output.
See https://github.com/dotnet/runtime/issues/126518

TODO: Once dotnet/runtime#126518 is fixed and flows to dotnet/android,
simplify this to use TrimmerRootAssembly instead of the XML descriptor.
No TrimmerRootDescriptor is needed: PreTrimmingFixLegacyDesigner rewrites library
assemblies *before* ILLink, replacing designer field loads with calls to the designer
assembly's property getters. ILLink then naturally preserves only the referenced
properties through its mark step, allowing everything else to be trimmed.
-->
<Target Name="_AddResourceDesignerToPublishFiles"
Condition=" '$(AndroidUseDesignerAssembly)' == 'True' "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<UsingTask TaskName="Xamarin.Android.Tasks.RemoveRegisterAttribute" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateProguardConfiguration" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.PostTrimmingPipeline" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.PreTrimmingFixLegacyDesigner" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

<PropertyGroup>
<_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag</_RemoveRegisterFlag>
Expand Down Expand Up @@ -223,6 +224,60 @@
</ItemGroup>
</Target>

<!--
Fix legacy resource designer references *before* ILLink runs. This rewrites
library assemblies so their resource field accesses (ldsfld) become calls to the
designer assembly's property getters, and clears the per-library designer classes.
Because the rewritten IL no longer references the old designer fields, ILLink can
freely trim unused designer types without needing a root descriptor.

Modified assemblies are written to an intermediate directory (not in-place) to
avoid mutating files in the shared NuGet cache or shared intermediate output paths.
-->
<Target Name="_CollectPreTrimmingAssemblies"
BeforeTargets="_PreTrimmingFixLegacyDesigner"
Condition=" '$(PublishTrimmed)' == 'true' and '$(AndroidUseDesignerAssembly)' == 'True' ">
<ItemGroup>
<_PreTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' and '%(ResolvedFileToPublish.PostprocessAssembly)' == 'true' " />
</ItemGroup>
</Target>

<Target Name="_PreTrimmingFixLegacyDesigner"
BeforeTargets="ILLink"
DependsOnTargets="_CollectPreTrimmingAssemblies"
Condition=" '$(PublishTrimmed)' == 'true' and '$(AndroidUseDesignerAssembly)' == 'True' "
Inputs="@(_PreTrimmingAssembly)"
Outputs="$(_AndroidStampDirectory)_PreTrimmingFixLegacyDesigner.stamp">
<PreTrimmingFixLegacyDesigner
Assemblies="@(_PreTrimmingAssembly)"
TargetName="$(TargetName)"
OutputDirectory="$(IntermediateOutputPath)prelink"
Deterministic="$(Deterministic)">
<Output TaskParameter="ModifiedAssemblies" ItemName="_PreTrimmingModifiedAssembly" />
</PreTrimmingFixLegacyDesigner>
<Touch Files="$(_AndroidStampDirectory)_PreTrimmingFixLegacyDesigner.stamp" AlwaysCreate="true" />
<ItemGroup>
<FileWrites Include="@(_PreTrimmingModifiedAssembly)" />
</ItemGroup>
</Target>

<!--
Replace ResolvedFileToPublish entries with the prelink copies so ILLink picks them up.
This must run even when _PreTrimmingFixLegacyDesigner is skipped (up-to-date) because
ResolvedFileToPublish is populated fresh each build.
-->
<Target Name="_PreTrimmingFixLegacyDesignerUpdateItems"
BeforeTargets="ILLink"
DependsOnTargets="_PreTrimmingFixLegacyDesigner"
Condition=" '$(PublishTrimmed)' == 'true' and '$(AndroidUseDesignerAssembly)' == 'True' ">
<ItemGroup>
<_PreTrimmingSwappableItem Include="@(ResolvedFileToPublish)"
Condition=" '%(Extension)' == '.dll' and Exists('$(IntermediateOutputPath)prelink/%(Filename)%(Extension)') " />
<ResolvedFileToPublish Remove="@(_PreTrimmingSwappableItem)" />
<ResolvedFileToPublish Include="@(_PreTrimmingSwappableItem->'$(IntermediateOutputPath)prelink/%(Filename)%(Extension)')" />
</ItemGroup>
</Target>

<Target Name="_TouchAndroidLinkFlag"
AfterTargets="ILLink"
Condition=" '$(PublishTrimmed)' == 'true' and '$(RunILLink)' != 'false' and Exists('$(_LinkSemaphore)') "
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#nullable enable

using System.Collections.Generic;
using System.IO;
using Java.Interop.Tools.Cecil;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;
using MonoDroid.Tuner;

namespace Xamarin.Android.Tasks;

/// <summary>
/// Runs <see cref="FixLegacyResourceDesignerStep"/> on assemblies that are about to be
/// trimmed by ILLink. This rewrites library assemblies so their resource field accesses
/// (ldsfld) become calls to the designer assembly's property getters.
///
/// Running this *before* ILLink means the trimmer sees the rewritten IL and can freely
/// trim unused designer types/fields. This avoids the need to root the entire designer
/// assembly during trimming (which causes an APK size regression).
///
/// Modified assemblies are written to <see cref="OutputDirectory"/> rather than in-place,
/// to avoid mutating files in the shared NuGet cache or shared intermediate output paths.
/// </summary>
public class PreTrimmingFixLegacyDesigner : AndroidTask
{
public override string TaskPrefix => "PTD";

[Required]
public ITaskItem [] Assemblies { get; set; } = [];

[Required]
public string TargetName { get; set; } = "";

[Required]
public string OutputDirectory { get; set; } = "";

public bool Deterministic { get; set; }

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

public override bool RunTask ()
{
Directory.CreateDirectory (OutputDirectory);

using var resolver = new DirectoryAssemblyResolver (
this.CreateTaskLogger (), loadDebugSymbols: true);

foreach (var assembly in Assemblies) {
var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? "");
if (!resolver.SearchDirectories.Contains (dir)) {
resolver.SearchDirectories.Add (dir);
}
}

var linkContext = new MSBuildLinkContext (resolver, Log);
var fixLegacyStep = new FixLegacyResourceDesignerStep ();
fixLegacyStep.Initialize (linkContext);

var modified = new List<ITaskItem> ();

foreach (var item in Assemblies) {
// Match the filtering in FixLegacyResourceDesignerStep.ProcessAssembly:
// skip the main assembly and framework/BCL assemblies.
if (Path.GetFileNameWithoutExtension (item.ItemSpec) == TargetName) {
continue;
}
if (MonoAndroidHelper.IsFrameworkAssembly (item)) {
continue;
}

var assembly = resolver.GetAssembly (item.ItemSpec);
if (fixLegacyStep.ProcessAssemblyDesigner (assembly)) {
var outputPath = Path.Combine (OutputDirectory, Path.GetFileName (item.ItemSpec));
Log.LogDebugMessage ($" Writing modified assembly: {outputPath}");
assembly.Write (outputPath, new WriterParameters {
WriteSymbols = assembly.MainModule.HasSymbols,
DeterministicMvid = Deterministic,
});

var outputItem = new TaskItem (outputPath);
item.CopyMetadataTo (outputItem);
outputItem.SetMetadata ("OriginalPath", item.ItemSpec);
modified.Add (outputItem);
}
}

ModifiedAssemblies = modified.ToArray ();

return !Log.HasLoggedErrors;
}
}