diff --git a/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml b/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml index aa27d4dc9a4..e4f6c6a5564 100644 --- a/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml +++ b/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml @@ -49,6 +49,14 @@ stages: artifactName: $(TestAssembliesArtifactName) downloadPath: ${{ parameters.xaSourcePath }}/bin/Test$(XA.Build.Configuration) + # Currently needed for samples/NativeAOT + - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self + parameters: + project: Xamarin.Android.sln + arguments: -t:PrepareJavaInterop -c $(XA.Build.Configuration) --no-restore + displayName: prepare java.interop $(XA.Build.Configuration) + continueOnError: false + - template: /build-tools/automation/yaml-templates/start-stop-emulator.yaml parameters: xaSourcePath: ${{ parameters.xaSourcePath }} diff --git a/samples/NativeAOT/AndroidManifest.xml b/samples/NativeAOT/AndroidManifest.xml new file mode 100644 index 00000000000..70e89c99003 --- /dev/null +++ b/samples/NativeAOT/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/NativeAOT/JavaInteropRuntime.cs b/samples/NativeAOT/JavaInteropRuntime.cs new file mode 100644 index 00000000000..4e54afeb3ba --- /dev/null +++ b/samples/NativeAOT/JavaInteropRuntime.cs @@ -0,0 +1,54 @@ +using Android.Runtime; +using Java.Interop; +using System.Runtime.InteropServices; + +namespace NativeAOT; + +static class JavaInteropRuntime +{ + static JniRuntime? runtime; + + [UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")] + static int JNI_OnLoad (IntPtr vm, IntPtr reserved) + { + try { + AndroidLog.Print (AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnLoad()"); + LogcatTextWriter.Init (); + return (int) JniVersion.v1_6; + } + catch (Exception e) { + AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JNI_OnLoad() failed: {e}"); + return 0; + } + } + + [UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")] + static void JNI_OnUnload (IntPtr vm, IntPtr reserved) + { + AndroidLog.Print(AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnUnload"); + runtime?.Dispose (); + } + + // symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h` + [UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")] + static void init (IntPtr jnienv, IntPtr klass) + { + try { + var options = new JreRuntimeOptions { + EnvironmentPointer = jnienv, + TypeManager = new NativeAotTypeManager (), + ValueManager = new NativeAotValueManager (), + UseMarshalMemberBuilder = false, + JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"), + JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"), + }; + runtime = options.CreateJreVM (); + + // Entry point into Mono.Android.dll + JNIEnvInit.InitializeJniRuntime (runtime); + } + catch (Exception e) { + AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JavaInteropRuntime.init: error: {e}"); + } + } +} \ No newline at end of file diff --git a/samples/NativeAOT/JavaInteropRuntime.java b/samples/NativeAOT/JavaInteropRuntime.java new file mode 100644 index 00000000000..1eb07e9f2e5 --- /dev/null +++ b/samples/NativeAOT/JavaInteropRuntime.java @@ -0,0 +1,15 @@ +package net.dot.jni.nativeaot; + +import android.util.Log; + +public class JavaInteropRuntime { + static { + Log.d("JavaInteropRuntime", "Loading NativeAOT.so..."); + System.loadLibrary("NativeAOT"); + } + + private JavaInteropRuntime() { + } + + public static native void init(); +} diff --git a/samples/NativeAOT/Logging.cs b/samples/NativeAOT/Logging.cs new file mode 100644 index 00000000000..5073ba9e5a3 --- /dev/null +++ b/samples/NativeAOT/Logging.cs @@ -0,0 +1,77 @@ +// NOTE: logging methods below are need temporarily due to: +// 1) linux-bionic BCL doesn't redirect stdout/stderr to logcat +// 2) Android.Util.Log won't work until we initialize the Java.Interop.JreRuntime + +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace NativeAOT; + +internal sealed class LogcatTextWriter : TextWriter { + + public static void Init () + { + // This method is a no-op, but it's necessary to ensure the static + // constructor is executed. + } + + static LogcatTextWriter () + { + Console.SetOut (new LogcatTextWriter (AndroidLogLevel.Info)); + Console.SetError (new LogcatTextWriter (AndroidLogLevel.Error)); + } + + AndroidLogLevel Level; + string Tag; + + internal LogcatTextWriter (AndroidLogLevel level, string tag = "NativeAotFromAndroid") + { + Level = level; + Tag = tag; + } + + public override Encoding Encoding => Encoding.UTF8; + public override string NewLine => "\n"; + + public override void WriteLine (string? value) + { + if (value == null) { + AndroidLog.Print (Level, Tag, ""); + return; + } + ReadOnlySpan span = value; + while (!span.IsEmpty) { + if (span.IndexOf ('\n') is int n && n < 0) { + break; + } + var line = span.Slice (0, n); + AndroidLog.Print (Level, Tag, line.ToString ()); + span = span.Slice (n + 1); + } + AndroidLog.Print (Level, Tag, span.ToString ()); + } +} + +static class AndroidLog { + + [DllImport ("log", EntryPoint = "__android_log_print", CallingConvention = CallingConvention.Cdecl)] + private static extern void __android_log_print(AndroidLogLevel level, string? tag, string format, string args, IntPtr ptr); + + internal static void Print(AndroidLogLevel level, string? tag, string message) => + __android_log_print(level, tag, "%s", message, IntPtr.Zero); + +} + +internal enum AndroidLogLevel +{ + Unknown = 0x00, + Default = 0x01, + Verbose = 0x02, + Debug = 0x03, + Info = 0x04, + Warn = 0x05, + Error = 0x06, + Fatal = 0x07, + Silent = 0x08 +} \ No newline at end of file diff --git a/samples/NativeAOT/MainActivity.cs b/samples/NativeAOT/MainActivity.cs new file mode 100644 index 00000000000..545983d62fe --- /dev/null +++ b/samples/NativeAOT/MainActivity.cs @@ -0,0 +1,18 @@ +using Android.Runtime; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace NativeAOT; + +[Register("my/MainActivity")] // Required for typemap in NativeAotTypeManager +[Activity(Label = "@string/app_name", MainLauncher = true)] +public class MainActivity : Activity +{ + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + // Set our view from the "main" layout resource + SetContentView(Resource.Layout.activity_main); + } +} \ No newline at end of file diff --git a/samples/NativeAOT/NativeAOT.csproj b/samples/NativeAOT/NativeAOT.csproj new file mode 100644 index 00000000000..e392eaa7646 --- /dev/null +++ b/samples/NativeAOT/NativeAOT.csproj @@ -0,0 +1,36 @@ + + + $(DotNetAndroidTargetFramework) + 21 + Exe + enable + enable + net.dot.hellonativeaot + 1 + 1.0 + apk + true + + true + ..\..\product.snk + + android-arm64 + + true + true + + + + + + android-x64 + <_NuGetFolderOnCI>..\..\bin\Build$(Configuration)\nuget-unsigned + $(_NuGetFolderOnCI) + <_FastDeploymentDiagnosticLogging>true + + + + + + + \ No newline at end of file diff --git a/samples/NativeAOT/NativeAotRuntimeProvider.java b/samples/NativeAOT/NativeAotRuntimeProvider.java new file mode 100644 index 00000000000..b87f23f25c7 --- /dev/null +++ b/samples/NativeAOT/NativeAotRuntimeProvider.java @@ -0,0 +1,51 @@ +package net.dot.jni.nativeaot; + +import android.util.Log; + +public class NativeAotRuntimeProvider + extends android.content.ContentProvider +{ + private static final String TAG = "NativeAotRuntimeProvider"; + + public NativeAotRuntimeProvider() { + Log.d(TAG, "NativeAotRuntimeProvider()"); + } + + @Override + public boolean onCreate() { + Log.d(TAG, "NativeAotRuntimeProvider.onCreate()"); + return true; + } + + @Override + public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) { + Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…"); + JavaInteropRuntime.init(); + super.attachInfo (context, info); + } + + @Override + public android.database.Cursor query(android.net.Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + throw new RuntimeException ("This operation is not supported."); + } + + @Override + public String getType(android.net.Uri uri) { + throw new RuntimeException ("This operation is not supported."); + } + + @Override + public android.net.Uri insert(android.net.Uri uri, android.content.ContentValues initialValues) { + throw new RuntimeException ("This operation is not supported."); + } + + @Override + public int delete(android.net.Uri uri, String where, String[] whereArgs) { + throw new RuntimeException ("This operation is not supported."); + } + + @Override + public int update(android.net.Uri uri, android.content.ContentValues values, String where, String[] whereArgs) { + throw new RuntimeException ("This operation is not supported."); + } +} \ No newline at end of file diff --git a/samples/NativeAOT/NativeAotTypeManager.cs b/samples/NativeAOT/NativeAotTypeManager.cs new file mode 100644 index 00000000000..29499d17847 --- /dev/null +++ b/samples/NativeAOT/NativeAotTypeManager.cs @@ -0,0 +1,164 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Java.Interop; +using Java.Interop.Tools.TypeNameMappings; + +namespace NativeAOT; + +partial class NativeAotTypeManager : JniRuntime.JniTypeManager { + + internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; + internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; + + // TODO: list of types specific to this application + Dictionary typeMappings = new () { + ["android/app/Activity"] = typeof (Android.App.Activity), + ["android/content/Context"] = typeof (Android.Content.Context), + ["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper), + ["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle), + ["android/os/Bundle"] = typeof (Android.OS.Bundle), + ["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper), + ["my/MainActivity"] = typeof (MainActivity), + }; + + public NativeAotTypeManager () + { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: NativeAotTypeManager()"); + } + + public override void RegisterNativeMembers ( + JniType nativeClass, + [DynamicallyAccessedMembers (MethodsAndPrivateNested)] + Type type, + ReadOnlySpan methods) + { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: RegisterNativeMembers: nativeClass={nativeClass} type=`{type}`"); + + if (methods.IsEmpty) { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", "methods.IsEmpty"); + return; + } + + int methodCount = CountMethods (methods); + if (methodCount < 1) { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"methodCount < 1: {methodCount}"); + return; + } + + JniNativeMethodRegistration [] natives = new JniNativeMethodRegistration [methodCount]; + int nativesIndex = 0; + + ReadOnlySpan methodsSpan = methods; + bool needToRegisterNatives = false; + + while (!methodsSpan.IsEmpty) { + int newLineIndex = methodsSpan.IndexOf ('\n'); + + ReadOnlySpan methodLine = methodsSpan.Slice (0, newLineIndex != -1 ? newLineIndex : methodsSpan.Length); + if (!methodLine.IsEmpty) { + SplitMethodLine (methodLine, + out ReadOnlySpan name, + out ReadOnlySpan signature, + out ReadOnlySpan callbackString, + out ReadOnlySpan callbackDeclaringTypeString); + + Delegate? callback = null; + if (callbackString.SequenceEqual ("__export__")) { + throw new InvalidOperationException (FormattableString.Invariant ($"Methods such as {callbackString.ToString ()} are not implemented!")); + } else { + Type callbackDeclaringType = type; + if (!callbackDeclaringTypeString.IsEmpty) { + callbackDeclaringType = Type.GetType (callbackDeclaringTypeString.ToString (), throwOnError: true)!; + } + while (callbackDeclaringType.ContainsGenericParameters) { + callbackDeclaringType = callbackDeclaringType.BaseType!; + } + + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: Delegate.CreateDelegate callbackDeclaringType={callbackDeclaringType}, callbackString={callbackString}"); + GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), + callbackDeclaringType, callbackString.ToString ()); + callback = connector (); + } + + if (callback != null) { + needToRegisterNatives = true; + natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); + } + } + + methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; + } + + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: needToRegisterNatives={needToRegisterNatives}"); + + if (needToRegisterNatives) { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: RegisterNatives: nativeClass={nativeClass} type=`{type}` natives={natives.Length} nativesIndex={nativesIndex}"); + JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); + } + } + + + protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) + { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}`"); + if (typeMappings.TryGetValue (jniSimpleReference, out var target)) { + Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{target}`"); + yield return target; + } + foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) { + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{t}`"); + yield return t; + } + } + + protected override IEnumerable GetSimpleReferences (Type type) + { + return base.GetSimpleReferences (type) + .Concat (CreateSimpleReferencesEnumerator (type)); + } + + IEnumerable CreateSimpleReferencesEnumerator (Type type) + { + if (typeMappings == null) + yield break; + foreach (var e in typeMappings) { + if (e.Value == type) + yield return e.Key; + } + } + + static int CountMethods (ReadOnlySpan methodsSpan) + { + int count = 0; + while (!methodsSpan.IsEmpty) { + count++; + + int newLineIndex = methodsSpan.IndexOf ('\n'); + methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; + } + return count; + } + + static void SplitMethodLine ( + ReadOnlySpan methodLine, + out ReadOnlySpan name, + out ReadOnlySpan signature, + out ReadOnlySpan callback, + out ReadOnlySpan callbackDeclaringType) + { + int colonIndex = methodLine.IndexOf (':'); + name = methodLine.Slice (0, colonIndex); + methodLine = methodLine.Slice (colonIndex + 1); + + colonIndex = methodLine.IndexOf (':'); + signature = methodLine.Slice (0, colonIndex); + methodLine = methodLine.Slice (colonIndex + 1); + + colonIndex = methodLine.IndexOf (':'); + callback = methodLine.Slice (0, colonIndex != -1 ? colonIndex : methodLine.Length); + + callbackDeclaringType = colonIndex != -1 ? methodLine.Slice (colonIndex + 1) : default; + } + + delegate Delegate GetCallbackHandler (); +} diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs new file mode 100644 index 00000000000..8fb2c55d4e2 --- /dev/null +++ b/samples/NativeAOT/NativeAotValueManager.cs @@ -0,0 +1,10 @@ +using Android.Runtime; + +namespace NativeAOT; + +// TODO: make a NativeAOT-specific implementation of AndroidValueManager +// This is enough for "Hello World" to launch +internal class NativeAotValueManager : AndroidValueManager +{ + +} diff --git a/samples/NativeAOT/Resources/layout/activity_main.xml b/samples/NativeAOT/Resources/layout/activity_main.xml new file mode 100644 index 00000000000..f94985291bf --- /dev/null +++ b/samples/NativeAOT/Resources/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon.xml b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon.xml new file mode 100644 index 00000000000..7751f69514d --- /dev/null +++ b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon_round.xml b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon_round.xml new file mode 100644 index 00000000000..7751f69514d --- /dev/null +++ b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon_round.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/NativeAOT/Resources/mipmap-hdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-hdpi/appicon.png new file mode 100644 index 00000000000..0abfc1b581c Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-hdpi/appicon.png differ diff --git a/samples/NativeAOT/Resources/mipmap-hdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_background.png new file mode 100644 index 00000000000..513e69d8412 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_background.png differ diff --git a/samples/NativeAOT/Resources/mipmap-hdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_foreground.png new file mode 100644 index 00000000000..99d3a291bc3 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_foreground.png differ diff --git a/samples/NativeAOT/Resources/mipmap-mdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-mdpi/appicon.png new file mode 100644 index 00000000000..7b5a2e2bf8d Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-mdpi/appicon.png differ diff --git a/samples/NativeAOT/Resources/mipmap-mdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_background.png new file mode 100644 index 00000000000..9e2d1e4d8d4 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_background.png differ diff --git a/samples/NativeAOT/Resources/mipmap-mdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_foreground.png new file mode 100644 index 00000000000..a28d342c1c7 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_foreground.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xhdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon.png new file mode 100644 index 00000000000..b28b73c6545 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_background.png new file mode 100644 index 00000000000..658be3fb6a3 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_background.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_foreground.png new file mode 100644 index 00000000000..70a542ac03c Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_foreground.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon.png new file mode 100644 index 00000000000..f9af1173ead Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_background.png new file mode 100644 index 00000000000..9171c3e4044 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_background.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_foreground.png new file mode 100644 index 00000000000..cb63bfb9f8e Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_foreground.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon.png new file mode 100644 index 00000000000..1d948d6b5d8 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_background.png new file mode 100644 index 00000000000..1232d8c8d23 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_background.png differ diff --git a/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_foreground.png new file mode 100644 index 00000000000..9f9c9e6d932 Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_foreground.png differ diff --git a/samples/NativeAOT/Resources/values/ic_launcher_background.xml b/samples/NativeAOT/Resources/values/ic_launcher_background.xml new file mode 100644 index 00000000000..6ec24e6413c --- /dev/null +++ b/samples/NativeAOT/Resources/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #2C3E50 + \ No newline at end of file diff --git a/samples/NativeAOT/Resources/values/strings.xml b/samples/NativeAOT/Resources/values/strings.xml new file mode 100644 index 00000000000..8d13608953c --- /dev/null +++ b/samples/NativeAOT/Resources/values/strings.xml @@ -0,0 +1,4 @@ + + HelloNativeAOT + Hello, NativeAOT on Android! + diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 19ff11a0913..cb1f39a8ec5 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -48,7 +48,7 @@ internal struct JnienvInitializeArgs { internal static IntPtr java_class_loader; internal static JniMethodInfo? mid_Class_forName; - internal static AndroidRuntime? androidRuntime; + internal static JniRuntime? androidRuntime; [UnmanagedCallersOnly] static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) @@ -78,6 +78,14 @@ static Type TypeGetType (string typeName) => ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); } + // NOTE: should have different name than `Initialize` to avoid: + // * Assertion at /__w/1/s/src/mono/mono/metadata/icall.c:6258, condition `!only_unmanaged_callers_only' not met + internal static void InitializeJniRuntime (JniRuntime runtime) + { + androidRuntime = runtime; + ValueManager = runtime.ValueManager; + } + [UnmanagedCallersOnly] internal static unsafe void Initialize (JnienvInitializeArgs* args) { diff --git a/src/Mono.Android/Properties/AssemblyInfo.cs.in b/src/Mono.Android/Properties/AssemblyInfo.cs.in index 14f600a3113..9d8b6f5694b 100644 --- a/src/Mono.Android/Properties/AssemblyInfo.cs.in +++ b/src/Mono.Android/Properties/AssemblyInfo.cs.in @@ -45,5 +45,7 @@ using System.Runtime.Versioning; [assembly: InternalsVisibleTo("Mono.Android-TestsMultiDex, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] [assembly: InternalsVisibleTo("Mono.Android-TestsAppBundle, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] [assembly: InternalsVisibleTo("Mono.Android.NET-Tests, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] +// Temporary for samples/NativeAOT +[assembly: InternalsVisibleTo("NativeAOT, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] [assembly: SuppressMessage ("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "Analyzer fails due to extended characters.", Scope = "member", Target = "~F:Android.Util.Patterns.GoodIriChar")] diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 868a8fa9f19..de6970db4d0 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -30,8 +30,10 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. BeforeTargets="SetupProperties"> - <_NdkSysrootAbi>aarch64-linux-android - <_NdkClangPrefix>aarch64-linux-android21- + <_NdkAbi Condition=" '$(RuntimeIdentifier)' == 'android-arm64' ">aarch64 + <_NdkAbi Condition=" '$(RuntimeIdentifier)' == 'android-x64' ">x86_64 + <_NdkSysrootAbi>$(_NdkAbi)-linux-android + <_NdkClangPrefix>$(_NdkAbi)-linux-android21- <_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('windows')) ">windows-x86_64 <_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('osx')) ">darwin-x86_64 <_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('linux')) ">linux-x86_64 @@ -49,11 +51,14 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. --> <_OriginalSuppressTrimAnalysisWarnings>$(SuppressTrimAnalysisWarnings) true + + <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --notrimwarn <_OriginalRuntimeIdentifier>$(RuntimeIdentifier) linux-bionic-arm64 + linux-bionic-x64 <_targetOS>linux @@ -78,6 +83,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. + + diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 24badd0933b..33143c74a8d 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -5,6 +5,7 @@ using System.Text; using System.Xml.Linq; using System.Xml.XPath; +using Microsoft.VisualStudio.TestPlatform.Utilities; using Mono.Cecil; using NUnit.Framework; @@ -1277,5 +1278,30 @@ public static void logEvent(String eventName) {{ RunProjectAndAssert (proj, builder); } + [Test] + public void NativeAOTSample () + { + string [] properties = [ + $"AndroidNdkDirectory={AndroidNdkPath}", + "Configuration=Release", + ]; + var projectDirectory = Path.Combine (XABuildPaths.TopDirectory, "samples", "NativeAOT"); + try { + var dotnet = new DotNetCLI (Path.Combine (projectDirectory, "NativeAOT.csproj")); + Assert.IsTrue (dotnet.Build (target: "Run", parameters: properties), "`dotnet build -t:Run` should succeed"); + + bool didLaunch = WaitForActivityToStart ("my", "MainActivity", + Path.Combine (projectDirectory, "logcat.log"), 30); + Assert.IsTrue (didLaunch, "Activity should have started."); + } catch { + foreach (var file in Directory.GetFiles (projectDirectory, "*.log", SearchOption.AllDirectories)) { + TestContext.AddTestAttachment (file); + } + foreach (var bl in Directory.GetFiles (projectDirectory, "*.binlog", SearchOption.AllDirectories)) { + TestContext.AddTestAttachment (bl); + } + throw; + } + } } }