From f043149b20e9894ccd92ab9d1abc3e217ddcf6c0 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 30 Mar 2023 19:59:42 +0200 Subject: [PATCH 1/3] [linker/trimmer] Add opt-in support for not marking NSObjects in user assemblies. Fixes #15723. We mark all types that derive from NSObject when we find them in user assemblies, so that these types may be used in storyboards (where the linker can't see them), without having to go through hoops to make sure the linker doesn't remove these types (which would make the storyboard fail to load, because it would reference types that were linked away). However, not everybody uses storyboards, so in some cases it may make sense to link away as much as possible, so make it opt-in to skip this custom marking. This is an experimental feature, and will break at least some apps. It may break most apps, but if someone wants to try it out, they're welcome! Fixes #15723. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 1 + .../Tasks/ParseBundlerArgumentsTaskBase.cs | 6 ++++++ msbuild/Xamarin.Shared/Xamarin.Shared.targets | 1 + tools/common/Application.cs | 2 ++ tools/common/Driver.cs | 3 +++ tools/dotnet-linker/LinkerConfiguration.cs | 5 +++++ tools/linker/MarkNSObjects.cs | 5 ++++- 7 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 599fe215a8f1..517286d86190 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -507,6 +507,7 @@ SdkDevPath=$(_SdkDevPath) SdkRootDirectory=$(_XamarinSdkRootDirectory) SdkVersion=$(_SdkVersion) + SkipMarkingNSObjectsInUserAssemblies=$(_SkipMarkingNSObjectsInUserAssemblies) TargetArchitectures=$(TargetArchitectures) TargetFramework=$(_ComputedTargetFrameworkMoniker) UseLlvm=$(MtouchUseLlvm) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs index 3eb91b871dd8..2647157a177e 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs @@ -53,6 +53,9 @@ public abstract class ParseBundlerArgumentsTaskBase : XamarinTask { [Output] public string NoStrip { get; set; } + [Output] + public string SkipMarkingNSObjectsInUserAssemblies { get; set; } + [Output] public int Verbosity { get; set; } @@ -165,6 +168,9 @@ public override bool Execute () item.SetMetadata ("Value", value.Substring (colon + 1)); envVariables.Add (item); break; + case "skip-marking-nsobjects-in-user-assemblies": + SkipMarkingNSObjectsInUserAssemblies = ParseBool (value) ? "false" : "true"; + break; case "xml": if (xml == null) xml = new List (); diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 1726a8f196a7..8a95ff787e18 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -1851,6 +1851,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. + diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 37c2354e5efd..5ba7a1ad06d0 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -165,6 +165,8 @@ public bool IsDefaultMarshalManagedExceptionMode { public bool EnableMarkerOnlyBitCode { get { return BitCodeMode == BitCodeMode.MarkerOnly; } } public bool EnableBitCode { get { return BitCodeMode != BitCodeMode.None; } } + public bool SkipMarkingNSObjectsInUserAssemblies { get; set; } + // assembly_build_targets describes what kind of native code each assembly should be compiled into for mobile targets (iOS, tvOS, watchOS). // An assembly can be compiled into: static object (.o), dynamic library (.dylib) or a framework (.framework). // In the case of a framework, each framework may contain the native code for multiple assemblies. diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index 6a14ed5afe2f..b0570a8b3d5b 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -254,6 +254,9 @@ static bool ParseOptions (Application app, Mono.Options.OptionSet options, strin options.Add ("require-pinvoke-wrappers:", v => { app.RequiresPInvokeWrappers = ParseBool (v, "--require-pinvoke-wrappers"); }); + options.Add ("skip-marking-nsobjects-in-user-assemblies:", "Don't mark NSObject (and any subclass of NSObject) in user assemblies in the linker. This may break your app, use at own risk.", v => { + app.SkipMarkingNSObjectsInUserAssemblies = ParseBool (v, "--skip-marking-nsobjects-in-user-assemblies"); + }); // Keep the ResponseFileSource option at the end. diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 6cf3b44e7f2e..de8f86d0cd47 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -257,6 +257,11 @@ public static LinkerConfiguration GetInstance (LinkContext context, bool createI throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); SdkVersion = sdk_version; break; + case "SkipMarkingNSObjectsInUserAssemblies": + if (!TryParseOptionalBoolean (value, out var skip_marking_nsobjects_in_user_assemblies)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies; + break; case "TargetArchitectures": if (!Enum.TryParse (value, out var arch)) throw new InvalidOperationException ($"Unknown target architectures: {value} in {linker_file}"); diff --git a/tools/linker/MarkNSObjects.cs b/tools/linker/MarkNSObjects.cs index 21fc01b282fa..33a3e37b90f5 100644 --- a/tools/linker/MarkNSObjects.cs +++ b/tools/linker/MarkNSObjects.cs @@ -143,8 +143,11 @@ static bool IsProductMethod (MethodDefinition method) return (method.DeclaringType.Module.Assembly.Name.Name == ProductAssembly); } - static bool IsProductType (TypeDefinition type) + bool IsProductType (TypeDefinition type) { + if (LinkContext.App.SkipMarkingNSObjectsInUserAssemblies) + return true; + var name = type.Module.Assembly.Name.Name; switch (name) { case "Xamarin.Forms.Platform.iOS": From 995ffeeb900a3ce06c0eb8bbbd639a5f33f5fae1 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 10 Apr 2023 13:10:30 +0200 Subject: [PATCH 2/3] Fix build problem. --- tools/dotnet-linker/LinkerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index de8f86d0cd47..1cbd7ffc2e05 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -260,7 +260,7 @@ public static LinkerConfiguration GetInstance (LinkContext context, bool createI case "SkipMarkingNSObjectsInUserAssemblies": if (!TryParseOptionalBoolean (value, out var skip_marking_nsobjects_in_user_assemblies)) throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies; + Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies.Value; break; case "TargetArchitectures": if (!Enum.TryParse (value, out var arch)) From 5f6f84c0ff7097d5276e84c2114c37356ce5d608 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 12 Apr 2023 15:14:50 +0200 Subject: [PATCH 3/3] Fix boolean logic. --- .../Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs index 2647157a177e..993314f44236 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ParseBundlerArgumentsTaskBase.cs @@ -169,7 +169,7 @@ public override bool Execute () envVariables.Add (item); break; case "skip-marking-nsobjects-in-user-assemblies": - SkipMarkingNSObjectsInUserAssemblies = ParseBool (value) ? "false" : "true"; + SkipMarkingNSObjectsInUserAssemblies = ParseBool (value) ? "true" : "false"; break; case "xml": if (xml == null)