Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 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
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 @@ -16,8 +16,6 @@
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\External\Linker\BaseMarkHandler.cs" Link="External\BaseMarkHandler.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\AndroidLinkConfiguration.cs" Link="MonoDroid.Tuner\AndroidLinkConfiguration.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\Extensions.cs" Link="MonoDroid.Tuner\Extensions.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\FixLegacyResourceDesignerStep.cs" Link="MonoDroid.Tuner\FixLegacyResourceDesignerStep.cs" />
<Compile Include="..\Xamarin.Android.Build.Tasks\Linker\MonoDroid.Tuner\LinkDesignerBase.cs" Link="MonoDroid.Tuner\LinkDesignerBase.cs" />

<!--Other .NET for Android / Java.Interop files-->
<Compile Include="..\..\external\Java.Interop\src\Java.Interop.Tools.Cecil\Java.Interop.Tools.Cecil\CustomAttributeProviderRocks.cs" Link="Java.Interop\CustomAttributeProviderRocks.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ public virtual void LogMessage (string message)

public virtual void LogError (int code, string message)
{
#if ILLINK
Context.LogMessage (MessageContainer.CreateCustomErrorMessage (message, code, origin: new MessageOrigin ()));
#else // !ILLINK
Context.LogError ($"XA{code}", message);
#endif // !ILLINK
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,11 @@
using Mono.Linker.Steps;

using Mono.Tuner;
#if ILLINK
using Resources = Microsoft.Android.Sdk.ILLink.Properties.Resources;
#else // !ILLINK
using Resources = Xamarin.Android.Tasks.Properties.Resources;
#endif // ILLINK

namespace MonoDroid.Tuner
{
public class FixLegacyResourceDesignerStep : LinkDesignerBase
#if !ILLINK
, Xamarin.Android.Tasks.IAssemblyModifierPipelineStep
#endif // !ILLINK
public class FixLegacyResourceDesignerStep : LinkDesignerBase, Xamarin.Android.Tasks.IAssemblyModifierPipelineStep
{
internal const string DesignerAssemblyName = "_Microsoft.Android.Resource.Designer";
internal const string DesignerAssemblyNamespace = "_Microsoft.Android.Resource.Designer";
Expand All @@ -36,14 +29,6 @@ public class FixLegacyResourceDesignerStep : LinkDesignerBase
Dictionary<string, MethodDefinition> lookup;
Dictionary<string, MethodDefinition> lookupCaseInsensitive;

protected override void EndProcess ()
{
if (designerAssembly != null) {
LogMessage ($" Setting Action on {designerAssembly.Name} to Link.");
Annotations.SetAction (designerAssembly, AssemblyAction.Link);
}
}

protected override void LoadDesigner ()
{
if (designerLoaded)
Expand Down Expand Up @@ -72,7 +57,6 @@ protected override void LoadDesigner ()
}
}

#if !ILLINK
public void ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.StepContext context)
{
// Only run this step on non-main user Android assemblies
Expand All @@ -81,7 +65,6 @@ public void ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.

context.IsAssemblyModified |= ProcessAssemblyDesigner (assembly);
}
#endif // !ILLINK

internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,27 @@
using Mono.Linker;
using Mono.Linker.Steps;
using System;
using System.Linq;
using Xamarin.Android.Tasks;
using System.Collections.Generic;
using System.Globalization;
using Mono.Cecil.Cil;
using System.Text.RegularExpressions;
using Mono.Collections.Generic;
#if ILLINK
using Microsoft.Android.Sdk.ILLink;
#endif


namespace MonoDroid.Tuner {
public abstract class LinkDesignerBase : BaseStep
{
protected IMetadataResolver Cache => Context;

public
#if !ILLINK
override
#endif
void LogMessage (string message)
public override void LogMessage (string message)
{
Context.LogMessage (message);
}

public
#if !ILLINK
override
#endif
void LogError (int code, string error)
public override void LogError (int code, string error)
{
#if ILLINK
Context.LogMessage (MessageContainer.CreateCustomErrorMessage (error, code, origin: new MessageOrigin ()));
#else // !ILLINK
Context.LogError ($"XA{code}", error);
#endif // !ILLINK
}

public virtual AssemblyDefinition Resolve (AssemblyNameReference name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ Copyright (C) 2016 Xamarin. All rights reserved.
In additon we MUST set the `PostprocessAssembly` metadata to `true` so that the file
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.

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 @@ -167,7 +168,7 @@
<Target Name="_PrepareLinking"
Condition=" '$(PublishTrimmed)' == 'true' "
AfterTargets="ComputeResolvedFilesToPublishList"
DependsOnTargets="GetReferenceAssemblyPaths;_CreatePropertiesCache">
DependsOnTargets="GetReferenceAssemblyPaths;_CreatePropertiesCache;_AddResourceDesignerToPublishFiles">
<PropertyGroup>
<TrimmerRemoveSymbols Condition=" '$(AndroidIncludeDebugSymbols)' != 'true' ">true</TrimmerRemoveSymbols>
<_ExtraTrimmerArgs Condition=" '$(_EnableSerializationDiscovery)' != 'false' ">--enable-serialization-discovery $(_ExtraTrimmerArgs)</_ExtraTrimmerArgs>
Expand Down Expand Up @@ -204,12 +205,6 @@
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" />
<!-- Custom steps that run after MarkStep -->
<!-- Custom steps that run after CleanStep -->
<_TrimmerCustomSteps
Condition=" '$(AndroidUseDesignerAssembly)' == 'true' "
Include="$(_AndroidLinkerCustomStepAssembly)"
BeforeStep="MarkStep"
Type="MonoDroid.Tuner.FixLegacyResourceDesignerStep"
/>
<_TrimmerCustomSteps
Condition=" '$(_AndroidTypeMapImplementation)' == 'managed' "
Include="$(_AndroidLinkerCustomStepAssembly)"
Expand All @@ -229,6 +224,25 @@
</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.
-->
<Target Name="_PreTrimmingFixLegacyDesigner"
BeforeTargets="ILLink"
Condition=" '$(PublishTrimmed)' == 'true' and '$(AndroidUseDesignerAssembly)' == 'True' ">
<ItemGroup>
<_PreTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' and '%(ResolvedFileToPublish.PostprocessAssembly)' == 'true' " />
</ItemGroup>
<PreTrimmingFixLegacyDesigner
Assemblies="@(_PreTrimmingAssembly)"
TargetName="$(TargetName)"
Deterministic="$(Deterministic)" />
</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,71 @@
#nullable enable

using System.IO;
using Java.Interop.Tools.Cecil;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
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).
/// </summary>
public class PreTrimmingFixLegacyDesigner : AndroidTask
{
public override string TaskPrefix => "PTD";

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

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

public bool Deterministic { get; set; }

public override bool RunTask ()
{
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);

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)) {
Log.LogDebugMessage ($" Writing modified assembly: {item.ItemSpec}");
assembly.Write (item.ItemSpec, new WriterParameters {
WriteSymbols = assembly.MainModule.HasSymbols,
DeterministicMvid = Deterministic,
});
}
}

return !Log.HasLoggedErrors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
<Compile Include="..\..\bin\Build$(Configuration)\XABuildConfig.cs" />

<!-- Include only the linker sources required for the build tasks (LinkAssembliesNoShrink and PostTrimmingPipeline) -->
<!-- These include: AddKeepAlivesStep, AddKeepAlivesHelper, CheckForObsoletePreserveAttributeStep, StripEmbeddedLibrariesStep, PostTrimmingAddKeepAlivesStep, FixAbstractMethodsStep, FixLegacyResourceDesignerStep -->
<!-- These include: AddKeepAlivesStep, AddKeepAlivesHelper, CheckForObsoletePreserveAttributeStep, StripEmbeddedLibrariesStep, PostTrimmingAddKeepAlivesStep, FixAbstractMethodsStep, FixLegacyResourceDesignerStep, LinkDesignerBase -->
<Compile Remove="Linker\**" />
<Compile Include="Linker\MonoDroid.Tuner\FindTypeMapObjectsStep.cs" />
<Compile Include="Linker\MonoDroid.Tuner\FindJavaObjectsStep.cs" />
Expand Down
5 changes: 2 additions & 3 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1420,10 +1420,9 @@ private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
using (var b = CreateApkBuilder (Path.Combine ("temp", TestName, app.ProjectName))) {
b.BuildLogFile = "build1.log";
b.ThrowOnBuildFailure = false;
// TODO: fix for NativeAOT
if (!addResource && runtime != AndroidRuntime.NativeAOT) {
if (!addResource) {
Assert.IsFalse (b.Build (app, doNotCleanupOnUpdate: true), $"Build of {app.ProjectName} should have failed.");
Assert.IsTrue (b.LastBuildOutput.ContainsText (isRelease ? "IL8000" : "XA8000"));
Assert.IsTrue (b.LastBuildOutput.ContainsText ("XA8000"));
Assert.IsTrue (b.LastBuildOutput.ContainsText ("@styleable/SKCanvasView"), "Expected '@styleable/SKCanvasView' in build output.");
Assert.IsTrue (b.LastBuildOutput.ContainsText ("@styleable/SKCanvasView_ignorePixelScaling"), "Expected '@styleable/SKCanvasView_ignorePixelScaling' in build output.");
return;
Expand Down