Commit f6f11a5
authored
[Xamarin.Android.Build.Tasks] MAM Member Remapping? (#6591)
Fixes: dotnet/java-interop#867
Context: dotnet/java-interop@1f27ab5
Context: #6142 (comment)
Context: #7020
Changes: dotnet/java-interop@843f3c7...1f27ab5
* dotnet/java-interop@1f27ab55: [Java.Interop] Type & Member Remapping Support (#936)
* dotnet/java-interop@02aa54e0: [Java.Interop.Tools.JavaCallableWrappers] marshal method decl types (#987)
* dotnet/java-interop@e7bacc37: [ci] Update azure-pipelines.yaml to Pin .NET 6.0.202 (#986)
* dotnet/java-interop@fb94d598: [Java.Interop.Tools.JavaCallableWrappers] Collect overriden methods (#985)
* dotnet/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, dotnet/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 dotnet/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:
#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/1 parent 9fd37e3 commit f6f11a5
File tree
35 files changed
+1160
-62
lines changed- Documentation/workflow
- external
- src
- Mono.Android
- Android.Runtime
- Xamarin.Android.Build.Tasks
- Microsoft.Android.Sdk/targets
- Tasks
- Tests
- Xamarin.Android.Build.Tests
- Utilities
- Xamarin.ProjectTools
- Android
- Resources/Base
- Utilities
- java-runtime
- java/mono/android
- monodroid
- jni
- tests
- MSBuildDeviceIntegration
- Resources
- Tests
- Mono.Android-Tests/Runtime-Microsoft.Android.Sdk
- tools/remap-mam-json-to-xml
35 files changed
+1160
-62
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
5 | 17 | | |
6 | 18 | | |
7 | 19 | | |
| |||
Submodule Java.Interop updated from 843f3c7 to 1f27ab5
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
263 | 264 | | |
264 | 265 | | |
265 | 266 | | |
266 | | - | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
267 | 272 | | |
268 | 273 | | |
269 | 274 | | |
| |||
274 | 279 | | |
275 | 280 | | |
276 | 281 | | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
277 | 294 | | |
278 | | - | |
| 295 | + | |
279 | 296 | | |
280 | | - | |
281 | | - | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
282 | 426 | | |
283 | 427 | | |
284 | 428 | | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
285 | 439 | | |
286 | 440 | | |
287 | 441 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
18 | 23 | | |
19 | 24 | | |
20 | 25 | | |
| |||
35 | 40 | | |
36 | 41 | | |
37 | 42 | | |
| 43 | + | |
| 44 | + | |
38 | 45 | | |
39 | 46 | | |
40 | 47 | | |
| |||
61 | 68 | | |
62 | 69 | | |
63 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
64 | 76 | | |
65 | 77 | | |
66 | 78 | | |
| |||
166 | 178 | | |
167 | 179 | | |
168 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
169 | 188 | | |
170 | 189 | | |
171 | 190 | | |
| |||
0 commit comments