diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index e5e5b4d5f571..186244756bc7 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -124,7 +124,6 @@ false true - false true true false @@ -142,6 +141,12 @@ false <_ComObjectDescriptorSupport Condition="'$(_ComObjectDescriptorSupport)' == ''">false + + + <_SuppressStartupHookSupportTrimWarning Condition="'$(StartupHookSupport)' == '' And '$(Optimize)' != 'true'">true + true + false + false @@ -694,6 +699,7 @@ + @@ -1266,6 +1272,7 @@ <_RuntimeConfigReservedProperties Include="NATIVE_DLL_SEARCH_DIRECTORIES" /> <_RuntimeConfigReservedProperties Include="RUNTIME_IDENTIFIER" /> <_RuntimeConfigReservedProperties Include="APP_CONTEXT_BASE_DIRECTORY" /> + <_RuntimeConfigReservedProperties Include="STARTUP_HOOKS" /> + + + + + + ILLink + IL2026 + + + + diff --git a/tests/dotnet/StartupHookLibrary/MacCatalyst/StartupHookLibrary.csproj b/tests/dotnet/StartupHookLibrary/MacCatalyst/StartupHookLibrary.csproj new file mode 100644 index 000000000000..02fabd2f8260 --- /dev/null +++ b/tests/dotnet/StartupHookLibrary/MacCatalyst/StartupHookLibrary.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + + diff --git a/tests/dotnet/StartupHookLibrary/StartupHook.cs b/tests/dotnet/StartupHookLibrary/StartupHook.cs new file mode 100644 index 000000000000..c8a6d94e4b68 --- /dev/null +++ b/tests/dotnet/StartupHookLibrary/StartupHook.cs @@ -0,0 +1,17 @@ +using System; + +using Foundation; + + +class StartupHook { + public static void Initialize () + { + Console.WriteLine ("STARTUP"); + + StartupStatus.Initialized = true; + } +} + +public static class StartupStatus { + public static bool Initialized { get; internal set; } +} diff --git a/tests/dotnet/StartupHookLibrary/iOS/StartupHookLibrary.csproj b/tests/dotnet/StartupHookLibrary/iOS/StartupHookLibrary.csproj new file mode 100644 index 000000000000..bb9259517c64 --- /dev/null +++ b/tests/dotnet/StartupHookLibrary/iOS/StartupHookLibrary.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + + diff --git a/tests/dotnet/StartupHookLibrary/macOS/StartupHookLibrary.csproj b/tests/dotnet/StartupHookLibrary/macOS/StartupHookLibrary.csproj new file mode 100644 index 000000000000..71b28ba48c7c --- /dev/null +++ b/tests/dotnet/StartupHookLibrary/macOS/StartupHookLibrary.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + + diff --git a/tests/dotnet/StartupHookLibrary/shared.csproj b/tests/dotnet/StartupHookLibrary/shared.csproj new file mode 100644 index 000000000000..753c8fe96d98 --- /dev/null +++ b/tests/dotnet/StartupHookLibrary/shared.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/StartupHookLibrary/tvOS/StartupHookLibrary.csproj b/tests/dotnet/StartupHookLibrary/tvOS/StartupHookLibrary.csproj new file mode 100644 index 000000000000..388e767c58ed --- /dev/null +++ b/tests/dotnet/StartupHookLibrary/tvOS/StartupHookLibrary.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + + diff --git a/tests/dotnet/StartupHookTest/AppDelegate.cs b/tests/dotnet/StartupHookTest/AppDelegate.cs new file mode 100644 index 000000000000..22b018a22d00 --- /dev/null +++ b/tests/dotnet/StartupHookTest/AppDelegate.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; + +using Foundation; + +namespace MySimpleApp { + public class Program { + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + Console.WriteLine ($"Startup.Initialized: {StartupHook.Initialized}"); + Console.WriteLine ($"StartupStatus.Initialized: {StartupStatus.Initialized}"); + + var rv = 0; + + if (!StartupHook.Initialized) + rv += 1; + + if (!StartupStatus.Initialized) + rv += 2; + + return rv; + } + } +} + +class StartupHook { + public static bool Initialized { get; private set; } + public static void Initialize () + { + Initialized = true; + } +} diff --git a/tests/dotnet/StartupHookTest/MacCatalyst/Makefile b/tests/dotnet/StartupHookTest/MacCatalyst/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/StartupHookTest/MacCatalyst/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/StartupHookTest/MacCatalyst/StartupHookTest.csproj b/tests/dotnet/StartupHookTest/MacCatalyst/StartupHookTest.csproj new file mode 100644 index 000000000000..6b0e2c773180 --- /dev/null +++ b/tests/dotnet/StartupHookTest/MacCatalyst/StartupHookTest.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + diff --git a/tests/dotnet/StartupHookTest/Makefile b/tests/dotnet/StartupHookTest/Makefile new file mode 100644 index 000000000000..6affa45ff122 --- /dev/null +++ b/tests/dotnet/StartupHookTest/Makefile @@ -0,0 +1,2 @@ +TOP=../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/StartupHookTest/iOS/Makefile b/tests/dotnet/StartupHookTest/iOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/StartupHookTest/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/StartupHookTest/iOS/StartupHookTest.csproj b/tests/dotnet/StartupHookTest/iOS/StartupHookTest.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/StartupHookTest/iOS/StartupHookTest.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/StartupHookTest/macOS/Makefile b/tests/dotnet/StartupHookTest/macOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/StartupHookTest/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/StartupHookTest/macOS/StartupHookTest.csproj b/tests/dotnet/StartupHookTest/macOS/StartupHookTest.csproj new file mode 100644 index 000000000000..a77287b9ba00 --- /dev/null +++ b/tests/dotnet/StartupHookTest/macOS/StartupHookTest.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + diff --git a/tests/dotnet/StartupHookTest/shared.csproj b/tests/dotnet/StartupHookTest/shared.csproj new file mode 100644 index 000000000000..193d25df8cee --- /dev/null +++ b/tests/dotnet/StartupHookTest/shared.csproj @@ -0,0 +1,17 @@ + + + + Exe + + StartupHookTest + com.xamarin.startuphooktest + + + + + + + + + + diff --git a/tests/dotnet/StartupHookTest/shared.mk b/tests/dotnet/StartupHookTest/shared.mk new file mode 100644 index 000000000000..7224dd07d815 --- /dev/null +++ b/tests/dotnet/StartupHookTest/shared.mk @@ -0,0 +1,3 @@ +TOP=../../../.. +TESTNAME=StartupHookTest +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/StartupHookTest/tvOS/Makefile b/tests/dotnet/StartupHookTest/tvOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/StartupHookTest/tvOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/StartupHookTest/tvOS/StartupHookTest.csproj b/tests/dotnet/StartupHookTest/tvOS/StartupHookTest.csproj new file mode 100644 index 000000000000..bd487ddcd88d --- /dev/null +++ b/tests/dotnet/StartupHookTest/tvOS/StartupHookTest.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + diff --git a/tests/dotnet/UnitTests/StartupHookTest.cs b/tests/dotnet/UnitTests/StartupHookTest.cs new file mode 100644 index 000000000000..e8b22e33d0af --- /dev/null +++ b/tests/dotnet/UnitTests/StartupHookTest.cs @@ -0,0 +1,114 @@ +#nullable enable + +namespace Xamarin.Tests { + [TestFixture] + public class StartupHookTest : TestBaseClass { + const string project = "StartupHookTest"; + + [TestCase (ApplePlatform.MacOSX, "osx-arm64")] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-x64")] + public void EnabledForDebug (ApplePlatform platform, string runtimeIdentifiers) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + var configuration = "Debug"; + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration); + Clean (project_path); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Configuration"] = configuration; + DotNet.AssertBuild (project_path, properties); + + if (CanExecute (platform, properties)) { + var appExecutable = GetNativeExecutable (platform, appPath); + var env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookTest:StartupHookLibrary" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env); + + env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookLibrary" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 1); // this should fail + + env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookTest" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 2); // this should fail + } + } + + [TestCase (ApplePlatform.MacOSX, "osx-arm64")] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-x64")] + public void DisabledForRelease (ApplePlatform platform, string runtimeIdentifiers) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + var configuration = "Release"; + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration); + Clean (project_path); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Configuration"] = configuration; + DotNet.AssertBuild (project_path, properties); + + if (CanExecute (platform, properties)) { + var appExecutable = GetNativeExecutable (platform, appPath); + var env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookTest:StartupHookLibrary" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 3); // this should fail + + env = new Dictionary (); + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 3); // this should fail + + env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookLibrary" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 3); // this should fail + + env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookTest" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 3); // this should fail + } + } + + [TestCase (ApplePlatform.MacOSX, "osx-arm64")] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-x64")] + public void Enableable (ApplePlatform platform, string runtimeIdentifiers) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + var configuration = "Release"; + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration); + Clean (project_path); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Configuration"] = configuration; + properties ["StartupHookSupport"] = "true"; + DotNet.AssertBuild (project_path, properties); + + if (CanExecute (platform, properties)) { + var appExecutable = GetNativeExecutable (platform, appPath); + var env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookTest:StartupHookLibrary" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env); // this should work + + env = new Dictionary (); + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 3); // this should fail + + env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookLibrary" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 1); // this should fail + + env = new Dictionary { + { "DOTNET_STARTUP_HOOKS", "StartupHookTest" }, + }; + ExecuteWithMagicWordAndAssert (appExecutable, env, expectedExitCode: 2); // this should fail + } + } + } +} diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index fed8415f5fc5..6b35686da384 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -386,7 +386,7 @@ protected string ExecuteWithMagicWordAndAssert (ApplePlatform platform, string r return ExecuteWithMagicWordAndAssert (executable, environment); } - protected string ExecuteWithMagicWordAndAssert (string executable, Dictionary? environment = null) + protected string ExecuteWithMagicWordAndAssert (string executable, Dictionary? environment = null, int expectedExitCode = 0) { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { Console.WriteLine ($"Not executing '{executable}' because we're on Windows."); @@ -395,8 +395,8 @@ protected string ExecuteWithMagicWordAndAssert (string executable, Dictionary