Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Android.Build.Tasks] <GenerateJavaStubs /> generates a short…
…er acw-map.txt (#1131) Fixes: https://bugzilla.xamarin.com/show_bug.cgi?id=61073 Context: dotnet/java-interop@429dc2a Methods such as `JNIEnv.GetJniName(Type)` and `TypeManager.GetJavaToManagedType(string)` are used to map `System.Type` values to JNI type references, and to map from JNI type references to `System.Type`-compatible values. Once upon a time this was done through System.Reflection; see [`JavaNativeTypeManager.ToJniName(Type)`][to-jni] and [`JavaNativeTypeManager.ToCliType(string)`][from-jni]. [to-jni]: https://github.com/xamarin/java.interop/blob/5e77d91085820611ce3eda65537a6e7c19df90ef/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs#L147-L151 [from-jni]: https://github.com/xamarin/java.interop/blob/5e77d91085820611ce3eda65537a6e7c19df90ef/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs#L117-L126 Heavy use of reflection was deemed a terrible mistake, so as a fast path we added support for ["type mapping files"][typemap-format], a pair of files generated at packaging time. The `typemap.jm` file contained mappings from JNI type references to Assembly-Qualified type names, while the `typemap.mj` file contained mappings from Assembly-Qualified type names to JNI type references. (Reflection was preserved as a fallback in case we missed something in the introduction of type mapping files.) [typemap-format]: https://github.com/xamarin/java.interop/blob/5e77d91085820611ce3eda65537a6e7c19df90ef/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L15-L57 For example, `typemap.jm` would contain: android/app/Activity Android.App.Activity, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 The type mapping files are found in the intermediate dir after a build in `obj/$(Configuration)/acw-map.txt`, or `$(_AcwMapFile)`. (Use `strings` in order to easily read the contents of a file; the type mapping files are in a baroque binary file format.) Unfortunately, Assembly-Qualified names interact with [`AssemblyVersionAttribute`][ava]. `AssemblyVersionAttribute` is used to specify the assembly version, and the C# compiler allows this attribute to contain *wildcards*: [ava]: https://msdn.microsoft.com/en-us/library/system.reflection.assemblyversionattribute(v=vs.110).aspx [assembly: AssemblyVersion ("1.0.0.*")] If the `AssemblyVersionAttribute` contains a wildcard, then the assembly version will change on *every build*. For example, on the first build, there might be an Assembly-Qualified name of: Foo.Bar, Example, Version=1.0.0.0, Culture=neutral, PublicKeyToken= On the next build, it may change to: Foo.Bar, Example, Version=1.0.0.1, Culture=neutral, PublicKeyToken= The type mapping files use the Assembly-Qualified names, but they are only rebuilt when `<GenerateJavaStubs>` executes, which is not necessarily when the assembly changes. (This is intentional for commercial fast deployment support: if the Java Callable Wrappers or type mapping files changed, then the `.apk` would need to be rebuilt and redeployed, slowing down the deployment+debug cycle.) In short, type mapping files are acting as a cache, and that cache can be invalidated by using the `[AssemblyVersion]` custom attribute. The result is that it's possible to raise a `Java.Lang.ClassNotFoundException` by simply rebuilding and re-running a project: 1. Create a new Xamarin.Android application project. 2. Add an `[AssemblyVersion]` custom attribute which contains a wildcard. 3. Run the application in Debug configuration, with fast deployment enabled (which is the default with the commercial SDK). 4. Touch a `.cs` file in the application project, and re-run the app. The app *should* work. Instead, it may fail: JNIEnv.FindClass(Type) caught unexpected exception: Java.Lang.ClassNotFoundException: md50039d44cbb3b194ba4f4e52eaa252795.BrowserPagerAdapter ---> Java.Lang.ClassNotFoundException: Didn't find class "md50039d44cbb3b194ba4f4e52eaa252795.BrowserPagerAdapter" on path: DexPathList[[zip file "/data/app/com.outcoder.browser-1/base.apk"],nativeLibraryDirectories=[/data/app/com.outcoder.browser-1/lib/arm, /vendor/lib, /system/lib]] --- End of inner exception stack trace --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <657aa8fea4454dc898a9e5f379c58734>:0 at Java.Interop.JniEnvironment+StaticMethods.CallStaticObjectMethod (Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00069] in <54816278eed9488eb28d3597fecd78f8>:0 at Android.Runtime.JNIEnv.CallStaticObjectMethod (System.IntPtr jclass, System.IntPtr jmethod, Android.Runtime.JValue* parms) [0x0000e] in <28e323a707a2414f8b493f6d4bb27c8d>:0 at Android.Runtime.JNIEnv.CallStaticObjectMethod (System.IntPtr jclass, System.IntPtr jmethod, Android.Runtime.JValue[] parms) [0x00017] in <28e323a707a2414f8b493f6d4bb27c8d>:0 at Android.Runtime.JNIEnv.FindClass (System.String classname) [0x0003d] in <28e323a707a2414f8b493f6d4bb27c8d>:0 at Android.Runtime.JNIEnv.FindClass (System.Type type) [0x00015] in <28e323a707a2414f8b493f6d4bb27c8d>:0 --- End of managed Java.Lang.ClassNotFoundException stack trace --- java.lang.ClassNotFoundException: md50039d44cbb3b194ba4f4e52eaa252795.BrowserPagerAdapter at java.lang.Class.classForName(Native Method) at java.lang.Class.forName(Class.java:309) ... Caused by: java.lang.ClassNotFoundException: Didn't find class "md50039d44cbb3b194ba4f4e52eaa252795.BrowserPagerAdapter" on path: DexPathList[[zip file "/data/app/com.outcoder.browser-1/base.apk"],nativeLibraryDirectories=[/data/app/com.outcoder.browser-1/lib/arm, /vendor/lib, /system/lib]] ... Fix this by no longer using Assembly-Qualified names in the type mapping files. Instead, use a *partially* Assembly-Qualified name, which is just the type name and assembly, no version, culture, or PublicKeyToken information: Foo.Bar, Example Bump to Java.Interop/master/429dc2a, which updates `TypeNameMapGenerator` to generate partial Assembly-Qualified names. Update `JNIEnv.GetJniName(Type)` to use a partial Assembly-Qualified name for the `monodroid_typemap_managed_to_java()` invocation. Update the `<GenerateJavaStubs>` task so that it no longer emits lines containing full Assembly-Qualified names. (It was already emitting partially Assembly-Qualified names.) Fix `JnienvTest` so that the correct format is tested. Add a test case to ensure that using `[assembly:AssemblyVersion]` doesn't result in changes `acw-map.txt`.
- Loading branch information