[Xamarin.Android.Build.Tasks] MAM Member Remapping? (#6591)

Fixes: https://github.com/xamarin/java.interop/issues/867

Context: 1f27ab552d
Context: https://github.com/xamarin/xamarin-android/issues/6142#issuecomment-889435599
Context: https://github.com/xamarin/xamarin-android/issues/7020

Changes: 843f3c7817...1f27ab552d

  * xamarin/java.interop@1f27ab55: [Java.Interop] Type & Member Remapping Support (#936)
  * xamarin/java.interop@02aa54e0: [Java.Interop.Tools.JavaCallableWrappers] marshal method decl types (#987)
  * xamarin/java.interop@e7bacc37: [ci] Update azure-pipelines.yaml to Pin .NET 6.0.202 (#986)
  * xamarin/java.interop@fb94d598: [Java.Interop.Tools.JavaCallableWrappers] Collect overriden methods (#985)
  * xamarin/java.interop@3fcce746: [Java.Interop.{Dynamic,Export}] Nullable Reference Type support (#980)

~~ The Scenarios ~~

Our Java binding infrastructure involves looking up types and methods
via [JNI][0], and assumes that types and methods won't "move" in an
unexpected manner.  Methods which move from a subclass to a
superclass works transparently.  Methods which are moved to an
entirely different class causes us problems.

Case in point: [desugaring][0], which can *move* Java types to places
that our bindings don't expect.  For example, [`Arrays.stream(T[])`][1]
may be moved into a `DesugarArrays` class, or a default interface
method `Example.m()` may be moved into an `Example$-CC` type.
Java.Interop has not supported such changes, resulting in throwing a
`Java.Lang.NoSuchMethodError` on Android versions where methods are
not where we expect.

Additionally, the [InTune Mobile Application Management][3] team
needs a expanded type and member lookup mechanism in order to
simplify how they maintain their product.  Currently they make things
work by rewriting IL, which can be brittle.


~~ Build actions ~~

To improve support for this, xamarin/java.interop#936 introduces
new `virtual` methods into `Java.Interop.JniRuntime.JniTypeManager`
which are called as part of type and member lookup, allowing
`AndroidTypeManager` to participate in the type and member resolution
process.

`AndroidTypeManager` in turn needs to know what types and members can
be remapped, and what they should be remapped to.  Some of these can
be algorithmic, such as pre-pending `Desugar` or appending `$-CC` for
the Desugar case.

The InTune use case involves a table, contained within the
[Microsoft.Intune.MAM.Remapper.Tasks NuGet package][4].

Update `src/Xamarin.Android.Build.Tasks` to add a new
`@(_AndroidRemapMembers)` Build action.  This build action is not
externally supported; it's to help test the feature.  Files with this
build action are XML files which control type and member remapping:

	<replacements>
	  <replace-type
	      from="android/app/Activity"
	      to="com/microsoft/intune/mam/client/app/MAMActivity" />
	  <replace-method
	      source-type="com/microsoft/intune/mam/client/app/MAMActivity"
	      source-method-name="onCreate" source-method-signature="(Landroid/os/Bundle;)V"
	      target-type="com/microsoft/intune/mam/client/app/MAMActivity"
	      target-method-name="onMAMCreate" target-method-instance-to-static="false" />
	</replacements>

`//replacements/replace-method` is structured with each attribute
corresponding to a member on the `JniRuntime.ReplacementMethodInfo`
structure, in xamarin/java.interop@1f27ab55.

  * `//replace-method/@source-type` is
    `JniRuntime.ReplacementMethodInfo.SourceJniType`
  * `//replace-method/@source-method-name` is
    `JniRuntime.ReplacementMethodInfo.SourceJniMethodName`
  * `//replace-method/@source-method-signature` is
    `JniRuntime.ReplacementMethodInfo.SourceJniMethodSignature`
  * `//replace-method/@target-type` is
    `JniRuntime.ReplacementMethodInfo.TargetJniType`
  * `//replace-method/@target-method-name` is
    `JniRuntime.ReplacementMethodInfo.TargetJniMethodName`
  * `//replace-method/@target-method-signature` is
    `JniRuntime.ReplacementMethodInfo.TargetJniMethodSignature`
    This attribute is optional.
  * `//replace-method/@target-method-parameter-count` is
    `JniRuntime.ReplacementMethodInfo.TargetJniMethodParameterCount`.
    This attribute is optional.
  * `//replace-method/@target-method-instance-to-static` is
    `JniRuntime.ReplacementMethodInfo.TargetJniMethodIsStatic`

`@source-type`, `@source-method-name`, and `@source-method-signature`
combined serve as a "key" for looking up the associated `@target-*`
information.

Update `src/Xamarin.Android.Build.Tasks` to add a new
`@(_AndroidMamMappingFile)` Build action.  This build action is not
externally supported; it's to help test the feature.  Files with this
build action are expected to be JSON documents which follow the
current conventions of `remapping-config.json`, within the
`Microsoft.Intune.MAM.Remapper.Tasks` NuGet package.  This build
action is not externally supported; this is currently for testing
purposes.  `@(_AndroidMamMappingFile)` files are processed at build
time into `@(_AndroidRemapMembers)` XML files.

During App builds, all `@(_AndroidRemapMembers)` files are merged
into an `@(AndrodAsset)` named `xa-internal/xa-mam-mapping.xml`.
This asset is opened and provided to `JNIEnv.Initialize()` as part of
native app startup.


~~ Putting it all together ~~

This will only work on .NET 7+.

App project has a `@(_AndroidRemapMembers)` file.  This item is
processed during App build, stored into the `.apk`, and read during
app startup on an Android device.

Given a Java binding such as:

	public partial class Activity {
	    static readonly JniPeerMembers _members = new XAPeerMembers ("android/app/Activity", typeof (Activity));
	}

when the `JniPeerMembers` constructor runs, it will call
`JniEnvironment.Runtime.TypeManager.GetReplacementType("android/app/Activity")`.
If `@(_AndroidRemapMembers)` is based on the InTune
`remapping-config.json` file, then `android/app/Activity` is mapped
to `com/microsoft/intune/mam/client/app/MAMActivity`, and
`JNIEnv::FindClass()` will be told to lookup `MAMActivity`, *not*
`Activity`.

*If `MAMActivity` can't be found*, e.g. you're testing this all out,
the app will ~immediately crash, as `MAMActivity` doesn't exist. 😅

If `MAMActivity` can be found, eventually `Activity.OnCreate()` will
need to be invoked:

	partial class Activity {
	    protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState)
	    {
	        const string __id = "onCreate.(Landroid/os/Bundle;)V";
	        try {
	            JniArgumentValue* __args = stackalloc JniArgumentValue [1];
	            __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle);
	            _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
	        } finally {
	            global::System.GC.KeepAlive (savedInstanceState);
	        }
	    }
	}

`_members.InstanceMethods.InvokeVirtualVoidMethod()` will internally
make a call similar to:

	var r = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (
	    "com/microsoft/intune/mam/client/app/MAMActivity",
	    "onCreate",
	    "(Landroid/os/Bundle;)V"
	);

The data returned will be equivalent to:

	var r = new JniRuntime.ReplacementMethodInfo {
	    SourceJniType                   = "com/microsoft/intune/mam/client/app/MAMActivity",    // from input parameter
	    SourceJniMethodName             = "onCreate",                                           // from input parameter
	    SourceJniMethodSignature        = "(Landroid/os/Bundle;)V",                             // from input parameter

	    TargetJniType                   = "com/microsoft/intune/mam/client/app/MAMActivity",    // from //replace-method/@target-type
	    TargetJniMethodName             = "onMAMCreate",                                        // from //replace-method/@target-method-name
	    TargetJniMethodSignature        = "(Landroid/os/Bundle;)V",                             // from input parameter, as signature didn't change
	    TargetJniMethodParameterCount   = 1,                                                    // computed based on signature
	    TargetJniMethodIsStatic         = false,                                                // from //replace-method/@target-method-instance-to-static
	}

This will allow `_members.InstanceMethods.InvokeVirtualVoidMethod()`
to instead resolve and invoke `MAMActivity.onMAMCreate()`.


~~ Tools ~~

`tools/remap-mam-json-to-xml` is added, and will process the InTune
JSON file into `@(_AndroidRemapMembers)` XML:

	$ dotnet run --project tools/remap-mam-json-to-xml  -- \
	  $HOME/.nuget/packages/microsoft.intune.mam.remapper.tasks/0.1.4635.1/content/MonoAndroid10/remapping-config.json
	<replacements>…


~~ Unit Tests ~~

`@(_AndroidRemapMembers)` usage is required by
`Mono.Android.NET-Tests.apk`, as `Java.Interop-Tests.dll` exercises
the type and member remapping logic.


~~ Unrelated `gref+` logging fixes ~~

When `debug.mono.log` = `gref+`, the app could crash:

	signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x79c045dead

This was likely because a constant string was provided to
`OSBridge::_write_stack_trace()`, which tried to write into the
constant string, promptly blowing things up.

Workaround: don't use `gref+` logging when a GC occurs?
(Fortunately, `gref+` logging isn't the default.)

Better workaround: Don't Do That™. Don't write to const strings.


~~ About `@(_AndroidRemapMembers)` Semantics… ~~

 1. Changing the Java hierarchy "requires" changing the managed
    hierarchy to mirror it.

    If we rename `Activity` to `RemapActivity` but *don't* change
    `MainActivity` to inherit the (bound!) `Example.RemapActivity`,
    the app *crashes*:

        JNI DETECTED ERROR IN APPLICATION: can't call void example.RemapActivity.onMyCreate(android.os.Bundle) on instance of example.MainActivity

    This can be "fixed" *without* changing the base class
    of `MainActivity` by instead changing the base class of the
    Java Callable Wrapper for `MainActivity` to `example.RemapActivity`.
    This can be done manually (just edit the files in `obj/…`!), but
    isn't really supported in "normal" xamarin-android usage
    (the next Clean will wipe your changes).

    Presumably InTune would make this Just Work by e.g. patching the
    `MainActivity.class` file.

 2. `/replacements/replace-type` interacts with
    `/replacements/replace-method`: at runtime, `//replace-type@from`
    *no longer exists*, meaning you ***cannot*** use that name
    in `//replace-method/@source-type` either!

    If `Activity` is remapped to `RemapActivity`, then *there is no*
    `Activity.onCreate()` method to similarly remap.  Instead, you
    need to specify `RemapActivity.onCreate()`.

    This warps the brain a bit.

    This:

        <replace-method
            source-type="example/RemapActivity"
            source-method-name="onCreate"
            target-type="example/RemapActivity"
            target-method-name="onMyCreate" target-method-instance-to-static="false" />

    not this:

        <replace-method
            source-type="android/app/Activity"
            source-method-name="onCreate"
            target-type="example/RemapActivity"
            target-method-name="onMyCreate" target-method-instance-to-static="false" />

 3. Don't intermix type renames with
    `/replace-method/@target-method-instance-to-static='true']`.
    It *can* be done, but also warps the brain.

    The deal with `@target-method-instance-to-static` is that it
    it changes the target method signature -- unless explicitly
    provided in `/replace-method/@target-method-signature` --
    so that the "source declaring type" is a prefix.

    Thus given

        <replace-method
            source-type="android/view/View"
            source-method-name="setOnClickListener"
            target-type="example/ViewHelper"
            target-method-name="mySetOnClickListener" target-method-instance-to-static="true" />

    we'll look for `ViewHelper.mySetOnClickListener(View, View.OnClickListener)`.

    If we renamed `View` to `MyView`, we would instead look for
    `ViewHelper.mySetOnClickListener(MyView, View.OnClickListener)`
    (note changed parameter type).

    This almost certainly *won't* work.


~~ InTune Integration Testing? ~~

For "more complete" InTune integration testing, one will want the
path to `remapping-config.json`, without hardcoding things.
This can be done with `%(PackageReference.GeneratePathProperty)`=True
and using `$(PkgMicrosoft_Intune_MAM_Remapper_Tasks)`:

	<ItemGroup>
	  <PackageReference
	      Include="Microsoft.Intune.MAM.Remapper.Tasks"
	      Version="0.1.4635.1"
	      IncludeAssets="none"
	      GeneratePathProperty="True"
	      ReferenceOutputAssembly="False"
	  />
	</ItemGroup>
	<Target Name="_AddMamFiles"
	    BeforeTargets="_AddAndroidCustomMetaData">
	  <ItemGroup>
	    <_AndroidMamMappingFile Include="$(PkgMicrosoft_Intune_MAM_Remapper_Tasks)/content/MonoAndroid10/remapping-config.json" />
	  </ItemGroup>
	</Target>

This is still fraught with some peril, as it likely also depends on
getting the right "inner" build, which may require using the plural
`$(TargetFrameworks)` property, not the singular `$(TargetFramework)`.
This might still be a useful start.


~~ TODO ~~

Optimize this mess:
https://github.com/xamarin/xamarin-android/issues/7020

[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
[1]: https://developer.android.com/studio/write/java8-support#library-desugaring
[2]: https://developer.android.com/reference/java/util/Arrays#stream(T[])
[3]: https://docs.microsoft.com/en-us/mem/intune/fundamentals/what-is-intune
[4]: https://www.nuget.org/packages/Microsoft.Intune.MAM.Remapper.Tasks/
This commit is contained in:
Jonathan Pryor 2022-05-20 08:27:59 -04:00 коммит произвёл GitHub
Родитель 9fd37e3dfb
Коммит f6f11a5a79
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
35 изменённых файлов: 1161 добавлений и 63 удалений

Просмотреть файл

@ -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

2
external/Java.Interop поставляемый

@ -1 +1 @@
Subproject commit 843f3c7817dc4bdae9ce69d04274f29fce574e09
Subproject commit 1f27ab552d03aeb74cdc6f8985fcffbfdb9a7ddf

Просмотреть файл

@ -4,6 +4,7 @@ using System.Diagnostics;
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 @@ namespace Android.Runtime {
{
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 @@ namespace Android.Runtime {
protected override IEnumerable<string> GetSimpleReferences (Type type)
{
string? j = JNIEnv.TypemapManagedToJava (type);
if (j != null) {
yield return j;
}
#if NET
j = GetReplacementTypeCore (j) ?? j;
#endif // NET
if (JNIEnv.IsRunningOnDesktop) {
yield return JavaNativeTypeManager.ToJniName (type);
string? d = JavaNativeTypeManager.ToJniName (type);
if (j != null && d != null) {
return new[]{j, d};
}
if (d != null) {
return new[]{d};
}
}
if (j != null) {
return new[]{j};
}
return Array.Empty<string> ();
}
#if NET
protected override IReadOnlyList<string>? GetStaticMethodFallbackTypesCore (string jniSimpleReference)
{
ReadOnlySpan<char> 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<char> 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<char> GetNextString (ref ReadOnlySpan<char> 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;

Просмотреть файл

@ -15,6 +15,11 @@ using Java.Interop;
using Java.Interop.Tools.TypeNameMappings;
using System.Diagnostics.CodeAnalysis;
#if NET
using ReplacementTypesDict = System.Collections.Generic.Dictionary<string, string>;
using ReplacementMethodsDict = System.Collections.Generic.Dictionary<string, string>;
#endif // NET
namespace Android.Runtime {
#pragma warning disable 0649
struct JnienvInitializeArgs {
@ -35,6 +40,8 @@ namespace Android.Runtime {
public int packageNamingPolicy;
public byte ioExceptionType;
public int jniAddNativeMethodRegistrationAttributePresent;
public IntPtr mappingXml;
public int mappingXmlLen;
}
#pragma warning restore 0649
@ -61,6 +68,11 @@ namespace Android.Runtime {
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 @@ namespace Android.Runtime {
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

Просмотреть файл

@ -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<string, string>;
using ReplacementMethodsDictStrings = System.Collections.Generic.Dictionary<string, string>;
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
// <identifier> ::= <identifier_start> ( <identifier_start> | <identifier_extend> )*
// <identifier_start> ::= [{Lu}{Ll}{Lt}{Lm}{Lo}{Nl}]
// <identifier_extend> ::= [{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)
{
// <replace-type
// from="android/app/Activity"'
// to="com/microsoft/intune/mam/client/app/MAMActivity"
// />
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)
{
// <replace-method
// source-type="jni-simple-type"
// source-method-name="method-name"
// source-method-signature="jni-method-signature"
// target-type="jni-simple-type"
// target-method-name="method-name"
// target-method-signature="jni-method-signature"
// target-method-parameter-count="int"
// target-method-instance-to-static="bool"
// />
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);
}
}
}

Просмотреть файл

@ -253,6 +253,7 @@
<Compile Include="Android.Runtime\JObjectRefType.cs" />
<Compile Include="Android.Runtime\JValue.cs" />
<Compile Include="Android.Runtime\Logger.cs" />
<Compile Include="Android.Runtime\MamXmlParser.cs" />
<Compile Include="Android.Runtime\NamespaceMappingAttribute.cs" />
<Compile Include="Android.Runtime\OutputStreamAdapter.cs" />
<Compile Include="Android.Runtime\OutputStreamInvoker.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;
}
}
}

Просмотреть файл

@ -109,6 +109,8 @@ projects, these properties are set in Xamarin.Android.Legacy.targets.
<CoreResolveReferencesDependsOn>
_SeparateAppExtensionReferences;
$(ResolveReferencesDependsOn);
_ConvertAndroidMamMappingFileToXml;
_CollectAndroidRemapMembers;
_AddAndroidCustomMetaData;
_ResolveAars;
</CoreResolveReferencesDependsOn>

Просмотреть файл

@ -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<string> (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 `<replacements/>`. 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 ()}");
}
}
}
}

Просмотреть файл

@ -88,9 +88,21 @@ namespace Xamarin.Android.Build.Tests
"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 [] {

Просмотреть файл

@ -18,6 +18,8 @@ namespace Xamarin.Android.Build.Tests
static Lazy<byte []> apacheHttpClient_cs = new Lazy<byte []> (() => GetResourceData ("ApacheHttpClient.cs"));
static Lazy<byte []> javadocCopyright = new Lazy<byte []> (() => GetResourceData ("javadoc-copyright.xml"));
static Lazy<byte []> javaSourceTestExtension = new Lazy<byte []> (() => GetResourceData ("JavaSourceTestExtension.java"));
static Lazy<byte []> remapActivityJava = new Lazy<byte []> (() => GetResourceData ("RemapActivity.java"));
static Lazy<byte []> remapActivityXml = new Lazy<byte []> (() => GetResourceData ("RemapActivity.xml"));
public static byte[] JavaSourceJarTestJar => javaSourceJarTestJar.Value;
public static byte[] JavaSourceJarTestSourcesJar => javaSourceJarTestSourcesJar.Value;
@ -28,10 +30,15 @@ namespace Xamarin.Android.Build.Tests
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 ();

Просмотреть файл

@ -22,6 +22,7 @@ namespace Xamarin.ProjectTools
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";

Просмотреть файл

@ -59,6 +59,17 @@ namespace Xamarin.ProjectTools
{
}
}
public class _AndroidRemapMembers : BuildItem
{
public _AndroidRemapMembers (string include)
: this (() => include)
{
}
public _AndroidRemapMembers (Func<string> include)
: base (AndroidBuildActions._AndroidRemapMembers, include)
{
}
}
public class EmbeddedJar : BuildItem
{
public EmbeddedJar (string include)

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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<string, string>;
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<TraceLevel, string> Logger;
public readonly ReplacementTypesDict ReplacementTypes = new ();
public readonly ReplacementMethodsDict ReplacementMethods = new ();
public MamJsonParser (Action<TraceLevel, string> 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<string> ();
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<XElement> 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<XElement> 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);
}
}
}

Просмотреть файл

@ -68,6 +68,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<UsingTask TaskName="Xamarin.Android.Tasks.LogErrorsForFiles" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.LogWarningsForFiles" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.MakeBundleNativeCodeExternal" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.MergeRemapXml" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.MamJsonToXml" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.ManifestMerger" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.MonoSymbolicate" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.RemoveDirFixed" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
@ -431,6 +433,48 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
</FilterAssemblies>
</Target>
<Target Name="_ConvertAndroidMamMappingFileToXml"
Condition=" '@(_AndroidMamMappingFile->Count())' != '0' "
Inputs="@(_AndroidMamMappingFile)"
Outputs="$($(MonoAndroidAssetsDirIntermediate)xa-internal/xa-mam-mapping.xml)">
<MakeDir
Condition=" '@(_AndroidMamMappingFile->Count())' != '0' "
Directories="$(MonoAndroidAssetsDirIntermediate)/xa-internal"
/>
<MamJsonToXml
MappingFiles="@(_AndroidMamMappingFile)"
XmlMappingOutput="$($(MonoAndroidAssetsDirIntermediate)xa-internal/xa-mam-mapping.xml)"
/>
<Touch Files="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-mam-mapping.xml" AlwaysCreate="true" />
<ItemGroup>
<FileWrites Include="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-mam-mapping.xml" />
</ItemGroup>
<ItemGroup>
<_AndroidRemapMembers Include="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-mam-mapping.xml" />
</ItemGroup>
</Target>
<Target Name="_CollectAndroidRemapMembers"
Condition=" '@(_AndroidRemapMembers->Count())' != '0' "
Inputs="@(_AndroidRemapMembers)"
Outputs="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-remap-members.xml">
<MakeDir
Condition=" '@(_AndroidRemapMembers->Count())' != '0' "
Directories="$(MonoAndroidAssetsDirIntermediate)/xa-internal"
/>
<MergeRemapXml
InputRemapXmlFiles="@(_AndroidRemapMembers)"
OutputFile="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-remap-members.xml"
/>
<ItemGroup>
<FileWrites Include="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-remap-members.xml" />
</ItemGroup>
<ItemGroup
Condition=" Exists ('$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-remap-members.xml') ">
<AndroidAsset Include="$(MonoAndroidAssetsDirIntermediate)xa-internal/xa-remap-members.xml" LogicalName="xa-internal/xa-remap-members.xml" />
</ItemGroup>
</Target>
<Target Name="_CheckNonIdealConfigurations">
<AndroidWarning Code="XA0119"
ResourceName="XA0119_AOT"

Просмотреть файл

@ -54,7 +54,11 @@
<_AndroidJar>"$(AndroidSdkDirectory)\platforms\android-$(AndroidJavaRuntimeApiLevel)\android.jar"</_AndroidJar>
</PropertyGroup>
<Exec
Command="&quot;$(JavaCPath)&quot; $(_Target) -d %(_RuntimeOutput.IntermediateRuntimeOutputPath) -bootclasspath $(_AndroidJar)$(PathSeparator)&quot;%(_RuntimeOutput.OutputJar)&quot; @%(_RuntimeOutput.IntermediateRuntimeClassesTxt)"
Command="&quot;$(JavaCPath)&quot; $(_Target) -d %(_RuntimeOutput.IntermediateRuntimeOutputPath) -h %(_RuntimeOutput.IntermediateRuntimeOutputPath) -bootclasspath $(_AndroidJar)$(PathSeparator)&quot;%(_RuntimeOutput.OutputJar)&quot; @%(_RuntimeOutput.IntermediateRuntimeClassesTxt)"
/>
<Copy
SourceFiles="$(IntermediateOutputPath)release/mono_android_Runtime.h"
DestinationFolder="$(OutputPath)"
/>
<!-- These files are auto generated at app build time so should be removed from the runtime.jar -->
<Delete Files="%(_RuntimeOutput.IntermediateRuntimeOutputPath)\mono\MonoPackageManager_Resources.class" />

Просмотреть файл

@ -101,6 +101,8 @@ public class MonoPackageManager {
System.loadLibrary("monodroid");
byte[] mappingXml = getMappingXml (context);
Runtime.initInternal (
language,
apks,
@ -108,6 +110,8 @@ public class MonoPackageManager {
appDirs,
loader,
MonoPackageManager_Resources.Assemblies,
mappingXml,
mappingXml == null ? 0 : mappingXml.length,
Build.VERSION.SDK_INT,
isEmulator (),
haveSplitApks
@ -162,4 +166,31 @@ public class MonoPackageManager {
{
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;
}
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<PROPERTY_VALUE_BUFFER_LEN>& 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);

Просмотреть файл

@ -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<jnienv_initialize_fn> (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

Просмотреть файл

@ -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<char*>(from), LOG_GREF);
if (from_writable) {
_write_stack_trace (nullptr, const_cast<char*>(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<char*>(from), LOG_GREF);
if (from_writable) {
_write_stack_trace (nullptr, const_cast<char*>(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<char*>(from), LOG_GREF);
if (from_writable) {
_write_stack_trace (nullptr, const_cast<char*>(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<char*>(from), LOG_GREF);
if (from_writable) {
_write_stack_trace (nullptr, const_cast<char*>(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<char*>(from), LOG_LREF);
if (from_writable) {
_write_stack_trace (nullptr, const_cast<char*>(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<char*>(from), LOG_LREF);
if (from_writable) {
_write_stack_trace (nullptr, const_cast<char*>(from), LOG_GREF);
} else {
log_info (LOG_GREF, "%s", from);
}
}
if (!lref_log)
return;

Просмотреть файл

@ -16,6 +16,7 @@
<Import Project="monodroid.targets" />
<ItemGroup>
<ProjectReference Include="..\java-runtime\java-runtime.csproj" ReferenceOutputAssembly="False" />
<!--
`jnimarshalmethod-gen.exe` needs to be built first because our
`_CreateJavaInteropDllConfigs` target replaces

Просмотреть файл

@ -32,11 +32,22 @@
<Target Name="_GenerateIncludeFiles"
Inputs="@(_EmbeddedBlobSource);jni\config.h"
Outputs="@(_EmbeddedBlobDestination);$(MSBuildThisFileDirectory)bin\$(Configuration)\include\config.h"
DependsOnTargets="_TestPinvokeTables">
DependsOnTargets="_TestPinvokeTables;_GetMonoAndroidRuntimeH">
<Copy SourceFiles="jni/config.h" DestinationFiles="$(MSBuildThisFileDirectory)bin\$(Configuration)\include\config.h" />
<GenerateMonoDroidIncludes SourceFiles="@(_EmbeddedBlobSource)" DestinationFiles="@(_EmbeddedBlobDestination)" />
</Target>
<Target Name="_GetMonoAndroidRuntimeH"
Inputs="$(OutputPath)..\mono_android_Runtime.h"
Outputs="jni/mono_android_Runtime.h">
<Copy
SourceFiles="$(OutputPath)..\mono_android_Runtime.h"
DestinationFiles="jni/mono_android_Runtime.h"
SkipUnchangedFiles="True"
/>
<Touch Files="jni/mono_android_Runtime.h" />
</Target>
<Target Name="RunStaticAnalysis"
Condition=" '$(HostOS)' != 'Windows' "
Inputs="jni\*.cc;jni\**\*.c"

Просмотреть файл

@ -27,6 +27,11 @@
<Compile Include="..\..\tools\assembly-store-reader\AssemblyStore*.cs" />
<EmbeddedResource Include="Resources\LinkDescTest\*.*" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\RemapActivity*">
<LogicalName>%(FileName)%(Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NodaTime" Version="2.4.5" />

Просмотреть файл

@ -0,0 +1,17 @@
package example;
import android.util.Log;
public class RemapActivity extends android.app.Activity {
public void onMyCreate (android.os.Bundle bundle) {
Log.d ("*REMAP-TEST*", "RemapActivity.onMyCreate() invoked!");
super.onCreate(bundle);
}
}
class ViewHelper {
public static void mySetOnClickListener (android.view.View view, android.view.View.OnClickListener listener) {
Log.d ("*REMAP-TEST*", "ViewHelper.mySetOnClickListener() invoked!");
view.setOnClickListener (listener);
}
}

Просмотреть файл

@ -0,0 +1,13 @@
<replacements>
<replace-type from="android/app/Activity" to="example/RemapActivity" />
<replace-method
source-type="example/RemapActivity"
source-method-name="onCreate"
target-type="example/RemapActivity"
target-method-name="onMyCreate" target-method-instance-to-static="false" />
<replace-method
source-type="android/view/View"
source-method-name="setOnClickListener"
target-type="example/ViewHelper"
target-method-name="mySetOnClickListener" target-method-instance-to-static="true" />
</replacements>

Просмотреть файл

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using Mono.Debugging.Client;
using Mono.Debugging.Soft;
@ -83,6 +84,57 @@ namespace Xamarin.Android.Build.Tests
Assert.IsTrue(didLaunch, "Activity should have started.");
}
[Test]
public void TypeAndMemberRemapping ([Values (false, true)] bool isRelease)
{
AssertHasDevices ();
var proj = new XASdkProject () {
IsRelease = isRelease,
OtherBuildItems = {
new AndroidItem._AndroidRemapMembers ("RemapActivity.xml") {
Encoding = Encoding.UTF8,
TextContent = () => ResourceData.RemapActivityXml,
},
new AndroidItem.AndroidJavaSource ("RemapActivity.java") {
Encoding = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false),
TextContent = () => ResourceData.RemapActivityJava,
Metadata = {
{ "Bind", "True" },
},
},
},
};
proj.MainActivity = proj.DefaultMainActivity.Replace (": Activity", ": global::Example.RemapActivity");
proj.SetRuntimeIdentifier (DeviceAbi);
var relativeProjDir = Path.Combine ("temp", TestName);
var fullProjDir = Path.Combine (Root, relativeProjDir);
TestOutputDirectories [TestContext.CurrentContext.Test.ID] = fullProjDir;
var files = proj.Save ();
proj.Populate (relativeProjDir, files);
proj.CopyNuGetConfig (relativeProjDir);
var dotnet = new DotNetCLI (proj, Path.Combine (fullProjDir, proj.ProjectFilePath));
Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
Assert.IsTrue (dotnet.Run (), "`dotnet run` should succeed");
bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity",
Path.Combine (fullProjDir, "logcat.log"));
Assert.IsTrue (didLaunch, "MainActivity should have launched!");
var logcatOutput = File.ReadAllText (Path.Combine (fullProjDir, "logcat.log"));
StringAssert.Contains (
"RemapActivity.onMyCreate() invoked!",
logcatOutput,
"Activity.onCreate() wasn't remapped to RemapActivity.onMyCreate()!"
);
StringAssert.Contains (
"ViewHelper.mySetOnClickListener() invoked!",
logcatOutput,
"View.setOnClickListener() wasn't remapped to ViewHelper.mySetOnClickListener()!"
);
}
[Test]
[Category ("Debugger"), Category ("Node-4")]
public void DotNetDebug ()

Просмотреть файл

@ -33,6 +33,10 @@
<AndroidLinkTool Condition=" '$(AndroidLinkTool)' == '' ">r8</AndroidLinkTool>
</PropertyGroup>
<ItemGroup>
<_AndroidRemapMembers Include="Remaps.xml" />
</ItemGroup>
<ItemGroup>
<Compile Remove="..\Resources\Resource.designer.cs" />
<Compile Include="System\AppContextTests.cs" />

Просмотреть файл

@ -0,0 +1,27 @@
<replacements>
<replace-type
from="com/xamarin/interop/RenameClassBase1"
to="com/xamarin/interop/RenameClassBase2" />
<replace-method
source-type="java/lang/Object"
source-method-name="remappedToToString"
source-method-signature="()Ljava/lang/String;"
target-type="java/lang/Object"
target-method-name="toString" target-method-instance-to-static="false" />
<replace-method
source-type="java/lang/Object"
source-method-name="remappedToStaticHashCode"
target-type="com/xamarin/interop/ObjectHelper"
target-method-name="getHashCodeHelper" target-method-instance-to-static="true" />
<replace-method
source-type="java/lang/Runtime"
source-method-name="remappedToGetRuntime"
target-type="java/lang/Runtime"
target-method-name="getRuntime" target-method-instance-to-static="false" />
<replace-method
source-type="com/xamarin/interop/RenameClassBase2"
source-method-name="hashCode"
source-method-signature="()"
target-type="com/xamarin/interop/RenameClassBase2"
target-method-name="myNewHashCode" target-method-instance-to-static="false" />
</replacements>

Просмотреть файл

@ -0,0 +1,47 @@
using System.Diagnostics;
using Android.Runtime;
using Xamarin.Android.Tasks;
var mam = new MamJsonParser (Log);
foreach (var path in args) {
mam.Load (path);
}
var xmlTree = mam.ToXml ();
var xml = xmlTree.ToString ();
Console.WriteLine (xml);
var x = MamXmlParser.ParseStructured (xml);
if (x.ReplacementTypes.Count != mam.ReplacementTypes.Count) {
Console.WriteLine ("missing types!");
}
if (x.ReplacementMethods.Count != mam.ReplacementMethods.Count) {
Console.WriteLine ("missing methods!");
}
foreach (var k in mam.ReplacementTypes.Keys) {
var ev = mam.ReplacementTypes [k];
var av = x.ReplacementTypes [k];
if (ev != av) {
Console.Error.WriteLine ($"bad replacement type for `{k}`: expected `{ev}` got `{av}");
}
}
foreach (var k in mam.ReplacementMethods.Keys) {
var ev = mam.ReplacementMethods [k];
var av = x.ReplacementMethods [k];
if (ev != av) {
Console.Error.WriteLine ($"bad replacement type for `{k}`: expected `{ev}` got `{av}");
}
}
void Log (TraceLevel level, string message)
{
switch (level) {
case TraceLevel.Error:
Console.Error.WriteLine ($"remap-mam-json-to-xml: {message}");
break;
default:
Console.WriteLine (message);
break;
}
}

Просмотреть файл

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Xamarin.Android.Tools.RemapMapjsonToXml</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputPath>..\..\bin\Test$(Configuration)</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Mono.Android\Android.Runtime\MamXmlParser.cs" />
<Compile Include="..\..\src\Xamarin.Android.Build.Tasks\Utilities\MamJsonParser.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.Intune.MAM.Remapper.Tasks"
Version="0.1.4635.1"
IncludeAssets="none"
ReferenceOutputAssembly="False"
/>
<PackageReference
Include="Newtonsoft.Json"
Version="$(NewtonsoftJsonPackageVersion)"
/>
</ItemGroup>
</Project>