From 338be4aec592e3aa4d15ac485dd499c90ff3d7d8 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 28 Jul 2026 19:38:34 +0200 Subject: [PATCH 1/3] [dotnet] Fix latent trimmable-static registrar bugs on non-NativeAOT runtimes. Two fixes for building with an explicit 'Registrar=trimmable-static' on Mono/CoreCLR (these paths aren't hit by the NativeAOT default, but the registrar is a supported option on the other runtimes): - Only tell the trimmer to remove [ProtocolMember] attributes when the dynamic registrar has been removed (DynamicRegistrationSupported == false). The dynamic registrar reads these attributes via reflection at runtime, so trimming them while it's present errors out with IL2045. The option is moved into _SetDynamicRegistrationSupportedFeature so the computed _DynamicRegistrationSupported value is available (it isn't yet in _ComputeLinkerInputs). NativeAOT is unaffected (DynamicRegistrationSupported is always false there, so the attributes are still trimmed). - Fall back to the file name in _StripAssemblyIL when a ResolvedFileToPublish item has no OriginalRelativePath (e.g. the generated TypeMap assemblies). Otherwise the strip OutputPath collapsed to the bare stripped directory and ILStrip failed with 'Access to the path .../stripped/ is denied'. Also updates the SelectRegistrar comment to note NativeAOT defaults to trimmable-static on .NET 11+ (set earlier, in Xamarin.Shared.Sdk.props). --- dotnet/targets/Xamarin.Shared.Sdk.targets | 35 +++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index d437ace87292..fff7a68113b7 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -554,6 +554,9 @@ - NativeAOT supported platforms in all configurations - ios/tvos device builds in all configurations - macOS/MacCatalyst builds in release configuration + (On .NET 11+ NativeAOT defaults to 'trimmable-static' instead - that's set earlier, at + evaluation time, in Xamarin.Shared.Sdk.props. The trimmable-static registrar is only smaller + under NativeAOT, so the other runtimes keep managed-static.) - Set 'partial-static' for: - when no assemblies are trimmed and when MarshalManagedExceptionMode is default (or not set) - Otherwise set 'dynamic' @@ -601,6 +604,21 @@ + + + + - @@ -1214,6 +1223,14 @@ $(_StrippedAssemblyDirectory)\%(ResolvedFileToPublish.OriginalRelativePath) + + <_AssembliesToBeStripped Condition="'%(ResolvedFileToPublish.OriginalRelativePath)' == ''"> + $(_StrippedAssemblyDirectory)\%(Filename)%(Extension) + + From 592d722241021c1c8dc3973293467491568f0f76 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 29 Jul 2026 16:23:26 +0200 Subject: [PATCH 2/3] [dotnet] Batch the ILStrip OutputPath fallback on the right item type. The condition referenced %(ResolvedFileToPublish.OriginalRelativePath) while modifying @(_AssembliesToBeStripped), which makes MSBuild batch over ResolvedFileToPublish instead of the items being modified: a single publish item without OriginalRelativePath would have applied the fallback OutputPath to every assembly, throwing away the valid relative paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6aeeddfd-3bd8-4129-ad90-7292860c4b66 --- dotnet/targets/Xamarin.Shared.Sdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index fff7a68113b7..956ace954fcc 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -1227,7 +1227,7 @@ TypeMap assemblies the trimmable-static registrar generates). Without it the OutputPath above collapses to the bare stripped directory, and ILStrip fails trying to write to a directory ("Access to the path '.../stripped/' is denied"). Fall back to the file name in that case. --> - <_AssembliesToBeStripped Condition="'%(ResolvedFileToPublish.OriginalRelativePath)' == ''"> + <_AssembliesToBeStripped Condition="'%(_AssembliesToBeStripped.OriginalRelativePath)' == ''"> $(_StrippedAssemblyDirectory)\%(Filename)%(Extension) From 7e255657e9db7a135c050242389df2c0b1ec1c91 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 30 Jul 2026 09:44:35 +0200 Subject: [PATCH 3/3] [tests] Only assert NSSet is trimmed when the dynamic registrar is removed. The trimmable-static registrar only tells the trimmer to remove the [ProtocolMember] attributes (which reference generic types like NSSet in optional protocol members) when the dynamic registrar has been removed - it reads these attributes via reflection at runtime, so they're kept when it's present. When the attributes aren't trimmed, NSSet stays too, so the LinkedAwayGenericTypeAsOptionalMemberInProtocol test has nothing to verify. Ignore the test in that case (e.g. the prepare-assemblies|coreclr|trimmable- static-registrar variation on macOS, where DynamicRegistrationSupported is true), instead of failing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/linker/link all/LinkAllTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/linker/link all/LinkAllTest.cs b/tests/linker/link all/LinkAllTest.cs index 2a36636ef7d6..e70abcec6855 100644 --- a/tests/linker/link all/LinkAllTest.cs +++ b/tests/linker/link all/LinkAllTest.cs @@ -508,6 +508,12 @@ public void LinkedAwayGenericTypeAsOptionalMemberInProtocol () if (!global::XamarinTests.ObjCRuntime.Registrar.IsTrimmableStaticRegistrar) Assert.Ignore ("This test only applies to the trimmable static registrar."); + // The [ProtocolMember] attributes (which reference NSSet) are only trimmed away when the dynamic + // registrar has been removed - it reads these attributes via reflection at runtime, so they're kept + // when it's present. Without trimming the attributes, NSSet stays too, so there's nothing to verify. + if (global::ObjCRuntime.Runtime.DynamicRegistrationSupported) + Assert.Ignore ("This test only applies when the dynamic registrar has been removed."); + // https://github.com/dotnet/macios/issues/3523 // Don't use constants here, because the linker can see what we're trying to do and keeps the type we're verifying has been removed. string prefix = NamespacePrefix;