diff --git a/Documentation/workflow/DevelopmentTips.md b/Documentation/workflow/DevelopmentTips.md index 52077bcc67f..68f96dc1ac0 100644 --- a/Documentation/workflow/DevelopmentTips.md +++ b/Documentation/workflow/DevelopmentTips.md @@ -2,6 +2,18 @@ Tips and tricks while developing Xamarin.Android. +# Run MSBuild-Based On-Device Unit Tests + +The [`tests/MSBuildDeviceIntegration`](tests/MSBuildDeviceIntegration) +directory contains NUnit-based unit tests which need to run against an attached +Android device (hardware or emulator). There are *lots* of tests in here, and +running them all can take a significant amount of time. + +If you need to run only *one* `[Test]` method, you can use +[`dotnet test --filter`](https://docs.microsoft.com/dotnet/core/testing/selective-unit-tests?pivots=mstest): + + ./dotnet-local.sh test bin/TestDebug/MSBuildDeviceIntegration/net6.0/MSBuildDeviceIntegration.dll --filter "Name~TypeAndMemberRemapping" + # Update directory When a Xamarin.Android app launches on an Android device, and the app was diff --git a/external/Java.Interop b/external/Java.Interop index 843f3c7817d..1f27ab552d0 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 843f3c7817dc4bdae9ce69d04274f29fce574e09 +Subproject commit 1f27ab552d03aeb74cdc6f8985fcffbfdb9a7ddf diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 93a91c65b78..1530b967188 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Reflection; @@ -263,7 +264,11 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl { string? j = JNIEnv.TypemapManagedToJava (type); if (j != null) { - return j; + return +#if NET + GetReplacementTypeCore (j) ?? +#endif // NET + j; } if (JNIEnv.IsRunningOnDesktop) { return JavaNativeTypeManager.ToJniName (type); @@ -274,14 +279,163 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl protected override IEnumerable GetSimpleReferences (Type type) { string? j = JNIEnv.TypemapManagedToJava (type); +#if NET + j = GetReplacementTypeCore (j) ?? j; +#endif // NET + if (JNIEnv.IsRunningOnDesktop) { + string? d = JavaNativeTypeManager.ToJniName (type); + if (j != null && d != null) { + return new[]{j, d}; + } + if (d != null) { + return new[]{d}; + } + } if (j != null) { - yield return j; + return new[]{j}; } - if (JNIEnv.IsRunningOnDesktop) { - yield return JavaNativeTypeManager.ToJniName (type); + return Array.Empty (); + } + +#if NET + protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) + { + ReadOnlySpan name = jniSimpleReference; + int slash = name.LastIndexOf ('/'); + var desugarType = new StringBuilder (jniSimpleReference.Length + "Desugar".Length); + if (slash > 0) { + desugarType.Append (name.Slice (0, slash+1)) + .Append ("Desugar") + .Append (name.Slice (slash+1)); + } else { + desugarType.Append ("Desugar").Append (name); + } + + return new[]{ + desugarType.ToString (), + $"{jniSimpleReference}$-CC" + }; + } + + protected override string? GetReplacementTypeCore (string jniSimpleReference) + { + if (JNIEnv.ReplacementTypes == null) { + return null; + } + if (JNIEnv.ReplacementTypes.TryGetValue (jniSimpleReference, out var v)) { + return v; + } + return null; + } + + protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) + { + if (JNIEnv.ReplacementMethods == null) { + return null; + } +#if !STRUCTURED + if (!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, jniMethodSignature), out var r) && + !JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) && + !JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, null), out r)) { + return null; + } + ReadOnlySpan replacementInfo = r; + + var targetType = GetNextString (ref replacementInfo); + var targetName = GetNextString (ref replacementInfo); + var targetSig = GetNextString (ref replacementInfo); + var paramCountStr = GetNextString (ref replacementInfo); + var isStaticStr = GetNextString (ref replacementInfo); + + int? paramCount = null; + if (!paramCountStr.IsEmpty) { + if (!int.TryParse (paramCountStr, 0, System.Globalization.CultureInfo.InvariantCulture, out var count)) { + return null; + } + paramCount = count; + } + + bool isStatic = false; + if (isStaticStr.Equals ("true", StringComparison.Ordinal)) { + isStatic = true; + } + + if (targetSig.IsEmpty && isStatic) { + paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature); + paramCount++; + jniMethodSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length); + } + + return new JniRuntime.ReplacementMethodInfo { + SourceJniType = jniSourceType, + SourceJniMethodName = jniMethodName, + SourceJniMethodSignature = jniMethodSignature, + TargetJniType = targetType.IsEmpty ? jniSourceType : new string (targetType), + TargetJniMethodName = targetName.IsEmpty ? jniMethodName : new string (targetName), + TargetJniMethodSignature = targetSig.IsEmpty ? jniMethodSignature : new string (targetSig), + TargetJniMethodParameterCount = paramCount, + TargetJniMethodInstanceToStatic = isStatic, + }; +#else + if (!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, jniMethodSignature), out var r) && + !JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) && + !JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, null), out r)) { + return null; + } + var targetSig = r.TargetSignature; + var paramCount = r.ParamCount; + if (targetSig == null && r.TurnStatic) { + targetSig = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length); + paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature); + paramCount++; + } + return new JniRuntime.ReplacementMethodInfo { + SourceJniType = jniSourceType, + SourceJniMethodName = jniMethodName, + SourceJniMethodSignature = jniMethodSignature, + TargetJniType = r.TargetType ?? jniSourceType, + TargetJniMethodName = r.TargetName ?? jniMethodName, + TargetJniMethodSignature = targetSig ?? jniMethodSignature, + TargetJniMethodParameterCount = paramCount, + TargetJniMethodInstanceToStatic = r.TurnStatic, + }; +#endif // !STRUCTURED + + string GetMethodSignatureWithoutReturnType () + { + int i = jniMethodSignature.IndexOf (')'); + return jniMethodSignature.Substring (0, i+1); + } + + string GetValue (string? value) + { + return value == null ? "null" : $"\"{value}\""; + } + + ReadOnlySpan GetNextString (ref ReadOnlySpan info) + { + int index = info.IndexOf ('\t'); + var r = info; + if (index >= 0) { + r = info.Slice (0, index); + info = info.Slice (index+1); + return r; + } + info = default; + return r; } } + static string CreateReplacementMethodsKey (string? sourceType, string? methodName, string? methodSignature) => + new StringBuilder () + .Append (sourceType) + .Append ('\t') + .Append (methodName) + .Append ('\t') + .Append (methodSignature) + .ToString (); +#endif // NET + delegate Delegate GetCallbackHandler (); static MethodInfo? dynamic_callback_gen; diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 6df3792fa4b..db8da3d0f37 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -15,6 +15,11 @@ using Java.Interop.Tools.TypeNameMappings; using System.Diagnostics.CodeAnalysis; +#if NET +using ReplacementTypesDict = System.Collections.Generic.Dictionary; +using ReplacementMethodsDict = System.Collections.Generic.Dictionary; +#endif // NET + namespace Android.Runtime { #pragma warning disable 0649 struct JnienvInitializeArgs { @@ -35,6 +40,8 @@ struct JnienvInitializeArgs { public int packageNamingPolicy; public byte ioExceptionType; public int jniAddNativeMethodRegistrationAttributePresent; + public IntPtr mappingXml; + public int mappingXmlLen; } #pragma warning restore 0649 @@ -61,6 +68,11 @@ public static partial class JNIEnv { static AndroidRuntime? androidRuntime; static BoundExceptionType BoundExceptionType; +#if NET + internal static ReplacementTypesDict? ReplacementTypes; + internal static ReplacementMethodsDict? ReplacementMethods; +#endif // NET + [ThreadStatic] static byte[]? mvid_bytes; @@ -166,6 +178,13 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) gref_class = args->grefClass; mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); +#if NET + if (args->mappingXml != IntPtr.Zero) { + var xml = Encoding.UTF8.GetString ((byte*) args->mappingXml, args->mappingXmlLen); + (ReplacementTypes, ReplacementMethods) = MamXmlParser.ParseStrings (xml); + } +#endif // NET + if (args->localRefsAreIndirect == 1) IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); else diff --git a/src/Mono.Android/Android.Runtime/MamXmlParser.cs b/src/Mono.Android/Android.Runtime/MamXmlParser.cs new file mode 100644 index 00000000000..f865f9a7693 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/MamXmlParser.cs @@ -0,0 +1,159 @@ +// File must be "stand-alone"; it's included by +// `tools/remap-mam-json-to-xml` + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Xml; + +using ReplacementTypesDict = System.Collections.Generic.Dictionary; +using ReplacementMethodsDictStrings = System.Collections.Generic.Dictionary; +using ReplacementMethodsDictStructured = System.Collections.Generic.Dictionary< + (string SourceType, string SourceName, string? SourceSignature), + (string? TargetType, string? TargetName, string? TargetSignature, int? ParamCount, bool IsStatic) +>; + +namespace Android.Runtime { + + class MamXmlParser { + + // https://www.unicode.org/reports/tr15/tr15-18.html#Programming%20Language%20Identifiers + // ::= ( | )* + // ::= [{Lu}{Ll}{Lt}{Lm}{Lo}{Nl}] + // ::= [{Mn}{Mc}{Nd}{Pc}{Cf}] + // + // Categories which can't be part of an identifier: Cc, Me, No, Pd, Pe, Pf, Pi, Po, Ps, Sc, Sk, Sm, So, Zl, Zp, Zs + // + // Use `\t` U+0009, Category=Cc, to separate out items in ReplacementMethodsDictStrings + + public static (ReplacementTypesDict ReplacementTypes, ReplacementMethodsDictStrings ReplacementMethods) ParseStrings (string xml) + { + var (types, methodsStructured) = ParseStructured (xml); + + var methodsStrings = new ReplacementMethodsDictStrings (); + foreach (var e in methodsStructured) { + var key = $"{e.Key.SourceType}\t{e.Key.SourceName}\t{e.Key.SourceSignature}"; + var value = $"{e.Value.TargetType}\t{e.Value.TargetName}\t{e.Value.TargetSignature}\t{e.Value.ParamCount?.ToString () ?? ""}\t{(e.Value.IsStatic ? "true" : "false")}"; + methodsStrings [key] = value; + } + + return (types, methodsStrings); + } + + public static (ReplacementTypesDict ReplacementTypes, ReplacementMethodsDictStructured ReplacementMethods) ParseStructured (string xml) + { + var replacementTypes = new ReplacementTypesDict (); + var replacementMethods = new ReplacementMethodsDictStructured (); + + using var t = new StringReader (xml); + using var reader = XmlReader.Create (t, new XmlReaderSettings { XmlResolver = null }); + while (reader.Read ()) { + if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment) { + continue; + } + if (!reader.IsStartElement ()) { + continue; + } + if (!reader.HasAttributes) { + continue; + } + switch (reader.LocalName) { + case "replace-type": + ParseReplaceTypeAttributes (replacementTypes, reader); + break; + case "replace-method": + ParseReplaceMethodAttributes (replacementMethods, reader); + break; + } + } + + return (replacementTypes, replacementMethods); + } + + static void ParseReplaceTypeAttributes (ReplacementTypesDict replacementTypes, XmlReader reader) + { + // + string? from = null; + string? to = null; + while (reader.MoveToNextAttribute ()) { + switch (reader.LocalName) { + case "from": + from = reader.Value; + break; + case "to": + to = reader.Value; + break; + } + } + if (string.IsNullOrEmpty (from) || string.IsNullOrEmpty (to)) { + return; + } + replacementTypes [from] = to; + } + + static void ParseReplaceMethodAttributes (ReplacementMethodsDictStructured replacementMethods, XmlReader reader) + { + // + + string? sourceType = null; + string? sourceMethod = null; + string? sourceMethodSig = null; + string? targetType = null; + string? targetMethod = null; + string? targetMethodSig = null; + int? targetMethodParamCount = null; + bool targetMethodInstanceToStatic = false; + + while (reader.MoveToNextAttribute ()) { + switch (reader.LocalName) { + case "source-type": + sourceType = reader.Value; + break; + case "source-method-name": + sourceMethod = reader.Value; + break; + case "source-method-signature": + sourceMethodSig = reader.Value; + break; + case "target-type": + targetType = reader.Value; + break; + case "target-method-name": + targetMethod = reader.Value; + break; + case "target-method-signature": + targetMethodSig = reader.Value; + break; + case "target-method-parameter-count": + if (int.TryParse (reader.Value, 0, CultureInfo.InvariantCulture, out var v)) { + targetMethodParamCount = v; + } + break; + case "target-method-instance-to-static": + targetMethodInstanceToStatic = reader.Value == "true"; + break; + } + } + if (string.IsNullOrEmpty (sourceType) || string.IsNullOrEmpty (sourceMethod)) { + return; + } + replacementMethods [(sourceType, sourceMethod, sourceMethodSig)] + = (targetType, targetMethod, targetMethodSig, targetMethodParamCount, targetMethodInstanceToStatic); + } + } +} diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 9e3d8a6c3fd..da86b4581dc 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -253,6 +253,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs b/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs new file mode 100644 index 00000000000..2ccd521eaf3 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + public class MamJsonToXml : AndroidTask + { + public override string TaskPrefix => "A2C"; + + [Required] + public ITaskItem[] MappingFiles { get; set; } + + [Required] + public ITaskItem XmlMappingOutput { get; set; } + + public override bool RunTask () + { + var parser = new MamJsonParser (this.CreateTaskLogger ()); + foreach (var file in MappingFiles) { + parser.Load (file.ItemSpec); + } + var tree = parser.ToXml (); + using (var o = File.CreateText (XmlMappingOutput.ItemSpec)) { + o.WriteLine (tree.ToString ()); + } + return true; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets index 02cf141b5e5..89ba48fabd0 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets @@ -109,6 +109,8 @@ projects, these properties are set in Xamarin.Android.Legacy.targets. _SeparateAppExtensionReferences; $(ResolveReferencesDependsOn); + _ConvertAndroidMamMappingFileToXml; + _CollectAndroidRemapMembers; _AddAndroidCustomMetaData; _ResolveAars; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs new file mode 100644 index 00000000000..c929a94a0db --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tasks +{ + public class MergeRemapXml : AndroidTask + { + public override string TaskPrefix => "MRX"; + + public ITaskItem[] InputRemapXmlFiles { get; set; } + + [Required] + public ITaskItem OutputFile { get; set; } + + public override bool RunTask () + { + Directory.CreateDirectory (Path.GetDirectoryName (OutputFile.ItemSpec)); + + var settings = new XmlWriterSettings () { + Encoding = new UTF8Encoding (false), + Indent = true, + OmitXmlDeclaration = true, + }; + using var output = new StreamWriter (OutputFile.ItemSpec, append: false, encoding: settings.Encoding); + using (var writer = XmlWriter.Create (output, settings)) { + writer.WriteStartElement ("replacements"); + var seen = new HashSet (StringComparer.OrdinalIgnoreCase); + foreach (var file in InputRemapXmlFiles) { + if (!seen.Add (file.ItemSpec)) { + continue; + } + MergeInputFile (writer, file.ItemSpec); + } + writer.WriteEndElement (); + } + output.WriteLine (); + return !Log.HasLoggedErrors; + } + + void MergeInputFile (XmlWriter writer, string file) + { + if (!File.Exists (file)) { + Log.LogWarning ($"Specified input file `{file}` does not exist. Ignoring."); + return; + } + var settings = new XmlReaderSettings { + XmlResolver = null, + }; + try { + using var reader = XmlReader.Create (File.OpenRead (file), settings); + if (reader.MoveToContent () != XmlNodeType.Element) { + return; + } + if (reader.LocalName != "replacements") { + Log.LogWarning ($"Input file `{file}` does not start with ``. Skipping."); + return; + } + while (reader.Read ()) { + if (reader.NodeType != XmlNodeType.Element) { + continue; + } + writer.WriteNode (reader, defattr: true); + } + } + catch (Exception e) { + Log.LogWarning ($"Input file `{file}` could not be read: {e.Message} Skipping."); + Log.LogDebugMessage ($"Input file `{file}` could not be read: {e.ToString ()}"); + } + } + } +} + diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index fc45875c5ac..308617c3f4e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -88,9 +88,21 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto "Java.Interop.dll", "Mono.Android.dll", "rc.bin", + "System.Collections.dll", + "System.Collections.Concurrent.dll", + "System.Collections.NonGeneric.dll", + "System.Console.dll", + "System.IO.Compression.dll", + "System.Net.Http.dll", + "System.Net.Primitives.dll", + "System.Net.Requests.dll", "System.Private.CoreLib.dll", "System.Runtime.dll", "System.Linq.dll", + "System.Private.Uri.dll", + "System.Private.Xml.dll", + "System.Security.Cryptography.dll", + "System.Text.RegularExpressions.dll", "UnnamedProject.dll", } : new [] { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ResourceData.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ResourceData.cs index 163241116fe..198e5084e2e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ResourceData.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ResourceData.cs @@ -18,6 +18,8 @@ static class ResourceData static Lazy apacheHttpClient_cs = new Lazy (() => GetResourceData ("ApacheHttpClient.cs")); static Lazy javadocCopyright = new Lazy (() => GetResourceData ("javadoc-copyright.xml")); static Lazy javaSourceTestExtension = new Lazy (() => GetResourceData ("JavaSourceTestExtension.java")); + static Lazy remapActivityJava = new Lazy (() => GetResourceData ("RemapActivity.java")); + static Lazy remapActivityXml = new Lazy (() => GetResourceData ("RemapActivity.xml")); public static byte[] JavaSourceJarTestJar => javaSourceJarTestJar.Value; public static byte[] JavaSourceJarTestSourcesJar => javaSourceJarTestSourcesJar.Value; @@ -28,10 +30,15 @@ static class ResourceData public static byte [] JavadocCopyright => javadocCopyright.Value; public static string JavaSourceTestExtension => Encoding.ASCII.GetString (javaSourceTestExtension.Value); + public static string RemapActivityJava => Encoding.UTF8.GetString (remapActivityJava.Value); + public static string RemapActivityXml => Encoding.UTF8.GetString (remapActivityXml.Value); static byte[] GetResourceData (string name) { using var s = typeof (ResourceData).Assembly.GetManifestResourceStream (name); + if (s == null) { + throw new NotSupportedException ($"Could not find resource `{name}` in assembly `{typeof (ResourceData).Assembly}`!"); + } using var m = new MemoryStream (checked ((int) s.Length)); s.CopyTo (m); return m.ToArray (); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidBuildActions.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidBuildActions.cs index 513993ed100..965a0166f2f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidBuildActions.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidBuildActions.cs @@ -22,6 +22,7 @@ public static class AndroidBuildActions public const string AndroidLibrary = "AndroidLibrary"; public const string AndroidLintConfig = "AndroidLintConfig"; public const string AndroidNativeLibrary = "AndroidNativeLibrary"; + public const string _AndroidRemapMembers = "_AndroidRemapMembers"; public const string ProguardConfiguration = "ProguardConfiguration"; public const string TransformFile = "TransformFile"; public const string InputJar = "InputJar"; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidItem.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidItem.cs index a375ebc4cea..15c2b7ea4b0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidItem.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidItem.cs @@ -59,6 +59,17 @@ public AndroidLintConfig (Func include) { } } + public class _AndroidRemapMembers : BuildItem + { + public _AndroidRemapMembers (string include) + : this (() => include) + { + } + public _AndroidRemapMembers (Func include) + : base (AndroidBuildActions._AndroidRemapMembers, include) + { + } + } public class EmbeddedJar : BuildItem { public EmbeddedJar (string include) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 7f9de03e661..2f75b4732ca 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,31 +5,67 @@ "Size": 3032 }, "assemblies/Java.Interop.dll": { - "Size": 55100 + "Size": 60751 }, "assemblies/Mono.Android.dll": { - "Size": 88786 + "Size": 153019 }, "assemblies/rc.bin": { "Size": 1131 }, + "assemblies/System.Collections.Concurrent.dll": { + "Size": 8764 + }, + "assemblies/System.Collections.dll": { + "Size": 4201 + }, + "assemblies/System.Collections.NonGeneric.dll": { + "Size": 6239 + }, + "assemblies/System.Console.dll": { + "Size": 6507 + }, + "assemblies/System.IO.Compression.dll": { + "Size": 16164 + }, "assemblies/System.Linq.dll": { - "Size": 9970 + "Size": 10189 + }, + "assemblies/System.Net.Http.dll": { + "Size": 65063 + }, + "assemblies/System.Net.Primitives.dll": { + "Size": 21261 + }, + "assemblies/System.Net.Requests.dll": { + "Size": 3521 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 484888 + "Size": 573152 + }, + "assemblies/System.Private.Uri.dll": { + "Size": 37760 + }, + "assemblies/System.Private.Xml.dll": { + "Size": 140763 }, "assemblies/System.Runtime.dll": { "Size": 2412 }, + "assemblies/System.Security.Cryptography.dll": { + "Size": 7437 + }, + "assemblies/System.Text.RegularExpressions.dll": { + "Size": 9596 + }, "assemblies/UnnamedProject.dll": { - "Size": 3553 + "Size": 3560 }, "classes.dex": { - "Size": 347544 + "Size": 348440 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 483888 + "Size": 484512 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4667280 @@ -44,19 +80,19 @@ "Size": 146816 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 9272 + "Size": 15904 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 2469 + "Size": 3773 }, "META-INF/MANIFEST.MF": { - "Size": 2342 + "Size": 3646 }, "res/drawable-hdpi-v4/icon.png": { - "Size": 4791 + "Size": 4762 }, "res/drawable-mdpi-v4/icon.png": { "Size": 2200 @@ -80,5 +116,5 @@ "Size": 1904 } }, - "PackageSize": 2959252 + "PackageSize": 3451764 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 35e3e7e2061..06cebd1dd52 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -8,10 +8,10 @@ "Size": 7114 }, "assemblies/Java.Interop.dll": { - "Size": 62071 + "Size": 66821 }, "assemblies/Mono.Android.dll": { - "Size": 445013 + "Size": 448188 }, "assemblies/mscorlib.dll": { "Size": 3892 @@ -41,7 +41,7 @@ "Size": 6106 }, "assemblies/System.Console.dll": { - "Size": 6578 + "Size": 6678 }, "assemblies/System.Core.dll": { "Size": 2057 @@ -86,13 +86,13 @@ "Size": 731431 }, "assemblies/System.Private.DataContractSerialization.dll": { - "Size": 185320 + "Size": 185437 }, "assemblies/System.Private.Uri.dll": { "Size": 42820 }, "assemblies/System.Private.Xml.dll": { - "Size": 216418 + "Size": 221792 }, "assemblies/System.Private.Xml.Linq.dll": { "Size": 16697 @@ -119,7 +119,7 @@ "Size": 1912 }, "assemblies/UnnamedProject.dll": { - "Size": 117244 + "Size": 117250 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 5941 @@ -188,10 +188,10 @@ "Size": 40004 }, "classes.dex": { - "Size": 3460156 + "Size": 3460820 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 483888 + "Size": 484512 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4667280 @@ -206,7 +206,7 @@ "Size": 146816 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 98480 + "Size": 98440 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -773,7 +773,7 @@ "Size": 470 }, "res/drawable-hdpi-v4/icon.png": { - "Size": 4791 + "Size": 4762 }, "res/drawable-hdpi-v4/notification_bg_low_normal.9.png": { "Size": 212 @@ -1961,5 +1961,5 @@ "Size": 341228 } }, - "PackageSize": 8261965 + "PackageSize": 8278349 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MamJsonParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MamJsonParser.cs new file mode 100644 index 00000000000..26a2878f2ae --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MamJsonParser.cs @@ -0,0 +1,234 @@ +// File must be "stand-alone"; it's included by +// `tools/remap-mam-json-to-xml` + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using ReplacementTypesDict = System.Collections.Generic.Dictionary; +using ReplacementMethodsDict = System.Collections.Generic.Dictionary< + (string SourceType, string SourceName, string? SourceSignature), + (string? TargetType, string? TargetName, string? TargetSignature, int? ParamCount, bool IsStatic) +>; + +namespace Xamarin.Android.Tasks +{ + class MamJsonParser + { + Action Logger; + + public readonly ReplacementTypesDict ReplacementTypes = new (); + public readonly ReplacementMethodsDict ReplacementMethods = new (); + + public MamJsonParser (Action logger) + { + Logger = logger; + } + + public void Load (string jsonPath) + { + var json = ReadJson (jsonPath); + + var classRewrites = json["ClassRewrites"]; + if (classRewrites != null) { + ReadClassRewrites (classRewrites); + } + + var globalMethodCalls = json["GlobalMethodCalls"]; + if (globalMethodCalls != null) { + ReadGlobalMethodCalls (globalMethodCalls); + } + } + + public XElement ToXml () + { + return new XElement ("replacements", + GetReplacementTypes (), + GetReplacementMethods ()); + } + + static JObject ReadJson (string path) + { + using (var f = File.OpenText (path)) + using (var r = new JsonTextReader (f)) + return (JObject) JToken.ReadFrom (r); + } + + void ReadClassRewrites (JToken classRewrites) + { + foreach (var classRewrite in classRewrites) { + if (!TryReadClassFromTo (classRewrite, out var from, out var to)) { + Logger (TraceLevel.Verbose, $"No from or to! {classRewrite}"); + continue; + } + ReplacementTypes [from] = to; + var methods = classRewrite ["Methods"]; + if (methods == null) { + continue; + } + foreach (var method in methods) { + var makeStatic = (bool?) method["MakeStatic"] ?? false; + var oldName = (string?) method["OriginalName"]; + var newName = (string?) method["NewName"]; + var oldSig = ReadSignature (method["OriginalParams"]); + if (oldName == null || newName == null) { + continue; + } + ReplacementMethods [(to, oldName, oldSig)] = (to, newName, null, null, makeStatic); + } + } + } + + bool TryReadClassFromTo (JToken token, [NotNullWhen(true)] out string? from, [NotNullWhen(true)] out string? to) + { + from = (string?) token["Class"]?["From"]; + to = (string?) token["Class"]?["To"]; + if (from == null || to == null) { + return false; + } + from = JavaToJniType (from); + to = JavaToJniType (to); + return true; + } + + string? ReadSignature (JToken? token) + { + if (token == null) + return null; + var types = new List (); + foreach (var type in token) { + if (type == null) { + continue; + } + var javaType = ((string?) type) switch { + "boolean" => "Z", + "byte" => "B", + "char" => "C", + "double" => "D", + "float" => "F", + "int" => "I", + "long" => "J", + "short" => "S", + "void" => "V", + var o => JavaToJniTypeSignature (o), + }; + if (javaType == null) { + continue; + } + types.Add (javaType); + } + if (types.Count == 0) + return null; + var sig = new StringBuilder (); + sig.Append ("("); + foreach (var type in types) { + sig.Append (type); + } + sig.Append (")"); + return sig.ToString (); + } + + string JavaToJniType (string javaType) + { + return javaType.Replace (".", "/"); + } + + StringBuilder JavaToJniType (StringBuilder javaType) + { + return javaType.Replace ('.', '/'); + } + + string? JavaToJniTypeSignature (string? javaType) + { + if (javaType == null) { + return null; + } + var jniType = new StringBuilder (javaType); + int arrayCount = 0; + while (jniType.Length > 2 && jniType [jniType.Length-2] == '[' && jniType [jniType.Length-1] == ']') { + arrayCount++; + jniType.Length -= 2; + } + JavaToJniType (jniType); + jniType.Append (";"); + jniType.Insert (0, 'L'); + for (int i = 0; i < arrayCount; ++i) { + jniType.Insert (0, '['); + } + return jniType.ToString (); + } + + void ReadGlobalMethodCalls (JToken globalMethodCalls) + { + foreach (var globalMethodCall in globalMethodCalls) { + if (!TryReadClassFromTo (globalMethodCall, out var from, out var to)) { + Logger (TraceLevel.Info, $"No from or to! {globalMethodCall}"); + continue; + } + var methods = globalMethodCall ["Methods"]; + if (methods == null) { + continue; + } + foreach (var method in methods) { + var makeStatic = (bool?) method["MakeStatic"] ?? false; + var oldName = (string?) method["OriginalName"]; + var oldSig = ReadSignature (method["OriginalParams"]); + if (oldSig != null) { + throw new Exception ("huh?"); + } + if (oldName == null || oldName.Length < 1) { + continue; + } + var newName = oldName; + ReplacementMethods [(from, oldName, null)] = (to, newName, null, null, makeStatic); + } + } + } + + IEnumerable GetReplacementTypes () + { + foreach (var k in ReplacementTypes.Keys.OrderBy (k => k)) { + yield return new XElement ("replace-type", + new XAttribute ("from", k), + new XAttribute ("to", ReplacementTypes [k])); + } + } + + IEnumerable GetReplacementMethods () + { + var entries = ReplacementMethods.Keys.OrderBy (e => e.SourceType) + .ThenBy (e => e.SourceName) + .ThenBy (e => e.SourceSignature); + foreach (var k in entries) { + var v = ReplacementMethods [k]; + yield return new XElement ("replace-method", + new XAttribute ("source-type", k.SourceType), + new XAttribute ("source-method-name", k.SourceName), + CreateAttribute ("source-method-signature", k.SourceSignature), + CreateAttribute ("target-type", v.TargetType), + CreateAttribute ("target-method-name", v.TargetName), + CreateAttribute ("target-method-signature", v.TargetSignature), + CreateAttribute ("target-method-parameter-count", v.ParamCount.HasValue ? v.ParamCount.Value.ToString () : null), + CreateAttribute ("target-method-instance-to-static", v.IsStatic ? "true" : "false")); + } + } + + XAttribute? CreateAttribute (string name, string? value) + { + if (value == null) { + return null; + } + return new XAttribute (name, value); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 352a9c11cdb..97006ecbd88 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -68,6 +68,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + + @@ -431,6 +433,48 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + + + + + + + + + <_AndroidRemapMembers Include="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-mam-mapping.xml" /> + + + + + + + + + + + + + + "$(AndroidSdkDirectory)\platforms\android-$(AndroidJavaRuntimeApiLevel)\android.jar" + diff --git a/src/java-runtime/java/mono/android/MonoPackageManager.java b/src/java-runtime/java/mono/android/MonoPackageManager.java index 4a8fac7dbdb..2903d63fbe9 100644 --- a/src/java-runtime/java/mono/android/MonoPackageManager.java +++ b/src/java-runtime/java/mono/android/MonoPackageManager.java @@ -101,6 +101,8 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack System.loadLibrary("monodroid"); + byte[] mappingXml = getMappingXml (context); + Runtime.initInternal ( language, apks, @@ -108,6 +110,8 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack appDirs, loader, MonoPackageManager_Resources.Assemblies, + mappingXml, + mappingXml == null ? 0 : mappingXml.length, Build.VERSION.SDK_INT, isEmulator (), haveSplitApks @@ -162,4 +166,31 @@ public static String[] getDependencies () { return MonoPackageManager_Resources.Dependencies; } + + static byte[] getMappingXml (Context context) + { + try { + AssetManager manager = context.getAssets(); + String[] assets = manager.list ("xa-internal"); + if (assets == null) { + return null; + } + for (String asset: assets) { + if (!asset.equals ("xa-remap-members.xml")) { + continue; + } + try (InputStream s = manager.open ("xa-internal/xa-remap-members.xml")) { + byte[] contents = new byte[s.available ()]; + int r = s.read (contents); + if (r != -1 && r != contents.length) { + Log.w ("monodroid", "Only read " + r + " bytes, not the expected " + contents.length + " bytes!"); + } + return contents; + } + } + } catch (IOException e) { + Log.wtf ("monodroid", "Error reading `xa-internal/xa-remap-members.xml`", e); + } + return null; + } } diff --git a/src/java-runtime/java/mono/android/Runtime.java b/src/java-runtime/java/mono/android/Runtime.java index d40080e4608..2a8c3429012 100644 --- a/src/java-runtime/java/mono/android/Runtime.java +++ b/src/java-runtime/java/mono/android/Runtime.java @@ -15,7 +15,19 @@ public class Runtime { } public static native void init (String lang, String[] runtimeApks, String runtimeDataDir, String[] appDirs, ClassLoader loader, String[] externalStorageDirs, String[] assemblies, String packageName, int apiLevel, String[] environmentVariables); - public static native void initInternal (String lang, String[] runtimeApks, String runtimeDataDir, String[] appDirs, ClassLoader loader, String[] assemblies, int apiLevel, boolean isEmulator, boolean haveSplitApks); + public static native void initInternal ( + String lang, + String[] runtimeApks, + String runtimeDataDir, + String[] appDirs, + ClassLoader loader, + String[] assemblies, + byte[] mappingXml, + int mappingXmlLen, + int apiLevel, + boolean isEmulator, + boolean haveSplitApks + ); public static native void register (String managedType, java.lang.Class nativeClass, String methods); public static native void notifyTimeZoneChanged (); public static native int createNewContext (String[] runtimeApks, String[] assemblies, ClassLoader loader); diff --git a/src/monodroid/jni/mono_android_Runtime.h b/src/monodroid/jni/mono_android_Runtime.h index 38e787d53e7..2dfa0a6cf00 100644 --- a/src/monodroid/jni/mono_android_Runtime.h +++ b/src/monodroid/jni/mono_android_Runtime.h @@ -18,10 +18,10 @@ JNIEXPORT void JNICALL Java_mono_android_Runtime_init /* * Class: mono_android_Runtime * Method: initInternal - * Signature: (Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/String;IZ)V + * Signature: (Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/ClassLoader;[Ljava/lang/String;[BIIZZ)V */ JNIEXPORT void JNICALL Java_mono_android_Runtime_initInternal -(JNIEnv *, jclass, jstring, jobjectArray, jstring, jobjectArray, jobject, jobjectArray, jint, jboolean, jboolean); + (JNIEnv *, jclass, jstring, jobjectArray, jstring, jobjectArray, jobject, jobjectArray, jbyteArray, jint, jint, jboolean, jboolean); /* * Class: mono_android_Runtime @@ -39,15 +39,6 @@ JNIEXPORT void JNICALL Java_mono_android_Runtime_register JNIEXPORT void JNICALL Java_mono_android_Runtime_notifyTimeZoneChanged (JNIEnv *, jclass); -/* - * Class: mono_android_Runtime - * Method: dumpTimingData - * Signature: ()V -*/ -JNIEXPORT void JNICALL Java_mono_android_Runtime_dumpTimingData - (JNIEnv *, jclass); - -#if !defined (ANDROID) /* * Class: mono_android_Runtime * Method: createNewContext @@ -59,7 +50,7 @@ JNIEXPORT jint JNICALL Java_mono_android_Runtime_createNewContext /* * Class: mono_android_Runtime * Method: createNewContextWithData - * Signature: ([Ljava/lang/String;[Ljava/lang/String;[[BL[Ljava/lang/String;java/lang/ClassLoader;Z)I + * Signature: ([Ljava/lang/String;[Ljava/lang/String;[[B[Ljava/lang/String;Ljava/lang/ClassLoader;Z)I */ JNIEXPORT jint JNICALL Java_mono_android_Runtime_createNewContextWithData (JNIEnv *, jclass, jobjectArray, jobjectArray, jobjectArray, jobjectArray, jobject, jboolean); @@ -79,7 +70,6 @@ JNIEXPORT void JNICALL Java_mono_android_Runtime_switchToContext */ JNIEXPORT void JNICALL Java_mono_android_Runtime_destroyContexts (JNIEnv *, jclass, jintArray); -#endif // ndef ANDROID /* * Class: mono_android_Runtime @@ -89,6 +79,14 @@ JNIEXPORT void JNICALL Java_mono_android_Runtime_destroyContexts JNIEXPORT void JNICALL Java_mono_android_Runtime_propagateUncaughtException (JNIEnv *, jclass, jobject, jthrowable); +/* + * Class: mono_android_Runtime + * Method: dumpTimingData + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mono_android_Runtime_dumpTimingData + (JNIEnv *, jclass); + #ifdef __cplusplus } #endif diff --git a/src/monodroid/jni/monodroid-glue-designer.cc b/src/monodroid/jni/monodroid-glue-designer.cc index 8e8d6ab4cba..cbc6e45a7fb 100644 --- a/src/monodroid/jni/monodroid-glue-designer.cc +++ b/src/monodroid/jni/monodroid-glue-designer.cc @@ -42,7 +42,7 @@ MonodroidRuntime::Java_mono_android_Runtime_createNewContextWithData (JNIEnv *en jstring_array_wrapper runtimeApks (env, runtimeApksJava); jstring_array_wrapper assemblies (env, assembliesJava); jstring_array_wrapper assembliePaths (env, assembliesPaths); - MonoDomain *domain = create_and_initialize_domain (env, klass, runtimeApks, assemblies, assembliesBytes, assembliePaths, loader, /*is_root_domain:*/ false, force_preload_assemblies, /* have_split_apks */ false); + MonoDomain *domain = create_and_initialize_domain (env, klass, runtimeApks, assemblies, assembliesBytes, assembliePaths, loader, nullptr, 0, /*is_root_domain:*/ false, force_preload_assemblies, /* have_split_apks */ false); mono_domain_set (domain, FALSE); int domain_id = mono_domain_get_id (domain); current_context_id = domain_id; diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index d7c44a23cfb..ed557fe0ab1 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -131,6 +131,8 @@ namespace xamarin::android::internal int packageNamingPolicy; uint8_t boundExceptionType; int jniAddNativeMethodRegistrationAttributePresent; + jbyte* mappingXml; + int mappingXmlLen; }; #if defined (NET) @@ -174,7 +176,8 @@ namespace xamarin::android::internal void Java_mono_android_Runtime_register (JNIEnv *env, jstring managedType, jclass nativeClass, jstring methods); void Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava, jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader, - jobjectArray assembliesJava, jint apiLevel, jboolean isEmulator, + jobjectArray assembliesJava, jbyteArray mappingXml, jint mappingXmlLen, + jint apiLevel, jboolean isEmulator, jboolean haveSplitApks); #if !defined (ANDROID) jint Java_mono_android_Runtime_createNewContextWithData (JNIEnv *env, jclass klass, jobjectArray runtimeApksJava, jobjectArray assembliesJava, @@ -299,9 +302,9 @@ namespace xamarin::android::internal void parse_gdb_options (); void mono_runtime_init (dynamic_local_string& runtime_args); #if defined (NET) - void init_android_runtime (JNIEnv *env, jclass runtimeClass, jobject loader); + void init_android_runtime (JNIEnv *env, jclass runtimeClass, jobject loader, jbyteArray mappingXml, jint mappingXmlLen); #else //def NET - void init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass runtimeClass, jobject loader); + void init_android_runtime (MonoDomain *domain, JNIEnv *env, jclass runtimeClass, jobject loader, jbyteArray mappingXml, jint mappingXmlLen); void setup_bundled_app (const char *dso_name); #endif // ndef NET void set_environment_variable_for_directory (const char *name, jstring_wrapper &value, bool createDirectory, mode_t mode); @@ -326,7 +329,7 @@ namespace xamarin::android::internal MonoDomain* create_domain (JNIEnv *env, jstring_array_wrapper &runtimeApks, bool is_root_domain, bool have_split_apks); MonoDomain* create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring_array_wrapper &assemblies, jobjectArray assembliesBytes, jstring_array_wrapper &assembliesPaths, - jobject loader, bool is_root_domain, bool force_preload_assemblies, + jobject loader, jbyteArray mappingXml, jint mappingXmlLen, bool is_root_domain, bool force_preload_assemblies, bool have_split_apks); void gather_bundled_assemblies (jstring_array_wrapper &runtimeApks, size_t *out_user_assemblies_count, bool have_split_apks); diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index cc1eb942dc2..53ef0a1bc61 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1050,7 +1050,7 @@ MonodroidRuntime::init_android_runtime ( #if !defined (NET) MonoDomain *domain, #endif // ndef NET - JNIEnv *env, jclass runtimeClass, jobject loader) + JNIEnv *env, jclass runtimeClass, jobject loader, jbyteArray mappingXml, jint mappingXmlLen) { constexpr char icall_typemap_java_to_managed[] = "Java.Interop.TypeManager::monodroid_typemap_java_to_managed"; constexpr char icall_typemap_managed_to_java[] = "Android.Runtime.JNIEnv::monodroid_typemap_managed_to_java"; @@ -1192,6 +1192,12 @@ MonodroidRuntime::init_android_runtime ( native_to_managed_index = internal_timing->start_event (TimingEventKind::NativeToManagedTransition); } + if (mappingXml != nullptr && mappingXmlLen > 0) { + init.mappingXml = env->GetByteArrayElements (mappingXml, nullptr); + init.mappingXmlLen = mappingXmlLen; + log_warn (LOG_DEFAULT, "# jonp: mappingXml? len=%i, xml=%p", init.mappingXmlLen, init.mappingXml); + } + #if defined (NET) && defined (ANDROID) MonoError error; auto initialize = reinterpret_cast (mono_method_get_unmanaged_callers_only_ftnptr (method, &error)); @@ -1205,6 +1211,10 @@ MonodroidRuntime::init_android_runtime ( utils.monodroid_runtime_invoke (domain, method, nullptr, args, nullptr); #endif // ndef NET && ndef ANDROID + if (init.mappingXml != nullptr) { + env->ReleaseByteArrayElements (mappingXml, init.mappingXml, JNI_ABORT); + } + if (XA_UNLIKELY (FastTiming::enabled ())) { internal_timing->end_event (native_to_managed_index); } @@ -1959,7 +1969,7 @@ monodroid_Mono_UnhandledException_internal ([[maybe_unused]] MonoException *ex) MonoDomain* MonodroidRuntime::create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring_array_wrapper &assemblies, [[maybe_unused]] jobjectArray assembliesBytes, - [[maybe_unused]] jstring_array_wrapper &assembliesPaths, jobject loader, bool is_root_domain, + [[maybe_unused]] jstring_array_wrapper &assembliesPaths, jobject loader, jbyteArray mappingXml, jint mappingXmlLen, bool is_root_domain, bool force_preload_assemblies, bool have_split_apks) { MonoDomain* domain = create_domain (env, runtimeApks, is_root_domain, have_split_apks); @@ -1987,10 +1997,10 @@ MonodroidRuntime::create_and_initialize_domain (JNIEnv* env, jclass runtimeClass #if defined (NET) load_assemblies (default_alc, preload, assemblies); - init_android_runtime (env, runtimeClass, loader); + init_android_runtime (env, runtimeClass, loader, mappingXml, mappingXmlLen); #else // def NET load_assemblies (domain, preload, assemblies); - init_android_runtime (domain, env, runtimeClass, loader); + init_android_runtime (domain, env, runtimeClass, loader, mappingXml, mappingXmlLen); #endif // ndef NET osBridge.add_monodroid_domain (domain); @@ -2150,7 +2160,7 @@ MonodroidRuntime::install_logging_handlers () inline void MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava, jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader, - jobjectArray assembliesJava, jint apiLevel, jboolean isEmulator, + jobjectArray assembliesJava, jbyteArray mappingXml, jint mappingXmlLen, jint apiLevel, jboolean isEmulator, jboolean haveSplitApks) { char *mono_log_mask_raw = nullptr; @@ -2363,7 +2373,7 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl jstring_array_wrapper assemblies (env, assembliesJava); jstring_array_wrapper assembliesPaths (env); /* the first assembly is used to initialize the AppDomain name */ - create_and_initialize_domain (env, klass, runtimeApks, assemblies, nullptr, assembliesPaths, loader, /*is_root_domain:*/ true, /*force_preload_assemblies:*/ false, haveSplitApks); + create_and_initialize_domain (env, klass, runtimeApks, assemblies, nullptr, assembliesPaths, loader, mappingXml, mappingXmlLen, /*is_root_domain:*/ true, /*force_preload_assemblies:*/ false, haveSplitApks); #if defined (ANDROID) && !defined (NET) // Mono from mono/mono has a bug which requires us to install the handlers after `mono_init_jit_version` is called @@ -2451,6 +2461,8 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject appDirs, loader, assembliesJava, + /* mappingXml */ nullptr, + /* mappingXmlLen */ 0, apiLevel, /* isEmulator */ JNI_FALSE, /* haveSplitApks */ JNI_FALSE @@ -2460,7 +2472,7 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject JNIEXPORT void JNICALL Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava, jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader, - jobjectArray assembliesJava, jint apiLevel, jboolean isEmulator, + jobjectArray assembliesJava, jbyteArray mappingXml, jint mappingXmlLen, jint apiLevel, jboolean isEmulator, jboolean haveSplitApks) { monodroidRuntime.Java_mono_android_Runtime_initInternal ( @@ -2472,6 +2484,8 @@ Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, appDirs, loader, assembliesJava, + mappingXml, + mappingXmlLen, apiLevel, isEmulator, haveSplitApks diff --git a/src/monodroid/jni/osbridge.cc b/src/monodroid/jni/osbridge.cc index c5d147bcac8..d05b0a61947 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -262,7 +262,11 @@ OSBridge::_monodroid_gref_log_new (jobject curHandle, char curType, jobject newH threadName, threadId); if (gref_to_logcat) { - _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + if (from_writable) { + _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + } else { + log_info (LOG_GREF, "%s", from); + } } if (!gref_log) return c; @@ -299,7 +303,11 @@ OSBridge::_monodroid_gref_log_delete (jobject handle, char type, const char *thr threadName, threadId); if (gref_to_logcat) { - _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + if (from_writable) { + _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + } else { + log_info (LOG_GREF, "%s", from); + } } if (!gref_log) return; @@ -334,7 +342,11 @@ OSBridge::_monodroid_weak_gref_new (jobject curHandle, char curType, jobject new threadName, threadId); if (gref_to_logcat) { - _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + if (from_writable) { + _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + } else { + log_info (LOG_GREF, "%s", from); + } } if (!gref_log) return; @@ -369,7 +381,11 @@ OSBridge::_monodroid_weak_gref_delete (jobject handle, char type, const char *th threadName, threadId); if (gref_to_logcat) { - _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + if (from_writable) { + _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + } else { + log_info (LOG_GREF, "%s", from); + } } if (!gref_log) return; @@ -400,7 +416,11 @@ OSBridge::_monodroid_lref_log_new (int lrefc, jobject handle, char type, const c threadName, threadId); if (lref_to_logcat) { - _write_stack_trace (nullptr, const_cast(from), LOG_LREF); + if (from_writable) { + _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + } else { + log_info (LOG_GREF, "%s", from); + } } if (!lref_log) return; @@ -430,7 +450,11 @@ OSBridge::_monodroid_lref_log_delete (int lrefc, jobject handle, char type, cons threadName, threadId); if (lref_to_logcat) { - _write_stack_trace (nullptr, const_cast(from), LOG_LREF); + if (from_writable) { + _write_stack_trace (nullptr, const_cast(from), LOG_GREF); + } else { + log_info (LOG_GREF, "%s", from); + } } if (!lref_log) return; diff --git a/src/monodroid/monodroid.csproj b/src/monodroid/monodroid.csproj index e7364786d44..d6bf5ead80f 100644 --- a/src/monodroid/monodroid.csproj +++ b/src/monodroid/monodroid.csproj @@ -16,6 +16,7 @@ +