Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Hello-NativeAOTFromJNI] Add NativeAOT sample #1153

Merged
merged 52 commits into from
Feb 22, 2024

Commits on Oct 24, 2023

  1. [Hello-NativeAOTFromJNI] Add NativeAOT sample

    Instead of a managed app creating a Java VM and calling into Java,
    do the opposite: have a Java app call into Native Library built using
    NativeAOT, using `[UnmanagedCallersOnly]` to provide the entry points.
    jonpryor committed Oct 24, 2023
    Configuration menu
    Copy the full SHA
    007317b View commit details
    Browse the repository at this point in the history

Commits on Oct 25, 2023

  1. Try to create a JreRuntime

    BROKEN BROKEN BROKEN BROKEN
    
    What do we want?  To create a `JreRuntime` instance!
    
    …which is shockingly difficult.
    
    But first, *why* do we want to create a `JreRuntime` instance?
    (It's not like we've needed it so far!)
    
    Force the issue by changing `NativeAOTInit.sayHello()` to return a
    `String` instance, thus requiring that for `NativeAOTInit.sayHello()`
    to return a non-`null` value, it needs a `JreRuntime` instance!
    
    This is where everything falls apart.  `JreRuntime` and
    `JreRuntimeOptions` were not designed with this scenario in mind!
    Update `JreRuntime` & co so that it doesn't require `JvmLibraryPath`
    when `InvocationPointer` isn't null, and don't require that
    `JreRuntimeOptions.ClassPath` contain the path to `java-interop.jar`
    when `InvocationPointer` isn't null.
    
    This allows us to create the `JreRuntimeOptions` instance.  Yay!
    
    The next problem is that setting
    `%(ProjectReference.AdditionalProperties)` to include `Standalone=true`
    doesn't appear to work, in that the `Java.Interop.dll` that is
    included still contains P/Invokes instead of the `JniNativeMethods`
    class.  Update `Java.Interop.csproj` so that `$(Standalone)`=true
    is now the default.  (This *shouldn't* break anybody, but… it now means
    the P/Invoke backend is getting NO testing.  ¯\_(ツ)_/¯ )
    
    The next part is where it blows up GOOD: `options.CreateJreVM()`
    throws, which aborts everything:
    
    	% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App)
    	Hello from Java!
    	# jonp: JNI_OnLoad: vm=10a9c0b00
    	# jonp: JNI_OnLoad: created options…
    	# jonp: builder.InvocationPointer=10a9c0b00
    	JNI_OnLoad: error: System.TypeInitializationException: A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.
    	 ---> System.NotSupportedException: 'Java.Interop.ManagedPeer+ConstructMarshalMethod' is missing delegate marshalling data. This can happen for code that is not compatible with AOT. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
    	   at Internal.Runtime.CompilerHelpers.RuntimeInteropData.GetDelegateMarshallingStub(RuntimeTypeHandle, Boolean) + 0x78
    	   at System.Runtime.InteropServices.PInvokeMarshal.AllocateThunk(Delegate del) + 0x6b
    	   at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValueLocked(TKey, ConditionalWeakTable`2.CreateValueCallback) + 0x27
    	   at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValue(TKey, ConditionalWeakTable`2.CreateValueCallback) + 0x41
    	   at System.Runtime.InteropServices.PInvokeMarshal.GetFunctionPointerForDelegate(Delegate) + 0xd5
    	   at libHello-NativeAOTFromJNI!<BaseAddress>+0x8adeb
    	   at Java.Interop.JniEnvironment.Types._RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], Int32) + 0x90
    	   at Java.Interop.JniEnvironment.Types.RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], Int32) + 0x65
    	   at Java.Interop.JniType.RegisterNativeMethods(JniNativeMethodRegistration[]) + 0x30
    	   at Java.Interop.ManagedPeer..cctor() + 0x175
    	   at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb4
    	   --- End of inner exception stack trace ---
    	   at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x147
    	   at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0x9
    	   at Java.Interop.JniRuntime..ctor(JniRuntime.CreationOptions) + 0x5a2
    	   at Java.Interop.JreRuntime..ctor(JreRuntimeOptions) + 0x22
    	   at Hello_NativeAOTFromJNI.JNIEnvInit.JNI_OnLoad(IntPtr, IntPtr) + 0x9e
    	Exception in thread "main" java.lang.UnsatisfiedLinkError: unsupported JNI version 0x00000000 required by /Volumes/Xamarin-Work/src/xamarin/Java.Interop/samples/Hello-NativeAOTFromJNI/bin/Release/osx-x64/publish/libHello-NativeAOTFromJNI.dylib
    		at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
    		at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:388)
    		at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:232)
    		at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:174)
    		at java.base/jdk.internal.loader.NativeLibraries.findFromPaths(NativeLibraries.java:315)
    		at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:287)
    		at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2427)
    		at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:818)
    		at java.base/java.lang.System.loadLibrary(System.java:1989)
    		at com.microsoft.hello_from_jni.NativeAOTInit.<clinit>(NativeAOTInit.java:5)
    		at com.microsoft.hello_from_jni.App.main(App.java:7)
    
    Within the `JniRuntime` constructor, we're hitting:
    
    	#if !XA_JI_EXCLUDE
    		ManagedPeer.Init ();
    	#endif  // !XA_JI_EXCLUDE
    
    This invokes the `ManagedPeer` static constructor, which attempts to
    call `JniType.RegisterNativeMethods()`, which attempts to call
    `JNIEnv::RegisterNatives()`, but before it can get that far it needs
    to marshal things:
    
     1. The `JniNativeMethodRegistration` struct, which in turn requires
     2. The `JniNativeMethodRegistration.Marshaler` delegate field.
    
    This apparently isn't supported by NativeAOT, at least not without
    additional work that I am not currently privy to.
    
    Given that `JniNativeMethodRegistration` structure marshaling is how
    *all* JNI method registration is done in .NET Android, this is a bit
    of a blocker for this sample.
    
    TODO? Figure out how to allow NativeAOT to marshal
    `JniNativeMethodRegistration` with a minimum of effort?
    
    TODO? *Require* `jnimarshalmethod-gen`, and update it so that it
    emits `[UnmanagedCallersOnlyAttribute]` on all generated marshal
    methods *and* emits `UnmanagedCallersOnlyAttribute.EntryPoint` to
    a `Java_…` symbol name so that we *don't* hit the
    `JniNativeMethodRegistration` struct marshaling codepath.
    (See also 77800dd).
    
    [0]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives
    jonpryor committed Oct 25, 2023
    Configuration menu
    Copy the full SHA
    db84cc3 View commit details
    Browse the repository at this point in the history
  2. icanhaz blittable method registration?

    Commit db84cc3 "didn't work" because the "traditional" form of
    method registration through `JNIEnv::RegisterNatives()` used a
    `System.Delegate` instance to refer to a C# method to invoke, but
    this fails under NativeAOT because the NativeAOT infrastructure needs
    to be able to produce "stubs" for the methods based on compile-time
    scans, and it doesn't represent our code pattern and/or our pattern
    is not supportable with how NativeAOT works.
    
    Oops.
    
    "Ecosystem-wise", I'm not sure how to solve this, other than
    handy-wavy ideas such as "rely on `jnimarshalmethod-gen`!".
    
    Short-term, we can at least resolve `ManagedPeer`.
    
    Create a new `JniBlittableNativeMethodRegistration` struct which
    contains only blittable types, vs. `JniNativeMethodRegistration`
    which contains `string` fields and `Delegate` fields and is not
    easily supportable by NativeAOT.
    
    	public partial struct JniBlittableNativeMethodRegistration {
    	    public JniBlittableNativeMethodRegistration (IntPtr name, IntPtr signature, IntPtr marshaler);
    	    // To support `new JniBlittableNativeMethodRegistration("name"u8, "signature"u8, marshaler)`
    	    public JniBlittableNativeMethodRegistration (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature, IntPtr marshaler);
    	}
    
    "Plumb through" use of `JniBlittableNativeMethodRegistration` to
    `JniEnvironment.Types.RegisterNatives()` overloads:
    
    	partial class JniEnvironment {
    	    partial class Types {
    	        public static void RegisterNatives(JniObjectReference type, ReadOnlySpan<JniBlittableNativeMethodRegistration> methods);
    	    }
    	}
    
    This in turn requires updating `jnienv-gen` so that
    `RegisterNatives()` is bound with the `const JNINativeMethod *methods`
    parameter bound as an `IntPtr` instead of a
    `JniNativeMethodRegistration[]`.
    
    Update the `ManagedPeer` static constructor to use
    `[UnmanagedCallersOnly]` methods and native function pointers to
    register the marshal methods that `ManagedPeer` requires.
    jonpryor committed Oct 25, 2023
    Configuration menu
    Copy the full SHA
    a32e244 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    4c30ab5 View commit details
    Browse the repository at this point in the history
  4. Fix build break.

    jonpryor committed Oct 25, 2023
    Configuration menu
    Copy the full SHA
    a08dce8 View commit details
    Browse the repository at this point in the history
  5. Use [UnmanagedFunctionPointer]

    Commit db84cc3 noted that the sample failed to run because
    NativeAOT didn't know how to produce the required "stubs" to allow
    a `System.Delegate` field to be marshaled.
    
    Commit a32e244 was an attempt to "fix" this by introducing a
    `JniBlittableNativeMethodRegistration` and using C#9 function pointers
    to register `ManagedPeer` marshal methods instead of delegates.
    While this works, this is an extensive change, and leaves various
    "corner cases" in how things fit together, such as the
    `JniNativeMethodRegistrationArguments` struct used as part of
    `jnimarshalmethod-gen`-based method registration.
    
    @lambdageek found an easier solution: place
    `[UnmanagedFunctionPointer]` onto the delegate declaration.  This
    appears to tell NativeAOT to generate the required stubs, with
    significantly fewer changes.  This also could fit nicely into a future
    `generator` change to place `[UnmanagedFunctionPointer]` on all the
    `_JniMarshal_*` declarations (56955d9).
    jonpryor committed Oct 25, 2023
    Configuration menu
    Copy the full SHA
    da9f188 View commit details
    Browse the repository at this point in the history

Commits on Nov 2, 2023

  1. "Full(er)" sample

    What `Hello-NativeAOTFromJNI` previously did was quite minimal:
    
     1. Use `[UnmanagedCallersOnly]` to provide a `JNI_OnLoad()` method which
     2. Initialized the Java.Interop runtime, allowing
     3. An `[UnmanagedCallersOnly]`-provided `Java_…` method which is called from Java.
    
    All quite low level, all miles away from .NET Android.
    
    Expand the sample to:
    
     1. Contain a `Java.Lang.Object` subclass, which contains a
        `[JavaCallable]` method.
     2. Call `jcw-gen` to generate Java Callable Wrappers for (1),
        containing the `[JavaCallable]` method.
     3. Call `jnimarshalmethod-gen` to generate marshal methods for (1),
        as NativeAOT doesn't support System.Reflection.Emit.
     4. Instantiate (1) *from Java*, and invoke the `[JavaCallable]` method.
    
    *Now* we're (kinda) getting something that looks like .NET Android.
    
    But first, we need to make that *work*:
    
    Update `Java.Interop.Tools.JavaCallableWrappers` so that it will emit
    `native` method declarations for `[JavaCallable]` methods, not just
    method overrides and `[Export]` methods.
    
    Update `Java.Interop.Tools.Expressions` so that the `_JniMarshal_*`
    delegate types have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`,
    as this is what allows NativeAOT to emit appropriate "stubs"; see
    also da9f188.
    
    Update `Java.Interop.Tools.Expressions.ExpressionAssemblyBuilder` to
    no longer attempt to "remove and fixup" `System.Private.CoreLib`.
    So long as `ExpressionAssemblyBuilder` output is *only* used in
    "completed" apps (not distributed in NuGet packages in some
    "intermediate" form), referencing `System.Private.CoreLib` is "fine".
    Additionally, trying to remove `System.Private.CoreLib` broke things
    when adding `[UnmanagedFunctionPointer]`, as `CallingConvention`
    could not be resolved, resulting in `jnimarshalmethod-gen` erroring
    out with:
    
    	error JM4006: jnimarshalmethod-gen: Unable to process assembly '/Volumes/Xamarin-Work/src/xamarin/Java.Interop/samples/Hello-NativeAOTFromJNI/bin/Debug/Hello-NativeAOTFromJNI.dll'
    	Failed to resolve System.Runtime.InteropServices.CallingConvention
    	Mono.Cecil.ResolutionException: Failed to resolve System.Runtime.InteropServices.CallingConvention
    	   at Mono.Cecil.Mixin.CheckedResolve(TypeReference self)
    	   at Mono.Cecil.SignatureWriter.WriteCustomAttributeEnumValue(TypeReference enum_type, Object value)
    	   …
    
    (This is because `CallingConvention` is in
    `System.Runtime.InteropServices.dll`, which isn't referenced.)
    
    We could "fix" this by explicitly adding a reference to
    `System.Runtime.InteropServices.dll`, but this is just one of an
    unknown number of corner cases.  Give up for now.
    
    Update `jnimarshalmethod-gen` assembly location probing: it
    was given the *full assembly name* of `Java.Base`:
    
    	# jonp: resolving assembly: Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null
    
    …and failing to find `Java.Base.dll`, because it was looking for
    `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null.dll`.
    Oops.
    
    Use `AssemblyName` to parse the string and extract out the assembly
    name., so that `Java.Base.dll` is probed for and found.
    
    With all that…it still fails:
    
    	% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App)
    	Hello from Java!
    	C# init()
    	Hello from .NET NativeAOT!
    	String returned to Java: Hello from .NET NativeAOT!
    	Exception in thread "main" com.xamarin.java_interop.internal.JavaProxyThrowable: System.IO.FileNotFoundException: Could not resolve assembly 'Hello-NativeAOTFromJNI'.
    	   at System.Reflection.TypeNameParser.ResolveAssembly(String) + 0x97
    	   at System.Reflection.TypeNameParser.GetType(String, ReadOnlySpan`1, String) + 0x32
    	   at System.Reflection.TypeNameParser.NamespaceTypeName.ResolveType(TypeNameParser&, String) + 0x17
    	   at System.Reflection.TypeNameParser.GetType(String, Func`2, Func`4, Boolean, Boolean, Boolean, String) + 0x99
    	   at Java.Interop.ManagedPeer.RegisterNativeMembers(IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, IntPtr n_assemblyQualifiedName, IntPtr n_methods) + 0x103
    		at com.xamarin.java_interop.ManagedPeer.registerNativeMembers(Native Method)
    		at example.ManagedType.<clinit>(ManagedType.java:15)
    		at com.microsoft.hello_from_jni.App.main(App.java:13)
    
    `App.main()` has `new example.ManagedType()`, which hits the
    `ManagedType` static constructor of:
    
    	public /* partial */ class ManagedType
    	  extends java.lang.Object
    	  implements
    	    com.xamarin.java_interop.GCUserPeerable
    	{
    	/** @hide */
    	  public static final String __md_methods;
    	  static {
    	    __md_methods =
    	      "n_GetString:()Ljava/lang/String;:__export__\n" +
    	      "";
    	    com.xamarin.java_interop.ManagedPeer.registerNativeMembers (ManagedType.class, "Example.ManagedType, Hello-NativeAOTFromJNI", __md_methods);
    	  }
    	}
    
    The `ManagedPeer.registerNativeMembers()` call is what is needed
    to register the native `ManagedPeer.getString()` method, so that
    it can be called.  This is good.  (Though `__md_methods` containing
    *anything* is not desired, but that's a different problem.)
    
    `ManagedPeer.RegisterNativeMembers()` is given the assembly-qualified
    name `Example.ManagedType, Hello-NativeAOTFromJNI`, and tries to:
    
    	Type.GetType ("Example.ManagedType, Hello-NativeAOTFromJNI", throwOnError: true);
    
    …which then proceeds to throw, because in NativeAOT
    *there are no assemblies*, and thus `Type.GetType()` *cannot work*.
    
    Oops.
    
    Thus, the only way to make something remotely like .NET Android
    infrastructure work is to *require* the use of `Java_…` native method
    names and `[UnmanagedCallersOnly]` on marshal methods.
    (In .NET Android parlance, the experimental
    `$(AndroidEnableMarshalMethods)`=True is required.)
    jonpryor committed Nov 2, 2023
    Configuration menu
    Copy the full SHA
    69c90ba View commit details
    Browse the repository at this point in the history
  2. Use JniTransition.

    jonpryor committed Nov 2, 2023
    Configuration menu
    Copy the full SHA
    1fc0af5 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    a499a1d View commit details
    Browse the repository at this point in the history
  4. Make it (mostly) work!

    Commit 69c90ba expanded into a "Full(er)" sample, with just one issue:
    it didn't fully work:
    
    	Exception in thread "main" com.xamarin.java_interop.internal.JavaProxyThrowable: System.IO.FileNotFoundException: Could not resolve assembly 'Hello-NativeAOTFromJNI'.
    	   at System.Reflection.TypeNameParser.ResolveAssembly(String) + 0x97
    	   at System.Reflection.TypeNameParser.GetType(String, ReadOnlySpan`1, String) + 0x32
    	   at System.Reflection.TypeNameParser.NamespaceTypeName.ResolveType(TypeNameParser&, String) + 0x17
    	   at System.Reflection.TypeNameParser.GetType(String, Func`2, Func`4, Boolean, Boolean, Boolean, String) + 0x99
    	   at Java.Interop.ManagedPeer.RegisterNativeMembers(IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, IntPtr n_assemblyQualifiedName, IntPtr n_methods) + 0x103
    	        at com.xamarin.java_interop.ManagedPeer.registerNativeMembers(Native Method)
    	        at example.ManagedType.<clinit>(ManagedType.java:15)
    	        at com.microsoft.hello_from_jni.App.main(App.java:13)
    
    The problem is that `ManagedPeer.RegisterNativeMembers()` calls
    `Type.GetType("Example.ManagedType, Hello-NativeAOTFromJNI")`, which
    throws `FileNotFoundException`.
    
    Let's attempt to fix that:
    
    Update `MangedPeer.RegisterNativeMembers()` to call the (new!) method:
    
    	partial class JniRuntime {
    	  partial class JniTypeManager {
    	    public virtual void RegisterNativeMembers (
    	        JniType nativeClass,
    	        ReadOnlySpan<char> assmblyQualifiedTypeName,
    	        ReadOnlySpan<char> methods);
    	  }
    	}
    
    which allows a subclass to *avoid* the `Type.GetType()` call.
    
    Add a `NativeAotTypeManager` class which subclasses
    `JniRuntime.JniTypeManager`, overriding `RegisterNativeMembers()`
    so as to avoid the `Type.GetType()` call.  (It also "fakes" its own
    "typemap" implementation…)
    
    Add `Hello-NativeAOTFromJNI.xml`, a Linker Descriptor, to preserve
    the `JavaException` constructors, which are needed when things
    break horrifically.
    
    TODO: figure out the appropriate `DynamicDependencyAttribute`
    incantations to replace `Hello-NativeAOTFromJNI.xml`.
    
    Update the `_AddMarshalMethods` build task to *also* update
    `$(IntermediateOutputPath)$(TargetFileName)`, as the copy in
    `$(IntermediateOutputPath)` is used by the `IlcCompile` target.
    *Not* updating the copy in `$(IntermediateOutputPath)` means that
    we don't get *any* marshal methods, and things break.
    
    Rename `ExpressionAssemblyBuilder.CreateRegistrationMethod()` to
    Rename `ExpressionAssemblyBuilder.AddRegistrationMethod()`, so that
    the `EmitConsoleWriteLine()` invocation can provide the *full*
    type name of the `__RegisterNativeMembers()` method, which helps
    when there is more than one such method running around…
    
    Various changes to `JniRuntime.JniTypeManager.cs`, to increase
    logging verbosity, and to make the optimization effort in
    `TryLoadJniMarshalMethods()` actually work; `Type.GetRuntimeMethod()`
    *will not find* non-public methods, and `__RegisterNativeMembers()`
    is rarely/never public.  Thus, it was basically dead code, and we'd
    always fallback to the "look at all methods and see which ones have
    `[JniAddNativeMethodRegistration]`" codepath, which is by definition
    slower.  Use `Type.GetMethod()` instead.
    
    Update `jnimarshalmethod-gen` & co so that they're consistent with
    the output of `jcw-gen`.  Without these changes, the JCW would declare
    `n_GetString()`, while `jnimarshalmethod-gen` would try to register
    `getString`, and Java would thrown an exception because there is no
    `getString` member to register.  Doh!
    
    Finally, and the one thing that keeps this from being "perfect",
    add an "activation constructor"
    `Example.ManagedType(ref JniObjectReference, JniObjectReferenceOptions)`.
    
    This constructor is currently needed because "JavaInterop1"-style
    Java Callable Wrappers don't contain constructors (?!), so no
    `Example.ManagedType` instance is created *until* the
    `ManagedType.n_GetString()` marshal method is executed and attempts
    to invoke the `ManagedType.GetString()` method.
    
    We'll need to update `jcw-gen` & related to address this.
    
    Current output:
    
    	% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App)
    	Hello from Java!
    	C# init()
    	Hello from .NET NativeAOT!
    	String returned to Java: Hello from .NET NativeAOT!
    	C# RegisterNativeMembers(JniType(Name='example/ManagedType' PeerReference=0x7fe545812ed8/G), "Example.ManagedType, Hello-NativeAOTFromJNI", "n_GetString:()Ljava/lang/String;:__export__
    	")
    	# jonp: called `Example.ManagedType/__<$>_jni_marshal_methods.__RegisterNativeMembers()` w/ 1 methods to register.
    	mt.getString()=Hello from C#, via Java.Interop!
    jonpryor committed Nov 2, 2023
    Configuration menu
    Copy the full SHA
    e1822f0 View commit details
    Browse the repository at this point in the history

Commits on Nov 7, 2023

  1. Fix Java.Interop.Export-Tests

    A funny thing happened on the way through CI:
    `Java.Interop.Export-Tests` *with* `jnimarshalmethod-gen` failed!
    
    	  Failed AddExportMethods [169 ms]
    	  Error Message:
    	   Java.Interop.JavaException : Could not initialize class com.xamarin.interop.export.ExportType
    	  Stack Trace:
    	     at Java.Interop.JniEnvironment.StaticMethods.GetStaticMethodID(JniObjectReference type, String name, String signature) in /Users/runner/work/1/s/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 21407
    	   at Java.Interop.JniType.GetStaticMethod(String name, String signature) in /Users/runner/work/1/s/src/Java.Interop/Java.Interop/JniType.cs:line 315
    	   at Java.InteropTests.MarshalMemberBuilderTest.AddExportMethods()
    	   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
    	   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
    	  --- End of managed Java.Interop.JavaException stack trace ---
    	java.lang.NoClassDefFoundError: Could not initialize class com.xamarin.interop.export.ExportType
    
    The cause?  e1822f0 updated `jnimarshalmethod-gen`:
    
    	- registrations.Add (new ExpressionMethodRegistration (name, signature, mmDef));
    	+ // Assume that `JavaCallableAttribute.Name` is "public" JCW name, and JCW's declare `n_`-prefixed `native` methods…
    	+ registrations.Add (new ExpressionMethodRegistration ("n_" + method.Name, signature, mmDef));
    
    That is, instead of attempting to register e.g.
    `ExportType.action()V` (via `[JavaCallable("action")]`), it was
    instead attempting to register `ExportType.n_action()V`, because of
    the introduced `"n_" + method.Name` change.
    
    The "problem" is that `jnimarshalmethod-gen` was written in the
    context of Java.Interop, *not* .NET Android, and the existing
    `Java.Interop.Export-Tests` unit tests assume that there is no
    `n_`-prefix on native method declarations.
    
    There are two plausible solutions: update the unit tests to conform
    to existing .NET Android convention, and use an `n_` prefix on
    `native` method declarations.
    
    Or update `jcw-gen` so that when using
    `jcw-gen --codegen-target=JavaInterop1`, the `JavaCallableAttribute.Name`
    value is used for the `native` method declaration.
    
    Because @jonpryor is a glutton for punishment and "cleanliness",
    let's try the latter.  `JavaCallableWrapperGenerator` already has
    a `.CodeGenerationTarget` property, How Hard Could It Be™?
    
    Turns out, harder than expected: `JavaCallableWrapperGenerator`
    was doing *lots* of work in its constructor, including the all
    important bit of populating `Signature` values, and the constructor
    runs *before* the `.CodeGenerationTarget` property is set.
    This is a somewhat straightforward fix: turn most of the
    `JavaCallableWrapperGenerator` constructor into a `Initialize()`
    method, and call `Initialize()` from the public methods.
    This creates a semantic change: some exceptions which were thrown
    by the constructor are now thrown from the public methods.
    We deem this as acceptable because all known uses of
    `JavaCallableWrapperGenerator` ~immediately call `.Generate()` after
    creating the instance, so the exception will still be thrown from a
    site near where it previously would have been thrown.
    
    The more annoying aspect is `Signature` initialization: we need to
    pass in `.CodeGenerationTarget`, which is Yet Another Constructor
    Parameter, and we already have A Lot™.  Attempt to bring sanity to
    this mess by introducing a new `SignatureOptions` type to hold the
    `Signature` parameters.
    
    Update `jnimarshalmethod-gen` to undo the change which broke
    `Java.Interop.Export-Tests`.
    
    Update `tests/Java.Interop.Tools.JavaCallableWrappers-Tests` to
    add a test for `.CodeGenerationTarget==JavaInterop1`.
    
    Add `$(NoWarn)` to `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj`
    in order to "work around" the warnings-as-errors:
    
    	…/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(19,63): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name
    	…/src/Java.Interop.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs(53,4): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name
    	…/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(12,16): error CA1813: Avoid unsealed attributes
    	…
    
    This is "weird"; the warnings/errors appear to come in because
    `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` now has:
    
    	<Compile Include="..\..\src\Java.Interop\Java.Interop\JniTypeSignatureAttribute.cs" />
    
    which appears to pull in `src/Java.Interop/.editorconfig`, which makes
    CA1019 and CA1813 errors.
    
    (insert massively confused face here.  Like, wat?)
    jonpryor committed Nov 7, 2023
    Configuration menu
    Copy the full SHA
    9027591 View commit details
    Browse the repository at this point in the history
  2. Merge remote-tracking branch 'origin/main' into dev/jonp/jonp-hello-f…

    …rom-jni
    
    All hail the new interface invokers in 1adb796!
    jonpryor committed Nov 7, 2023
    Configuration menu
    Copy the full SHA
    1d422da View commit details
    Browse the repository at this point in the history
  3. Remove more n_s?!

    9027591 tried to fix
    Java.Interop.Export-Tests, and in the process broke
    Java.Base-Tests!
    
    	A total of 1 test files matched the specified pattern.
    	  Failed InterfaceInvokerMethod [100 ms]
    	  Error Message:
    	   Java.Interop.JavaException : 'void example.MyIntConsumer.accept(int)'
    	  Stack Trace:
    	     at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 20370
    	   at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeAbstractVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) in /Users/runner/work/1/s/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 47
    	   at Java.Lang.IRunnableInvoker.Run() in /Users/runner/work/1/s/src/Java.Base/obj/Release-net7.0/mcw/Java.Lang.IRunnable.cs:line 34
    	   at Java.BaseTests.JavaToManagedTests.InterfaceInvokerMethod() in /Users/runner/work/1/s/tests/Java.Base-Tests/Java.Base/JavaToManagedTests.cs:line 28
    	   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
    	   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
    	  --- End of managed Java.Interop.JavaException stack trace ---
    	java.lang.UnsatisfiedLinkError: 'void example.MyIntConsumer.accept(int)'
    		at example.MyIntConsumer.accept(Native Method)
    		at com.microsoft.java_base_tests.Invoker$1.run(Invoker.java:15)
    
    The problem? That pesky `n_`!  Or rather, the lack thereof this time.
    
    The issue is that `JreTypeManager`/etc. and `jcw-gen` need to be consistent.
    9027591 altered the consistency,
    breaking Java.Base tests.
    
    Commit this to verify that things work on CI, not just locally.
    
    Assuming it works on CI, next step will be to largely revert
    9027591, and then fix
    `Java.Interop.Export-Tests` to use an `n_` prefix on all `native`
    method declarations.  That bit is hand-written; it can change.
    Keeping the tooling consistent is more important.
    jonpryor committed Nov 7, 2023
    Configuration menu
    Copy the full SHA
    902fe28 View commit details
    Browse the repository at this point in the history
  4. Enable activation

    Invoking the Java-side constructor should invoke the
    C#-side constructor too!  And now it does!
    
        Hello from Java!
        C# init()
        Hello from .NET NativeAOT!
        String returned to Java: Hello from .NET NativeAOT!
        C# RegisterNativeMembers(JniType(Name='example/ManagedType' PeerReference=0x7f9392713098/G), "Example.ManagedType, Hello-NativeAOTFromJNI", "getString:()Ljava/lang/String;:__export__
        ")
        # jonp: called `Example.ManagedType/__<$>_jni_marshal_methods.__RegisterNativeMembers()` w/ 1 methods to register.
        # jonp: ActivatePeer( False, 0x700001bc6a70/I, Void .ctor(), System.Object[])
        mt.getString()=Hello from C#, via Java.Interop!
    jonpryor committed Nov 7, 2023
    Configuration menu
    Copy the full SHA
    0392169 View commit details
    Browse the repository at this point in the history
  5. Allow marking constructors as callable from Java.

    Continuing on the "activation constructor" train, we want to allow
    Java to pass parameters to a constructor.
    
    In .NET Android parlance:
    
    	class Example : Java.Lang.Object {
    	    [Export]
    	    public Example (int value) {}
    	}
    
    Commit 9027591 updated `JavaCallableAttribute` to allow it to be
    placed on constructors, but if we try that with
    `samples/Hello-NativeAOTFromJNI`, the Java Callable Wrapper
    doesn't build:
    
    	// JCW
    	/* partial */ class Example extends java.lang.Object {
    	    public Example (int p0) {
    	        super (p0);
    	        if (getClass () == Example.class) ManagedPeer.construct(…);
    	    }
    	}
    
    To make this work, we need `ExportAttribute.SuperArgumentsString`.
    
    We *could* add this to `JavaCallableAttribute`, but that means
    we'd have a property which can only be used from constructors.
    (Yes, `ExportAttribute` has this "wonkiness", but that doens't mean
    we should continue it!)
    
    Add a new `JavaCallableConstructorAttribute`, which has a new
    `SuperConstructorExpression` property, along with `jcw-gen` support.
    This allows things to compile:
    
    	// C#
    	class Example : Java.Lang.Object {
    	    [JavaCallableConstructor (SuperConstructorExpression="")]
    	    public Example (int value) {}
    	}
    
    	// JCW
    	/* partial */ class Example extends java.lang.Object {
    	    public Example (int p0) {
    	        super ();
    	        if (getClass () == Example.class) ManagedPeer.construct(…);
    	    }
    	}
    
    …but it's not quite right yet, because the wrong constructor is
    invoked!
    
    	ManagedPeer.construct (
    	    /* self */      this,
    	    /* aqn */       "Namespace.Example, Assembly",
    	    /* ctor sig */  "",
    	    /* arguments */ new java.lang.Object[] { p0 }
    	);
    
    Oops!
    
    Update the `Signature`/`SignatureOptions` creation within
    `JavaCallableWrapperGenerator.cs` so that `ManagedParameters`
    is set for *all* constructor codepaths.
    
    	ManagedPeer.construct (
    	    /* self */      this,
    	    /* aqn */       "Namespace.Example, Assembly",
    	    /* ctor sig */  "System.Int32, System.Runtime",
    	    /* arguments */ new java.lang.Object[] { p0 }
    	);
    
    This appears to fix a long-standing bug/"thinko" in JCW generation!
    jonpryor committed Nov 7, 2023
    Configuration menu
    Copy the full SHA
    93a901a View commit details
    Browse the repository at this point in the history

Commits on Nov 8, 2023

  1. Partially revert 9027591

    Remember 902fe28?
    
    > Assuming it works on CI, next step will be to largely revert
    > 9027591, and then fix
    > `Java.Interop.Export-Tests` to use an `n_` prefix on all `native`
    > method declarations.  That bit is hand-written; it can change.
    > Keeping the tooling consistent is more important.
    
    Let's Do It!  Revert most of the `JavaCallableWrapperGenerator`
    bits of 9027591.
    
    Update the `JavaInterop1` JCW output to conform, using `n_`-prefixed
    native method declarations.
    
    *Retain the constructor signature fix* in 93a901a..
    
    Revert the `JreTypeManager` change in 902fe28.
    
    Update `jnimarshalmethod-gen` to register `n_`-prefixed names.
    
    Update `Java.Interop.Export-Tests` so that native method declarations
    now have `n_` prefixes, so that all is in sync.
    jonpryor committed Nov 8, 2023
    Configuration menu
    Copy the full SHA
    a50457b View commit details
    Browse the repository at this point in the history

Commits on Nov 9, 2023

  1. Rethink Type.GetType() alternatives

    Remember the one-off comment in 93a901a?
    
    > (Possible "quick hack": replace `Type.GetType()` use with calls to something
    > on `JniRuntime.JniTypeManager`, allowing a subclass to provide its own
    > mapping?  This feels "duplicative" of dotnet/runtime, though.)
    
    The more I think about it, the saner this feels.
    
    Add `JniRuntime.JniTypeManager.GetTypeFromAssemblyQualifiedName()`,
    and use that instead of `Type.GetType()`.  This allows
    `NativeAotTypeManager` to provide its equivalent functionality.
    
    This in turn allows `ManagedType` to provide a constructor which
    takes parameters, which previously broke because that codepath hit
    `Type.GetType()`!
    jonpryor committed Nov 9, 2023
    Configuration menu
    Copy the full SHA
    ce850ca View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    6920e6c View commit details
    Browse the repository at this point in the history

Commits on Nov 13, 2023

  1. Configuration menu
    Copy the full SHA
    4ceaa34 View commit details
    Browse the repository at this point in the history

Commits on Nov 14, 2023

  1. Configuration menu
    Copy the full SHA
    cf73d77 View commit details
    Browse the repository at this point in the history
  2. Reduce patch size.

    Some things were split into other PRs…
    jonpryor committed Nov 14, 2023
    Configuration menu
    Copy the full SHA
    986f0c4 View commit details
    Browse the repository at this point in the history

Commits on Nov 15, 2023

  1. Work with .NET 8 GA (…?)

    The Hello-NativeAOTFromJNI sample previously built and worked on
    .NET 8 RC2 on macOS+x64, but when I tried things on .NET 8 GA on
    macOS+arm64, things failed "inexplicably"
    
    	% dotnet publish -c Release -r osx-x64
    	EXEC : error JM4006: jnimarshalmethod-gen: Unable to process assembly '/Users/jon/Developer/src/xamarin/java.interop/samples/Hello-NativeAOTFromJNI/bin/Release/osx-x64/Hello-NativeAOTFromJNI.dll' [/Users/jon/Developer/src/xamarin/java.interop/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj]
    	  Could not load file or assembly 'Hello-NativeAOTFromJNI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
    	  System.IO.FileLoadException: Could not load file or assembly 'Hello-NativeAOTFromJNI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
    	     at System.Runtime.Loader.AssemblyLoadContext.InternalLoad(ReadOnlySpan`1 arrAssembly, ReadOnlySpan`1 arrSymbols)
    	     at Xamarin.Android.Tools.JniMarshalMethodGenerator.App.CreateMarshalMethodAssembly(String path) in /Users/jon/Developer/src/xamarin/java.interop/tools/jnimarshalmethod-gen/App.cs:line 445
    	     at Xamarin.Android.Tools.JniMarshalMethodGenerator.App.ProcessAssemblies(List`1 assemblies) in /Users/jon/Developer/src/xamarin/java.interop/tools/jnimarshalmethod-gen/App.cs:line 272
    
    After a few days of pulling my hair out, @lambdageek suggested:
    
    > so maybe coreclr on arm64 refuses to load a PE32+ image that is x86-64?
    
    This turns out to be the case, presenting two "workarounds":
    
     1. Build with an rid that matches the host platform, e.g. this worked:
    
            dotnet publish -c Release -r osx-arm64
    
     2. Setting `$(PlatformTarget)`=AnyCPU also works, as the resulting
        assembly isn't tied to a specific rid.
    
    Update `Hello-NativeAOTFromJNI.csproj` to set
    `$(PlatformTarget)`=AnyCPU, so that I can at least *build*
    osx-x64 from osx-arm64.  (I can't *run* it, but I can build it!)
    
    Also update `jnimarshalmethod-gen` to not load assemblies from
    directories containing `/ref/`, as reference assemblies cannot be
    loaded for execution:
    
    	System.ArgumentException: A BadImageFormatException has been thrown while parsing the signature. This is likely due to lack of a generic context. Ensure genericTypeArguments and genericMethodArguments are provided and contain enough context.
    	 ---> System.BadImageFormatException: Could not load file or assembly 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Reference assemblies cannot be loaded for execution. (0x80131058)
    	File name: 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' ---> System.BadImageFormatException: Could not load file or assembly 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL'. Reference assemblies cannot be loaded for execution. (0x80131058)
    	File name: 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL' ---> System.BadImageFormatException: Cannot load a reference assembly for execution.
    	   at System.Runtime.Loader.AssemblyLoadContext.<LoadFromPath>g____PInvoke|5_0(IntPtr ptrNativeAssemblyBinder, UInt16* ilPath, UInt16* niPath, ObjectHandleOnStack retAssembly)
    	   at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
    	   at System.Reflection.Assembly.LoadFile(String path)
    jonpryor committed Nov 15, 2023
    Configuration menu
    Copy the full SHA
    17a4403 View commit details
    Browse the repository at this point in the history

Commits on Nov 16, 2023

  1. Use net.dot.jni package prefix

    It's shorter! It avoids `_1` in Java method names!
    jonpryor committed Nov 16, 2023
    Configuration menu
    Copy the full SHA
    1b86f9a View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    f71cadb View commit details
    Browse the repository at this point in the history
  3. Don't add new public API

    I'm not sure that `GetTypeFromAssemblyQualifiedName()` is
    actually a good idea or not, so I'd rather not add it as public API,
    yet it's required in order to make the sample work?
    
    How do we square this circle?
    
    With build configs (redux!).  Commit c6c487b added the
    "Standalone" build config, while commit db84cc3 noted that
    it didn't quite work right, because "just" using
    `AdditionalProperties="Standalone=true"` didn't result in a
    `Java.Interop.dll` which had the right build config.
    
    Figure this out.  The magic voodoo is that the output directories
    need to differ for each build config, because reusing the same
    output path leads to "madness" (parallel builds overwrite files,
    resulting in possible build breaks, etc., etc.).
    
    The key properties that need to differ based on "build config" are:
    
      * `$(IntermediateOutputPath)`, which contains intermediate
        files like AssemblyInfo!
      * `$(OutputPath)`, which is where the final assemblies are
        written.
    
    Update `Java.Interop.csproj` to treat `$(UseNativeAot)` as a
    "distinct" build config, which also sets `FEATURE_NATIVE_AOT`.
    This allows `GetTypeFromAssemblyQualifiedName()` to be
    `internal` by default, and only public when used by/built from
    the Hello-NativeAOTFromJNI sample.
    
    The next new public API is `JniRuntime.UseMarshalMemberBuilder`.
    We can make that `internal` for now by making
    `Java.Runtime.Environment.dll` a "friend" assembly to
    `Java.Interop.dll` via `[InternalsVisibleTo]`, then fix up the
    resulting build errors (as both assemblies had `NativeMethods` types).
    
    Remove the inclusion of `ManagedValueManager.cs` from
    `Hello-NativeAOTFromJNI.csproj`.  It's no longer needed as of
    commit 6920e6c.
    jonpryor committed Nov 16, 2023
    Configuration menu
    Copy the full SHA
    b5fc4cf View commit details
    Browse the repository at this point in the history
  4. Reduce patch size.

    jonpryor committed Nov 16, 2023
    Configuration menu
    Copy the full SHA
    4741dc9 View commit details
    Browse the repository at this point in the history
  5. Remove more unneeded bits.

    jonpryor committed Nov 16, 2023
    Configuration menu
    Copy the full SHA
    8eb2850 View commit details
    Browse the repository at this point in the history

Commits on Nov 23, 2023

  1. Configuration menu
    Copy the full SHA
    c3e4870 View commit details
    Browse the repository at this point in the history

Commits on Nov 24, 2023

  1. Configuration menu
    Copy the full SHA
    aeed449 View commit details
    Browse the repository at this point in the history
  2. Remove GetTypeFromAssemblyQualifiedName()

    Fixes: #1165
    Context: #1157
    
    If we more strongly rely on JNI signatures, we can remove the need
    for Java Callable Wrappers to contain assembly-qualified type names,
    thus removing the need for `ManagedPeer` to use `Type.GetType()`
    entirely, removing the [IL2057][0] warnings.
    
    Furthermore, if we add `[DynamicallyAccessedMembers]` to
    `JniRuntime.JniTypeManager.GetType()`, we can fix some [IL2075][1]
    warnings which appeared after fixing the IL2057 warnings.
    
    Aside: Excising assembly-qualified type names from Java Callable
    Wrappers had some "interesting" knock-on effects in the unit tests,
    requiring that more typemap information be explicitly provided.
    (This same information was *implicitly* provided before, via the
    provision of assembly-qualified type names everywhere…)
    
    [0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/IL2057
    [1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/il2075
    jonpryor committed Nov 24, 2023
    Configuration menu
    Copy the full SHA
    1e71988 View commit details
    Browse the repository at this point in the history

Commits on Dec 2, 2023

  1. Configuration menu
    Copy the full SHA
    1b7be6c View commit details
    Browse the repository at this point in the history

Commits on Dec 3, 2023

  1. Cleanup.

    jonpryor committed Dec 3, 2023
    Configuration menu
    Copy the full SHA
    14f6713 View commit details
    Browse the repository at this point in the history

Commits on Feb 9, 2024

  1. Configuration menu
    Copy the full SHA
    508dcde View commit details
    Browse the repository at this point in the history

Commits on Feb 16, 2024

  1. Configuration menu
    Copy the full SHA
    74248fb View commit details
    Browse the repository at this point in the history
  2. Fix spelling of JNI_OnUnload

    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    12e187e View commit details
    Browse the repository at this point in the history
  3. Reduce unnecessary changes.

    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    cfdfeaf View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    0803048 View commit details
    Browse the repository at this point in the history
  5. Cleanup.

    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    f8bb97f View commit details
    Browse the repository at this point in the history
  6. Run the sample on CI!

    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    12cda04 View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    3861773 View commit details
    Browse the repository at this point in the history
  8. Remove unnecessary line.

    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    adf5773 View commit details
    Browse the repository at this point in the history
  9. Set publishWebProjects: false.

    `Hello-NativeAOTFromJNI.csproj` isn't an ASP.NET project, so it isn't
    a web project, and shouldn't be treated as such.
    
    We want to invoke the "real" `dotnet publish` command.
    
    Also use `projects: path/to/Hello-NativeAOTFromJNI.csproj`, just in case.
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    65f7d90 View commit details
    Browse the repository at this point in the history
  10. Try setting workingDirectory.

    Currently fails with:
    
        /Users/runner/work/1/s/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets(101,5): error MSB6003: The specified task executable "sh" could not be run. System.IO.DirectoryNotFoundException: The working directory "bin/Release/osx-x64/publish/" does not exist. [/Users/runner/work/1/s/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj]
    
    Maybe if we set workingDirectory to `samples/Hello-NativeAOTFromJNI`
    it will work?
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    429bb4d View commit details
    Browse the repository at this point in the history
  11. Setting workingDirectory didn't fix things.

    Printf debug this; what is `$(PublishDir)` when running
    `RunJavaSample`?  What files exist?
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    b4cddb7 View commit details
    Browse the repository at this point in the history
  12. Configuration menu
    Copy the full SHA
    42c7251 View commit details
    Browse the repository at this point in the history
  13. MOAR PRINTFS

    This is quite confusing; as per CI logs, during `dotnet publish`:
    
          Hello-NativeAOTFromJNI -> /Users/runner/work/1/s/samples/Hello-NativeAOTFromJNI/bin/Release/osx-x64/publish/
    
    which implies to me that `/Users/runner/work/1/s/samples/Hello-NativeAOTFromJNI/bin/Release/osx-x64/publish`
    *exists*.
    
    However, when we get to `dotnet build -t:RunJavaSample`:
    
        ##[error]samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets(106,5): Error MSB6003: The specified task executable "sh" could not be run.
          System.IO.DirectoryNotFoundException: The working directory
          "/Users/runner/work/1/s/samples/Hello-NativeAOTFromJNI/bin/Release/osx-x64/publish/"
          does not exist.
    
    Which looks the same to me!
    
    Dump out dir contents.
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    f656870 View commit details
    Browse the repository at this point in the history
  14. Avoid DotNetCoreCLI@2

    Through the glory of printing the contents of the directory
    after `dotnet publish` and as part of `dotnet build -t:RunJavaSample`,
    we see that the `$(PublishDir)` `bin/Release/osx-x64/publish`
    exists after `dotnet publish`, but is *gone* when `dotnet build`
    runs (?!).  In its place is a `publish.zip` file, which doesn't do
    us any good!
    
    We thus infer and assume that the DotNetCoreCLI@2 task is creating
    this `publish.zip` file and removing the directory, which prevents
    the execution step from actually executing.
    
    Replace DotNetCoreCLI@2 with a powershell script which directly invokes
    `dotnet publish` & co.  Hopefully this will avoid the problem.
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    1b8b1af View commit details
    Browse the repository at this point in the history
  15. Cleanup.

    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    59314fc View commit details
    Browse the repository at this point in the history
  16. Fix some dotnet publish linker warnings.

    Warnings fixed:
    
    `Type.MakeGenericType()` needs to ensure constructors are preserved:
    
        src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(378): Trim analysis warning IL2068: Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[]): 'Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[])' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' requirements. The parameter 'type' of method 'Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[])' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
    
    `Expression.New()` requires the constructor be kept, so "fake it" by
    wrapping `Type.GetType()` w/ `[DynamicallyAccessedMembers]`:
    
        src/Java.Interop/Java.Interop/JniValueMarshaler.cs(175): Trim analysis warning IL2072: Java.Interop.JniValueMarshaler.CreateSelf(JniValueMarshalerContext,ParameterExpression): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Linq.Expressions.Expression.New(Type)'. The return value of method 'System.Object.GetType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
    
    `[DynamicallyAccessedMembers]` should be on properties, not getters:
    
        src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs(33): Trim analysis warning IL2078: Java.Interop.JniValueMarshalerAttribute.MarshalerType.get: 'Java.Interop.JniValueMarshalerAttribute.MarshalerType.get' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor', 'DynamicallyAccessedMemberTypes.Interfaces' requirements. The field 'Java.Interop.JniValueMarshalerAttribute.<MarshalerType>k__BackingField' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
    
    Silence use of `Assembly.Location`, when it's optional:
    
        src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs(106): warning IL3000: Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions): 'System.Reflection.Assembly.Location.get' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'.
        src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs(323): warning IL3000: Java.Interop.JreNativeMethods..cctor(): 'System.Reflection.Assembly.Location.get' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'.
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    2a012c7 View commit details
    Browse the repository at this point in the history
  17. Configuration menu
    Copy the full SHA
    f79d3e1 View commit details
    Browse the repository at this point in the history
  18. $(Standalone) by default, and prefer native-library loader in JreRu…

    …ntime.
    
    These changes mean we don't need `java-interop.dll` on Windows, which
    vastly simplifies things.
    jonpryor committed Feb 16, 2024
    Configuration menu
    Copy the full SHA
    213a57b View commit details
    Browse the repository at this point in the history
  19. Configuration menu
    Copy the full SHA
    cf240f9 View commit details
    Browse the repository at this point in the history