diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index b8aa2e2507d..71b370a3cee 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -204,9 +204,11 @@ static void RunStartupHooks () return; } - // Pass empty string for diagnosticStartupHooks parameter - // The method will read STARTUP_HOOKS from AppContext internally - method.Invoke (null, [ "" ]); + // ProcessStartupHooks accepts startup hooks directly via parameter. + // It will also read STARTUP_HOOKS from AppContext internally. + // Pass DOTNET_STARTUP_HOOKS env var value so it works without needing AppContext setup. + string? startupHooks = Environment.GetEnvironmentVariable ("DOTNET_STARTUP_HOOKS"); + method.Invoke (null, [ startupHooks ?? "" ]); } static void SetSynchronizationContext () => diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets index 9cb93fb2e06..01a64d12687 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets @@ -33,4 +33,5 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets. + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets new file mode 100644 index 00000000000..b092912c2ef --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets @@ -0,0 +1,84 @@ + + + + + + + true + + + + + + + <_HotReloadAgentAssemblyName>$([System.IO.Path]::GetFileNameWithoutExtension('$(DotNetHotReloadAgentStartupHook)')) + + + + + + + + + + + + + + + <_HotReloadEnvironment Include="DOTNET_STARTUP_HOOKS=$(_HotReloadAgentAssemblyName)" /> + + + + + <_HotReloadEnvironment Include="@(DotNetHotReloadAgentEnvironment->'%(Identity)=%(Value)')" /> + + + + + + + + + + + + + + + + diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 5e4c4a5e1b8..8b29c9a2159 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -1582,5 +1582,109 @@ public void AppStartsWithManagedMarshalMethodsLookupEnabled () Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30); Assert.IsTrue (didLaunch, "Activity should have started."); } + + [Test] + public void DeployHotReloadAgentConfiguration () + { + // Create a library project that contains the startup hook + var startupHookLib = new XamarinAndroidLibraryProject { + ProjectName = "MyStartupHook", + Sources = { + new BuildItem.Source ("StartupHook.cs") { + TextContent = () => @" +using System; + +internal static class StartupHook +{ + public static void Initialize () + { + Console.WriteLine (""HOTRELOAD_TEST_HOOK_INITIALIZED=true""); + } +} +" + } + } + }; + + var proj = new XamarinAndroidApplicationProject { + ProjectName = nameof (DeployHotReloadAgentConfiguration), + IsRelease = false, + Imports = { + // Add a .targets file that simulates what dotnet-watch/IDE would inject + new Import (() => "HotReload.targets") { + TextContent = () => @" + + MyStartupHook + + + + + +" + }, + } + }; + proj.SetRuntime (AndroidRuntime.CoreCLR); + proj.AddReference (startupHookLib); + proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", @" + Console.WriteLine (""DOTNET_STARTUP_HOOKS="" + Environment.GetEnvironmentVariable(""DOTNET_STARTUP_HOOKS"")); + Console.WriteLine (""HOTRELOAD_TEST_VAR="" + Environment.GetEnvironmentVariable(""HOTRELOAD_TEST_VAR"")); + Console.WriteLine (""ANOTHER_VAR="" + Environment.GetEnvironmentVariable(""ANOTHER_VAR"")); +"); + + var rootPath = Path.Combine (Root, "temp", TestName); + using var libBuilder = CreateDllBuilder (Path.Combine (rootPath, startupHookLib.ProjectName)); + Assert.IsTrue (libBuilder.Build (startupHookLib), "Library build should have succeeded."); + + using var builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName)); + builder.Save (proj); + + var projectDirectory = Path.Combine (rootPath, proj.ProjectName); + var dotnet = new DotNetCLI (Path.Combine (projectDirectory, proj.ProjectFilePath)); + + // Build normally first + Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed"); + + // Run the DeployHotReloadAgentConfiguration target (hot reload properties come from HotReload.targets) + Assert.IsTrue (dotnet.Build (target: "DeployHotReloadAgentConfiguration"), + "`dotnet build -t:DeployHotReloadAgentConfiguration` should succeed"); + + // Launch the app using adb + ClearAdbLogcat (); + var result = AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity"); + Assert.IsTrue (result.Contains ("Starting: Intent"), $"Activity should have launched. adb output:\n{result}"); + + bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity", + Path.Combine (projectDirectory, "logcat.log"), 30); + Assert.IsTrue (didLaunch, "Activity should have started."); + + var logcatOutput = File.ReadAllText (Path.Combine (projectDirectory, "logcat.log")); + + // Verify the startup hook was set via DOTNET_STARTUP_HOOKS + StringAssert.Contains ( + "DOTNET_STARTUP_HOOKS=MyStartupHook", + logcatOutput, + "DOTNET_STARTUP_HOOKS should be set to MyStartupHook" + ); + + // Verify the startup hook was called + StringAssert.Contains ( + "HOTRELOAD_TEST_HOOK_INITIALIZED=true", + logcatOutput, + "StartupHook.Initialize() should have been called" + ); + + // Verify the additional env vars from DotNetHotReloadAgentEnvironment were set + StringAssert.Contains ( + "HOTRELOAD_TEST_VAR=TestValue123", + logcatOutput, + "HOTRELOAD_TEST_VAR should be set from DotNetHotReloadAgentEnvironment" + ); + StringAssert.Contains ( + "ANOTHER_VAR=AnotherValue456", + logcatOutput, + "ANOTHER_VAR should be set from DotNetHotReloadAgentEnvironment" + ); + } } } diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj index 8047c3713ef..eb93a305fa7 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj @@ -52,11 +52,12 @@ true - + - + + @@ -231,7 +232,8 @@ - + + diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs index f58bf1b48ab..02dc861eb82 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/System/StartupHookTest.cs @@ -7,6 +7,20 @@ namespace SystemTests [TestFixture] public class StartupHookTest { + [Test] + public void FeatureFlagIsEnabled () + { + // NOTE: this is set to true in tests\Mono.Android-Tests\Mono.Android-Tests\Mono.Android.NET-Tests.csproj + Assert.IsTrue (Microsoft.Android.Runtime.RuntimeFeature.StartupHookSupport, "RuntimeFeature.StartupHookSupport should be true"); + } + + [Test] + public void EnvironmentVariableIsSet () + { + var value = Environment.GetEnvironmentVariable ("DOTNET_STARTUP_HOOKS"); + Assert.AreEqual ("StartupHook", value, "DOTNET_STARTUP_HOOKS should be set to 'StartupHook'"); + } + [Test] public void IsInitialized () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt index d3141692f53..cb897b79238 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt @@ -1,4 +1,3 @@ # Environment Variables and system properties # debug.mono.log=gref,default debug.mono.debug=1 -DOTNET_STARTUP_HOOKS=StartupHook diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/hotreload.env b/tests/Mono.Android-Tests/Mono.Android-Tests/hotreload.env new file mode 100644 index 00000000000..2c9605d07dd --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/hotreload.env @@ -0,0 +1 @@ +DOTNET_STARTUP_HOOKS=StartupHook