Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
<ItemGroup>
<RuntimeHostConfigurationOption Include="Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap"
Value="true" Trim="true" />
<RuntimeHostConfigurationOption Include="Java.Interop.RuntimeFeature.ManagedPeerNativeRegistration"
Value="false" Trim="true" />
<RuntimeHostConfigurationOption Include="System.Runtime.InteropServices.TypeMappingEntryAssembly"
Value="$(_TypeMapAssemblyName)" />
<!-- [Export] metadata is still needed at compile time, but the legacy runtime helper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using Mono.Cecil;
using NUnit.Framework;
using Xamarin.Android.Tasks;
using Xamarin.Android.Tools;
using Xamarin.ProjectTools;
using Xamarin.Tools.Zip;

namespace Xamarin.Android.Build.Tests {
[TestFixture]
Expand Down Expand Up @@ -319,6 +321,16 @@ public void TrimmableTypeMap_RuntimeArtifacts_ArePackagedInSdk ()
}) {
FileAssert.Exists (Path.Combine (toolsDir, file), $"{file} should exist in the SDK pack.");
}

var trimmableJar = Path.Combine (toolsDir, "java_runtime_trimmable.jar");
using (var zip = ZipArchive.Open (trimmableJar, FileMode.Open)) {
zip.AssertDoesNotContainEntry (trimmableJar, "net/dot/jni/ManagedPeer.class");
}

var trimmableDex = Path.Combine (toolsDir, "java_runtime_trimmable.dex");
Assert.IsFalse (
FileContainsAscii (trimmableDex, "Lnet/dot/jni/ManagedPeer;"),
"java_runtime_trimmable.dex should not contain the Java ManagedPeer type descriptor.");
}

// T1: end-to-end build coverage for [Export] and [ExportField] under trimmable.
Expand Down Expand Up @@ -374,10 +386,11 @@ class ExportShapes : Java.Lang.Object {
string? exportShapesText = null;
foreach (var f in allJavaFiles) {
var text = File.ReadAllText (f);
if (text.Contains ("EchoString") && text.Contains ("InitialFoo")) {
StringAssert.DoesNotContain ("net.dot.jni.ManagedPeer", text,
Comment on lines 387 to +389
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 TestingStringAssert.DoesNotContain will fail and abort the loop on the first file that contains the string, so you won't see failures from subsequent files. If the goal is to check all generated Java files (which seems to be why the break was removed), consider collecting violations first and asserting once at the end, e.g. with Assert.Multiple or by accumulating failures into a list. Current behavior is fine for catching the issue but may make debugging harder if multiple files are affected.

Rule: Test assertions must be specific

$"Trimmable generated Java source should not reference net.dot.jni.ManagedPeer: {f}");
if (exportShapesJava == null && text.Contains ("EchoString") && text.Contains ("InitialFoo")) {
exportShapesJava = f;
exportShapesText = text;
break;
}
}
Assert.IsNotNull (exportShapesJava,
Expand Down Expand Up @@ -408,6 +421,9 @@ class ExportShapes : Java.Lang.Object {
var typemapDir = builder.Output.GetIntermediaryPath ("typemap");
var typemapDlls = Directory.GetFiles (typemapDir, "*.TypeMap.dll");
Assert.IsNotEmpty (typemapDlls, "Trimmable typemap should produce at least one *.TypeMap.dll.");

var apk = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk");
AssertApkDexDoesNotContain (apk, "Lnet/dot/jni/ManagedPeer;");
}

// T6: trim-warning baseline for [Export] under trimmable.
Expand Down Expand Up @@ -516,6 +532,41 @@ public ConcreteProvider (Android.Content.Context context) : base (context) { }
Assert.IsTrue (builder.Build (proj), "Build should have succeeded — abstract types with protected ctors should not cause XAGTT7009.");
}

static void AssertApkDexDoesNotContain (string apk, string value)
{
FileAssert.Exists (apk);
using var zip = ZipArchive.Open (apk, FileMode.Open);
var dexEntries = zip
.Where (entry => entry.FullName.StartsWith ("classes", StringComparison.Ordinal) &&
entry.FullName.EndsWith (".dex", StringComparison.Ordinal))
.ToArray ();
Assert.IsNotEmpty (dexEntries, $"{apk} should contain at least one dex file.");

foreach (var entry in dexEntries) {
Assert.IsFalse (
EntryContainsAscii (entry, value),
$"{entry.FullName} should not contain {value}.");
}
}

static bool EntryContainsAscii (ZipEntry entry, string value)
{
using var stream = new MemoryStream ();
entry.Extract (stream);
return ContainsAscii (stream.ToArray (), value);
}

static bool FileContainsAscii (string file, string value)
{
return ContainsAscii (File.ReadAllBytes (file), value);
}

static bool ContainsAscii (byte [] data, string value)
{
var pattern = Encoding.ASCII.GetBytes (value);
return data.AsSpan ().IndexOf (pattern) >= 0;
}

static void AssertTrimmableTypeMapOutputs (string typemapDir)
{
DirectoryAssert.Exists (typemapDir);
Expand All @@ -524,9 +575,9 @@ static void AssertTrimmableTypeMapOutputs (string typemapDir)

var javaDir = Path.Combine (typemapDir, "java");
DirectoryAssert.Exists (javaDir, "Trimmable JCW Java output directory should exist.");

var javaFiles = Directory.GetFiles (javaDir, "*.java", SearchOption.AllDirectories);
Assert.IsNotEmpty (javaFiles, "At least one trimmable JCW Java source file should be generated.");
Assert.IsNotEmpty (javaFiles, "At least one trimmable JCW Java source file should be generated.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 ⚠️ Code organization — This line is a duplicate of line 550 — same assertion message and expression. Looks like an accidental copy-paste. Remove one of the two.

Rule: Remove unused code (Postmortem #58)

}
DynamicCodeSupportProfile BuildDynamicCodeSupportProfile (string typemapImplementation, bool? dynamicCodeSupport)
{
Expand Down
2 changes: 1 addition & 1 deletion src/java-runtime/java-runtime.targets
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<OutputDex>$(OutputPath)java_runtime_trimmable.dex</OutputDex>
<IntermediateRuntimeOutputPath>$(IntermediateOutputPath)release-trimmable</IntermediateRuntimeOutputPath>
<IntermediateRuntimeClassesTxt>$(IntermediateOutputPath)release-trimmable.txt</IntermediateRuntimeClassesTxt>
<RemoveItems>..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\MonoPackageManager.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyObject.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyThrowable.java</RemoveItems>
<RemoveItems>..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\MonoPackageManager.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\ManagedPeer.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyObject.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyThrowable.java</RemoveItems>
<AddItems>java-trimmable\net\dot\jni\internal\JavaProxyObject.java;java-trimmable\net\dot\jni\internal\JavaProxyThrowable.java</AddItems>
</_RuntimeOutput>
<_RuntimeOutput Include="$(OutputPath)java_runtime_fastdev.jar">
Expand Down