Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This file contains the .NET 5-specific targets to customize ILLink

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<UsingTask TaskName="Xamarin.Android.Tasks.GenerateProguardConfiguration" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

<Target Name="_PrepareLinking"
Condition=" '$(PublishTrimmed)' == 'true' "
AfterTargets="ComputeResolvedFilesToPublishList"
Expand All @@ -33,11 +35,6 @@ 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)"
Expand All @@ -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" />
<!-- Custom steps that run after MarkStep -->
<_TrimmerCustomSteps
Condition=" '$(_ProguardProjectConfiguration)' != '' "
Include="$(_AndroidLinkerCustomStepAssembly)"
AfterStep="CleanStep"
Type="Mono.Linker.Steps.GenerateProguardConfiguration"
/>
<_TrimmerCustomSteps
Condition=" '$(AndroidAddKeepAlives)' == 'true' "
Include="$(_AndroidLinkerCustomStepAssembly)"
Expand Down Expand Up @@ -121,4 +111,23 @@ This file contains the .NET 5-specific targets to customize ILLink
<Touch Files="$(_AndroidLinkFlag)" AlwaysCreate="true" />
</Target>

<Target Name="_PrepareLinkedAssembliesForProguard"
AfterTargets="ILLink"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
<ItemGroup>
<_LinkedAssemblyForProguard Include="@(ManagedAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')"
Condition="Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" />
</ItemGroup>
</Target>

<Target Name="_GenerateProguardConfiguration"
AfterTargets="_PrepareLinkedAssembliesForProguard"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' "
Inputs="@(_LinkedAssemblyForProguard)"
Outputs="$(_ProguardProjectConfiguration)">
<GenerateProguardConfiguration
LinkedAssemblies="@(_LinkedAssemblyForProguard)"
OutputFile="$(_ProguardProjectConfiguration)" />
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -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 (" <init>(...);");
} else {
writer.WriteLine ($" *** {jname}(...);");
}
}
break;
}
}
}
}
}