diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index ce7f3580148..77fedd4c0c5 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -127,10 +127,12 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)startup-xf.aotprofile" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)r8.jar" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)bundletool.jar" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_net6.jar" ExcludeFromLegacy="true" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_net6.jar" ExcludeFromLegacy="true" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_net6.dex" ExcludeFromLegacy="true" /> - <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_net6.dex" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime.jar" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev.jar" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_trimmable.jar" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime.dex" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev.dex" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_trimmable.dex" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_clr.jar" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_clr.jar" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_clr.dex" ExcludeFromLegacy="true" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs index 22a15e6eb7c..44837f1ab82 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs @@ -124,5 +124,39 @@ public void TrimmableTypeMap_PreserveList_IsPackagedInSdk () FileAssert.Exists (path, $"{path} should exist in the SDK pack."); } + + [Test] + public void TrimmableTypeMap_RuntimeArtifacts_ArePackagedInSdk () + { + var toolsDir = TestEnvironment.AndroidMSBuildDirectory; + + foreach (var file in new [] { + "java_runtime.jar", + "java_runtime.dex", + "java_runtime_fastdev.jar", + "java_runtime_fastdev.dex", + "java_runtime_trimmable.jar", + "java_runtime_trimmable.dex", + "java_runtime_clr.jar", + "java_runtime_clr.dex", + "java_runtime_fastdev_clr.jar", + "java_runtime_fastdev_clr.dex", + }) { + FileAssert.Exists (Path.Combine (toolsDir, file), $"{file} should exist in the SDK pack."); + } + + foreach (var file in new [] { + "java_runtime_net6.jar", + "java_runtime_net6.dex", + "java_runtime_fastdev_net6.jar", + "java_runtime_fastdev_net6.dex", + "java_runtime_trimmable_net6.jar", + "java_runtime_trimmable_net6.dex", + "java_runtime_trimmable_clr.jar", + "java_runtime_trimmable_clr.dex", + }) { + FileAssert.DoesNotExist (Path.Combine (toolsDir, file), $"{file} should not be packaged in the SDK pack."); + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index dd2ab89a61e..5674bb72ff2 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1391,12 +1391,18 @@ because xbuild doesn't support framework reference assemblies. + - - <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_net6.jar + + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime.jar - + + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_trimmable.jar + + + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_clr.jar diff --git a/src/java-runtime/java-runtime.targets b/src/java-runtime/java-runtime.targets index b699c34133f..ebc2b09a6be 100644 --- a/src/java-runtime/java-runtime.targets +++ b/src/java-runtime/java-runtime.targets @@ -7,28 +7,21 @@ $(OutputPath)java_runtime.dex $(IntermediateOutputPath)release $(IntermediateOutputPath)release.txt - ..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug\BuildConfig.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\release-net6\BuildConfig.java;java\mono\android\clr\MonoPackageManager.java + ..\..\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\clr\MonoPackageManager.java + + <_RuntimeOutput Include="$(OutputPath)java_runtime_trimmable.jar"> + $(OutputPath)java_runtime_trimmable.jar + $(OutputPath)java_runtime_trimmable.dex + $(IntermediateOutputPath)release-trimmable + $(IntermediateOutputPath)release-trimmable.txt + ..\..\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 + java-trimmable\net\dot\jni\internal\JavaProxyObject.java;java-trimmable\net\dot\jni\internal\JavaProxyThrowable.java <_RuntimeOutput Include="$(OutputPath)java_runtime_fastdev.jar"> $(OutputPath)java_runtime_fastdev.jar $(OutputPath)java_runtime_fastdev.dex $(IntermediateOutputPath)fastdev $(IntermediateOutputPath)fastdev.txt - ..\..\src-ThirdParty\bazel\java\mono\android\release\MultiDexLoader.java;java\mono\android\release\BuildConfig.java;java\mono\android\release-net6\BuildConfig.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\clr\MonoPackageManager.java - - - <_RuntimeOutput Include="$(OutputPath)java_runtime_net6.jar"> - $(OutputPath)java_runtime_net6.jar - $(OutputPath)java_runtime_net6.dex - $(IntermediateOutputPath)release-net6 - $(IntermediateOutputPath)release-net6.txt - ..\..\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\clr\MonoPackageManager.java - - <_RuntimeOutput Include="$(OutputPath)java_runtime_fastdev_net6.jar"> - $(OutputPath)java_runtime_fastdev_net6.jar - $(OutputPath)java_runtime_fastdev_net6.dex - $(IntermediateOutputPath)fastdev-net6 - $(IntermediateOutputPath)fastdev-net6.txt ..\..\src-ThirdParty\bazel\java\mono\android\release\MultiDexLoader.java;java\mono\android\release-net6\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\clr\MonoPackageManager.java @@ -46,18 +39,33 @@ $(IntermediateOutputPath)fastdev-clr.txt ..\..\src-ThirdParty\bazel\java\mono\android\release\MultiDexLoader.java;java\mono\android\release-net6\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\MonoPackageManager.java + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_net6.jar" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_net6.dex" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_fastdev_net6.jar" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_fastdev_net6.dex" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_trimmable_net6.jar" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_trimmable_net6.dex" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_trimmable_clr.jar" /> + <_ObsoleteRuntimeOutput Include="$(OutputPath)java_runtime_trimmable_clr.dex" /> + + + + + + <_RuntimeSource Include="@(AllRuntimeSource)" /> <_RuntimeSource Remove="%(_RuntimeOutput.RemoveItems)" /> + <_RuntimeSource Include="%(_RuntimeOutput.AddItems)" Condition=" '%(_RuntimeOutput.AddItems)' != '' " /> + diff --git a/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java new file mode 100644 index 00000000000..f18107b89e5 --- /dev/null +++ b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java @@ -0,0 +1,45 @@ +package net.dot.jni.internal; + +import java.util.ArrayList; + +import net.dot.jni.GCUserPeerable; + +/* package */ final class JavaProxyObject + extends java.lang.Object + implements GCUserPeerable +{ + ArrayList managedReferences = new ArrayList (); + + // This trimmable runtime copy cannot use Java.Interop's native object methods: + // those are registered through ManagedPeer.registerNativeMembers, which is not + // supported in the trimmable typemap path. + // Trimmable proxies use Java identity semantics: equals/hashCode/toString + // do not delegate to the wrapped .NET object. + @Override + public boolean equals (Object obj) + { + return this == obj; + } + + @Override + public int hashCode () + { + return System.identityHashCode (this); + } + + @Override + public String toString () + { + return super.toString (); + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyThrowable.java b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyThrowable.java new file mode 100644 index 00000000000..4b5bad5874d --- /dev/null +++ b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyThrowable.java @@ -0,0 +1,31 @@ +package net.dot.jni.internal; + +import java.util.ArrayList; + +import net.dot.jni.GCUserPeerable; + +/* package */ final class JavaProxyThrowable + extends java.lang.Error + implements GCUserPeerable +{ + ArrayList managedReferences = new ArrayList (); + + public JavaProxyThrowable () + { + } + + public JavaProxyThrowable (String message) + { + super (message); + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs index 44671758e54..3ccae0da3ad 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs @@ -179,6 +179,84 @@ public void RegisteredPeer_CanCreateGenericHolder () Assert.AreEqual (42, holder.Value); } + [Test] + public void JavaProxyObject_ValueMarshalerUsesProxyType () + { + AssumeTrimmableTypeMapEnabled (); + + var value = new object (); + var marshaler = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (typeof (object)); + var state = marshaler.CreateObjectReferenceArgumentState (value); + + try { + Assert.AreEqual ("net/dot/jni/internal/JavaProxyObject", JNIEnv.GetClassNameFromInstance (state.ReferenceValue.Handle)); + } finally { + marshaler.DestroyArgumentState (value, ref state); + } + } + + [Test] + public void JavaProxyObject_CanBeUsedInObjectArray () + { + AssumeTrimmableTypeMapEnabled (); + + using var values = new JavaObjectArray (1); + values [0] = new object (); + + Assert.AreEqual ("[Ljava/lang/Object;", values.GetJniTypeName ()); + } + + [Test] + public void JavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () + { + AssumeTrimmableTypeMapEnabled (); + + var value = new object (); + var other = new object (); + var marshaler = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (typeof (object)); + var state = marshaler.CreateObjectReferenceArgumentState (value); + var otherState = marshaler.CreateObjectReferenceArgumentState (other); + + try { + var localProxy = state.ReferenceValue.NewLocalRef (); + var localOtherProxy = otherState.ReferenceValue.NewLocalRef (); + + try { + IntPtr proxyClass = JNIEnv.GetObjectClass (localProxy.Handle); + try { + IntPtr equals = JNIEnv.GetMethodID (proxyClass, "equals", "(Ljava/lang/Object;)Z"); + IntPtr hashCode = JNIEnv.GetMethodID (proxyClass, "hashCode", "()I"); + IntPtr toString = JNIEnv.GetMethodID (proxyClass, "toString", "()Ljava/lang/String;"); + var systemClass = JniEnvironment.Types.FindClass ("java/lang/System"); + + try { + IntPtr identityHashCode = JNIEnv.GetStaticMethodID (systemClass.Handle, "identityHashCode", "(Ljava/lang/Object;)I"); + + Assert.IsTrue (JNIEnv.CallBooleanMethod (localProxy.Handle, equals, new JValue (localProxy.Handle))); + Assert.IsFalse (JNIEnv.CallBooleanMethod (localProxy.Handle, equals, new JValue (localOtherProxy.Handle))); + Assert.AreEqual ( + JNIEnv.CallStaticIntMethod (systemClass.Handle, identityHashCode, new JValue (localProxy.Handle)), + JNIEnv.CallIntMethod (localProxy.Handle, hashCode)); + var proxyString = JNIEnv.GetString (JNIEnv.CallObjectMethod (localProxy.Handle, toString), JniHandleOwnership.TransferLocalRef); + Assert.IsTrue ( + proxyString.StartsWith ("net.dot.jni.internal.JavaProxyObject@", StringComparison.Ordinal), + proxyString); + } finally { + JniObjectReference.Dispose (ref systemClass); + } + } finally { + JNIEnv.DeleteLocalRef (proxyClass); + } + } finally { + JniObjectReference.Dispose (ref localProxy); + JniObjectReference.Dispose (ref localOtherProxy); + } + } finally { + marshaler.DestroyArgumentState (other, ref otherState); + marshaler.DestroyArgumentState (value, ref state); + } + } + [Test] public void TryGetArrayType_PrimitiveLeaf_DoesNotRequireRankMapEntry () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs index d69013f4063..ccff36c26c4 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs @@ -34,29 +34,6 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer) // net.dot.jni.test.CallVirtualFromConstructorDerived Java class not in APK "Java.InteropTests.InvokeVirtualFromConstructorTests", - // net.dot.jni.internal.JavaProxyObject. calls - // net.dot.jni.ManagedPeer.registerNativeMembers, which the trimmable - // typemap path rejects (Native methods must be registered by JCW - // static initializer blocks). Fixing this requires a parallel - // Android-trimmable variant of JavaProxyObject.java that registers - // its native equals/hashCode/toString via mono.android.Runtime.register - // — an architectural change tracked separately from the JavaCast / JavaAs - // work in this PR. See https://github.com/dotnet/android/issues/11170. - "Java.InteropTests.JavaObjectArray_object_ContractTest", - - // Same root cause as above (JavaProxyObject static init). - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateGenericArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateGenericObjectReferenceArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateGenericValue", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateObjectReferenceArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateValue", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.SpecificTypesAreUsed", - - // net.dot.jni.internal.JavaProxyThrowable static init — same JavaProxy* - // root cause as the JavaProxyObject exclusions above. - "Java.InteropTests.JavaExceptionTests.InnerExceptionIsNotAProxy", - // JNI method remapping not supported in trimmable typemap "Java.InteropTests.JniPeerMembersTests.ReplaceInstanceMethodName", "Java.InteropTests.JniPeerMembersTests.ReplaceInstanceMethodWithStaticMethod",