Skip to content
Open
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)" />

<!--
Expand All @@ -53,13 +50,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 @@ -117,4 +107,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,136 @@
#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);
Comment on lines +25 to +26
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path.GetDirectoryName can return null when the path doesn't contain directory information. The variable 'dir' should be checked for null before being passed to Directory.Exists and Directory.CreateDirectory. Consider using a pattern like the one in KeyTool.cs line 42 where the result is checked with IsNullOrEmpty before use.

Suggested change
if (!Directory.Exists (dir))
Directory.CreateDirectory (dir);
if (!dir.IsNullOrEmpty () && !Directory.Exists (dir)) {
Directory.CreateDirectory (dir);
}

Copilot uses AI. Check for mistakes.
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 ex) {
// Skip non-managed assemblies
Log.LogDebugMessage ($"Could not read assembly '{assemblyPath}': {ex.Message}");
}
}

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) {
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to jargs is useless, since its value is never read.

Suggested change
args.FixedArguments[1].Value is string jargs) {
args.FixedArguments[1].Value is string) {

Copilot uses AI. Check for mistakes.
if (jname == ".ctor") {
writer.WriteLine (" <init>(...);");
} else {
writer.WriteLine ($" *** {jname}(...);");
}
}
break;
}
}
}
}
}
Loading