Skip to content

Commit 22802ce

Browse files
rolfbjarneCopilot
andauthored
[tools] Strip resources from linked assemblies in Hot Reload + PrepareAssemblies builds. (#26300)
The assembly-preparer's RemoveUserResourcesSubStep unconditionally skipped resource stripping for every assembly in Hot Reload compatible builds. This was too broad: linked (trimmed) assemblies are re-serialized regardless, so stripping them doesn't break Hot Reload - only reloadable (non-linked, copied) assemblies must be left untouched. This matches the non-preparer (ILLink) code path, which only skips assemblies whose action isn't 'Link'. Without this fix, the 'prepare-assemblies|trimmable-static-registrar' variation of the 'link all' MacCatalyst test failed because embedded resources were left in the linked BundledResources assembly. Updated the unit tests to cover both the reloadable (kept) and linked (stripped) cases in a Hot Reload build. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 78f1fbf commit 22802ce

3 files changed

Lines changed: 29 additions & 12 deletions

File tree

tests/assembly-preparer/BaseClass.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public bool AssertPrepareCode (ApplePlatform platform, bool isCoreCLR, Action<As
5454

5555
// Builds the provided code into a Test.dll and returns an AssemblyPreparer configured for it, without
5656
// running any preparation steps. Use this when a test needs to run a custom set of steps.
57-
public AssemblyPreparer CreatePreparer (ApplePlatform platform, bool isCoreCLR, Action<AssemblyPreparer>? configure, string code, out AssemblyPreparerInfo testInfo, string extraCsproj = "", string extraConfig = "", IEnumerable<(string FileName, byte [] Content)>? extraFiles = null)
57+
public AssemblyPreparer CreatePreparer (ApplePlatform platform, bool isCoreCLR, Action<AssemblyPreparer>? configure, string code, out AssemblyPreparerInfo testInfo, string extraCsproj = "", string extraConfig = "", IEnumerable<(string FileName, byte [] Content)>? extraFiles = null, string testTrimMode = "link")
5858
{
5959
Configuration.IgnoreIfIgnoredPlatform (platform);
6060

@@ -101,7 +101,7 @@ public AssemblyPreparer CreatePreparer (ApplePlatform platform, bool isCoreCLR,
101101

102102
var assemblies = Configuration.GetImplementationAssemblies (platform, isCoreCLR);
103103
assemblies.Add (Path.Combine (assemblyDir, "Test.dll"));
104-
var infos = assemblies.Select (v => new AssemblyPreparerInfo (v, Path.Combine (assemblyDir, "out", Path.GetFileName (v)), true, "link")).ToArray ();
104+
var infos = assemblies.Select (v => new AssemblyPreparerInfo (v, Path.Combine (assemblyDir, "out", Path.GetFileName (v)), true, Path.GetFileNameWithoutExtension (v) == "Test" ? testTrimMode : "link")).ToArray ();
105105
var logger = new TestLogger () { Platform = platform };
106106
var preparer = new AssemblyPreparer (logger, infos, configpath);
107107
if (configure is not null)

tests/assembly-preparer/RemoveUserResourcesSubStepTests.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ static string GetContentPrefix (ApplePlatform platform)
2626

2727
// Builds a user assembly with an embedded MonoTouch/XamMac content resource, then runs the
2828
// RemoveUserResourcesSubStep with the provided value of HotReloadCompatibleBuild and returns the
29-
// resource names still present in the (in-memory) assembly after the step ran.
30-
List<string> GetResourcesAfterStep (ApplePlatform platform, bool isCoreCLR, bool hotReloadCompatibleBuild)
29+
// resource names still present in the (in-memory) assembly after the step ran. The 'trimMode'
30+
// controls whether the Test assembly is linked ("link") or copied ("copy", i.e. reloadable).
31+
List<string> GetResourcesAfterStep (ApplePlatform platform, bool isCoreCLR, bool hotReloadCompatibleBuild, string trimMode = "link")
3132
{
3233
var prefix = GetContentPrefix (platform);
3334
var resourceName = prefix + "TestResource.bin";
@@ -47,7 +48,7 @@ class MyClass : NSObject {
4748

4849
var extraConfig = $"HotReloadCompatibleBuild={(hotReloadCompatibleBuild ? "true" : "false")}";
4950

50-
using var preparer = CreatePreparer (platform, isCoreCLR, p => p.Registrar = RegistrarMode.Dynamic, code, out var testInfo, extraCsproj: extraCsproj, extraConfig: extraConfig, extraFiles: new [] { ("TestResource.bin", content) });
51+
using var preparer = CreatePreparer (platform, isCoreCLR, p => p.Registrar = RegistrarMode.Dynamic, code, out var testInfo, extraCsproj: extraCsproj, extraConfig: extraConfig, extraFiles: new [] { ("TestResource.bin", content) }, testTrimMode: trimMode);
5152

5253
var context = preparer.Configuration.DerivedLinkContext;
5354
new LoadAssembliesStep ().Process (context);
@@ -78,10 +79,25 @@ public void ResourceKeptForHotReload (ApplePlatform platform, bool isCoreCLR)
7879
{
7980
var prefix = GetContentPrefix (platform);
8081
var resourceName = prefix + "TestResource.bin";
81-
var resources = GetResourcesAfterStep (platform, isCoreCLR, hotReloadCompatibleBuild: true);
82+
// A reloadable (non-linked, i.e. copied) assembly must be left untouched: the step must not
83+
// remove the resource (which would upgrade the assembly to AssemblyAction.Save and break Hot Reload).
84+
var resources = GetResourcesAfterStep (platform, isCoreCLR, hotReloadCompatibleBuild: true, trimMode: "copy");
8285

83-
// The resource must be left untouched: the step must not remove it (and thus not upgrade the
84-
// user assembly to AssemblyAction.Save, which is what would break Hot Reload).
85-
Assert.That (resources, Has.Some.EqualTo (resourceName), "The user resource must be kept when HotReloadCompatibleBuild is enabled.");
86+
Assert.That (resources, Has.Some.EqualTo (resourceName), "The user resource must be kept for a reloadable assembly when HotReloadCompatibleBuild is enabled.");
87+
}
88+
89+
[Test]
90+
[TestCase (ApplePlatform.iOS, false)]
91+
[TestCase (ApplePlatform.TVOS, false)]
92+
[TestCase (ApplePlatform.MacCatalyst, false)]
93+
[TestCase (ApplePlatform.MacOSX, true)]
94+
public void ResourceRemovedForLinkedAssemblyInHotReload (ApplePlatform platform, bool isCoreCLR)
95+
{
96+
var prefix = GetContentPrefix (platform);
97+
// A linked assembly is re-serialized regardless (it's not reloadable), so its resources must be
98+
// stripped even in a Hot Reload compatible build.
99+
var resources = GetResourcesAfterStep (platform, isCoreCLR, hotReloadCompatibleBuild: true, trimMode: "link");
100+
101+
Assert.That (resources, Has.None.StartsWith (prefix), "The user resource should be stripped from a linked assembly even when HotReloadCompatibleBuild is enabled.");
86102
}
87103
}

tools/linker/RemoveUserResourcesSubStep.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@ bool ModifyAssembly (AssemblyDefinition assembly)
9797
return false;
9898

9999
#if ASSEMBLY_PREPARER
100-
// In the assembly-preparer any modification re-serializes (saves) the assembly, which breaks Hot
101-
// Reload. So skip resource stripping entirely for Hot Reload compatible builds.
102-
if (Configuration.HotReloadCompatibleBuild)
100+
// In Hot Reload compatible builds, don't strip resources from reloadable (non-linked) assemblies,
101+
// because modifying them re-serializes (saves) the assembly, breaking Hot Reload. Linked assemblies
102+
// are re-serialized regardless, so stripping them is fine.
103+
if (Configuration.HotReloadCompatibleBuild && Annotations.GetAction (assembly) != AssemblyAction.Link)
103104
return false;
104105
#endif
105106

0 commit comments

Comments
 (0)