diff --git a/src/Microsoft.Android.Sdk.ILLink/GenerateProguardConfiguration.cs b/src/Microsoft.Android.Sdk.ILLink/GenerateProguardConfiguration.cs deleted file mode 100644 index 28730b06540..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/GenerateProguardConfiguration.cs +++ /dev/null @@ -1,87 +0,0 @@ -// -// GenerateProguardConfiguration.cs -// -// Author: -// Atsushi Eno -// -// (C) 2014 Xamarin Inc. (http://www.xamarin.com) -// - -using System; -using System.IO; -using System.Linq; - -using Mono.Cecil; - -namespace Mono.Linker.Steps { - public class GenerateProguardConfiguration : BaseStep - { - string filename; - TextWriter writer; - - protected override void Process () - { - if (Context.TryGetCustomData ("ProguardConfiguration", out string proguardPath)) - filename = proguardPath; - var dir = Path.GetDirectoryName (filename); - if (!Directory.Exists (dir)) - Directory.CreateDirectory (dir); - - writer = File.CreateText (filename); - } - - protected override void EndProcess () - { - writer.Close (); - } - - protected override void ProcessAssembly (AssemblyDefinition assembly) - { - // Those assemblies that do not reference Mono.Android.dll (such as System.* - // assemblies and Mono.Android.dll itself) can be skipped. - // (Mono.Android.dll is special; android.jar is not part of classes.dex). - // - // FIXME: Those non-embedded jar bindings could visit here too, and they don't have to - // be part of proguard configuration. But they don't break (they will be NOTEd though). - if (!assembly.MainModule.AssemblyReferences.Any (r => r.Name == "Mono.Android")) - return; - - writer.WriteLine ("# ACW for " + assembly.Name.Name); - foreach (var type in assembly.MainModule.Types) - ProcessType (type); - } - - void ProcessType (TypeDefinition type) - { - foreach (var nt in type.NestedTypes) - ProcessType (nt); - if (!type.IsClass) - return; - var ra = type.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"); - if (ra == null) - return; - var jtype = ra.ConstructorArguments.First ().Value.ToString ().Replace ('/', '.'); - writer.WriteLine ("-keep class " + jtype); - writer.WriteLine ("-keepclassmembers class " + jtype + " {"); - foreach (var m in type.Methods) - ProcessMethod (m); - writer.WriteLine ("}"); - writer.WriteLine (); - } - - void ProcessMethod (MethodDefinition method) - { - var ra = method.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"); - if (ra == null) - return; - var jname = ra.ConstructorArguments.First ().Value.ToString (); - var jargs = ra.ConstructorArguments [1].Value.ToString (); - var pargs = jargs.StartsWith ("()", StringComparison.Ordinal) ? string.Empty : "***"; - // FIXME: do not preserve all overroads. - if (jname == ".ctor") - writer.WriteLine (" (...);", pargs); - else - writer.WriteLine (" *** {0}(...);", jname, pargs); - } - } -} 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 c0c7ff7de09..a1d221e09a4 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 @@ -9,6 +9,8 @@ This file contains the .NET 5-specific targets to customize ILLink + + <_TrimmerCustomData Include="AndroidHttpClientHandlerType" Value="$(AndroidHttpClientHandlerType)" /> <_TrimmerCustomData Include="AndroidCustomViewMapFile" Value="$(_OuterCustomViewMapFile)" /> - <_TrimmerCustomData - Condition=" '$(_ProguardProjectConfiguration)' != '' " - Include="ProguardConfiguration" - Value="$(_ProguardProjectConfiguration)" - /> <_TrimmerCustomData Include="SystemIOHashingAssemblyPath" Value="$(_SystemIOHashingAssemblyPath)" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" @@ -57,13 +54,6 @@ This file contains the .NET 5-specific targets to customize ILLink <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveRegistrations" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" /> - - <_TrimmerCustomSteps - Condition=" '$(_ProguardProjectConfiguration)' != '' " - Include="$(_AndroidLinkerCustomStepAssembly)" - AfterStep="CleanStep" - Type="Mono.Linker.Steps.GenerateProguardConfiguration" - /> <_TrimmerCustomSteps Condition=" '$(AndroidAddKeepAlives)' == 'true' " Include="$(_AndroidLinkerCustomStepAssembly)" @@ -121,4 +111,23 @@ This file contains the .NET 5-specific targets to customize ILLink + + + <_LinkedAssemblyForProguard Include="@(ManagedAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" + Condition="Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" /> + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs new file mode 100644 index 00000000000..76d84a590d3 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs @@ -0,0 +1,135 @@ +#nullable enable + +using System; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.Build.Framework; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + public class GenerateProguardConfiguration : AndroidTask + { + public override string TaskPrefix => "GPC"; + + [Required] + public ITaskItem[] LinkedAssemblies { get; set; } = []; + + [Required] + public string OutputFile { get; set; } = ""; + + public override bool RunTask () + { + var dir = Path.GetDirectoryName (OutputFile); + if (!Directory.Exists (dir)) + Directory.CreateDirectory (dir); + using var writer = File.CreateText (OutputFile); + + foreach (var assembly in LinkedAssemblies) { + ProcessAssembly (assembly.ItemSpec, writer); + } + + return !Log.HasLoggedErrors; + } + + void ProcessAssembly (string assemblyPath, TextWriter writer) + { + try { + using var stream = File.OpenRead (assemblyPath); + using var pe = new PEReader (stream); + + if (!pe.HasMetadata) + return; + + var reader = pe.GetMetadataReader (); + + // Those assemblies that do not reference Mono.Android.dll (such as System.* + // assemblies and Mono.Android.dll itself) can be skipped. + // (Mono.Android.dll is special; android.jar is not part of classes.dex). + // + // FIXME: Those non-embedded jar bindings could visit here too, and they don't have to + // be part of proguard configuration. But they don't break (they will be NOTEd though). + if (!ReferencesMonoAndroid (reader)) + return; + + var assemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name); + writer.WriteLine ($"# ACW for {assemblyName}"); + + foreach (var typeHandle in reader.TypeDefinitions) { + var type = reader.GetTypeDefinition (typeHandle); + ProcessType (reader, type, writer); + } + } catch (BadImageFormatException) { + // Skip non-managed assemblies + } + } + + static bool ReferencesMonoAndroid (MetadataReader reader) + { + foreach (var refHandle in reader.AssemblyReferences) { + var reference = reader.GetAssemblyReference (refHandle); + if (reader.GetString (reference.Name) == "Mono.Android") + return true; + } + return false; + } + + void ProcessType (MetadataReader reader, TypeDefinition type, TextWriter writer) + { + // RegisterAttribute can be applied to interfaces, but proguard rules are only needed for classes. + // Structs don't need to be checked because RegisterAttribute cannot be applied to them. + if ((type.Attributes & System.Reflection.TypeAttributes.Interface) != 0) + return; + + string? javaTypeName = null; + foreach (var attrHandle in type.GetCustomAttributes ()) { + var attr = reader.GetCustomAttribute (attrHandle); + var attrName = reader.GetCustomAttributeFullName (attr, Log); + if (attrName == "Android.Runtime.RegisterAttribute") { + var args = attr.GetCustomAttributeArguments (); + if (args.FixedArguments.Length > 0 && args.FixedArguments[0].Value is string jtype) { + javaTypeName = jtype.Replace ('/', '.'); + } + break; + } + } + + if (javaTypeName == null) + return; + + writer.WriteLine ($"-keep class {javaTypeName}"); + writer.WriteLine ($"-keepclassmembers class {javaTypeName} {{"); + + foreach (var methodHandle in type.GetMethods ()) { + ProcessMethod (reader, methodHandle, writer); + } + + writer.WriteLine ("}"); + writer.WriteLine (); + } + + void ProcessMethod (MetadataReader reader, MethodDefinitionHandle methodHandle, TextWriter writer) + { + var method = reader.GetMethodDefinition (methodHandle); + + foreach (var attrHandle in method.GetCustomAttributes ()) { + var attr = reader.GetCustomAttribute (attrHandle); + var attrName = reader.GetCustomAttributeFullName (attr, Log); + if (attrName == "Android.Runtime.RegisterAttribute") { + var args = attr.GetCustomAttributeArguments (); + if (args.FixedArguments.Length >= 2 && + args.FixedArguments[0].Value is string jname && + args.FixedArguments[1].Value is string jargs) { + if (jname == ".ctor") { + writer.WriteLine (" (...);"); + } else { + writer.WriteLine ($" *** {jname}(...);"); + } + } + break; + } + } + } + } +}