[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:
Родитель
9fd37e3dfb
Коммит
f6f11a5a79
|
@ -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
|
||||
|
|
|
@ -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=""$(JavaCPath)" $(_Target) -d %(_RuntimeOutput.IntermediateRuntimeOutputPath) -bootclasspath $(_AndroidJar)$(PathSeparator)"%(_RuntimeOutput.OutputJar)" @%(_RuntimeOutput.IntermediateRuntimeClassesTxt)"
|
||||
Command=""$(JavaCPath)" $(_Target) -d %(_RuntimeOutput.IntermediateRuntimeOutputPath) -h %(_RuntimeOutput.IntermediateRuntimeOutputPath) -bootclasspath $(_AndroidJar)$(PathSeparator)"%(_RuntimeOutput.OutputJar)" @%(_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>
|
Загрузка…
Ссылка в новой задаче