diff --git a/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs b/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs index feb182dfad6..290f45a70e6 100644 --- a/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs +++ b/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs @@ -5,6 +5,7 @@ using Mono.Linker; using Mono.Linker.Steps; using Mono.Tuner; +using Java.Interop.Tools.Cecil; using Xamarin.Android.Tasks; namespace MonoDroid.Tuner { @@ -16,9 +17,46 @@ public class MarkJavaObjects : BaseMarkHandler public override void Initialize (LinkContext context, MarkContext markContext) { base.Initialize (context, markContext); + context.TryGetCustomData ("AndroidHttpClientHandlerType", out string androidHttpClientHandlerType); + context.TryGetCustomData ("AndroidCustomViewMapFile", out string androidCustomViewMapFile); + var customViewMap = MonoAndroidHelper.LoadCustomViewMapFile (androidCustomViewMapFile); + + markContext.RegisterMarkAssemblyAction (assembly => ProcessAssembly (assembly, androidHttpClientHandlerType, customViewMap)); markContext.RegisterMarkTypeAction (type => ProcessType (type)); } + bool IsActiveFor (AssemblyDefinition assembly) + { + return assembly.MainModule.HasTypeReference ("System.Net.Http.HttpMessageHandler") || assembly.MainModule.HasTypeReference ("Android.Util.IAttributeSet"); + } + + public void ProcessAssembly (AssemblyDefinition assembly, string androidHttpClientHandlerType, Dictionary> customViewMap) + { + if (!IsActiveFor (assembly)) + return; + + foreach (var type in assembly.MainModule.Types) { + // Custom HttpMessageHandler + if (!string.IsNullOrEmpty (androidHttpClientHandlerType) && + androidHttpClientHandlerType.StartsWith (type.Name, StringComparison.Ordinal)) { + var assemblyQualifiedName = type.GetPartialAssemblyQualifiedName (Context); + if (assemblyQualifiedName == androidHttpClientHandlerType) { + Annotations.Mark (type); + PreservePublicParameterlessConstructors (type); + continue; + } + } + + // Custom views in Android .xml files + if (!type.ImplementsIJavaObject (cache)) + continue; + if (customViewMap.ContainsKey (type.FullName)) { + Annotations.Mark (type); + PreserveJavaObjectImplementation (type); + } + } + } + public void ProcessType (TypeDefinition type) { // If this isn't a JLO or IJavaObject implementer, @@ -48,6 +86,21 @@ void PreserveJavaObjectImplementation (TypeDefinition type) PreserveInterfaces (type); } + void PreservePublicParameterlessConstructors (TypeDefinition type) + { + if (!type.HasMethods) + return; + + foreach (var constructor in type.Methods) + { + if (!constructor.IsConstructor || constructor.IsStatic || !constructor.IsPublic || constructor.HasParameters) + continue; + + PreserveMethod (type, constructor); + break; // We can stop when found + } + } + void PreserveAttributeSetConstructor (TypeDefinition type) { if (!type.HasMethods) diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index 329ee6aa67a..902d7c53eb6 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -336,9 +336,7 @@ static IWebProxy GetDefaultProxy () [DynamicDependency (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof (Xamarin.Android.Net.AndroidMessageHandler))] static object GetHttpMessageHandler () { - // FIXME: https://github.com/xamarin/xamarin-android/issues/8797 - // Note that this is a problem for custom $(AndroidHttpClientHandlerType) or $XA_HTTP_CLIENT_HANDLER_TYPE - [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "DynamicDependency should preserve AndroidMessageHandler.")] + [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Preserved by the MarkJavaObjects trimmer step.")] [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] static Type TypeGetType (string typeName) => Type.GetType (typeName, throwOnError: false); diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 4ad281d2f17..6a616caaf80 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -96,6 +96,7 @@ _ResolveAssemblies MSBuild target. ;_OuterIntermediateAssembly=@(IntermediateAssembly) ;_OuterOutputPath=$(OutputPath) ;_OuterIntermediateOutputPath=$(IntermediateOutputPath) + ;_OuterCustomViewMapFile=$(_CustomViewMapFile) <_AndroidBuildRuntimeIdentifiersInParallel Condition=" '$(_AndroidBuildRuntimeIdentifiersInParallel)' == '' ">true diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets index d9478aed722..e99bfb0e722 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets @@ -34,6 +34,8 @@ This file contains the .NET 5-specific targets to customize ILLink Used for the value: https://github.com/dotnet/sdk/blob/a5393731b5b7b225692fff121f747fbbc9e8b140/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets#L147 --> + <_TrimmerCustomData Include="AndroidHttpClientHandlerType" Value="$(AndroidHttpClientHandlerType)" /> + <_TrimmerCustomData Include="AndroidCustomViewMapFile" Value="$(_OuterCustomViewMapFile)" /> <_TrimmerCustomData Include="XATargetFrameworkDirectories" Value="$(_XATargetFrameworkDirectories)" /> <_TrimmerCustomData Condition=" '$(_ProguardProjectConfiguration)' != '' " diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs index 86f2a319cc0..1248eb531c4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs @@ -156,14 +156,53 @@ static AssemblyDefinition CreateFauxMonoAndroidAssembly () return assm; } - private void PreserveCustomHttpClientHandler (string handlerType, string handlerAssembly, string testProjectName, string assemblyPath) + private void PreserveCustomHttpClientHandler ( + string handlerType, + string handlerAssembly, + string testProjectName, + string assemblyPath, + TrimMode trimMode) { - var proj = new XamarinAndroidApplicationProject () { IsRelease = true }; + testProjectName += trimMode.ToString (); + + var class_library = new XamarinAndroidLibraryProject { + IsRelease = true, + ProjectName = "MyClassLibrary", + Sources = { + new BuildItem.Source ("MyCustomHandler.cs") { + TextContent = () => """ + class MyCustomHandler : System.Net.Http.HttpMessageHandler + { + protected override Task SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) => + throw new NotImplementedException (); + } + """ + }, + new BuildItem.Source ("Bar.cs") { + TextContent = () => "public class Bar { }", + } + } + }; + using (var libBuilder = CreateDllBuilder ($"{testProjectName}/{class_library.ProjectName}")) { + Assert.IsTrue (libBuilder.Build (class_library), $"Build for {class_library.ProjectName} should have succeeded."); + } + + var proj = new XamarinAndroidApplicationProject { + ProjectName = "MyApp", + IsRelease = true, + TrimModeRelease = trimMode, + Sources = { + new BuildItem.Source ("Foo.cs") { + TextContent = () => "public class Foo : Bar { }", + } + } + }; + proj.AddReference (class_library); proj.AddReferences ("System.Net.Http"); string handlerTypeFullName = string.IsNullOrEmpty(handlerAssembly) ? handlerType : handlerType + ", " + handlerAssembly; proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidHttpClientHandlerType", handlerTypeFullName); proj.MainActivity = proj.DefaultMainActivity.Replace ("base.OnCreate (bundle);", "base.OnCreate (bundle);\nvar client = new System.Net.Http.HttpClient ();"); - using (var b = CreateApkBuilder (testProjectName)) { + using (var b = CreateApkBuilder ($"{testProjectName}/{proj.ProjectName}")) { Assert.IsTrue (b.Build (proj), "Build should have succeeded."); using (var assembly = AssemblyDefinition.ReadAssembly (Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, assemblyPath))) { @@ -173,12 +212,14 @@ private void PreserveCustomHttpClientHandler (string handlerType, string handler } [Test] - public void PreserveCustomHttpClientHandlers () + public void PreserveCustomHttpClientHandlers ([Values (TrimMode.Partial, TrimMode.Full)] TrimMode trimMode) { PreserveCustomHttpClientHandler ("Xamarin.Android.Net.AndroidMessageHandler", "", - "temp/PreserveAndroidMessageHandler", "android-arm64/linked/Mono.Android.dll"); + "temp/PreserveAndroidMessageHandler", "android-arm64/linked/Mono.Android.dll", trimMode); PreserveCustomHttpClientHandler ("System.Net.Http.SocketsHttpHandler", "System.Net.Http", - "temp/PreserveSocketsHttpHandler", "android-arm64/linked/System.Net.Http.dll"); + "temp/PreserveSocketsHttpHandler", "android-arm64/linked/System.Net.Http.dll", trimMode); + PreserveCustomHttpClientHandler ("MyCustomHandler", "MyClassLibrary", + "temp/MyCustomHandler", "android-arm64/linked/MyClassLibrary.dll", trimMode); } [Test] diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Linker.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Linker.cs index 480f0d1c09b..3a2ba68f765 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Linker.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.Linker.cs @@ -60,5 +60,24 @@ public static bool ExistsInFrameworkPath (string assembly) .Select (p => Path.GetDirectoryName (p.TrimEnd (Path.DirectorySeparatorChar))) .Any (p => assembly.StartsWith (p, StringComparison.OrdinalIgnoreCase)); } + + static readonly char [] CustomViewMapSeparator = [';']; + + public static Dictionary> LoadCustomViewMapFile (string mapFile) + { + var map = new Dictionary> (); + if (!File.Exists (mapFile)) + return map; + foreach (var s in File.ReadLines (mapFile)) { + var items = s.Split (CustomViewMapSeparator, count: 2); + var key = items [0]; + var value = items [1]; + HashSet set; + if (!map.TryGetValue (key, out set)) + map.Add (key, set = new HashSet ()); + set.Add (value); + } + return map; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index dc768408fe3..bfd3c67eb48 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -408,19 +408,7 @@ public static Dictionary> LoadCustomViewMapFile (IBuildE var cachedMap = engine?.GetRegisteredTaskObjectAssemblyLocal>> (mapFile, RegisteredTaskObjectLifetime.Build); if (cachedMap != null) return cachedMap; - var map = new Dictionary> (); - if (!File.Exists (mapFile)) - return map; - foreach (var s in File.ReadLines (mapFile)) { - var items = s.Split (new char [] { ';' }, count: 2); - var key = items [0]; - var value = items [1]; - HashSet set; - if (!map.TryGetValue (key, out set)) - map.Add (key, set = new HashSet ()); - set.Add (value); - } - return map; + return LoadCustomViewMapFile (mapFile); } public static bool SaveCustomViewMapFile (IBuildEngine4 engine, string mapFile, Dictionary> map) diff --git a/tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs b/tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs index ec2ab5bdc1d..78d62576a0e 100644 --- a/tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs +++ b/tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using Android.App; +using Android.App; using Android.Content; using Android.Util; using Android.Views; @@ -12,9 +11,14 @@ namespace Xamarin.Android.RuntimeTests [TestFixture] public class CustomWidgetTests { + public CustomWidgetTests() + { + // FIXME: https://github.com/xamarin/xamarin-android/issues/9008 + new Foo (); + } + // https://bugzilla.xamarin.com/show_bug.cgi?id=23880 [Test] - [DynamicDependency (DynamicallyAccessedMemberTypes.All, typeof (CustomTextView))] public void UpperCaseCustomWidget_ShouldNotThrowInflateException () { Assert.DoesNotThrow (() => { @@ -24,7 +28,6 @@ public void UpperCaseCustomWidget_ShouldNotThrowInflateException () } [Test] - [DynamicDependency (DynamicallyAccessedMemberTypes.All, typeof (CustomTextView))] public void LowerCaseCustomWidget_ShouldNotThrowInflateException () { Assert.DoesNotThrow (() => { @@ -34,7 +37,6 @@ public void LowerCaseCustomWidget_ShouldNotThrowInflateException () } [Test] - [DynamicDependency (DynamicallyAccessedMemberTypes.All, typeof (CustomTextView))] public void UpperAndLowerCaseCustomWidget_FromLibrary_ShouldNotThrowInflateException () { Assert.DoesNotThrow (() => { diff --git a/tests/Mono.Android-Tests/Mono.Android-Test.Library/Foo.cs b/tests/Mono.Android-Tests/Mono.Android-Test.Library/Foo.cs new file mode 100644 index 00000000000..36d1d0d9374 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Test.Library/Foo.cs @@ -0,0 +1,4 @@ +namespace Mono.Android_Test.Library; + +// FIXME: https://github.com/xamarin/xamarin-android/issues/9008 +public class Foo { }