From 5f4bb9fb3540df7ca890e67fca9148c6c2003787 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 16 Jun 2026 16:33:47 +0200 Subject: [PATCH 01/27] [dotnet] Quote MlaunchPath in generated mobile run scripts (#25680) ## Summary This restores the quoting fix for `MlaunchPath` on the `11.0` preview line. The regression is in `Microsoft.Sdk.Mobile.targets`: the direct `Exec` path is quoted, but the generated install/run scripts and `RunCommand` were emitted without shell-safe quoting. When the SDK lives under a path containing a space, `dotnet build -t:Run` fails before `mlaunch` starts. ## Changes - quote `$(MlaunchPath)` in generated install scripts - quote `$(MlaunchPath)` in generated run scripts - quote `RunCommand` - update `MlaunchTest` expectations for the quoted script content / run command ## Validation so far - temporary pack-swap validation succeeded: replacing the installed `Microsoft.Sdk.Mobile.targets` with this patched file made `dotnet build -t:Run -f net11.0-ios` launch a MAUI sample app successfully when the SDK path contained a space Ref: https://github.com/dotnet/macios/issues/22481 This PR is recreated from #25673 (because our CI won't build from forks). --------- Co-authored-by: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/targets/Microsoft.Sdk.Mobile.targets | 6 +++--- tests/dotnet/UnitTests/MlaunchTest.cs | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dotnet/targets/Microsoft.Sdk.Mobile.targets b/dotnet/targets/Microsoft.Sdk.Mobile.targets index 249dea203877..fcffcaff3449 100644 --- a/dotnet/targets/Microsoft.Sdk.Mobile.targets +++ b/dotnet/targets/Microsoft.Sdk.Mobile.targets @@ -70,7 +70,7 @@ - $(MlaunchPath) + '$(MlaunchPath)' $(MlaunchRunArguments) -- diff --git a/tests/dotnet/UnitTests/MlaunchTest.cs b/tests/dotnet/UnitTests/MlaunchTest.cs index 0610eb3f4dd0..1c01c7a456c1 100644 --- a/tests/dotnet/UnitTests/MlaunchTest.cs +++ b/tests/dotnet/UnitTests/MlaunchTest.cs @@ -51,7 +51,7 @@ public void GetMlaunchInstallArguments (ApplePlatform platform, string runtimeId Assert.That (mlaunchInstallArguments, Is.EqualTo (expectedArguments.ToString ())); var scriptContents = File.ReadAllText (outputPath).Trim ('\n'); - var expectedScriptContents = mlaunchPath + " " + expectedArguments.ToString (); + var expectedScriptContents = $"'{mlaunchPath}' " + expectedArguments.ToString (); Assert.That (scriptContents, Is.EqualTo (expectedScriptContents), "Script contents"); } @@ -96,6 +96,10 @@ public void GetMlaunchRunArguments (ApplePlatform platform, string runtimeIdenti Assert.Fail ("Could not find the property 'MlaunchPath' in the binlog."); Assert.That (mlaunchPath, Does.Exist, "mlaunch existence"); + if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "RunCommand", out var runCommand)) + Assert.Fail ("Could not find the property 'RunCommand' in the binlog."); + Assert.That (runCommand, Is.EqualTo ($"'{mlaunchPath}'"), "Run command"); + var expectedArguments = new StringBuilder (); var isSim = runtimeIdentifiers.Contains ("simulator"); expectedArguments.Append (isSim ? "--launchsim " : "--launchdev "); @@ -108,7 +112,7 @@ public void GetMlaunchRunArguments (ApplePlatform platform, string runtimeIdenti Assert.That (mlaunchRunArguments, Does.Match (expectedArguments.ToString ()), "arguments"); var scriptContents = File.ReadAllText (outputPath).Trim ('\n'); - var expectedScriptContents = mlaunchPath + " " + expectedArguments.ToString (); + var expectedScriptContents = $"'{mlaunchPath}' " + expectedArguments.ToString (); Assert.That (scriptContents, Does.Match (expectedScriptContents), "Script contents"); } } From b570c2527bd6abbe8e369c04ff8934a279f29f82 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 16 Jun 2026 17:22:12 +0200 Subject: [PATCH 02/27] [tests] Add a few missing warning filters. (#25650) --- tests/dotnet/UnitTests/ProjectTest.cs | 4 ++-- tests/dotnet/UnitTests/TemplateTest.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 8cbe51e3d86d..64d3af42e5d3 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -2400,7 +2400,7 @@ public void NoWarnCodesign (ApplePlatform platform, string runtimeIdentifiers, s properties ["ExcludeNUnitLiteReference"] = "true"; properties ["ExcludeTouchUnitReference"] = "true"; var rv = DotNet.AssertBuild (project_path, properties); - rv.AssertNoWarnings (); + rv.AssertNoWarnings ((evt) => !Extensions.IsFilteredWarning (evt, platform)); } [Test] @@ -2652,7 +2652,7 @@ public void MultiTargetLibrary (ApplePlatform platform) properties ["cmdline:AllTheTargetFrameworks"] = targetFrameworks; var rv = DotNet.AssertBuild (project_path, properties); - rv.AssertNoWarnings (); + rv.AssertNoWarnings ((evt) => !Extensions.IsFilteredWarning (evt, platform)); } // Mac Catalyst projects can't be built with an earlier version of Xcode (even library projects), diff --git a/tests/dotnet/UnitTests/TemplateTest.cs b/tests/dotnet/UnitTests/TemplateTest.cs index 00f8e790b0fe..b39ad52008e6 100644 --- a/tests/dotnet/UnitTests/TemplateTest.cs +++ b/tests/dotnet/UnitTests/TemplateTest.cs @@ -236,7 +236,7 @@ public void CreateAndBuildProjectTemplate (TemplateInfo info) rv = DotNet.AssertBuild (proj, properties); // There should still not be any warnings - warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (info.Platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings (2):\n\t{string.Join ("\n\t", warnings)}"); var appPath = GetAppPath (proj, platform, runtimeIdentifiers); @@ -280,7 +280,7 @@ bool IsMatching (TemplateLanguage lang, TemplateLanguage? value) var proj = Path.Combine (outputDir, $"{info.Template}.{language.AsFileExtension ()}"); var properties = GetDefaultProperties (); var rv = DotNet.AssertBuild (proj, properties); - var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}"); if (info.Execute) { @@ -295,7 +295,7 @@ bool IsMatching (TemplateLanguage lang, TemplateLanguage? value) rv = DotNet.AssertBuild (proj, properties); // There should still not be any warnings - warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message); + warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).FilterWarnings (platform).Select (v => v.Message); Assert.That (warnings, Is.Empty, $"Build warnings (2):\n\t{string.Join ("\n\t", warnings)}"); var appPath = GetAppPath (proj, platform, runtimeIdentifiers); From 72c0e378daeeb42bac26f578ee058a311480b0ca Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 16 Jun 2026 18:13:30 +0200 Subject: [PATCH 03/27] [devops] Don't clean ACES bots. (#25646) They're already clean. Looks like this will save ~2 min for every job. --- tools/devops/automation/scripts/bash/clean-bot.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/devops/automation/scripts/bash/clean-bot.sh b/tools/devops/automation/scripts/bash/clean-bot.sh index c70faed25a89..5d8b05cefe84 100755 --- a/tools/devops/automation/scripts/bash/clean-bot.sh +++ b/tools/devops/automation/scripts/bash/clean-bot.sh @@ -6,6 +6,11 @@ if [[ "$BUILD_REVISION" == "" ]] ; then exit 1 fi +if [[ "$ACES" != "" ]]; then + echo "Assuming ACES bots are already clean, so not cleaning anything." + exit 0 +fi + # Print disk status before cleaning df -h From 9390bdeeba6ec1408d98fff51809661e2580c734 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 16 Jun 2026 19:34:28 +0200 Subject: [PATCH 04/27] [tests] Clean up Configure.cs a bit. (#25692) --- tests/Makefile | 8 +-- tests/common/Configuration.cs | 55 +++++++++---------- tests/dotnet/UnitTests/AssetsTest.cs | 7 ++- .../AssemblySetup.cs | 2 +- .../TaskTests/ACToolTaskTest.cs | 2 +- .../TaskTests/GetAvailableDevicesTest.cs | 2 +- .../TaskTests/IBToolTaskTests.cs | 2 +- .../TaskTests/MergeAppBundleTaskTest.cs | 2 +- .../TestHelpers/TestBase.cs | 2 +- tests/mtouch/MLaunchTool.cs | 2 +- 10 files changed, 38 insertions(+), 46 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index 6a0802b2f561..34087555249a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -52,9 +52,7 @@ test.config: Makefile $(TOP)/Make.config $(TOP)/eng/Version.Details.xml @echo "JENKINS_RESULTS_DIRECTORY=$(abspath $(JENKINS_RESULTS_DIRECTORY))" >> $@ @echo "XCODE_DEVELOPER_ROOT=$(XCODE_DEVELOPER_ROOT)" >> $@ @echo "DOTNET=$(DOTNET)" >> $@ - @echo "IOS_SDK_VERSION=$(IOS_SDK_VERSION)" >> $@ - @echo "TVOS_SDK_VERSION=$(TVOS_SDK_VERSION)" >> $@ - @echo "MACOS_SDK_VERSION=$(MACOS_SDK_VERSION)" >> $@ + @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),$(platform)_SDK_VERSION=$($(platform)_SDK_VERSION)\\n)" | sed 's/^ //' >> $@ @echo "DOTNET_BCL_DIR=$(DOTNET_BCL_DIR)" >> $@ @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),DOTNET_$(platform)_RUNTIME_IDENTIFIERS_NO_ARCH='$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS_NO_ARCH)'\\n)" | sed 's/^ //' >> $@ @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),DOTNET_$(platform)_RUNTIME_IDENTIFIERS='$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS)'\\n)" | sed 's/^ //' >> $@ @@ -83,9 +81,7 @@ test-system.config: Makefile $(TOP)/Make.config $(TOP)/eng/Version.Details.xml @rm -f $@ @echo "JENKINS_RESULTS_DIRECTORY=$(abspath $(JENKINS_RESULTS_DIRECTORY))" >> $@ @echo "DOTNET=$(DOTNET)" >> $@ - @echo "IOS_SDK_VERSION=$(IOS_SDK_VERSION)" >> $@ - @echo "TVOS_SDK_VERSION=$(TVOS_SDK_VERSION)" >> $@ - @echo "MACOS_SDK_VERSION=$(MACOS_SDK_VERSION)" >> $@ + @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),$(platform)_SDK_VERSION=$($(platform)_SDK_VERSION)\\n)" | sed 's/^ //' >> $@ @echo "DOTNET_TFM=$(DOTNET_TFM)" >> $@ @echo "DOTNET_BCL_DIR=$(DOTNET_BCL_DIR)" >> $@ @printf "$(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),DOTNET_$(platform)_RUNTIME_IDENTIFIERS='$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS)'\\n)" | sed 's/^ //' >> $@ diff --git a/tests/common/Configuration.cs b/tests/common/Configuration.cs index 8154d4e9b100..1735ac8df815 100644 --- a/tests/common/Configuration.cs +++ b/tests/common/Configuration.cs @@ -10,25 +10,17 @@ namespace Xamarin.Tests { static partial class Configuration { - public const string XI_ProductName = "MonoTouch"; - public const string XM_ProductName = "Xamarin.Mac"; - public static string DotNetBclDir = ""; public static string DotNetCscCommand = ""; public static string DotNetExecutable; public static string DotNetTfm; - public static string? mt_src_root; - public static string sdk_version; + public static string ios_sdk_version; public static string tvos_sdk_version; public static string macos_sdk_version; - public static string xcode_root; + public static string maccatalyst_sdk_version; + static string xcode_root; public static string? XcodeVersionString; - public static string xcode83_root; - public static string xcode94_root; -#if MONOMAC - public static string mac_xcode_root; -#endif - public static Dictionary make_config = new Dictionary (); + static Dictionary make_config = new Dictionary (); public static bool include_ios; public static bool include_mac; @@ -286,12 +278,11 @@ static Configuration () { ParseConfigFiles (); - sdk_version = GetVariable ("IOS_SDK_VERSION", "8.0"); + ios_sdk_version = GetVariable ("IOS_SDK_VERSION", "8.0"); tvos_sdk_version = GetVariable ("TVOS_SDK_VERSION", "9.0"); macos_sdk_version = GetVariable ("MACOS_SDK_VERSION", "10.12"); + maccatalyst_sdk_version = GetVariable ("MACCATALYST_SDK_VERSION", "14.0"); xcode_root = GetVariable ("XCODE_DEVELOPER_ROOT", "/Applications/Xcode.app/Contents/Developer"); - xcode83_root = GetVariable ("XCODE83_DEVELOPER_ROOT", "/Applications/Xcode83.app/Contents/Developer"); - xcode94_root = GetVariable ("XCODE94_DEVELOPER_ROOT", "/Applications/Xcode94.app/Contents/Developer"); include_ios = !string.IsNullOrEmpty (GetVariable ("INCLUDE_IOS", "")); include_mac = !string.IsNullOrEmpty (GetVariable ("INCLUDE_MAC", "")); include_tvos = !string.IsNullOrEmpty (GetVariable ("INCLUDE_TVOS", "")); @@ -305,16 +296,10 @@ static Configuration () DOTNET_DIR = GetVariable ("DOTNET_DIR", ""); XcodeVersionString = GetVariable ("XCODE_VERSION", GetXcodeVersion (xcode_root)); -#if MONOMAC - mac_xcode_root = xcode_root; -#endif Console.WriteLine ("Test configuration:"); - Console.WriteLine (" SDK_VERSION={0}", sdk_version); Console.WriteLine (" XCODE_ROOT={0}", xcode_root); -#if MONOMAC - Console.WriteLine (" MAC_XCODE_ROOT={0}", mac_xcode_root); -#endif + Console.WriteLine (" XCODE_VERSION={0}", XcodeVersionString); Console.WriteLine (" INCLUDE_IOS={0}", include_ios); Console.WriteLine (" INCLUDE_MAC={0}", include_mac); Console.WriteLine (" INCLUDE_TVOS={0}", include_tvos); @@ -371,13 +356,7 @@ public static bool TryGetRootPath ([NotNullWhen (true)] out string? rootPath) } } - public static string SourceRoot { - get { - if (mt_src_root is null) - mt_src_root = RootPath; - return mt_src_root; - } - } + public static string SourceRoot => RootPath; public static string TestProjectsDirectory { get { @@ -694,7 +673,7 @@ public static void SetBuildVariables (ApplePlatform platform, [NotNullIfNotNull if (environment is null) environment = new Dictionary (); - environment ["DEVELOPER_DIR"] = Path.GetDirectoryName (Path.GetDirectoryName (xcode_root)!)!; + environment ["DEVELOPER_DIR"] = Path.GetDirectoryName (Path.GetDirectoryName (XcodeLocation)!)!; // This is set by `dotnet test` and can cause building legacy projects to fail to build with: // Microsoft.NET.Build.Extensions.ConflictResolution.targets(30,5): @@ -732,6 +711,22 @@ public static string GetTestLibraryDirectory (ApplePlatform platform, bool? simu return Path.Combine (SourceRoot, "tests", "test-libraries", ".libs", dir); } + public static string GetSdkVersion (ApplePlatform platform) + { + switch (platform) { + case ApplePlatform.iOS: + return ios_sdk_version; + case ApplePlatform.MacOSX: + return macos_sdk_version; + case ApplePlatform.TVOS: + return tvos_sdk_version; + case ApplePlatform.MacCatalyst: + return maccatalyst_sdk_version; + default: + throw new NotImplementedException ($"Unknown platform: {platform}"); + } + } + // This implementation of Touch is to update a timestamp (not to make sure a certain file exists). public static void Touch (string file) { diff --git a/tests/dotnet/UnitTests/AssetsTest.cs b/tests/dotnet/UnitTests/AssetsTest.cs index 3466457299e9..14277ec3ecb6 100644 --- a/tests/dotnet/UnitTests/AssetsTest.cs +++ b/tests/dotnet/UnitTests/AssetsTest.cs @@ -136,9 +136,9 @@ public static string GetFullSdkVersion (ApplePlatform platform, string runtimeId switch (platform) { case ApplePlatform.iOS: if (runtimeIdentifiers.Contains ("simulator")) { - return $"iphonesimulator{Configuration.sdk_version}"; + return $"iphonesimulator{Configuration.ios_sdk_version}"; } else { - return $"iphoneos{Configuration.sdk_version}"; + return $"iphoneos{Configuration.ios_sdk_version}"; } case ApplePlatform.TVOS: if (runtimeIdentifiers.Contains ("simulator")) { @@ -147,8 +147,9 @@ public static string GetFullSdkVersion (ApplePlatform platform, string runtimeId return $"appletvos{Configuration.tvos_sdk_version}"; } case ApplePlatform.MacOSX: - case ApplePlatform.MacCatalyst: return $"macosx{Configuration.macos_sdk_version}"; + case ApplePlatform.MacCatalyst: + return $"macosx{Configuration.maccatalyst_sdk_version}"; default: throw new ArgumentOutOfRangeException ($"Unknown platform: {platform}"); } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs index 5059aab86236..28575a0412a9 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/AssemblySetup.cs @@ -17,7 +17,7 @@ public void AssemblyInitialization () const string msbuild_exe_path = "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/msbuild/15.0/bin/MSBuild.dll"; if (is_in_vsmac) { var env = new Dictionary { - { "DEVELOPER_DIR", Path.GetDirectoryName (Path.GetDirectoryName (Configuration.xcode_root)) ?? string.Empty }, + { "DEVELOPER_DIR", Path.GetDirectoryName (Path.GetDirectoryName (Configuration.XcodeLocation)) ?? string.Empty }, { "MSBUILD_EXE_PATH", msbuild_exe_path }, }; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs index 218b3cf4fa9e..958fcdb63f8b 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs @@ -60,7 +60,7 @@ ACTool CreateACToolTask (ApplePlatform platform, string projectDir, out string i task.MinimumOSVersion = Xamarin.SdkVersions.GetMinVersion (platform).ToString (); task.OutputPath = Path.Combine (intermediateOutputPath, "OutputPath"); task.ProjectDir = projectDir; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; task.SdkPlatform = sdkPlatform; task.SdkVersion = version.ToString (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs index c202f5e615aa..dc6ab813dde8 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetAvailableDevicesTest.cs @@ -31,7 +31,7 @@ GetAvailableDevicesTaskWrapper CreateTask (ApplePlatform platform, string simctl SimCtlJson = simctlJson, DeviceCtlJson = devicectlJson, }; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); if (!string.IsNullOrEmpty (appManifest)) { diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs index 2b66ae4a5f93..bc6fdbf5f6a0 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/IBToolTaskTests.cs @@ -44,7 +44,7 @@ IBTool CreateIBToolTask (ApplePlatform framework, string projectDir, string inte task.MinimumOSVersion = PDictionary.OpenFile (Path.Combine (projectDir, "Info.plist")).GetMinimumOSVersion (); task.ResourcePrefix = "Resources"; task.ProjectDir = projectDir; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; task.SdkPlatform = platform; task.SdkVersion = version.ToString (); task.TargetFrameworkMoniker = TargetFramework.DotNet_iOS_String; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs index 309d9aa597f9..afe1a7eaa7b6 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/MergeAppBundleTaskTest.cs @@ -60,7 +60,7 @@ MergeAppBundles CreateTask (string outputBundle, params string [] inputBundles) var task = CreateTask (); task.InputAppBundles = inputItems.ToArray (); task.OutputAppBundle = outputBundle; - task.SdkDevPath = Configuration.xcode_root; + task.SdkDevPath = Configuration.XcodeLocation; return task; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs index 05b391b10fbf..8f0e1ba36e6d 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TestHelpers/TestBase.cs @@ -54,7 +54,7 @@ public virtual void Setup () var t = new T (); t.BuildEngine = Engine; if (t is XamarinTask xt) - xt.SdkDevPath = Configuration.xcode_root; + xt.SdkDevPath = Configuration.XcodeLocation; return t; } diff --git a/tests/mtouch/MLaunchTool.cs b/tests/mtouch/MLaunchTool.cs index 5185e8f6cfd2..789d29a1dd4c 100644 --- a/tests/mtouch/MLaunchTool.cs +++ b/tests/mtouch/MLaunchTool.cs @@ -97,7 +97,7 @@ IList BuildArguments () } if (!string.IsNullOrEmpty (platformName) && !string.IsNullOrEmpty (simType)) { - var device = string.Format (":v2:runtime=com.apple.CoreSimulator.SimRuntime.{0}-{1},devicetype=com.apple.CoreSimulator.SimDeviceType.{2}", platformName, Configuration.sdk_version.Replace ('.', '-'), simType); + var device = string.Format (":v2:runtime=com.apple.CoreSimulator.SimRuntime.{0}-{1},devicetype=com.apple.CoreSimulator.SimDeviceType.{2}", platformName, Configuration.ios_sdk_version.Replace ('.', '-'), simType); sb.Add ($"--device:{device}"); } From 3bda37b75477c3a84625f5f20d1af344c4d16207 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 16 Jun 2026 19:45:17 +0200 Subject: [PATCH 05/27] [tools] Simplify the creation of the list of frameworks a bit. (#25618) We don't need to know whether we're building for a simulator or device when creating the list of frameworks, because now it's possible to specify different values for simulator vs device. This makes it possible to simplify the code a little bit. Most of this is indentation changes, so it's easier to review if hiding whitespace changes. --- .../generate-frameworks-constants.cs | 5 +- .../Validators/SmartEnumValidator.cs | 3 +- tests/introspection/ApiFrameworkTest.cs | 4 +- tests/xtro-sharpie/xtro-sanity/Sanitizer.cs | 11 +- tools/common/Driver.cs | 3 +- tools/common/Frameworks.cs | 557 +++++++++--------- 6 files changed, 298 insertions(+), 285 deletions(-) diff --git a/scripts/generate-frameworks-constants/generate-frameworks-constants.cs b/scripts/generate-frameworks-constants/generate-frameworks-constants.cs index 979f62b473cb..001593364636 100644 --- a/scripts/generate-frameworks-constants/generate-frameworks-constants.cs +++ b/scripts/generate-frameworks-constants/generate-frameworks-constants.cs @@ -32,7 +32,10 @@ static ApplePlatform GetPlatform (string platform) static int Fix (ApplePlatform platform, string output) { - var frameworks = Frameworks.GetFrameworks (platform, false)!.Values.Where (v => !v.IsFrameworkUnavailable ()); + if (!Frameworks.TryGetFrameworks (platform, out var fwks)) + return 1; + + var frameworks = fwks.Values.Where (v => !v.IsFrameworkUnavailable ()); var sb = new StringBuilder (); sb.AppendLine ("namespace ObjCRuntime {"); diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/SmartEnumValidator.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/SmartEnumValidator.cs index 11f00d6c2c1e..be0058d0619a 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/SmartEnumValidator.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Validators/SmartEnumValidator.cs @@ -134,8 +134,7 @@ internal static bool ValidateLibraryPathValue (Binding binding, RootContext cont return false; } - var appleFrameworks = Frameworks.GetFrameworks (platformName.ToApplePlatform (), false); - if (appleFrameworks is null) { + if (!Frameworks.TryGetFrameworks (platformName.ToApplePlatform (), out var appleFrameworks)) { // we could not get the frameworks, we have a bug // we could not identify the platform, we have a bug diagnostics = [Diagnostic.Create ( diff --git a/tests/introspection/ApiFrameworkTest.cs b/tests/introspection/ApiFrameworkTest.cs index bfcd1af6c7ab..ea03f8e191e3 100644 --- a/tests/introspection/ApiFrameworkTest.cs +++ b/tests/introspection/ApiFrameworkTest.cs @@ -72,9 +72,9 @@ public bool Skip (string? @namespace) Frameworks GetFrameworks () { #if __MACCATALYST__ - return Frameworks.GetMacCatalystFrameworks (); + return Frameworks.MacCatalystFrameworks; #elif __IOS__ - return Frameworks.GetiOSFrameworks (app.IsSimulatorBuild); + return Frameworks.iOSFrameworks; #elif __TVOS__ return Frameworks.TVOSFrameworks; #elif __MACOS__ diff --git a/tests/xtro-sharpie/xtro-sanity/Sanitizer.cs b/tests/xtro-sharpie/xtro-sanity/Sanitizer.cs index 2afdc4d6d6d4..67325b5274d4 100644 --- a/tests/xtro-sharpie/xtro-sanity/Sanitizer.cs +++ b/tests/xtro-sharpie/xtro-sanity/Sanitizer.cs @@ -357,7 +357,7 @@ static bool IsFrameworkIncludedInPlatform (string platform, string framework) return true; // If the framework is registered for the current platform, then it's included. - var frameworks = Frameworks.GetFrameworks (ApplePlatformExtensions.Parse (platform), false); + var frameworks = GetFrameworks (platform); if (frameworks?.Any (x => string.Equals (x.Key, framework, StringComparison.OrdinalIgnoreCase)) == true) return true; @@ -372,9 +372,16 @@ static List GetAllFrameworks () return all_frameworks; } + static Frameworks GetFrameworks (ApplePlatform platform) + { + if (!Frameworks.TryGetFrameworks (platform, out var frameworks)) + throw new Exception ($"No frameworks for {platform}?"); + return frameworks; + } + static Frameworks GetFrameworks (string platform) { - return Frameworks.GetFrameworks (ApplePlatformExtensions.Parse (platform), false)!; + return GetFrameworks (ApplePlatformExtensions.Parse (platform)); } static List GetFrameworks (IEnumerable platforms) diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index fe912e00e4a8..a49cda6eb684 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -394,8 +394,7 @@ public static string CorlibName { public static Frameworks GetFrameworks (Application app) { - var rv = Frameworks.GetFrameworks (app.Platform, app.IsSimulatorBuild); - if (rv is null) + if (!Frameworks.TryGetFrameworks (app.Platform, out var rv)) throw ErrorHelper.CreateError (71, Errors.MX0071, app.Platform, app.ProductName); return rv; } diff --git a/tools/common/Frameworks.cs b/tools/common/Frameworks.cs index 8e40602e764a..a98d2dace76a 100644 --- a/tools/common/Frameworks.cs +++ b/tools/common/Frameworks.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; #if LEGACY_TOOLS || BUNDLER @@ -326,197 +327,194 @@ public static Frameworks MacFrameworks { } static Frameworks? ios_frameworks; - public static Frameworks GetiOSFrameworks (bool is_simulator_build) - { - if (ios_frameworks is null) - ios_frameworks = CreateiOSFrameworks (is_simulator_build); - return ios_frameworks; - } + public static Frameworks iOSFrameworks { + get { + if (ios_frameworks is null) { + ios_frameworks = new Frameworks () { + { "AddressBook", "AddressBook", 3 }, + { "Security", "Security", 3 }, + { "AudioUnit", "AudioToolbox", 3 }, + { "AddressBookUI", "AddressBookUI", 3 }, + { "AudioToolbox", "AudioToolbox", 3 }, + { "AVFoundation", "AVFoundation", 3 }, + { "CFNetwork", "CFNetwork", 3 }, + { "CoreAnimation", "QuartzCore", 3 }, + { "CoreAudio", "CoreAudio", 3 }, + { "CoreData", "CoreData", 3 }, + { "CoreFoundation", "CoreFoundation", 3 }, + { "CoreGraphics", "CoreGraphics", 3 }, + { "CoreLocation", "CoreLocation", 3 }, + { "ExternalAccessory", "ExternalAccessory", 3 }, + { "Foundation", "Foundation", 3 }, + { "GameKit", "GameKit", 3 }, + { "MapKit", "MapKit", 3 }, + { "MediaPlayer", "MediaPlayer", 3 }, + { "MessageUI", "MessageUI", 3 }, + { "MobileCoreServices", "MobileCoreServices", 3 }, + { "StoreKit", "StoreKit", 3 }, + { "SystemConfiguration", "SystemConfiguration", 3 }, + { "OpenGLES", "OpenGLES", 3 }, + { "UIKit", "UIKit", 3 }, + + { "Accelerate", "Accelerate", 4 }, + { "AssetsLibrary", "AssetsLibrary", new Version (4, 0), null, false, null, /* version_unavailable = */ new Version (17, 4) }, + { "EventKit", "EventKit", 4 }, + { "EventKitUI", "EventKitUI", 4 }, + { "CoreMotion", "CoreMotion", 4 }, + { "CoreMedia", "CoreMedia", 4 }, + { "CoreVideo", "CoreVideo", 4 }, + { "CoreTelephony", "CoreTelephony", 4 }, + { "QuickLook", "QuickLook", 4 }, + { "ImageIO", "ImageIO", 4 }, + { "CoreText", "CoreText", 4 }, + { "CoreMidi", "CoreMIDI", 4 }, + + { "Accounts", "Accounts", 5 }, + { "GLKit", "GLKit", 5 }, + { "NewsstandKit", "NewsstandKit", new Version (5, 0), null, false, null, /* version_unavailable = */ new Version (17, 0) }, + { "CoreImage", "CoreImage", 5 }, + { "CoreBluetooth", "CoreBluetooth", 5 }, + { "Twitter", "Twitter", 5 }, + { "GSS", "GSS", 5 }, + + { "MediaToolbox", "MediaToolbox", 6 }, + { "PassKit", "PassKit", 6 }, + { "Social", "Social", 6 }, + { "AdSupport", "AdSupport", 6 }, + + { "GameController", "GameController", 7 }, + { "JavaScriptCore", "JavaScriptCore", 7 }, + { "MediaAccessibility", "MediaAccessibility", 7 }, + { "MultipeerConnectivity", "MultipeerConnectivity", 7 }, + { "SafariServices", "SafariServices", 7 }, + { "SpriteKit", "SpriteKit", 7 }, + + { "HealthKit", "HealthKit", 8 }, + { "HomeKit", "HomeKit", 8 }, + { "LocalAuthentication", "LocalAuthentication", 8 }, + { "NotificationCenter", "NotificationCenter", 8 }, + { "PushKit", "PushKit", 8 }, + { "Photos", "Photos", 8 }, + { "PhotosUI", "PhotosUI", 8 }, + { "SceneKit", "SceneKit", 8 }, + { "CloudKit", "CloudKit", 8 }, + { "AVKit", "AVKit", 8 }, + { "CoreAudioKit", "CoreAudioKit", new Version (8, 0), new Version (9, 0) }, + { "Metal", "Metal", new Version (8, 0), new Version (9, 0) }, + { "WebKit", "WebKit", 8 }, + { "NetworkExtension", "NetworkExtension", 8 }, + { "VideoToolbox", "VideoToolbox", 8 }, + + { "ReplayKit", "ReplayKit", 9 }, + { "Contacts", "Contacts", 9 }, + { "ContactsUI", "ContactsUI", 9 }, + { "CoreSpotlight", "CoreSpotlight", 9 }, + { "WatchConnectivity", "WatchConnectivity", 9 }, + { "ModelIO", "ModelIO", 9 }, + { "MetalKit", "MetalKit", 9 }, + { "MetalPerformanceShaders", "MetalPerformanceShaders", new Version (9, 0), new Version (11, 0) /* MPS got simulator headers in Xcode 9 */ }, + { "GameplayKit", "GameplayKit", 9 }, + { "HealthKitUI", "HealthKitUI", 9, 3 }, - public static Frameworks CreateiOSFrameworks (bool is_simulator_build) - { - return new Frameworks () { - { "AddressBook", "AddressBook", 3 }, - { "Security", "Security", 3 }, - { "AudioUnit", "AudioToolbox", 3 }, - { "AddressBookUI", "AddressBookUI", 3 }, - { "AudioToolbox", "AudioToolbox", 3 }, - { "AVFoundation", "AVFoundation", 3 }, - { "CFNetwork", "CFNetwork", 3 }, - { "CoreAnimation", "QuartzCore", 3 }, - { "CoreAudio", "CoreAudio", 3 }, - { "CoreData", "CoreData", 3 }, - { "CoreFoundation", "CoreFoundation", 3 }, - { "CoreGraphics", "CoreGraphics", 3 }, - { "CoreLocation", "CoreLocation", 3 }, - { "ExternalAccessory", "ExternalAccessory", 3 }, - { "Foundation", "Foundation", 3 }, - { "GameKit", "GameKit", 3 }, - { "MapKit", "MapKit", 3 }, - { "MediaPlayer", "MediaPlayer", 3 }, - { "MessageUI", "MessageUI", 3 }, - { "MobileCoreServices", "MobileCoreServices", 3 }, - { "StoreKit", "StoreKit", 3 }, - { "SystemConfiguration", "SystemConfiguration", 3 }, - { "OpenGLES", "OpenGLES", 3 }, - { "UIKit", "UIKit", 3 }, - - { "Accelerate", "Accelerate", 4 }, - { "AssetsLibrary", "AssetsLibrary", new Version (4, 0), null, false, null, /* version_unavailable = */ new Version (17, 4) }, - { "EventKit", "EventKit", 4 }, - { "EventKitUI", "EventKitUI", 4 }, - { "CoreMotion", "CoreMotion", 4 }, - { "CoreMedia", "CoreMedia", 4 }, - { "CoreVideo", "CoreVideo", 4 }, - { "CoreTelephony", "CoreTelephony", 4 }, - { "QuickLook", "QuickLook", 4 }, - { "ImageIO", "ImageIO", 4 }, - { "CoreText", "CoreText", 4 }, - { "CoreMidi", "CoreMIDI", 4 }, - - { "Accounts", "Accounts", 5 }, - { "GLKit", "GLKit", 5 }, - { "NewsstandKit", "NewsstandKit", new Version (5, 0), null, false, null, /* version_unavailable = */ new Version (17, 0) }, - { "CoreImage", "CoreImage", 5 }, - { "CoreBluetooth", "CoreBluetooth", 5 }, - { "Twitter", "Twitter", 5 }, - { "GSS", "GSS", 5 }, - - { "MediaToolbox", "MediaToolbox", 6 }, - { "PassKit", "PassKit", 6 }, - { "Social", "Social", 6 }, - { "AdSupport", "AdSupport", 6 }, - - { "GameController", "GameController", 7 }, - { "JavaScriptCore", "JavaScriptCore", 7 }, - { "MediaAccessibility", "MediaAccessibility", 7 }, - { "MultipeerConnectivity", "MultipeerConnectivity", 7 }, - { "SafariServices", "SafariServices", 7 }, - { "SpriteKit", "SpriteKit", 7 }, - - { "HealthKit", "HealthKit", 8 }, - { "HomeKit", "HomeKit", 8 }, - { "LocalAuthentication", "LocalAuthentication", 8 }, - { "NotificationCenter", "NotificationCenter", 8 }, - { "PushKit", "PushKit", 8 }, - { "Photos", "Photos", 8 }, - { "PhotosUI", "PhotosUI", 8 }, - { "SceneKit", "SceneKit", 8 }, - { "CloudKit", "CloudKit", 8 }, - { "AVKit", "AVKit", 8 }, - { "CoreAudioKit", "CoreAudioKit", is_simulator_build ? 9 : 8 }, - { "Metal", "Metal", new Version (8, 0), new Version (9, 0) }, - { "WebKit", "WebKit", 8 }, - { "NetworkExtension", "NetworkExtension", 8 }, - { "VideoToolbox", "VideoToolbox", 8 }, - - { "ReplayKit", "ReplayKit", 9 }, - { "Contacts", "Contacts", 9 }, - { "ContactsUI", "ContactsUI", 9 }, - { "CoreSpotlight", "CoreSpotlight", 9 }, - { "WatchConnectivity", "WatchConnectivity", 9 }, - { "ModelIO", "ModelIO", 9 }, - { "MetalKit", "MetalKit", 9 }, - { "MetalPerformanceShaders", "MetalPerformanceShaders", new Version (9, 0), new Version (11, 0) /* MPS got simulator headers in Xcode 9 */ }, - { "GameplayKit", "GameplayKit", 9 }, - { "HealthKitUI", "HealthKitUI", 9,3 }, - - { "CallKit", "CallKit", 10 }, - { "Messages", "Messages", 10 }, - { "Speech", "Speech", 10 }, - { "VideoSubscriberAccount", "VideoSubscriberAccount", 10 }, - { "UserNotifications", "UserNotifications", 10 }, - { "UserNotificationsUI", "UserNotificationsUI", 10 }, - { "Intents", "Intents", 10 }, - { "IntentsUI", "IntentsUI", 10 }, - - { "ARKit", "ARKit", 11 }, - { "CoreNFC", "CoreNFC", new Version (11, 0), new Version (15, 0), true }, /* not always present, e.g. iPad w/iOS 12, so must be weak linked; doesn't work in the simulator in Xcode 12 (https://stackoverflow.com/q/63915728/183422), but works in at least Xcode 15 (maybe earlier too) */ - { "DeviceCheck", "DeviceCheck", new Version (11, 0), new Version (13, 0) }, - { "IdentityLookup", "IdentityLookup", 11 }, - { "IOSurface", "IOSurface", new Version (11, 0), new Version (26, 0) /* The headers were broken at some point, not sure when they started working again */ }, - { "CoreML", "CoreML", 11 }, - { "Vision", "Vision", 11 }, - { "FileProvider", "FileProvider", 11 }, - { "FileProviderUI", "FileProviderUI", 11 }, - { "PdfKit", "PDFKit", 11 }, - - { "BusinessChat", "BusinessChat", 11, 3 }, - - { "ClassKit", "ClassKit", 11,4 }, - - { "AuthenticationServices", "AuthenticationServices", 12,0 }, - { "CarPlay", "CarPlay", 12,0 }, - { "CoreServices", "MobileCoreServices", 12, 0 }, - { "IdentityLookupUI", "IdentityLookupUI", 12,0 }, - { "NaturalLanguage", "NaturalLanguage", 12,0 }, - { "Network", "Network", 12, 0 }, - - { "BackgroundTasks", "BackgroundTasks", 13, 0 }, - { "CoreHaptics", "CoreHaptics", 13, 0 }, - { "CryptoTokenKit", "CryptoTokenKit", 13, 0 }, - { "LinkPresentation", "LinkPresentation", 13, 0 }, - { "MetricKit", "MetricKit", 13, 0 }, - { "PencilKit", "PencilKit", 13, 0 }, - { "QuickLookThumbnailing", "QuickLookThumbnailing", 13,0 }, - { "SoundAnalysis", "SoundAnalysis", 13, 0 }, - { "VisionKit", "VisionKit", 13, 0 }, - - { "AutomaticAssessmentConfiguration", "AutomaticAssessmentConfiguration", 13, 4 }, - - { "Accessibility", "Accessibility", 14,0 }, - { "AppClip", "AppClip", 14,0 }, - { "AppTrackingTransparency", "AppTrackingTransparency", 14,0 }, - { "MediaSetup", "MediaSetup", new Version (14, 0), NotAvailableInSimulator /* no headers in beta 3 */ }, - { "MetalPerformanceShadersGraph", "MetalPerformanceShadersGraph", 14,0 }, - { "MLCompute", "MLCompute", new Version (14,0), NotAvailableInSimulator }, - { "NearbyInteraction", "NearbyInteraction", 14,0 }, - { "ScreenTime", "ScreenTime", 14,0 }, - { "SensorKit", "SensorKit", new Version (14, 0), null, true }, /* not always present on device, e.g. any iPad, so must be weak linked; https://github.com/dotnet/macios/issues/9938 */ - { "UniformTypeIdentifiers", "UniformTypeIdentifiers", 14,0 }, - - { "AdServices", "AdServices", 14,3 }, - - { "CoreLocationUI", "CoreLocationUI", 15,0 }, - - { "DataDetection", "DataDetection", 15, 0 }, - { "Phase", "PHASE", new Version (15, 0), new Version (26, 0) /* not certain about the exact version when this framework was added to the simulator, but this should be a safe default */ }, - { "OSLog", "OSLog", 15,0 }, - { "ShazamKit", "ShazamKit", new Version (15,0), new Version (16, 0)}, - { "ThreadNetwork", "ThreadNetwork", new Version (15,0), NotAvailableInSimulator}, - - - { "AVRouting", "AVRouting", 16,0}, - { "BackgroundAssets", "BackgroundAssets", 16,0}, - { "DeviceDiscoveryExtension", "DeviceDiscoveryExtension", 16, 0}, - { "MetalFX", "MetalFX", new Version (16,0), NotAvailableInSimulator }, - { "PushToTalk", "PushToTalk", new Version (16,0), new Version (16, 2) /* available to build with, although it's unusable */}, - { "SafetyKit", "SafetyKit", 16, 0 }, - { "SharedWithYou", "SharedWithYou", 16, 0 }, - { "SharedWithYouCore", "SharedWithYouCore", 16, 0 }, - - { "Cinematic", "Cinematic", new Version (17, 0), NotAvailableInSimulator }, - { "Symbols", "Symbols", 17, 0 }, - { "SensitiveContentAnalysis", "SensitiveContentAnalysis", 17, 0 }, - { "BrowserEngineCore", "BrowserEngineCore", 17, 4}, - { "BrowserEngineKit", "BrowserEngineKit", 17, 4}, - - { "AccessorySetupKit", "AccessorySetupKit", 18, 0 }, - - { "SecurityUI", "SecurityUI", 18, 4 }, - - { "DeviceDiscoveryUI", "DeviceDiscoveryUI", 26, 0 }, - { "ExtensionKit", "ExtensionKit", 26, 0 }, - { "GameSave", "GameSave", 26, 0 }, - { "TouchController", "TouchController", 26, 0 }, - // the above MUST be kept in sync with simlauncher - // see tools/mtouch/Makefile - // please also keep it sorted to ease comparison - // - // The following tests also need to be updated: - // - // * RegistrarTest.MT4134 - }; + { "CallKit", "CallKit", 10 }, + { "Messages", "Messages", 10 }, + { "Speech", "Speech", 10 }, + { "VideoSubscriberAccount", "VideoSubscriberAccount", 10 }, + { "UserNotifications", "UserNotifications", 10 }, + { "UserNotificationsUI", "UserNotificationsUI", 10 }, + { "Intents", "Intents", 10 }, + { "IntentsUI", "IntentsUI", 10 }, + + { "ARKit", "ARKit", 11 }, + { "CoreNFC", "CoreNFC", new Version (11, 0), new Version (15, 0), true }, /* not always present, e.g. iPad w/iOS 12, so must be weak linked; doesn't work in the simulator in Xcode 12 (https://stackoverflow.com/q/63915728/183422), but works in at least Xcode 15 (maybe earlier too) */ + { "DeviceCheck", "DeviceCheck", new Version (11, 0), new Version (13, 0) }, + { "IdentityLookup", "IdentityLookup", 11 }, + { "IOSurface", "IOSurface", new Version (11, 0), new Version (26, 0) /* The headers were broken at some point, not sure when they started working again */ }, + { "CoreML", "CoreML", 11 }, + { "Vision", "Vision", 11 }, + { "FileProvider", "FileProvider", 11 }, + { "FileProviderUI", "FileProviderUI", 11 }, + { "PdfKit", "PDFKit", 11 }, + + { "BusinessChat", "BusinessChat", 11, 3 }, + + { "ClassKit", "ClassKit", 11, 4 }, + + { "AuthenticationServices", "AuthenticationServices", 12, 0 }, + { "CarPlay", "CarPlay", 12, 0 }, + { "CoreServices", "MobileCoreServices", 12, 0 }, + { "IdentityLookupUI", "IdentityLookupUI", 12, 0 }, + { "NaturalLanguage", "NaturalLanguage", 12, 0 }, + { "Network", "Network", 12, 0 }, + + { "BackgroundTasks", "BackgroundTasks", 13, 0 }, + { "CoreHaptics", "CoreHaptics", 13, 0 }, + { "CryptoTokenKit", "CryptoTokenKit", 13, 0 }, + { "LinkPresentation", "LinkPresentation", 13, 0 }, + { "MetricKit", "MetricKit", 13, 0 }, + { "PencilKit", "PencilKit", 13, 0 }, + { "QuickLookThumbnailing", "QuickLookThumbnailing", 13, 0 }, + { "SoundAnalysis", "SoundAnalysis", 13, 0 }, + { "VisionKit", "VisionKit", 13, 0 }, + + { "AutomaticAssessmentConfiguration", "AutomaticAssessmentConfiguration", 13, 4 }, + + { "Accessibility", "Accessibility", 14, 0 }, + { "AppClip", "AppClip", 14, 0 }, + { "AppTrackingTransparency", "AppTrackingTransparency", 14, 0 }, + { "MediaSetup", "MediaSetup", new Version (14, 0), NotAvailableInSimulator /* no headers in beta 3 */ }, + { "MetalPerformanceShadersGraph", "MetalPerformanceShadersGraph", 14, 0 }, + { "MLCompute", "MLCompute", new Version (14, 0), NotAvailableInSimulator }, + { "NearbyInteraction", "NearbyInteraction", 14, 0 }, + { "ScreenTime", "ScreenTime", 14, 0 }, + { "SensorKit", "SensorKit", new Version (14, 0), null, true }, /* not always present on device, e.g. any iPad, so must be weak linked; https://github.com/dotnet/macios/issues/9938 */ + { "UniformTypeIdentifiers", "UniformTypeIdentifiers", 14, 0 }, + + { "AdServices", "AdServices", 14, 3 }, + + { "CoreLocationUI", "CoreLocationUI", 15, 0 }, + + { "DataDetection", "DataDetection", 15, 0 }, + { "Phase", "PHASE", new Version (15, 0), new Version (26, 0) /* not certain about the exact version when this framework was added to the simulator, but this should be a safe default */ }, + { "OSLog", "OSLog", 15, 0 }, + { "ShazamKit", "ShazamKit", new Version (15, 0), new Version (16, 0) }, + { "ThreadNetwork", "ThreadNetwork", new Version (15, 0), NotAvailableInSimulator }, + + + { "AVRouting", "AVRouting", 16, 0 }, + { "BackgroundAssets", "BackgroundAssets", 16, 0 }, + { "DeviceDiscoveryExtension", "DeviceDiscoveryExtension", 16, 0 }, + { "MetalFX", "MetalFX", new Version (16, 0), NotAvailableInSimulator }, + { "PushToTalk", "PushToTalk", new Version (16, 0), new Version (16, 2) /* available to build with, although it's unusable */}, + { "SafetyKit", "SafetyKit", 16, 0 }, + { "SharedWithYou", "SharedWithYou", 16, 0 }, + { "SharedWithYouCore", "SharedWithYouCore", 16, 0 }, + + { "Cinematic", "Cinematic", new Version (17, 0), NotAvailableInSimulator }, + { "Symbols", "Symbols", 17, 0 }, + { "SensitiveContentAnalysis", "SensitiveContentAnalysis", 17, 0 }, + { "BrowserEngineCore", "BrowserEngineCore", 17, 4 }, + { "BrowserEngineKit", "BrowserEngineKit", 17, 4 }, + + { "AccessorySetupKit", "AccessorySetupKit", 18, 0 }, + + { "SecurityUI", "SecurityUI", 18, 4 }, + + { "DeviceDiscoveryUI", "DeviceDiscoveryUI", 26, 0 }, + { "ExtensionKit", "ExtensionKit", 26, 0 }, + { "GameSave", "GameSave", 26, 0 }, + { "TouchController", "TouchController", 26, 0 }, + // the above MUST be kept in sync with simlauncher + // see tools/mtouch/Makefile + // please also keep it sorted to ease comparison + // + // The following tests also need to be updated: + // + // * RegistrarTest.MT4134 + }; + } + return ios_frameworks; + } } static Frameworks? tvos_frameworks; @@ -633,100 +631,107 @@ public static Frameworks TVOSFrameworks { } static Frameworks? catalyst_frameworks; - public static Frameworks GetMacCatalystFrameworks () - { - if (catalyst_frameworks is null) { - catalyst_frameworks = CreateiOSFrameworks (false); - // not present in iOS but present in catalyst - catalyst_frameworks.Add ("CoreWlan", "CoreWLAN", 15, 0); - - var min = new Version (13, 0); - var v14_0 = new Version (14, 0); - var v14_2 = new Version (14, 2); - var v16_1 = new Version (16, 1); - var v18_0 = new Version (18, 0); - var v26_0 = new Version (26, 0); - var v16_0 = new Version (16, 0); - foreach (var f in catalyst_frameworks.Values) { - switch (f.Name) { - // These frameworks were added to Catalyst after they were added to iOS, so we have to adjust the Versions fields - case "CoreTelephony": - case "HomeKit": - case "Messages": - f.Version = v14_0; - f.VersionAvailableInSimulator = v14_0; - break; - case "AddressBook": - case "ClassKit": - case "UserNotificationsUI": - f.Version = v14_2; - f.VersionAvailableInSimulator = v14_2; - break; - case "ThreadNetwork": - f.Version = v16_1; - break; - case "Cinematic": - f.Version = v26_0; - break; - case "MediaSetup": - f.Version = v16_0; - break; - case "BrowserEngineKit": - case "DeviceDiscoveryExtension": - f.Version = v18_0; - break; - // These frameworks are not available on Mac Catalyst - case "DeviceDiscoveryUI": // xtro and introspection says it's not in Mac Catalyst, Apple's website says it is. For now, listen to xtro and introspection, until proven otherwise. - case "OpenGLES": - case "NotificationCenter": - case "GLKit": - case "VideoSubscriberAccount": - case "AccessorySetupKit": - // The headers for FileProviderUI exist, but the native linker fails - case "FileProviderUI": - // The headers for Twitter are there, , but no documentation whatsoever online and the native linker fails too - case "Twitter": - // headers-based xtro reporting those are *all* unknown API for Catalyst - case "AddressBookUI": - case "ARKit": - case "BrowserEngineCore": - case "CarPlay": - case "WatchConnectivity": - f.Unavailable = true; - break; - // and nothing existed before Catalyst 13.0 - default: - if (f.Version < min) - f.Version = min; - if (f.VersionAvailableInSimulator < min) - f.VersionAvailableInSimulator = min; - break; + public static Frameworks MacCatalystFrameworks { + get { + if (catalyst_frameworks is null) { + catalyst_frameworks = iOSFrameworks; + // We're going to mutate the value returned from iOSFrameworks, so clear the cached value so the next time iOSFrameworks is called it's re-generated. + ios_frameworks = null; + // not present in iOS but present in catalyst + catalyst_frameworks.Add ("CoreWlan", "CoreWLAN", 15, 0); + + var min = new Version (13, 0); + var v14_0 = new Version (14, 0); + var v14_2 = new Version (14, 2); + var v16_1 = new Version (16, 1); + var v18_0 = new Version (18, 0); + var v26_0 = new Version (26, 0); + var v16_0 = new Version (16, 0); + foreach (var f in catalyst_frameworks.Values) { + switch (f.Name) { + // These frameworks were added to Catalyst after they were added to iOS, so we have to adjust the Versions fields + case "CoreTelephony": + case "HomeKit": + case "Messages": + f.Version = v14_0; + f.VersionAvailableInSimulator = v14_0; + break; + case "AddressBook": + case "ClassKit": + case "UserNotificationsUI": + f.Version = v14_2; + f.VersionAvailableInSimulator = v14_2; + break; + case "ThreadNetwork": + f.Version = v16_1; + break; + case "Cinematic": + f.Version = v26_0; + break; + case "MediaSetup": + f.Version = v16_0; + break; + case "BrowserEngineKit": + case "DeviceDiscoveryExtension": + f.Version = v18_0; + break; + // These frameworks are not available on Mac Catalyst + case "DeviceDiscoveryUI": // xtro and introspection says it's not in Mac Catalyst, Apple's website says it is. For now, listen to xtro and introspection, until proven otherwise. + case "OpenGLES": + case "NotificationCenter": + case "GLKit": + case "VideoSubscriberAccount": + case "AccessorySetupKit": + // The headers for FileProviderUI exist, but the native linker fails + case "FileProviderUI": + // The headers for Twitter are there, , but no documentation whatsoever online and the native linker fails too + case "Twitter": + // headers-based xtro reporting those are *all* unknown API for Catalyst + case "AddressBookUI": + case "ARKit": + case "BrowserEngineCore": + case "CarPlay": + case "WatchConnectivity": + f.Unavailable = true; + break; + // and nothing existed before Catalyst 13.0 + default: + if (f.Version < min) + f.Version = min; + if (f.VersionAvailableInSimulator < min) + f.VersionAvailableInSimulator = min; + break; + } } - } - // Add frameworks that are not in iOS - catalyst_frameworks.Add ("AppKit", 13, 0); - catalyst_frameworks.Add ("ExecutionPolicy", 16, 0); - catalyst_frameworks.Add ("ServiceManagement", 16, 0); - catalyst_frameworks.Add ("ScreenCaptureKit", 18, 2); + // Add frameworks that are not in iOS + catalyst_frameworks.Add ("AppKit", 13, 0); + catalyst_frameworks.Add ("ExecutionPolicy", 16, 0); + catalyst_frameworks.Add ("ServiceManagement", 16, 0); + catalyst_frameworks.Add ("ScreenCaptureKit", 18, 2); + } + return catalyst_frameworks; } - return catalyst_frameworks; } - // returns null if the platform doesn't exist (the ErrorHandler machinery is heavy and this file is included in several projects, which makes throwing an exception complicated) - public static Frameworks? GetFrameworks (ApplePlatform platform, bool is_simulator_build) + public static bool TryGetFrameworks (ApplePlatform platform, [NotNullWhen (true)] out Frameworks? frameworks) { switch (platform) { case ApplePlatform.iOS: - return GetiOSFrameworks (is_simulator_build); + frameworks = iOSFrameworks; + return true; case ApplePlatform.TVOS: - return TVOSFrameworks; + frameworks = TVOSFrameworks; + return true; case ApplePlatform.MacOSX: - return MacFrameworks; + frameworks = MacFrameworks; + return true; case ApplePlatform.MacCatalyst: - return GetMacCatalystFrameworks (); + frameworks = MacCatalystFrameworks; + return true; default: - return null; + frameworks = null; + return false; } } @@ -771,9 +776,9 @@ public static bool TryGetFramework (Application app, TypeDefinition? td, [NotNul if (!TryGetFramework (app, td, out string? frameworkName)) return false; - var all_frameworks = GetFrameworks (app.Platform, app.IsSimulatorBuild); - if (all_frameworks is null) + if (!TryGetFrameworks (app.Platform, out var all_frameworks)) return false; + return all_frameworks.TryGetValue (frameworkName, out framework); } @@ -810,9 +815,9 @@ static void Gather (Application app, IEnumerable assemblies, } // Iterate over all the namespaces and check which frameworks we need to link with. - var all_frameworks = GetFrameworks (app.Platform, app.IsSimulatorBuild); - if (all_frameworks is null) + if (!TryGetFrameworks (app.Platform, out var all_frameworks)) return; + foreach (var nspace in namespaces) { if (!all_frameworks.TryGetValue (nspace, out var framework)) continue; From dadb6cccdcfbe44decf0c003f308f09f2699a27b Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Tue, 16 Jun 2026 19:52:09 +0200 Subject: [PATCH 06/27] [dotnet] Use desktop MSBuild task assemblies in VS for all platforms (#25662) ## Description Building a MacCatalyst app inside Visual Studio on Windows ARM64 fails with: ``` error MSB4216: Could not run the "MakeDir" task because MSBuild could not create or connect to a task host with runtime "NET" and architecture "*". ``` ## Root cause Inside Visual Studio, our MSBuild tasks should run in-process using the `netstandard2.0` assemblies. This is controlled by the `_UseDesktopTaskAssemblies` property. When it is not set, the tasks instead use the `.NET` assemblies, which need a separate .NET task host. That task host does not work reliably from Visual Studio, and fails on Windows ARM64. #25417 set `_UseDesktopTaskAssemblies` for Visual Studio builds, but only in the iOS SDK. MacCatalyst, macOS and tvOS were missed, so they still try to use the .NET task host and break. ## Fix Set `_UseDesktopTaskAssemblies` in the shared `Xamarin.Shared.Sdk.props`, which is imported by all four platforms, so the workaround applies everywhere. The iOS SDK keeps setting it earlier (it also needs it for `CoreiOSSdkDirectory`); the `== ''` guard makes the shared copy a no-op for iOS. Ref: #25418 --------- Co-authored-by: Milos Kotlar Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/targets/Xamarin.Shared.Sdk.props | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.props b/dotnet/targets/Xamarin.Shared.Sdk.props index ce84e27e899b..46c763ae7168 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.props +++ b/dotnet/targets/Xamarin.Shared.Sdk.props @@ -19,6 +19,9 @@ + + <_UseDesktopTaskAssemblies Condition="'$(_UseDesktopTaskAssemblies)' == '' And '$(BuildingInsideVisualStudio)' == 'true'">true + true From 58e18416cddf5b9e7d1156e9008f95588c117f44 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 16 Jun 2026 20:01:24 +0200 Subject: [PATCH 07/27] [dotnet] Put type-map.txt into the linker cache directory. (#25654) This way it's copied back to Windows automatically after a remote build. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 02926aa4ed8c..ccbe646239a8 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -596,10 +596,6 @@ <_LinkerItemsDirectory>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)linker-items')) <_LinkerItemsDirectory Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)linker-items - - <_LinkerCacheDirectory>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)linker-cache')) - <_LinkerCacheDirectory Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)linker-cache - <_IsSimulatorFeature Condition="'$(SdkIsMobile)' == 'true' And '$(_SdkIsSimulator)' == 'true'">true <_IsSimulatorFeature Condition="'$(_IsSimulatorFeature)' == ''">false @@ -1290,6 +1286,10 @@ <_AOTInputDirectory>$(_IntermediateNativeLibraryDir)aot-input/ <_AOTOutputDirectory>$(_IntermediateNativeLibraryDir)aot-output/ + + <_LinkerCacheDirectory>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)linker-cache')) + <_LinkerCacheDirectory Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)linker-cache + - <_TypeMapFilePath Condition="'$(_TypeMapFilePath)' == ''">$(DeviceSpecificIntermediateOutputPath)type-map.txt + <_TypeMapFilePath Condition="'$(_TypeMapFilePath)' == ''">$(_LinkerCacheDirectory)/type-map.txt From cf356c024449a49f40bc7eddd067ad16a2153da2 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Jun 2026 09:48:13 +0200 Subject: [PATCH 08/27] [tests] Update MSBuild.StructuredLogger to 2.2.243 (#25679) ## Summary - update `MSBuild.StructuredLogger` from `2.2.158` to `2.2.243` - pick the smallest verified bump that fixes the local binlog parser crash ## Why Local Apple test binlogs can record an empty `CurrentUICulture=` event under `LANG=C.UTF-8`. With `MSBuild.StructuredLogger` `2.2.158`, the reader synthesizes `PropertyReassignment` messages from localized string resources, but those resources are only initialized after a non-empty culture event. When the binlog reaches the first `PropertyReassignment`, `Strings.PropertyReassignment` can still be null, which crashes parsing with `ArgumentNullException("format")` instead of reporting the actual build result. `2.2.243` already contains the upfront string initialization that avoids that failure, so the same local binlogs parse successfully without needing a larger package jump. ## Validation - built `tests/dotnet/UnitTests/DotNetUnitTests.csproj` on current `main` - re-ran the standalone binlog repro against a previously failing successful local Apple binlog and `2.2.243` parsed property reassignments successfully This PR is recreated from #25674 (because our CI won't build from forks). --------- Co-authored-by: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index ac90e9e40ed2..847ba3e62337 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ true - 2.2.158 + 2.2.243 17.13.26 $(MicrosoftBuildPackageVersion) $(MicrosoftBuildPackageVersion) From 0b7248fa4495a9aa31488c5681ec2aa5a8f49434 Mon Sep 17 00:00:00 2001 From: "CSIGS@microsoft.com" Date: Wed, 17 Jun 2026 01:37:57 -0700 Subject: [PATCH 09/27] LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260603175250477 to main (#25633) LEGO: Pull request from lego/hb_5df43909-4a19-4f55-bc3f-9ea8fccf3c82_20260603175250477 to main with localized lcls --- .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ .../MSBStrings.resx.lcl | 27 +++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 429efcd0d6a5..faf7c0361889 100644 --- a/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/ja/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + コンポーネント) からインストールしてください。]]> + + + + + + + Components).]]> + + コンポーネント) から更新してください。]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index ff190b437318..c8408a7c4bd2 100644 --- a/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/ru/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + Компоненты).]]> + + + + + + + Components).]]> + + Компоненты).]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 3251464cd07f..9e41267b38b6 100644 --- a/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/tr/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + Bileşenleri).]]> + + + + + + + Components).]]> + + Bileşenler) simülatör çalışma zamanını güncelleştirin.]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + diff --git a/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 2e41c4b28761..40bd2cca9335 100644 --- a/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/macios/Localize/loc/zh-Hans/macios/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -2734,6 +2734,24 @@ + + + Components).]]> + + “组件”)运行 ‘xcodebuild -downloadPlatform {0}’ 来安装它。]]> + + + + + + + Components).]]> + + “组件”)运行 ‘xcodebuild -downloadPlatform {0}’ 来更新模拟器运行时。]]> + + + + @@ -3661,6 +3679,15 @@ + + + + + + + + + From c06da3579ce2a0f493569922d3459d5e51280f5d Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 08:44:42 +0000 Subject: [PATCH 10/27] [main] Update dependencies from dotnet/xharness (#25586) This pull request updates the following dependencies ## From https://github.com/dotnet/xharness - **Subscription**: [02e03784-16b3-4ced-b02a-3715797fc7da](https://maestro.dot.net/subscriptions?search=02e03784-16b3-4ced-b02a-3715797fc7da) - **Build**: [20260529.1](https://dev.azure.com/dnceng/internal/_build/results?buildId=2987132) ([316341](https://maestro.dot.net/channel/2/github:dotnet:xharness/build/316341)) - **Date Produced**: May 29, 2026 3:33:16 PM UTC - **Commit**: [2cee83bf4841d72c9734a343f3003aeabdd46edf](https://github.com/dotnet/xharness/commit/2cee83bf4841d72c9734a343f3003aeabdd46edf) - **Branch**: [main](https://github.com/dotnet/xharness/tree/main) - **Dependency Updates**: - From [11.0.0-prerelease.26264.1 to 11.0.0-prerelease.26279.1][1] - Microsoft.DotNet.XHarness.iOS.Shared [1]: https://github.com/dotnet/xharness/compare/51ca379106...2cee83bf48 --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 51bd77497e11..d4428e5b8170 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -30,7 +30,7 @@ This file should be imported by eng/Versions.props 18.5.9227 26.5.9004 - 11.0.0-prerelease.26264.1 + 11.0.0-prerelease.26316.1 18.0.9617 18.0.9617 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f31e396323df..4bbb585ed22b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -107,9 +107,9 @@ https://github.com/dotnet/dotnet aec921632e75e1f29327709dd52e98c41f3b55cf - + https://github.com/dotnet/xharness - 51ca379106cfd749a498cb0822210ef1aa926e41 + 8d6fae7775722b0ec2c57d119efc164ec36cb837 https://github.com/dotnet/dotnet From 1f5fd5cc4f5aa377b6d3939de024541660855835 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 08:50:23 +0000 Subject: [PATCH 11/27] [main] Update dependencies from dotnet/dotnet (#25657) This pull request updates the following dependencies ## From https://github.com/dotnet/dotnet - **Subscription**: [da09b56a-0fb1-439a-b894-def14d2ec0a4](https://maestro.dot.net/subscriptions?search=da09b56a-0fb1-439a-b894-def14d2ec0a4) - **Build**: [20260612.3](https://dev.azure.com/dnceng/internal/_build/results?buildId=2998757) ([318480](https://maestro.dot.net/channel/10307/github:dotnet:dotnet/build/318480)) - **Date Produced**: June 12, 2026 12:29:31 PM UTC - **Commit**: [e4cee613b82457afd92ddfce82a67dcaaa227a84](https://github.com/dotnet/dotnet/commit/e4cee613b82457afd92ddfce82a67dcaaa227a84) - **Branch**: [release/10.0.4xx](https://github.com/dotnet/dotnet/tree/release/10.0.4xx) - **Dependency Updates**: - From [10.0.0-beta.26281.104 to 10.0.0-beta.26312.103][5] - Microsoft.DotNet.Arcade.Sdk - Microsoft.DotNet.Build.Tasks.Feed - Microsoft.DotNet.SharedFramework.Sdk - From [10.0.400-preview.0.26281.104 to 10.0.400-preview.0.26312.103][5] - Microsoft.NET.Sdk - From [10.0.400-preview.26281.104 to 10.0.400-preview.26312.103][5] - Microsoft.TemplateEngine.Authoring.Tasks [5]: https://github.com/dotnet/dotnet/compare/aec921632e...e4cee613b8 --- eng/Version.Details.props | 10 +++++----- eng/Version.Details.xml | 20 ++++++++++---------- global.json | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index d4428e5b8170..5ed98e2639a6 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,16 +6,16 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26281.104 - 10.0.0-beta.26281.104 + 10.0.0-beta.26316.107 + 10.0.0-beta.26316.107 0.11.5-alpha.26070.104 - 10.0.0-beta.26281.104 + 10.0.0-beta.26316.107 10.0.3-servicing.26070.104 10.0.3 10.0.3 - 10.0.400-preview.0.26281.104 + 10.0.400-preview.0.26316.107 10.0.3 - 10.0.400-preview.26281.104 + 10.0.400-preview.26316.107 26.0.11017 18.5.9227 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4bbb585ed22b..e525a0bdd7ee 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/dotnet - aec921632e75e1f29327709dd52e98c41f3b55cf + db90bcd4329f0e4488a7c050bc59d957db1715ee https://github.com/dotnet/dotnet @@ -95,25 +95,25 @@ - + https://github.com/dotnet/dotnet - aec921632e75e1f29327709dd52e98c41f3b55cf + db90bcd4329f0e4488a7c050bc59d957db1715ee - + https://github.com/dotnet/dotnet - aec921632e75e1f29327709dd52e98c41f3b55cf + db90bcd4329f0e4488a7c050bc59d957db1715ee - + https://github.com/dotnet/dotnet - aec921632e75e1f29327709dd52e98c41f3b55cf + db90bcd4329f0e4488a7c050bc59d957db1715ee https://github.com/dotnet/xharness 8d6fae7775722b0ec2c57d119efc164ec36cb837 - + https://github.com/dotnet/dotnet - aec921632e75e1f29327709dd52e98c41f3b55cf + db90bcd4329f0e4488a7c050bc59d957db1715ee diff --git a/global.json b/global.json index b2855704689d..7e302c3d9bea 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.400-preview.0.26281.104", + "version": "10.0.400-preview.0.26316.107", "paths": [ "builds/downloads/dotnet", "$host$" @@ -8,9 +8,9 @@ "errorMessage": "The .NET SDK could not be found, please run 'make dotnet -C builds'." }, "tools": { - "dotnet": "10.0.400-preview.0.26281.104" + "dotnet": "10.0.400-preview.0.26316.107" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26281.104" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26316.107" } } From 022f20f88ca5fb59368c01b1f8796c31a0b43d71 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Jun 2026 12:34:55 +0200 Subject: [PATCH 12/27] [docs] Improve XML docs for SystemConfigurationException (#25704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve XML documentation for the `SystemConfigurationException` type: - Remove "To be added." placeholder entries - Add meaningful `` and `` descriptions - Simplify `` references (remove redundant namespace qualifiers) - Reorder XML doc elements to standard order (summary, value, param) - Remove empty `` nodes 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SystemConfigurationException.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/SystemConfiguration/SystemConfigurationException.cs b/src/SystemConfiguration/SystemConfigurationException.cs index 7a5d8a605f12..dacca8378086 100644 --- a/src/SystemConfiguration/SystemConfigurationException.cs +++ b/src/SystemConfiguration/SystemConfigurationException.cs @@ -11,21 +11,18 @@ namespace SystemConfiguration { - /// An exception relating to network reachability. The cause of the exception is specified by the property. - /// To be added. + /// An exception relating to network reachability. The cause of the exception is specified by the property. public class SystemConfigurationException : Exception { - /// To be added. - /// Creates a new wrapping the . - /// To be added. + /// Creates a new wrapping the specified . + /// The that caused this exception. public SystemConfigurationException (StatusCode statusErrorCode) : base (StatusCodeError.GetErrorDescription (statusErrorCode)) { StatusErrorCode = statusErrorCode; } - /// The wrapped in this . - /// To be added. - /// To be added. + /// Gets the that describes the cause of this exception. + /// The status code associated with this exception. public StatusCode StatusErrorCode { get; private set; } internal static SystemConfigurationException FromMostRecentCall () From 4e0d12a9d4cb5aebb341311e355bfce667f27531 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Jun 2026 14:46:47 +0200 Subject: [PATCH 13/27] [src] Fix numerous API typos and improve the introspection typo test. Fixes #25397. (#25706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix real typos in API names by introducing correctly-spelled alternatives and deprecating the old misspelled names. ## Approach For **binding methods/properties**, the old name keeps `[Export]` to preserve virtual dispatch (no breaking API changes), and the new name uses `[Wrap]`. In `XAMCORE_5_0` only the correctly spelled name will remain with `[Export]`. For **enum values**, the old value is `[Obsolete]` with `[EditorBrowsable(Never)]` and will be removed in `XAMCORE_5_0`. For **delegate methods with `[EventArgs]`**, only a `#if XAMCORE_5_0` rename is possible (the binding generator requires EventArgs on all delegate methods). For **manual code** (non-generated), the correctly-spelled method is the implementation; the old misspelled name is a thin wrapper marked `[Obsolete]`. ## Fixed typos | Old name | New name | Type | |----------|----------|------| | StandarizePath | StandardizePath | method | | ReplaceOcurrences | ReplaceOccurrences | method | | DequeueNotificationsMatchingcoalesceMask | DequeueNotificationsMatchingCoalesceMask | method | | SkipDescendents | SkipDescendants | method | | SetParamDescriptorforKeyword | SetParamDescriptorForKeyword | method | | SetAttributeDescriptorforKeyword | SetAttributeDescriptorForKeyword | method | | InsertDescriptoratIndex | InsertDescriptorAtIndex | method | | SetDescriptorforKeyword | SetDescriptorForKeyword | method | | WilllDownloadToUrl | WillDownloadToUrl | method | | SetImageforSearchBarIcon | SetImageForSearchBarIcon | method | | SetPositionAdjustmentforSearchBarIcon | SetPositionAdjustmentForSearchBarIcon | method | | GetNumberofItems | GetNumberOfItems | method | | TitleWidthConstraintedToSize | TitleWidthConstrainedToSize | method | | IndexOfItemWithTargetandAction | IndexOfItemWithTargetAndAction | method | | TryToPerformwith | TryToPerformWith | method | | ConvertRectfromBacking | ConvertRectFromBacking | method | | UpdateSpellingPanelWithGrammarl | UpdateSpellingPanelWithGrammarString | method | | SetIconforFile | SetIconForFile | method | | SetAttributesforExportedKey | SetAttributesForExportedKey | method | | SortedArrayFromArraycollationStringSelector | SortedArrayFromArrayCollationStringSelector | method | | DenimonatorExpression | DenominatorExpression | property | | GpsDifferental | GpsDifferential | property | | ExhangeDataMaximumSize | ExchangeDataMaximumSize | property | | RightCallpoutOffset | RightCalloutOffset | property | | RoundToOddHermitean | RoundToOddHermitian | property | | CommitedLoad | CommittedLoad | delegate (XAMCORE_5_0) | | ReplacementValueForAttributevalue | ReplacementValueForAttributeValue | delegate (XAMCORE_5_0) | | UpdatedCharacterteristicValue | UpdatedCharacteristicValue | delegate (XAMCORE_5_0) | | SetBaseWritingDirectionforRange | SetBaseWritingDirection | abstract (XAMCORE_5_0) | | NLContextualEmebeddingKey | NLContextualEmbeddingKey | enum type (XAMCORE_5_0) | | ExifSubsecTimeOrginal | (already obsoleted) | field | | WarichuPunctiation | WarichuPunctuation | enum value | | TrackableArae | TrackableArea | enum value | | DissapearingItemDefault | DisappearingItemDefault | enum value | | OcclussionAttenuation | OcclusionAttenuation | enum value | | FourtyMHz | FortyMHz | enum value | | ProcAppactive | ProcAppActive | enum value | | FromIdentityCertificatesPersistance | Create | static method | | PropogateAttachments | PropagateAttachments | method | | SetLocalEventsFilterDuringSupressionState | SetLocalEventsFilterDuringSuppressionState | method | | GetLocalEventsFilterDuringSupressionState | GetLocalEventsFilterDuringSuppressionState | method | | LocalEventsSupressionInterval | LocalEventsSuppressionInterval | property | | GpsDifferentalKey | GpsDifferentialKey | field | | Various others | (see enum fixes) | enum values | ## Other changes - Rewrote the introspection `TypoTest` to use a word-level dedup approach (faster and more reliable) - Documented all remaining allowed typos with comments explaining why they are valid - Wrapped typos that are fixed only in `XAMCORE_5_0` with `#if !XAMCORE_5_0` in the allowed list Fixes https://github.com/dotnet/macios/issues/25397 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/AppKit/Enums.cs | 14 +- src/AudioUnit/AUEnums.cs | 7 +- src/CoreGraphics/CGEnums.cs | 9 +- src/CoreGraphics/CGEventSource.cs | 28 +- src/CoreLocation/CLEnums.cs | 2 +- src/CoreVideo/CVBuffer.cs | 8 +- src/CoreWlan/Enums.cs | 8 +- src/Darwin/KernelNotification.cs | 8 +- src/Foundation/NSMetadataItem.cs | 16 +- src/Foundation/NSUrlCredential.cs | 9 +- src/HomeKit/HMEnums.cs | 9 +- src/NaturalLanguage/Enums.cs | 4 + src/Network/NWConnectionGroup.cs | 9 +- src/PrintCore/Defs.cs | 8 +- src/accessibility.cs | 10 + src/appkit.cs | 63 + src/authenticationservices.cs | 10 + src/avfoundation.cs | 10 + src/corebluetooth.cs | 4 + src/coreimage.cs | 9 + src/corespotlight.cs | 9 + src/foundation.cs | 91 + src/gamekit.cs | 12 + src/imageio.cs | 12 + src/imagekit.cs | 9 + src/mapkit.cs | 13 + src/metalperformanceshadersgraph.cs | 10 + src/mlcompute.cs | 8 +- src/uikit.cs | 35 + src/webkit.cs | 4 + .../Documentation.KnownFailures.txt | 39 +- tests/introspection/ApiTypoTest.cs | 1562 +++++++++-------- .../CoreGraphics/PdfTagTypeTest.cs | 2 +- 33 files changed, 1250 insertions(+), 801 deletions(-) diff --git a/src/AppKit/Enums.cs b/src/AppKit/Enums.cs index d3ef79c30b5c..ef7124374127 100644 --- a/src/AppKit/Enums.cs +++ b/src/AppKit/Enums.cs @@ -488,7 +488,12 @@ public enum NSCellHit : ulong { /// To be added. EditableTextArea = 2, /// To be added. - TrackableArae = 4, + TrackableArea = 4, +#if !XAMCORE_5_0 + [Obsolete ("Use 'TrackableArea' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + TrackableArae = TrackableArea, +#endif } [NoMacCatalyst] @@ -1718,7 +1723,12 @@ public enum NSCompositingOperation : ulong { [Native] public enum NSAnimationEffect : ulong { /// To be added. - DissapearingItemDefault = 0, + DisappearingItemDefault = 0, +#if !XAMCORE_5_0 + [Obsolete ("Use 'DisappearingItemDefault' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + DissapearingItemDefault = DisappearingItemDefault, +#endif /// To be added. EffectPoof = 10, } diff --git a/src/AudioUnit/AUEnums.cs b/src/AudioUnit/AUEnums.cs index 7f6bd0a50c2d..265f98b01abc 100644 --- a/src/AudioUnit/AUEnums.cs +++ b/src/AudioUnit/AUEnums.cs @@ -1064,7 +1064,12 @@ public enum AudioUnitParameterType // UInt32 in AudioUnitParameterInfo /// To be added. GlobalReverbGain = 9, /// To be added. - OcclussionAttenuation = 10, + OcclusionAttenuation = 10, +#if !XAMCORE_5_0 + [Obsolete ("Use 'OcclusionAttenuation' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + OcclussionAttenuation = OcclusionAttenuation, +#endif /// To be added. ObstructionAttenuation = 11, } diff --git a/src/CoreGraphics/CGEnums.cs b/src/CoreGraphics/CGEnums.cs index 60dc2ccdf34e..ab41f77cb6be 100644 --- a/src/CoreGraphics/CGEnums.cs +++ b/src/CoreGraphics/CGEnums.cs @@ -9,6 +9,8 @@ #nullable enable +using System.ComponentModel; + namespace CoreGraphics { public enum MatrixOrder { @@ -216,7 +218,12 @@ public enum CGPdfTagType /* int32_t */ { RubyPunctuation, Warichu, WarichuText, - WarichuPunctiation, + WarichuPunctuation, +#if !XAMCORE_5_0 + [Obsolete ("Use 'WarichuPunctuation' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + WarichuPunctiation = WarichuPunctuation, +#endif Figure = 700, Formula, Form, diff --git a/src/CoreGraphics/CGEventSource.cs b/src/CoreGraphics/CGEventSource.cs index 3557e5ecf8ce..a73588d0df65 100644 --- a/src/CoreGraphics/CGEventSource.cs +++ b/src/CoreGraphics/CGEventSource.cs @@ -13,6 +13,7 @@ #if MONOMAC || __MACCATALYST__ using CoreFoundation; +using System.ComponentModel; namespace CoreGraphics { /// To be added. @@ -157,11 +158,17 @@ public long UserData { /// To be added. /// To be added. /// To be added. - public void SetLocalEventsFilterDuringSupressionState (CGEventFilterMask filter, CGEventSuppressionState state) + public void SetLocalEventsFilterDuringSuppressionState (CGEventFilterMask filter, CGEventSuppressionState state) { CGEventSourceSetLocalEventsFilterDuringSuppressionState (Handle, filter, state); } +#if !XAMCORE_5_0 + [Obsolete ("Use 'SetLocalEventsFilterDuringSuppressionState' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public void SetLocalEventsFilterDuringSupressionState (CGEventFilterMask filter, CGEventSuppressionState state) => SetLocalEventsFilterDuringSuppressionState (filter, state); +#endif + [DllImport (Constants.ApplicationServicesCoreGraphicsLibrary)] extern static CGEventFilterMask CGEventSourceGetLocalEventsFilterDuringSuppressionState (IntPtr handle, CGEventSuppressionState state); @@ -169,11 +176,17 @@ public void SetLocalEventsFilterDuringSupressionState (CGEventFilterMask filter, /// To be added. /// To be added. /// To be added. - public CGEventFilterMask GetLocalEventsFilterDuringSupressionState (CGEventSuppressionState state) + public CGEventFilterMask GetLocalEventsFilterDuringSuppressionState (CGEventSuppressionState state) { return CGEventSourceGetLocalEventsFilterDuringSuppressionState (Handle, state); } +#if !XAMCORE_5_0 + [Obsolete ("Use 'GetLocalEventsFilterDuringSuppressionState' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public CGEventFilterMask GetLocalEventsFilterDuringSupressionState (CGEventSuppressionState state) => GetLocalEventsFilterDuringSuppressionState (state); +#endif + [DllImport (Constants.ApplicationServicesCoreGraphicsLibrary)] extern static void CGEventSourceSetLocalEventsSuppressionInterval (IntPtr handle, double seconds); @@ -183,7 +196,7 @@ public CGEventFilterMask GetLocalEventsFilterDuringSupressionState (CGEventSuppr /// To be added. /// To be added. /// To be added. - public double LocalEventsSupressionInterval { + public double LocalEventsSuppressionInterval { get { return CGEventSourceGetLocalEventsSuppressionInterval (Handle); } @@ -192,6 +205,15 @@ public double LocalEventsSupressionInterval { } } +#if !XAMCORE_5_0 + [Obsolete ("Use 'LocalEventsSuppressionInterval' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public double LocalEventsSupressionInterval { + get => LocalEventsSuppressionInterval; + set => LocalEventsSuppressionInterval = value; + } +#endif + } } diff --git a/src/CoreLocation/CLEnums.cs b/src/CoreLocation/CLEnums.cs index 60e3c155b38b..94ad45bea1fd 100644 --- a/src/CoreLocation/CLEnums.cs +++ b/src/CoreLocation/CLEnums.cs @@ -71,7 +71,7 @@ public enum CLError : long { DeferredFailed, /// The did not enter deferred mode because location updates were already paused or disabled. DeferredNotUpdatingLocation, - /// Deferred mode is not available for the requested accuracy. For deferred mode, the accuracy must be or . + /// Deferred mode is not available for the requested accuracy. For deferred mode, the accuracy must be or . DeferredAccuracyTooLow, /// Deferred mode does not allow distance filters. The must be set to . DeferredDistanceFiltered, diff --git a/src/CoreVideo/CVBuffer.cs b/src/CoreVideo/CVBuffer.cs index 158a44ce8596..b331e5e25e5d 100644 --- a/src/CoreVideo/CVBuffer.cs +++ b/src/CoreVideo/CVBuffer.cs @@ -231,7 +231,7 @@ unsafe static IntPtr CVBufferCopyAttachment (IntPtr buffer, IntPtr key, out CVAt /// To be added. /// To be added. /// To be added. - public void PropogateAttachments (CVBuffer destinationBuffer) + public void PropagateAttachments (CVBuffer destinationBuffer) { if (destinationBuffer is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (destinationBuffer)); @@ -240,6 +240,12 @@ public void PropogateAttachments (CVBuffer destinationBuffer) GC.KeepAlive (destinationBuffer); } +#if !XAMCORE_5_0 + [Obsolete ("Use 'PropagateAttachments' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public void PropogateAttachments (CVBuffer destinationBuffer) => PropagateAttachments (destinationBuffer); +#endif + [DllImport (Constants.CoreVideoLibrary)] extern static void CVBufferSetAttachment (/* CVBufferRef */ IntPtr buffer, /* CFStringRef */ IntPtr key, /* CFTypeRef */ IntPtr @value, CVAttachmentMode attachmentMode); diff --git a/src/CoreWlan/Enums.cs b/src/CoreWlan/Enums.cs index 659d029f085b..dd603702ab12 100644 --- a/src/CoreWlan/Enums.cs +++ b/src/CoreWlan/Enums.cs @@ -2,6 +2,7 @@ // Copyright 2019 Microsoft Corporation using CoreFoundation; +using System.ComponentModel; namespace CoreWlan { @@ -169,7 +170,12 @@ public enum CWChannelWidth : ulong { /// To be added. TwentyMHz = 1, /// To be added. - FourtyMHz = 2, + FortyMHz = 2, +#if !XAMCORE_5_0 + [Obsolete ("Use 'FortyMHz' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + FourtyMHz = FortyMHz, +#endif /// To be added. EightyMHz = 3, /// To be added. diff --git a/src/Darwin/KernelNotification.cs b/src/Darwin/KernelNotification.cs index 2aaba82e098e..0bf4e0733e7f 100644 --- a/src/Darwin/KernelNotification.cs +++ b/src/Darwin/KernelNotification.cs @@ -32,6 +32,7 @@ using CoreFoundation; using System.Runtime.CompilerServices; using System.Collections.Generic; +using System.ComponentModel; namespace Darwin { /// To be added. @@ -183,7 +184,12 @@ public enum FilterFlags : uint { // iOS only /// To be added. - ProcAppactive = 0x00800000, + ProcAppActive = 0x00800000, +#if !XAMCORE_5_0 + [Obsolete ("Use 'ProcAppActive' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + ProcAppactive = ProcAppActive, +#endif /// To be added. ProcAppBackground = 0x00400000, /// To be added. diff --git a/src/Foundation/NSMetadataItem.cs b/src/Foundation/NSMetadataItem.cs index b4fd6934196e..2f02115dacc4 100644 --- a/src/Foundation/NSMetadataItem.cs +++ b/src/Foundation/NSMetadataItem.cs @@ -1067,11 +1067,25 @@ public NSDate? GpsDateStamp { [UnsupportedOSPlatform ("maccatalyst")] [UnsupportedOSPlatform ("tvos")] [UnsupportedOSPlatform ("ios")] + public double? GpsDifferential { + get { + return GetNullableDouble (NSMetadataQuery.GpsDifferentialKey); + } + } + +#if !XAMCORE_5_0 + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("maccatalyst")] + [UnsupportedOSPlatform ("tvos")] + [UnsupportedOSPlatform ("ios")] + [System.Obsolete ("Use 'GpsDifferential' instead.")] + [System.ComponentModel.EditorBrowsable (System.ComponentModel.EditorBrowsableState.Never)] public double? GpsDifferental { get { - return GetNullableDouble (NSMetadataQuery.GpsDifferentalKey); + return GpsDifferential; } } +#endif /// To be added. /// To be added. diff --git a/src/Foundation/NSUrlCredential.cs b/src/Foundation/NSUrlCredential.cs index 31d575d2b4e7..6c3462595a0e 100644 --- a/src/Foundation/NSUrlCredential.cs +++ b/src/Foundation/NSUrlCredential.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Collections; +using System.ComponentModel; using Security; @@ -30,7 +31,7 @@ public NSUrlCredential (SecIdentity identity, SecCertificate [] certificates, NS /// The certificates to use for the credential. /// Specifies how long the credential should be kept. /// A new instance. - public static NSUrlCredential FromIdentityCertificatesPersistance (SecIdentity identity, SecCertificate [] certificates, NSUrlCredentialPersistence persistence) + public static NSUrlCredential Create (SecIdentity identity, SecCertificate [] certificates, NSUrlCredentialPersistence persistence) { ArgumentNullException.ThrowIfNull (identity); ArgumentNullException.ThrowIfNull (certificates); @@ -42,6 +43,12 @@ public static NSUrlCredential FromIdentityCertificatesPersistance (SecIdentity i } } +#if !XAMCORE_5_0 + [Obsolete ("Use 'Create' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public static NSUrlCredential FromIdentityCertificatesPersistance (SecIdentity identity, SecCertificate [] certificates, NSUrlCredentialPersistence persistence) => Create (identity, certificates, persistence); +#endif + /// Gets the identity (digital certificate + private key) associated with this credential. /// A object representing the identity, or if no identity is available. public SecIdentity? SecIdentity { diff --git a/src/HomeKit/HMEnums.cs b/src/HomeKit/HMEnums.cs index 84523a021886..c5c2127a75c4 100644 --- a/src/HomeKit/HMEnums.cs +++ b/src/HomeKit/HMEnums.cs @@ -1,4 +1,6 @@ +using System.ComponentModel; + namespace HomeKit { /// Enumerates possible failures in Home Kit operations. @@ -156,7 +158,12 @@ public enum HMError : long { /// The read or write failed. ReadWriteFailure = 74, /// The user or application is not signed in to iCloud. - NotSignedIntoiCloud = 75, + NotSignedIntoICloud = 75, +#if !XAMCORE_5_0 + [Obsolete ("Use 'NotSignedIntoICloud' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + NotSignedIntoiCloud = NotSignedIntoICloud, +#endif /// Keychain synchronization was not enabled. KeychainSyncNotEnabled = 76, /// Data was synchronizing. diff --git a/src/NaturalLanguage/Enums.cs b/src/NaturalLanguage/Enums.cs index 411802723c4e..8aeb5f5e80d1 100644 --- a/src/NaturalLanguage/Enums.cs +++ b/src/NaturalLanguage/Enums.cs @@ -369,7 +369,11 @@ public enum NLContextualEmbeddingAssetsResult : long { } [TV (17, 0), Mac (14, 0), iOS (17, 0), MacCatalyst (17, 0)] +#if XAMCORE_5_0 + public enum NLContextualEmbeddingKey { +#else public enum NLContextualEmebeddingKey { +#endif [Field ("NLContextualEmbeddingKeyLanguages")] Languages, [Field ("NLContextualEmbeddingKeyScripts")] diff --git a/src/Network/NWConnectionGroup.cs b/src/Network/NWConnectionGroup.cs index 906c68727e97..4dd06976a41b 100644 --- a/src/Network/NWConnectionGroup.cs +++ b/src/Network/NWConnectionGroup.cs @@ -5,6 +5,7 @@ using OS_nw_parameters = System.IntPtr; using OS_nw_content_context = System.IntPtr; using OS_nw_path = System.IntPtr; +using System.ComponentModel; using OS_nw_endpoint = System.IntPtr; using OS_nw_protocol_metadata = System.IntPtr; using OS_nw_protocol_definition = System.IntPtr; @@ -116,7 +117,7 @@ public void SetQueue (DispatchQueue queue) [DllImport (Constants.NetworkLibrary)] static extern OS_nw_endpoint nw_connection_group_copy_remote_endpoint_for_message (OS_nw_connection_group group, OS_nw_content_context context); - public NWEndpoint? GetRemmoteEndpoint (NWContentContext context) + public NWEndpoint? GetRemoteEndpoint (NWContentContext context) { if (context is null) ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (context)); @@ -125,6 +126,12 @@ public void SetQueue (DispatchQueue queue) return ptr == IntPtr.Zero ? null : new NWEndpoint (ptr, owns: true); } +#if !XAMCORE_5_0 + [Obsolete ("Use 'GetRemoteEndpoint' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + public NWEndpoint? GetRemmoteEndpoint (NWContentContext context) => GetRemoteEndpoint (context); +#endif + // can return null [DllImport (Constants.NetworkLibrary)] static extern OS_nw_connection nw_connection_group_extract_connection_for_message (OS_nw_connection_group group, OS_nw_content_context context); diff --git a/src/PrintCore/Defs.cs b/src/PrintCore/Defs.cs index d6fc9c73d980..af8eca7f868a 100644 --- a/src/PrintCore/Defs.cs +++ b/src/PrintCore/Defs.cs @@ -9,6 +9,7 @@ #nullable enable +using System.ComponentModel; using System.Threading; using System.IO; @@ -215,7 +216,12 @@ public enum PMStatusCode { /// To be added. PluginNotFound = -9701, /// To be added. - PluginRegisterationFailed = -9702, + PluginRegistrationFailed = -9702, +#if !XAMCORE_5_0 + [Obsolete ("Use 'PluginRegistrationFailed' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + PluginRegisterationFailed = PluginRegistrationFailed, +#endif /// To be added. FontNotFound = -9703, /// To be added. diff --git a/src/accessibility.cs b/src/accessibility.cs index 9f07273778e1..137acdeefd8d 100644 --- a/src/accessibility.cs +++ b/src/accessibility.cs @@ -1,4 +1,5 @@ using CoreGraphics; +using System.ComponentModel; #nullable enable @@ -613,7 +614,16 @@ interface AXMathExpressionFraction { AXMathExpression NumeratorExpression { get; } [Export ("denimonatorExpression")] +#if XAMCORE_5_0 + AXMathExpression DenominatorExpression { get; } +#else + [Obsolete ("Use 'DenominatorExpression' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] AXMathExpression DenimonatorExpression { get; } + + [Wrap ("DenimonatorExpression")] + AXMathExpression DenominatorExpression { get; } +#endif } [TV (18, 2), Mac (15, 2), iOS (18, 2), MacCatalyst (18, 2)] diff --git a/src/appkit.cs b/src/appkit.cs index f8eb8010f3c9..d74e637cf52e 100644 --- a/src/appkit.cs +++ b/src/appkit.cs @@ -3889,8 +3889,17 @@ interface NSCollectionViewDataSource { /// To be added. [Abstract] [Export ("collectionView:numberOfItemsInSection:")] +#if XAMCORE_5_0 + nint GetNumberOfItems (NSCollectionView collectionView, nint section); +#else + [Obsolete ("Use 'GetNumberOfItems' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] nint GetNumberofItems (NSCollectionView collectionView, nint section); + [Wrap ("GetNumberofItems (collectionView, section)")] + nint GetNumberOfItems (NSCollectionView collectionView, nint section); +#endif + /// To be added. /// To be added. /// To be added. @@ -8598,8 +8607,17 @@ partial interface NSFormCell { nfloat TitleWidth { get; set; } [Export ("titleWidth:")] +#if XAMCORE_5_0 + nfloat GetTitleWidth (CGSize size); +#else + [Obsolete ("Use 'GetTitleWidth' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] nfloat TitleWidthConstraintedToSize (CGSize aSize); + [Wrap ("TitleWidthConstraintedToSize (size)")] + nfloat GetTitleWidth (CGSize size); +#endif + [Export ("title")] string Title { get; set; } @@ -14582,8 +14600,17 @@ partial interface NSPopUpButtonCell { nint IndexOfItemWithRepresentedObject (NSObject obj); [Export ("indexOfItemWithTarget:andAction:")] +#if XAMCORE_5_0 + nint IndexOfItemWithTargetAndAction (NSObject target, Selector actionSelector); +#else + [Obsolete ("Use 'IndexOfItemWithTargetAndAction' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] nint IndexOfItemWithTargetandAction (NSObject target, Selector actionSelector); + [Wrap ("IndexOfItemWithTargetandAction (target, actionSelector)")] + nint IndexOfItemWithTargetAndAction (NSObject target, Selector actionSelector); +#endif + [Export ("itemAtIndex:")] NSMenuItem ItemAt (nint index); @@ -15651,8 +15678,17 @@ interface NSStandardKeyBindingResponding { [BaseType (typeof (NSObject))] partial interface NSResponder : NSCoding, NSTouchBarProvider, NSUserActivityRestoring { [Export ("tryToPerform:with:")] +#if XAMCORE_5_0 + bool TryToPerformWith (Selector anAction, [NullAllowed] NSObject anObject); +#else + [Obsolete ("Use 'TryToPerformWith' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] bool TryToPerformwith (Selector anAction, [NullAllowed] NSObject anObject); + [Wrap ("TryToPerformwith (anAction, anObject)")] + bool TryToPerformWith (Selector anAction, [NullAllowed] NSObject anObject); +#endif + [Export ("performKeyEquivalent:")] bool PerformKeyEquivalent (NSEvent theEvent); @@ -16238,8 +16274,17 @@ partial interface NSScreen { CGRect ConvertRectToBacking (CGRect aRect); [Export ("convertRectFromBacking:")] +#if XAMCORE_5_0 + CGRect ConvertRectFromBacking (CGRect aRect); +#else + [Obsolete ("Use 'ConvertRectFromBacking' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] CGRect ConvertRectfromBacking (CGRect aRect); + [Wrap ("ConvertRectfromBacking (aRect)")] + CGRect ConvertRectFromBacking (CGRect aRect); +#endif + [Export ("backingAlignedRect:options:")] CGRect GetBackingAlignedRect (CGRect globalScreenCoordRect, NSAlignmentOptions options); @@ -17470,8 +17515,17 @@ partial interface NSSpellChecker { void UpdateSpellingPanelWithMisspelledWord (string word); [Export ("updateSpellingPanelWithGrammarString:detail:")] +#if XAMCORE_5_0 + void UpdateSpellingPanelWithGrammarString (string theString, NSDictionary detail); +#else + [Obsolete ("Use 'UpdateSpellingPanelWithGrammarString' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void UpdateSpellingPanelWithGrammarl (string theString, NSDictionary detail); + [Wrap ("UpdateSpellingPanelWithGrammarl (theString, detail)")] + void UpdateSpellingPanelWithGrammarString (string theString, NSDictionary detail); +#endif + [Export ("spellingPanel")] NSPanel SpellingPanel { get; } @@ -26885,8 +26939,17 @@ interface NSWorkspace : NSWorkspaceAccessibilityExtensions { NSImage IconForFileType (IntPtr fileTypeOrTypeCode); [Export ("setIcon:forFile:options:"), ThreadSafe] +#if XAMCORE_5_0 + bool SetIconForFile (NSImage image, string fullPath, NSWorkspaceIconCreationOptions options); +#else + [Obsolete ("Use 'SetIconForFile' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] bool SetIconforFile (NSImage image, string fullPath, NSWorkspaceIconCreationOptions options); + [Wrap ("SetIconforFile (image, fullPath, options)")] + bool SetIconForFile (NSImage image, string fullPath, NSWorkspaceIconCreationOptions options); +#endif + [Export ("fileLabels"), ThreadSafe] string [] FileLabels { get; } diff --git a/src/authenticationservices.cs b/src/authenticationservices.cs index 28232837345a..2bee348254a3 100644 --- a/src/authenticationservices.cs +++ b/src/authenticationservices.cs @@ -5,6 +5,7 @@ // using Security; +using System.ComponentModel; #if MONOMAC using AppKit; using UIControl = AppKit.NSControl; @@ -1345,7 +1346,16 @@ interface ASWebAuthenticationSessionWebBrowserSessionManager { [NoMacCatalyst] [Static] [Export ("registerDefaultsForASWASInSetupAssistantIfNeeded")] + void RegisterDefaultsForAsWasInSetupAssistantIfNeeded (); + +#if !XAMCORE_5_0 + [Obsolete ("Use 'RegisterDefaultsForAsWasInSetupAssistantIfNeeded' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + [Wrap ("RegisterDefaultsForAsWasInSetupAssistantIfNeeded ()")] + [NoMacCatalyst] + [Static] void RegisterDefaultsForAswasInSetupAssistantIfNeeded (); +#endif } diff --git a/src/avfoundation.cs b/src/avfoundation.cs index 072eac814b82..ef6009288b0e 100644 --- a/src/avfoundation.cs +++ b/src/avfoundation.cs @@ -22537,8 +22537,18 @@ interface AVAssetDownloadDelegate : NSUrlSessionTaskDelegate { [MacCatalyst (18, 0), Mac (14, 0), iOS (18, 0)] [Export ("URLSession:assetDownloadTask:willDownloadToURL:")] +#if XAMCORE_5_0 + void WillDownloadToUrl (NSUrlSession session, AVAssetDownloadTask assetDownloadTask, NSUrl location); +#else + [Obsolete ("Use 'WillDownloadToUrl' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void WilllDownloadToUrl (NSUrlSession session, AVAssetDownloadTask assetDownloadTask, NSUrl location); + [MacCatalyst (18, 0), Mac (14, 0), iOS (18, 0)] + [Wrap ("WilllDownloadToUrl (session, assetDownloadTask, location)")] + void WillDownloadToUrl (NSUrlSession session, AVAssetDownloadTask assetDownloadTask, NSUrl location); +#endif + [MacCatalyst (26, 0), NoTV, Mac (26, 0), iOS (26, 0)] [Export ("URLSession:assetDownloadTask:didReceiveMetricEvent:")] void DidReceiveMetricEvent (NSUrlSession session, AVAssetDownloadTask assetDownloadTask, AVMetricEvent metricEvent); diff --git a/src/corebluetooth.cs b/src/corebluetooth.cs index 3b1fc5133c5c..b7798385ccc1 100644 --- a/src/corebluetooth.cs +++ b/src/corebluetooth.cs @@ -1005,7 +1005,11 @@ interface CBPeripheralDelegate { Event raised by the object. If developers do not assign a value to this event, this will reset the value for the WeakDelegate property to an internal handler that maps delegates to events. """)] +#if XAMCORE_5_0 + void UpdatedCharacteristicValue (CBPeripheral peripheral, CBCharacteristic characteristic, [NullAllowed] NSError error); +#else void UpdatedCharacterteristicValue (CBPeripheral peripheral, CBCharacteristic characteristic, [NullAllowed] NSError error); +#endif /// To be added. /// To be added. diff --git a/src/coreimage.cs b/src/coreimage.cs index 4dc8b5ccde2c..8845a1c417ff 100644 --- a/src/coreimage.cs +++ b/src/coreimage.cs @@ -2703,8 +2703,17 @@ interface CIFilterGenerator : CIFilterConstructor, NSSecureCoding, NSCopying { /// To be added. /// To be added. [Export ("setAttributes:forExportedKey:")] +#if XAMCORE_5_0 + void SetAttributes (NSDictionary attributes, NSString exportedKey); +#else + [Obsolete ("Use 'SetAttributes' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SetAttributesforExportedKey (NSDictionary attributes, NSString exportedKey); + [Wrap ("SetAttributesforExportedKey (attributes, exportedKey)")] + void SetAttributes (NSDictionary attributes, NSString exportedKey); +#endif + /// To be added. /// To be added. /// To be added. diff --git a/src/corespotlight.cs b/src/corespotlight.cs index 0ce6d2f6dfd5..4c2922f5df93 100644 --- a/src/corespotlight.cs +++ b/src/corespotlight.cs @@ -2285,8 +2285,17 @@ interface CSSearchableItemAttributeSet : NSCopying, NSSecureCoding { /// To be added. [NullAllowed] [Export ("GPSDifferental", ArgumentSemantic.Strong)] +#if XAMCORE_5_0 + NSNumber GpsDifferential { get; set; } +#else + [Obsolete ("Use 'GpsDifferential' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] NSNumber GpsDifferental { get; set; } + [Wrap ("GpsDifferental")] + NSNumber GpsDifferential { get; set; } +#endif + /// Gets or sets the fully formatted geographic address of the item. /// /// Map Kit provides this address. diff --git a/src/foundation.cs b/src/foundation.cs index 8ceba04a14c1..08c913182f56 100644 --- a/src/foundation.cs +++ b/src/foundation.cs @@ -4738,7 +4738,14 @@ interface NSMetadataQuery { /// To be added. [NoTV, NoiOS, NoMacCatalyst] [Field ("NSMetadataItemGPSDifferentalKey")] + NSString GpsDifferentialKey { get; } + +#if !XAMCORE_5_0 + [Obsolete ("Use 'GpsDifferentialKey' instead.")] + [NoTV, NoiOS, NoMacCatalyst] + [Wrap ("GpsDifferentialKey")] NSString GpsDifferentalKey { get; } +#endif /// To be added. /// To be added. @@ -5471,7 +5478,11 @@ interface NSMetadataQueryDelegate { Developers assign a function, delegate or anonymous method to this property to return a value to the object. If developers assign a value to this property, it this will reset the value for the Delegate property to an internal handler that maps delegates to events. """)] [Export ("metadataQuery:replacementValueForAttribute:value:"), DelegateName ("NSMetadataQueryValue"), DefaultValue (null)] +#if XAMCORE_5_0 + NSObject ReplacementValueForAttributeValue (NSMetadataQuery query, string attributeName, NSObject value); +#else NSObject ReplacementValueForAttributevalue (NSMetadataQuery query, string attributeName, NSObject value); +#endif } [BaseType (typeof (NSObject))] @@ -10081,7 +10092,15 @@ interface NSUrlCredential : NSSecureCoding, NSCopying { [Static] [Export ("credentialWithUser:password:persistence:")] + NSUrlCredential Create (string user, string password, NSUrlCredentialPersistence persistence); + +#if !XAMCORE_5_0 + [Obsolete ("Use 'Create' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + [Wrap ("Create (user, password, persistence)")] + [Static] NSUrlCredential FromUserPasswordPersistance (string user, string password, NSUrlCredentialPersistence persistence); +#endif [Export ("user")] string User { get; } @@ -12350,8 +12369,17 @@ interface NSString2 : NSSecureCoding, NSMutableCopying, CKRecordValue NSString ExpandTildeInPath (); [Export ("stringByStandardizingPath")] +#if XAMCORE_5_0 + NSString StandardizePath (); +#else + [Obsolete ("Use 'StandardizePath' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] NSString StandarizePath (); + [Wrap ("StandarizePath ()")] + NSString StandardizePath (); +#endif + [Export ("stringByResolvingSymlinksInPath")] NSString ResolveSymlinksInPath (); @@ -12587,8 +12615,17 @@ interface NSMutableString : NSCoding { [PreSnippet ("Check (range);", Optimizable = true)] [Export ("replaceOccurrencesOfString:withString:options:range:")] +#if XAMCORE_5_0 + nuint ReplaceOccurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range); +#else + [Obsolete ("Use 'ReplaceOccurrences' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] nuint ReplaceOcurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range); + [Wrap ("ReplaceOcurrences (target, replacement, options, range)")] + nuint ReplaceOccurrences (NSString target, NSString replacement, NSStringCompareOptions options, NSRange range); +#endif + [MacCatalyst (13, 1)] [EditorBrowsable (EditorBrowsableState.Advanced)] [Export ("applyTransform:reverse:range:updatedRange:")] @@ -15756,7 +15793,16 @@ interface NSNotificationQueue { void EnqueueNotification (NSNotification notification, NSPostingStyle postingStyle, NSNotificationCoalescing coalesceMask, [NullAllowed] NSRunLoopMode [] modes); [Export ("dequeueNotificationsMatching:coalesceMask:")] +#if XAMCORE_5_0 + void DequeueNotifications (NSNotification notification, NSNotificationCoalescing coalesceMask); +#else + [Obsolete ("Use 'DequeueNotifications' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void DequeueNotificationsMatchingcoalesceMask (NSNotification notification, NSNotificationCoalescing coalesceMask); + + [Wrap ("DequeueNotificationsMatchingcoalesceMask (notification, coalesceMask)")] + void DequeueNotifications (NSNotification notification, NSNotificationCoalescing coalesceMask); +#endif } [BaseType (typeof (NSObject))] @@ -18744,8 +18790,17 @@ interface NSDirectoryEnumerator { NSDictionary DirectoryAttributes { get; } [Export ("skipDescendents")] +#if XAMCORE_5_0 + void SkipDescendants (); +#else + [Obsolete ("Use 'SkipDescendants' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SkipDescendents (); + [Wrap ("SkipDescendents ()")] + void SkipDescendants (); +#endif + [NoMac] [MacCatalyst (13, 1)] [Export ("level")] @@ -20465,8 +20520,17 @@ interface NSAppleEventDescriptor : NSSecureCoding, NSCopying { AETransactionID TransactionID ();*/ [Export ("setParamDescriptor:forKeyword:")] +#if XAMCORE_5_0 + void SetParamDescriptor (NSAppleEventDescriptor descriptor, AEKeyword keyword); +#else + [Obsolete ("Use 'SetParamDescriptor' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SetParamDescriptorforKeyword (NSAppleEventDescriptor descriptor, AEKeyword keyword); + [Wrap ("SetParamDescriptorforKeyword (descriptor, keyword)")] + void SetParamDescriptor (NSAppleEventDescriptor descriptor, AEKeyword keyword); +#endif + [return: NullAllowed] [Export ("paramDescriptorForKeyword:")] NSAppleEventDescriptor ParamDescriptorForKeyword (AEKeyword keyword); @@ -20475,8 +20539,17 @@ interface NSAppleEventDescriptor : NSSecureCoding, NSCopying { void RemoveParamDescriptorWithKeyword (AEKeyword keyword); [Export ("setAttributeDescriptor:forKeyword:")] +#if XAMCORE_5_0 + void SetAttributeDescriptor (NSAppleEventDescriptor descriptor, AEKeyword keyword); +#else + [Obsolete ("Use 'SetAttributeDescriptor' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SetAttributeDescriptorforKeyword (NSAppleEventDescriptor descriptor, AEKeyword keyword); + [Wrap ("SetAttributeDescriptorforKeyword (descriptor, keyword)")] + void SetAttributeDescriptor (NSAppleEventDescriptor descriptor, AEKeyword keyword); +#endif + [return: NullAllowed] [Export ("attributeDescriptorForKeyword:")] NSAppleEventDescriptor AttributeDescriptorForKeyword (AEKeyword keyword); @@ -20489,8 +20562,17 @@ interface NSAppleEventDescriptor : NSSecureCoding, NSCopying { /// To be added. /// To be added. [Export ("insertDescriptor:atIndex:")] +#if XAMCORE_5_0 + void InsertDescriptor (NSAppleEventDescriptor descriptor, nint index); +#else + [Obsolete ("Use 'InsertDescriptor' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void InsertDescriptoratIndex (NSAppleEventDescriptor descriptor, nint index); + [Wrap ("InsertDescriptoratIndex (descriptor, index)")] + void InsertDescriptor (NSAppleEventDescriptor descriptor, nint index); +#endif + /// To be added. /// To be added. /// To be added. @@ -20506,8 +20588,17 @@ interface NSAppleEventDescriptor : NSSecureCoding, NSCopying { void RemoveDescriptorAtIndex (nint index); [Export ("setDescriptor:forKeyword:")] +#if XAMCORE_5_0 + void SetDescriptor (NSAppleEventDescriptor descriptor, AEKeyword keyword); +#else + [Obsolete ("Use 'SetDescriptor' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SetDescriptorforKeyword (NSAppleEventDescriptor descriptor, AEKeyword keyword); + [Wrap ("SetDescriptorforKeyword (descriptor, keyword)")] + void SetDescriptor (NSAppleEventDescriptor descriptor, AEKeyword keyword); +#endif + [return: NullAllowed] [Export ("descriptorForKeyword:")] NSAppleEventDescriptor DescriptorForKeyword (AEKeyword keyword); diff --git a/src/gamekit.cs b/src/gamekit.cs index a058cc9f1b22..899db615b624 100644 --- a/src/gamekit.cs +++ b/src/gamekit.cs @@ -14,6 +14,8 @@ #pragma warning disable 618 +using System.ComponentModel; + using CoreFoundation; using CoreGraphics; #if MONOMAC @@ -3035,8 +3037,18 @@ interface GKTurnBasedMatch { [MacCatalyst (13, 1)] [Export ("exchangeDataMaximumSize")] +#if XAMCORE_5_0 + nuint ExchangeDataMaximumSize { get; } +#else + [Obsolete ("Use 'ExchangeDataMaximumSize' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] nuint ExhangeDataMaximumSize { get; } + [MacCatalyst (13, 1)] + [Wrap ("ExhangeDataMaximumSize")] + nuint ExchangeDataMaximumSize { get; } +#endif + [MacCatalyst (13, 1)] [Export ("exchangeMaxInitiatedExchangesPerPlayer")] nuint ExchangeMaxInitiatedExchangesPerPlayer { get; } diff --git a/src/imageio.cs b/src/imageio.cs index c2ae4cc2db31..09f036778362 100644 --- a/src/imageio.cs +++ b/src/imageio.cs @@ -371,11 +371,14 @@ interface CGImageProperties { /// To be added. [Field ("kCGImagePropertyExifSubsecTime")] NSString ExifSubsecTime { get; } +#if !XAMCORE_5_0 /// Represents the value associated with the constant kCGImagePropertyExifSubsecTimeOrginal /// To be added. /// To be added. + [Obsolete ("Use 'ExifSubsecTimeOriginal' instead.")] [Field ("kCGImagePropertyExifSubsecTimeOrginal")] NSString ExifSubsecTimeOrginal { get; } +#endif /// Represents the value associated with the constant kCGImagePropertyExifSubsecTimeOriginal. /// To be added. /// To be added. @@ -838,11 +841,20 @@ interface CGImageProperties { /// To be added. [Field ("kCGImagePropertyGPSDateStamp")] NSString GPSDateStamp { get; } + /// Represents the value associated with the constant kCGImagePropertyGPSDifferental. + /// To be added. + /// To be added. + [Field ("kCGImagePropertyGPSDifferental")] + NSString GPSDifferential { get; } + +#if !XAMCORE_5_0 /// Represents the value associated with the constant kCGImagePropertyGPSDifferental /// To be added. /// To be added. + [Obsolete ("Use 'GPSDifferential' instead.")] [Field ("kCGImagePropertyGPSDifferental")] NSString GPSDifferental { get; } +#endif /// Represents the value associated with the constant kCGImagePropertyGPSHPositioningError /// To be added. diff --git a/src/imagekit.cs b/src/imagekit.cs index c1e5dafac0a4..3bc3dea8fed5 100644 --- a/src/imagekit.cs +++ b/src/imagekit.cs @@ -31,6 +31,7 @@ using ImageCaptureCore; using CoreGraphics; using CoreAnimation; +using System.ComponentModel; namespace ImageKit { @@ -2028,7 +2029,15 @@ interface IKSlideshow { /// To be added. [Static] [Export ("exportSlideshowItem:toApplication:")] + void ExportSlideshowItem (NSObject item, string applicationBundleIdentifier); + +#if !XAMCORE_5_0 + [Obsolete ("Use 'ExportSlideshowItem' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + [Wrap ("ExportSlideshowItem (item, applicationBundleIdentifier)")] + [Static] void ExportSlideshowItemtoApplication (NSObject item, string applicationBundleIdentifier); +#endif /// To be added. /// To be added. diff --git a/src/mapkit.cs b/src/mapkit.cs index bc577b1f9f5f..4b7966f5994b 100644 --- a/src/mapkit.cs +++ b/src/mapkit.cs @@ -14,6 +14,7 @@ using CoreFoundation; using CoreGraphics; using CoreLocation; +using System.ComponentModel; #if MONOMAC using AppKit; using UITraitCollection = System.Int32; @@ -216,8 +217,20 @@ interface MKAnnotationView { [NoTV] [MacCatalyst (13, 1)] [Export ("rightCalloutOffset")] +#if XAMCORE_5_0 + CGPoint RightCalloutOffset { get; set; } +#else + [Obsolete ("Use 'RightCalloutOffset' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] CGPoint RightCallpoutOffset { get; set; } + [NoiOS] + [NoTV] + [MacCatalyst (13, 1)] + [Wrap ("RightCallpoutOffset")] + CGPoint RightCalloutOffset { get; set; } +#endif + [MacCatalyst (13, 1)] [NullAllowed, Export ("clusteringIdentifier")] string ClusteringIdentifier { get; set; } diff --git a/src/metalperformanceshadersgraph.cs b/src/metalperformanceshadersgraph.cs index baa504eebcb3..a911eeabf78a 100644 --- a/src/metalperformanceshadersgraph.cs +++ b/src/metalperformanceshadersgraph.cs @@ -2,6 +2,7 @@ using CoreGraphics; using Metal; using MetalPerformanceShaders; +using System.ComponentModel; using MPSGraphTensorDataDictionary = Foundation.NSDictionary; using MPSGraphTensorShapedTypeDictionary = Foundation.NSDictionary; @@ -2477,8 +2478,17 @@ interface MPSGraphFftDescriptor : NSCopying { MPSGraphFftScalingMode ScalingMode { get; set; } [Export ("roundToOddHermitean")] +#if XAMCORE_5_0 + bool RoundToOddHermitian { get; set; } +#else + [Obsolete ("Use 'RoundToOddHermitian' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] bool RoundToOddHermitean { get; set; } + [Wrap ("RoundToOddHermitean")] + bool RoundToOddHermitian { get; set; } +#endif + [Static] [Export ("descriptor")] [return: NullAllowed] diff --git a/src/mlcompute.cs b/src/mlcompute.cs index ee16476111f7..7130d7ac33d7 100644 --- a/src/mlcompute.cs +++ b/src/mlcompute.cs @@ -1,5 +1,6 @@ using Metal; +using System.ComponentModel; namespace MLCompute { @@ -108,7 +109,12 @@ enum MLCDataType { Float16 = 3, Boolean = 4, Int64 = 5, - Inot32 = 7, + Int32 = 7, +#if !XAMCORE_5_0 + [Obsolete ("Use 'Int32' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + Inot32 = Int32, +#endif [iOS (15, 0), TV (15, 0), MacCatalyst (15, 0)] Int8 = 8, [iOS (15, 0), TV (15, 0), MacCatalyst (15, 0)] diff --git a/src/uikit.cs b/src/uikit.cs index ebceb52728f9..18b808e72295 100644 --- a/src/uikit.cs +++ b/src/uikit.cs @@ -10312,7 +10312,16 @@ interface UILocalizedIndexedCollation { nint GetSectionForObject (NSObject obj, Selector collationStringSelector); [Export ("sortedArrayFromArray:collationStringSelector:")] +#if XAMCORE_5_0 + NSObject [] SortedArrayFromArrayCollationStringSelector (NSObject [] array, Selector collationStringSelector); +#else + [Obsolete ("Use 'SortedArrayFromArrayCollationStringSelector' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] NSObject [] SortedArrayFromArraycollationStringSelector (NSObject [] array, Selector collationStringSelector); + + [Wrap ("SortedArrayFromArraycollationStringSelector (array, collationStringSelector)")] + NSObject [] SortedArrayFromArrayCollationStringSelector (NSObject [] array, Selector collationStringSelector); +#endif } /// Creates time-based notifications that the operating system delivers to the user. @@ -16699,8 +16708,17 @@ interface UISearchBar : UIBarPositioning, UITextInputTraits, UILookToDictateCapa /// To be added. [Export ("setImage:forSearchBarIcon:state:")] [Appearance] +#if XAMCORE_5_0 + void SetImageForSearchBarIcon ([NullAllowed] UIImage iconImage, UISearchBarIcon icon, UIControlState state); +#else + [Obsolete ("Use 'SetImageForSearchBarIcon' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SetImageforSearchBarIcon ([NullAllowed] UIImage iconImage, UISearchBarIcon icon, UIControlState state); + [Wrap ("SetImageforSearchBarIcon (iconImage, icon, state)")] + void SetImageForSearchBarIcon ([NullAllowed] UIImage iconImage, UISearchBarIcon icon, UIControlState state); +#endif + /// To be added. /// To be added. /// The image for the specified search bar icon type and control state. @@ -16755,8 +16773,17 @@ interface UISearchBar : UIBarPositioning, UITextInputTraits, UILookToDictateCapa [Appearance] [Export ("setPositionAdjustment:forSearchBarIcon:")] +#if XAMCORE_5_0 + void SetPositionAdjustmentForSearchBarIcon (UIOffset adjustment, UISearchBarIcon icon); +#else + [Obsolete ("Use 'SetPositionAdjustmentForSearchBarIcon' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] void SetPositionAdjustmentforSearchBarIcon (UIOffset adjustment, UISearchBarIcon icon); + [Wrap ("SetPositionAdjustmentforSearchBarIcon (adjustment, icon)")] + void SetPositionAdjustmentForSearchBarIcon (UIOffset adjustment, UISearchBarIcon icon); +#endif + [Appearance] [Export ("positionAdjustmentForSearchBarIcon:")] UIOffset GetPositionAdjustmentForSearchBarIcon (UISearchBarIcon icon); @@ -24555,7 +24582,15 @@ interface UITextChecker { [Static] [Export ("availableLanguages")] + string AvailableLanguages { get; } + +#if !XAMCORE_5_0 + [Obsolete ("Use 'AvailableLanguages' instead.")] + [EditorBrowsable (EditorBrowsableState.Never)] + [Wrap ("AvailableLanguages")] + [Static] string AvailableLangauges { get; } +#endif } /// Known values for that are hints to the system of the kind of data. diff --git a/src/webkit.cs b/src/webkit.cs index af8e1c34916b..230a3d99c3d9 100644 --- a/src/webkit.cs +++ b/src/webkit.cs @@ -2158,7 +2158,11 @@ partial interface WebFrameLoadDelegate { To be added. To be added. """)] +#if XAMCORE_5_0 + void CommittedLoad (WebView sender, WebFrame forFrame); +#else void CommitedLoad (WebView sender, WebFrame forFrame); +#endif /// To be added. /// To be added. diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt index 9e1603a34248..be4383668c70 100644 --- a/tests/cecil-tests/Documentation.KnownFailures.txt +++ b/tests/cecil-tests/Documentation.KnownFailures.txt @@ -2191,7 +2191,7 @@ F:CoreGraphics.CGPdfTagType.TableRow F:CoreGraphics.CGPdfTagType.Toc F:CoreGraphics.CGPdfTagType.Toci F:CoreGraphics.CGPdfTagType.Warichu -F:CoreGraphics.CGPdfTagType.WarichuPunctiation +F:CoreGraphics.CGPdfTagType.WarichuPunctuation F:CoreGraphics.CGPdfTagType.WarichuText F:CoreGraphics.CGToneMapping.Default F:CoreGraphics.CGToneMapping.ExrGamma @@ -5454,7 +5454,7 @@ F:MLCompute.MLCConvolutionType.Transposed F:MLCompute.MLCDataType.Boolean F:MLCompute.MLCDataType.Float16 F:MLCompute.MLCDataType.Float32 -F:MLCompute.MLCDataType.Inot32 +F:MLCompute.MLCDataType.Int32 F:MLCompute.MLCDataType.Int64 F:MLCompute.MLCDataType.Int8 F:MLCompute.MLCDataType.Invalid @@ -8514,11 +8514,13 @@ M:AppKit.NSCollectionLayoutAnchor.Create(AppKit.NSDirectionalRectEdge,AppKit.NSC M:AppKit.NSCollectionLayoutGroup.GetHorizontalGroup(AppKit.NSCollectionLayoutSize,AppKit.NSCollectionLayoutItem,System.IntPtr) M:AppKit.NSCollectionLayoutGroup.GetVerticalGroup(AppKit.NSCollectionLayoutSize,AppKit.NSCollectionLayoutItem,System.IntPtr) M:AppKit.NSCollectionView.Dispose(System.Boolean) +M:AppKit.NSCollectionViewDataSource.GetNumberOfItems(AppKit.NSCollectionView,System.IntPtr) M:AppKit.NSCollectionViewDelegate_Extensions.AcceptDrop(AppKit.INSCollectionViewDelegate,AppKit.NSCollectionView,AppKit.INSDraggingInfo,Foundation.NSIndexPath,AppKit.NSCollectionViewDropOperation) M:AppKit.NSCollectionViewDelegate_Extensions.AcceptDrop(AppKit.INSCollectionViewDelegate,AppKit.NSCollectionView,AppKit.INSDraggingInfo,System.IntPtr,AppKit.NSCollectionViewDropOperation) M:AppKit.NSCollectionViewDelegate_Extensions.UpdateDraggingItemsForDrag(AppKit.INSCollectionViewDelegate,AppKit.NSCollectionView,AppKit.INSDraggingInfo) M:AppKit.NSCollectionViewDelegate_Extensions.ValidateDrop(AppKit.INSCollectionViewDelegate,AppKit.NSCollectionView,AppKit.INSDraggingInfo,Foundation.NSIndexPath@,AppKit.NSCollectionViewDropOperation@) M:AppKit.NSCollectionViewDelegate_Extensions.ValidateDrop(AppKit.INSCollectionViewDelegate,AppKit.NSCollectionView,AppKit.INSDraggingInfo,System.IntPtr@,AppKit.NSCollectionViewDropOperation@) +M:AppKit.NSCollectionViewDiffableDataSource`2.GetNumberOfItems(AppKit.NSCollectionView,System.IntPtr) M:AppKit.NSCollectionViewItem.Dispose(System.Boolean) M:AppKit.NSCollectionViewLayout.Dispose(System.Boolean) M:AppKit.NSCollectionViewSectionHeaderView_Extensions.GetSectionCollapseButton(AppKit.INSCollectionViewSectionHeaderView) @@ -8576,6 +8578,7 @@ M:AppKit.NSFont.MonospacedSystemFont(System.Runtime.InteropServices.NFloat,Syste M:AppKit.NSFont.SystemFontOfSize(System.Runtime.InteropServices.NFloat,System.Runtime.InteropServices.NFloat,System.Runtime.InteropServices.NFloat) M:AppKit.NSFontDescriptor.Create(AppKit.NSFontDescriptorSystemDesign) M:AppKit.NSFontManager.Dispose(System.Boolean) +M:AppKit.NSFormCell.GetTitleWidth(CoreGraphics.CGSize) M:AppKit.NSGestureRecognizer.Dispose(System.Boolean) M:AppKit.NSGraphics.DrawTiledRects(CoreGraphics.CGRect,CoreGraphics.CGRect,AppKit.NSRectEdge[],System.Runtime.InteropServices.NFloat[]) M:AppKit.NSGraphics.FrameRect(CoreGraphics.CGRect,System.Runtime.InteropServices.NFloat,AppKit.NSCompositingOperation) @@ -8674,11 +8677,13 @@ M:AppKit.NSPathControlDelegate_Extensions.AcceptDrop(AppKit.INSPathControlDelega M:AppKit.NSPathControlDelegate_Extensions.ValidateDrop(AppKit.INSPathControlDelegate,AppKit.NSPathControl,AppKit.INSDraggingInfo) M:AppKit.NSPickerTouchBarItem.Dispose(System.Boolean) M:AppKit.NSPopover.Dispose(System.Boolean) +M:AppKit.NSPopUpButtonCell.IndexOfItemWithTargetAndAction(Foundation.NSObject,ObjCRuntime.Selector) M:AppKit.NSPreviewRepresentableActivityItem_Extensions.GetIconProvider(AppKit.INSPreviewRepresentableActivityItem) M:AppKit.NSPreviewRepresentableActivityItem_Extensions.GetImageProvider(AppKit.INSPreviewRepresentableActivityItem) M:AppKit.NSPreviewRepresentableActivityItem_Extensions.GetTitle(AppKit.INSPreviewRepresentableActivityItem) M:AppKit.NSPrintPanel.BeginSheetAsync(AppKit.NSPrintInfo,AppKit.NSWindow) M:AppKit.NSResponder_NSWritingToolsSupport.ShowWritingTools(AppKit.NSResponder,Foundation.NSObject) +M:AppKit.NSResponder.TryToPerformWith(ObjCRuntime.Selector,Foundation.NSObject) M:AppKit.NSRuleEditor.add_Changed(System.EventHandler) M:AppKit.NSRuleEditor.add_EditingBegan(System.EventHandler) M:AppKit.NSRuleEditor.add_EditingEnded(System.EventHandler) @@ -8700,6 +8705,7 @@ M:AppKit.NSSavePanel.remove_DidSelectType(System.EventHandler{AppKit.NSopenSaveP M:AppKit.NSSavePanel.remove_DirectoryDidChange(System.EventHandler{AppKit.NSOpenSaveFilenameEventArgs}) M:AppKit.NSSavePanel.remove_SelectionDidChange(System.EventHandler) M:AppKit.NSSavePanel.remove_WillExpand(System.EventHandler{AppKit.NSOpenSaveExpandingEventArgs}) +M:AppKit.NSScreen.ConvertRectFromBacking(CoreGraphics.CGRect) M:AppKit.NSScrubber.Dispose(System.Boolean) M:AppKit.NSScrubberLayout.Dispose(System.Boolean) M:AppKit.NSSearchField.add_SearchingEnded(System.EventHandler) @@ -8731,6 +8737,7 @@ M:AppKit.NSSpeechRecognizer.Dispose(System.Boolean) M:AppKit.NSSpeechSynthesizer.Dispose(System.Boolean) M:AppKit.NSSpellChecker.CheckString(System.String,Foundation.NSRange,Foundation.NSTextCheckingTypes,AppKit.NSTextCheckingOptions,System.IntPtr,Foundation.NSOrthography@,System.IntPtr@) M:AppKit.NSSpellChecker.RequestChecking(System.String,Foundation.NSRange,Foundation.NSTextCheckingTypes,AppKit.NSTextCheckingOptions,System.IntPtr,System.Action{System.IntPtr,Foundation.NSTextCheckingResult[],Foundation.NSOrthography,System.IntPtr}) +M:AppKit.NSSpellChecker.UpdateSpellingPanelWithGrammarString(System.String,Foundation.NSDictionary) M:AppKit.NSSplitView.Dispose(System.Boolean) M:AppKit.NSSpringLoadingDestination_Extensions.DraggingEnded(AppKit.INSSpringLoadingDestination,AppKit.INSDraggingInfo) M:AppKit.NSSpringLoadingDestination_Extensions.Entered(AppKit.INSSpringLoadingDestination,AppKit.INSDraggingInfo) @@ -8932,6 +8939,7 @@ M:AppKit.NSView.IsAccessibilityAttributeSettable(Foundation.NSString) M:AppKit.NSView.SetAccessibilityValue(Foundation.NSString,Foundation.NSObject) M:AppKit.NSView.SortSubviews(System.Func{AppKit.NSView,AppKit.NSView,Foundation.NSComparisonResult}) M:AppKit.NSViewController.Dispose(System.Boolean) +M:AppKit.NSViewController.TryToPerformWith(ObjCRuntime.Selector,Foundation.NSObject) M:AppKit.NSWindow.add_DidBecomeKey(System.EventHandler) M:AppKit.NSWindow.add_DidBecomeMain(System.EventHandler) M:AppKit.NSWindow.add_DidChangeBackingProperties(System.EventHandler) @@ -9019,6 +9027,7 @@ M:AppKit.NSWorkspace.SetDefaultApplicationToOpenContentTypeAsync(Foundation.NSUr M:AppKit.NSWorkspace.SetDefaultApplicationToOpenContentTypeAsync(Foundation.NSUrl,UniformTypeIdentifiers.UTType) M:AppKit.NSWorkspace.SetDefaultApplicationToOpenFileAsync(Foundation.NSUrl,Foundation.NSUrl) M:AppKit.NSWorkspace.SetDefaultApplicationToOpenUrlsAsync(Foundation.NSUrl,System.String) +M:AppKit.NSWorkspace.SetIconForFile(AppKit.NSImage,System.String,AppKit.NSWorkspaceIconCreationOptions) M:AppKit.NSWritingToolsCoordinator.Dispose(System.Boolean) M:AppTrackingTransparency.ATTrackingManager.RequestTrackingAuthorization(System.Action{AppTrackingTransparency.ATTrackingManagerAuthorizationStatus}) M:AppTrackingTransparency.ATTrackingManager.RequestTrackingAuthorizationAsync @@ -9334,7 +9343,7 @@ M:AuthenticationServices.ASWebAuthenticationSessionRequestDelegate_Extensions.Di M:AuthenticationServices.ASWebAuthenticationSessionRequestDelegate.DidCancel(AuthenticationServices.ASWebAuthenticationSessionRequest,Foundation.NSError) M:AuthenticationServices.ASWebAuthenticationSessionRequestDelegate.DidComplete(AuthenticationServices.ASWebAuthenticationSessionRequest,Foundation.NSUrl) M:AuthenticationServices.ASWebAuthenticationSessionWebBrowserSessionManager.Dispose(System.Boolean) -M:AuthenticationServices.ASWebAuthenticationSessionWebBrowserSessionManager.RegisterDefaultsForAswasInSetupAssistantIfNeeded +M:AuthenticationServices.ASWebAuthenticationSessionWebBrowserSessionManager.RegisterDefaultsForAsWasInSetupAssistantIfNeeded M:AuthenticationServices.IASAccountAuthenticationModificationControllerDelegate.DidFailRequest(AuthenticationServices.ASAccountAuthenticationModificationController,AuthenticationServices.ASAccountAuthenticationModificationRequest,Foundation.NSError) M:AuthenticationServices.IASAccountAuthenticationModificationControllerDelegate.DidSuccessfullyCompleteRequest(AuthenticationServices.ASAccountAuthenticationModificationController,AuthenticationServices.ASAccountAuthenticationModificationRequest,Foundation.NSDictionary) M:AuthenticationServices.IASAccountAuthenticationModificationControllerPresentationContextProviding.GetPresentationAnchor(AuthenticationServices.ASAccountAuthenticationModificationController) @@ -9399,10 +9408,10 @@ M:AVFoundation.AVAsset.LoadTracksWithMediaTypeAsync(System.String) M:AVFoundation.AVAsset.LoadTrackWithMediaCharacteristicsAsync(System.String) M:AVFoundation.AVAssetDownloadDelegate_Extensions.DidReceiveMetricEvent(AVFoundation.IAVAssetDownloadDelegate,Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,AVFoundation.AVMetricEvent) M:AVFoundation.AVAssetDownloadDelegate_Extensions.WillDownloadVariants(AVFoundation.IAVAssetDownloadDelegate,Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,AVFoundation.AVAssetVariant[]) -M:AVFoundation.AVAssetDownloadDelegate_Extensions.WilllDownloadToUrl(AVFoundation.IAVAssetDownloadDelegate,Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,Foundation.NSUrl) M:AVFoundation.AVAssetDownloadDelegate.DidCreateTask(Foundation.NSUrlSession,Foundation.NSUrlSessionTask) M:AVFoundation.AVAssetDownloadDelegate.DidReceiveInformationalResponse(Foundation.NSUrlSession,Foundation.NSUrlSessionTask,Foundation.NSHttpUrlResponse) M:AVFoundation.AVAssetDownloadDelegate.NeedNewBodyStream(Foundation.NSUrlSession,Foundation.NSUrlSessionTask,System.Int64,System.Action{Foundation.NSInputStream}) +M:AVFoundation.AVAssetDownloadDelegate.WillDownloadToUrl(Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,Foundation.NSUrl) M:AVFoundation.AVAssetExportSession.EstimateMaximumDurationAsync M:AVFoundation.AVAssetExportSession.EstimateOutputFileLengthAsync M:AVFoundation.AVAssetImageGenerator.GenerateCGImagesAsynchronously(Foundation.NSValue[],AVFoundation.AVAssetImageGeneratorCompletionHandler2) @@ -9691,7 +9700,6 @@ M:AVFoundation.AVVideoComposition.CreateAsync(AVFoundation.AVAsset) M:AVFoundation.AVVideoComposition.DetermineValidityAsync(AVFoundation.AVAsset,CoreMedia.CMTimeRange,AVFoundation.IAVVideoCompositionValidationHandling) M:AVFoundation.IAVAssetDownloadDelegate.DidReceiveMetricEvent(Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,AVFoundation.AVMetricEvent) M:AVFoundation.IAVAssetDownloadDelegate.WillDownloadVariants(Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,AVFoundation.AVAssetVariant[]) -M:AVFoundation.IAVAssetDownloadDelegate.WilllDownloadToUrl(Foundation.NSUrlSession,AVFoundation.AVAssetDownloadTask,Foundation.NSUrl) M:AVFoundation.IAVAssetReaderCaptionValidationHandling.DidVendCaption(AVFoundation.AVAssetReaderOutputCaptionAdaptor,AVFoundation.AVCaption,System.String[]) M:AVFoundation.IAVAssetWriterDelegate.DidOutputSegmentData(AVFoundation.AVAssetWriter,Foundation.NSData,AVFoundation.AVAssetSegmentType,AVFoundation.AVAssetSegmentReport) M:AVFoundation.IAVAssetWriterDelegate.DidOutputSegmentData(AVFoundation.AVAssetWriter,Foundation.NSData,AVFoundation.AVAssetSegmentType) @@ -10991,6 +10999,7 @@ M:CoreImage.CIContext.CreateCGImage(CoreImage.CIImage,CoreGraphics.CGRect,System M:CoreImage.CIContext.GetOpenEXRRepresentation(CoreImage.CIImage,Foundation.NSDictionary{Foundation.NSString,Foundation.NSObject},Foundation.NSError@) M:CoreImage.CIContext.WriteOpenExrRepresentation(CoreImage.CIImage,Foundation.NSUrl,Foundation.NSDictionary{Foundation.NSString,Foundation.NSObject},Foundation.NSError@) M:CoreImage.CIDetectorOptions.#ctor +M:CoreImage.CIFilterGenerator.SetAttributes(Foundation.NSDictionary,Foundation.NSString) M:CoreImage.CIImage.#ctor(AVFoundation.AVSemanticSegmentationMatte,Foundation.NSDictionary) M:CoreImage.CIImage.#ctor(AVFoundation.AVSemanticSegmentationMatte) M:CoreImage.CIImage.#ctor(ImageIO.CGImageSource,System.UIntPtr,CoreImage.CIImageInitializationOptionsWithMetadata) @@ -11850,6 +11859,10 @@ M:Foundation.INSUrlSessionTaskDelegate.NeedNewBodyStream(Foundation.NSUrlSession M:Foundation.INSUrlSessionWebSocketDelegate.DidClose(Foundation.NSUrlSession,Foundation.NSUrlSessionWebSocketTask,Foundation.NSUrlSessionWebSocketCloseCode,Foundation.NSData) M:Foundation.INSUrlSessionWebSocketDelegate.DidOpen(Foundation.NSUrlSession,Foundation.NSUrlSessionWebSocketTask,System.String) M:Foundation.INSXpcListenerDelegate.ShouldAcceptConnection(Foundation.NSXpcListener,Foundation.NSXpcConnection) +M:Foundation.NSAppleEventDescriptor.InsertDescriptor(Foundation.NSAppleEventDescriptor,System.IntPtr) +M:Foundation.NSAppleEventDescriptor.SetAttributeDescriptor(Foundation.NSAppleEventDescriptor,System.UInt32) +M:Foundation.NSAppleEventDescriptor.SetDescriptor(Foundation.NSAppleEventDescriptor,System.UInt32) +M:Foundation.NSAppleEventDescriptor.SetParamDescriptor(Foundation.NSAppleEventDescriptor,System.UInt32) M:Foundation.NSAttributedString.#ctor(System.String,AppKit.NSFont,AppKit.NSColor,AppKit.NSColor,AppKit.NSColor,AppKit.NSColor,AppKit.NSColor,Foundation.NSUnderlineStyle,Foundation.NSUnderlineStyle,AppKit.NSParagraphStyle,System.Single,AppKit.NSShadow,Foundation.NSUrl,System.Boolean,AppKit.NSTextAttachment,Foundation.NSLigatureType,System.Single,System.Single,System.Single,System.Single,AppKit.NSCursor,System.String,System.Int32,AppKit.NSGlyphInfo,Foundation.NSArray,System.Boolean,AppKit.NSTextLayoutOrientation,AppKit.NSTextAlternatives,AppKit.NSSpellingState) M:Foundation.NSAttributedString.LoadDataAsync(System.String,Foundation.NSProgress@) M:Foundation.NSAttributedString.LoadFromHtml(Foundation.NSData,Foundation.NSAttributedStringDocumentAttributes,Foundation.NSAttributedStringCompletionHandler) @@ -11874,6 +11887,7 @@ M:Foundation.NSDataDetector.#ctor(Foundation.NSTextCheckingType,Foundation.NSErr M:Foundation.NSDataDetector.Create(Foundation.NSTextCheckingType,Foundation.NSError@) M:Foundation.NSDate.op_Explicit(Foundation.NSDate)~System.DateTime M:Foundation.NSDate.op_Explicit(System.DateTime)~Foundation.NSDate +M:Foundation.NSDirectoryEnumerator.SkipDescendants M:Foundation.NSEnumerator`1.NextObject M:Foundation.NSExceptionError.#ctor(System.Exception) M:Foundation.NSFileAttributes.#ctor @@ -11912,6 +11926,7 @@ M:Foundation.NSKeyValueSharedObserverRegistration_NSObject.SetSharedObservers(Fo M:Foundation.NSKeyValueSharedObservers.#ctor(System.Type) M:Foundation.NSMachPort.Dispose(System.Boolean) M:Foundation.NSMetadataQuery.Dispose(System.Boolean) +M:Foundation.NSMutableString.ReplaceOccurrences(Foundation.NSString,Foundation.NSString,Foundation.NSStringCompareOptions,Foundation.NSRange) M:Foundation.NSNetService.add_AddressResolved(System.EventHandler) M:Foundation.NSNetService.add_DidAcceptConnection(System.EventHandler{Foundation.NSNetServiceConnectionEventArgs}) M:Foundation.NSNetService.add_Published(System.EventHandler) @@ -11946,6 +11961,7 @@ M:Foundation.NSNetServiceBrowser.remove_NotSearched(System.EventHandler{Foundati M:Foundation.NSNetServiceBrowser.remove_SearchStarted(System.EventHandler) M:Foundation.NSNetServiceBrowser.remove_SearchStopped(System.EventHandler) M:Foundation.NSNetServiceBrowser.remove_ServiceRemoved(System.EventHandler{Foundation.NSNetServiceEventArgs}) +M:Foundation.NSNotificationQueue.DequeueNotifications(Foundation.NSNotification,Foundation.NSNotificationCoalescing) M:Foundation.NSNotificationQueue.EnqueueNotification(Foundation.NSNotification,Foundation.NSPostingStyle,Foundation.NSNotificationCoalescing,Foundation.NSRunLoopMode[]) M:Foundation.NSObject.AddObserver(Foundation.NSObject,System.String,Foundation.NSKeyValueObservingOptions,System.IntPtr) M:Foundation.NSObject.AutomaticallyNotifiesObserversForKey(System.String) @@ -12008,6 +12024,7 @@ M:Foundation.NSStream.remove_OnEvent(System.EventHandler{Foundation.NSStreamEven M:Foundation.NSStreamSocksOptions.#ctor M:Foundation.NSString.CompareTo(Foundation.NSString) M:Foundation.NSString.LoadDataAsync(System.String,Foundation.NSProgress@) +M:Foundation.NSString.StandardizePath M:Foundation.NSTimer.Dispose(System.Boolean) M:Foundation.NSUrl.LoadDataAsync(System.String,Foundation.NSProgress@) M:Foundation.NSUrl.op_Equality(Foundation.NSUrl,Foundation.NSUrl) @@ -14998,7 +15015,7 @@ M:Network.NWConnectionGroup.GetLocalEndpoint(Network.NWContentContext) M:Network.NWConnectionGroup.GetPath(Network.NWContentContext) M:Network.NWConnectionGroup.GetProtocolMetadata(Network.NWContentContext,Network.NWProtocolDefinition) M:Network.NWConnectionGroup.GetProtocolMetadata(Network.NWContentContext) -M:Network.NWConnectionGroup.GetRemmoteEndpoint(Network.NWContentContext) +M:Network.NWConnectionGroup.GetRemoteEndpoint(Network.NWContentContext) M:Network.NWConnectionGroup.Reply(Network.NWContentContext,Network.NWContentContext,CoreFoundation.DispatchData) M:Network.NWConnectionGroup.Send(CoreFoundation.DispatchData,Network.NWEndpoint,Network.NWContentContext,System.Action{Network.NWError}) M:Network.NWConnectionGroup.SetNewConnectionHandler(System.Action{Network.NWConnection}) @@ -17209,6 +17226,7 @@ M:UIKit.UILargeContentViewerInteractionDelegate_Extensions.GetItem(UIKit.IUILarg M:UIKit.UILargeContentViewerInteractionDelegate_Extensions.GetViewController(UIKit.IUILargeContentViewerInteractionDelegate,UIKit.UILargeContentViewerInteraction) M:UIKit.UILayoutGuide.Dispose(System.Boolean) M:UIKit.UIListContentView.UIListContentViewAppearance.#ctor(System.IntPtr) +M:UIKit.UILocalizedIndexedCollation.SortedArrayFromArrayCollationStringSelector(Foundation.NSObject[],ObjCRuntime.Selector) M:UIKit.UIMenu.Create(System.String,UIKit.UIImage,UIKit.UIMenuIdentifier,UIKit.UIMenuOptions,UIKit.UIMenuElement[]) M:UIKit.UIMenuLeaf_Extensions.GetSelectedImage(UIKit.IUIMenuLeaf) M:UIKit.UIMenuLeaf_Extensions.SetSelectedImage(UIKit.IUIMenuLeaf,UIKit.UIImage) @@ -17399,9 +17417,10 @@ M:UIKit.UISearchBar.remove_OnEditingStopped(System.EventHandler) M:UIKit.UISearchBar.remove_SearchButtonClicked(System.EventHandler) M:UIKit.UISearchBar.remove_SelectedScopeButtonIndexChanged(System.EventHandler{UIKit.UISearchBarButtonIndexEventArgs}) M:UIKit.UISearchBar.remove_TextChanged(System.EventHandler{UIKit.UISearchBarTextChangedEventArgs}) +M:UIKit.UISearchBar.SetImageForSearchBarIcon(UIKit.UIImage,UIKit.UISearchBarIcon,UIKit.UIControlState) +M:UIKit.UISearchBar.SetPositionAdjustmentForSearchBarIcon(UIKit.UIOffset,UIKit.UISearchBarIcon) M:UIKit.UISearchBar.UISearchBarAppearance.#ctor(System.IntPtr) M:UIKit.UISearchBar.UISearchBarAppearance.GetPositionAdjustmentForSearchBarIcon(UIKit.UISearchBarIcon) -M:UIKit.UISearchBar.UISearchBarAppearance.SetPositionAdjustmentforSearchBarIcon(UIKit.UIOffset,UIKit.UISearchBarIcon) M:UIKit.UISearchBarDelegate_Extensions.ShouldChangeText(UIKit.IUISearchBarDelegate,UIKit.UISearchBar,Foundation.NSValue[],System.String) M:UIKit.UISearchController.Dispose(System.Boolean) M:UIKit.UISearchControllerDelegate_Extensions.DidChangeFromSearchBarPlacement(UIKit.IUISearchControllerDelegate,UIKit.UISearchController,UIKit.UINavigationItemSearchBarPlacement) @@ -18115,6 +18134,7 @@ M:WebKit.WKWebView.SetMicrophoneCaptureStateAsync(WebKit.WKMediaCaptureState) M:WebKit.WKWebView.StartDownloadAsync(Foundation.NSUrlRequest) M:WebKit.WKWebView.WKWebViewAppearance.#ctor(System.IntPtr) P:Accessibility.AXAnimatedImagesUtilities.Enabled +P:Accessibility.AXMathExpressionFraction.DenominatorExpression P:Accessibility.IAXBrailleMapRenderer.AccessibilityBrailleMapRenderer P:Accessibility.IAXBrailleMapRenderer.AccessibilityBrailleMapRenderRegion P:Accessibility.IAXChart.AccessibilityChartDescriptor @@ -20857,6 +20877,7 @@ P:CoreSpotlight.CSSearchableItem.IsUpdate P:CoreSpotlight.CSSearchableItem.UpdateListenerOptions P:CoreSpotlight.CSSearchableItemAttributeSet.ActionIdentifiers P:CoreSpotlight.CSSearchableItemAttributeSet.DarkThumbnailUrl +P:CoreSpotlight.CSSearchableItemAttributeSet.GpsDifferential P:CoreSpotlight.CSSearchableItemAttributeSet.IsPriority P:CoreSpotlight.CSSearchableItemAttributeSet.Item(CoreSpotlight.CSCustomAttributeKey) P:CoreSpotlight.CSSearchableItemAttributeSet.SharedItemContentType @@ -21450,6 +21471,7 @@ P:GameKit.GKPlayerEventArgs.PlayerID P:GameKit.GKPlayersEventArgs.PlayerIDs P:GameKit.GKStateEventArgs.PlayerId P:GameKit.GKStateEventArgs.State +P:GameKit.GKTurnBasedMatch.ExchangeDataMaximumSize P:GameplayKit.GKCompositeBehavior.Item(GameplayKit.GKBehavior) P:GameplayKit.GKCompositeBehavior.Item(System.UIntPtr) P:GameplayKit.IGKGameModelPlayer.PlayerId @@ -21642,6 +21664,7 @@ P:MapKit.IMKAnnotation.Subtitle P:MapKit.IMKAnnotation.Title P:MapKit.IMKOverlay.CanReplaceMapContent P:MapKit.MKAnnotationEventArgs.Annotation +P:MapKit.MKAnnotationView.RightCalloutOffset P:MapKit.MKAnnotationViewEventArgs.View P:MapKit.MKDidAddOverlayRenderersEventArgs.Renderers P:MapKit.MKDidFinishRenderingMapEventArgs.FullyRendered @@ -22354,7 +22377,7 @@ P:MetalPerformanceShadersGraph.MPSGraphExecutionDescriptor.CompletionHandler P:MetalPerformanceShadersGraph.MPSGraphExecutionDescriptor.ScheduledHandler P:MetalPerformanceShadersGraph.MPSGraphExecutionDescriptor.WaitUntilCompleted P:MetalPerformanceShadersGraph.MPSGraphFftDescriptor.Inverse -P:MetalPerformanceShadersGraph.MPSGraphFftDescriptor.RoundToOddHermitean +P:MetalPerformanceShadersGraph.MPSGraphFftDescriptor.RoundToOddHermitian P:MetalPerformanceShadersGraph.MPSGraphFftDescriptor.ScalingMode P:MetalPerformanceShadersGraph.MPSGraphGruDescriptor.Bidirectional P:MetalPerformanceShadersGraph.MPSGraphGruDescriptor.FlipZ diff --git a/tests/introspection/ApiTypoTest.cs b/tests/introspection/ApiTypoTest.cs index 14cd4fb69cb2..1c5758bca225 100644 --- a/tests/introspection/ApiTypoTest.cs +++ b/tests/introspection/ApiTypoTest.cs @@ -19,11 +19,11 @@ // limitations under the License. // + using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; -using System.Text; using System.Text.RegularExpressions; #if MONOMAC using AppKit; @@ -47,16 +47,6 @@ public ApiTypoTest () ContinueOnFailure = true; } - public virtual bool Skip (Type baseType, string typo) - { - return SkipAllowed (baseType.Name, null, typo); - } - - public virtual bool Skip (MemberInfo methodName, string typo) - { - return SkipAllowed (methodName.DeclaringType?.Name, methodName.Name, typo); - } - readonly HashSet allowedRule3 = new HashSet { "IARAnchorCopying", // We're showing a code snippet in the 'Advice' message and that shouldn't end with a dot. }; @@ -70,757 +60,807 @@ public virtual bool Skip (MemberInfo methodName, string typo) }; Dictionary allowed = new Dictionary () { - { "Aac", All }, - { "Abgr", All }, - { "Accurracy", All }, - { "Achivements", All }, - { "Acos", All }, - { "Acosh", All }, - { "Activatable", All }, - { "Addin", All }, - { "Addl", All }, - { "Addons", ApplePlatform.MacOSX }, - { "Addr", All }, - { "Adessive", All }, - { "Adjustmentfor", All & ~ApplePlatform.MacOSX }, - { "Afi", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Agc", All }, - { "Ahap", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Aifc", All }, - { "Aiff", All }, - { "Aime", ApplePlatform.MacOSX }, - { "Aio", ApplePlatform.MacOSX }, + { "Aac", All }, // Advanced Audio Coding + { "Abgr", All }, // alpha-blue-green-red + { "Achivements", All }, // Apple API spelling + { "Ack", All }, // acknowledgment + { "Acn", All }, // Ambisonic Channel Numbering + { "Acos", All }, // arc cosine + { "Acosh", All }, // inverse hyperbolic cosine + { "Activatable", All }, // valid English derivative + { "Addin", All }, // compound word + { "Addl", All }, // additional abbreviation + { "Addons", ApplePlatform.MacOSX }, // compound word + { "Addr", All }, // address abbreviation + { "Adessive", All }, // linguistic case + { "Adposition", All }, // linguistic term + { "Aes", All }, // Advanced Encryption Standard + { "Afi", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation + { "Agc", All }, // automatic gain control + { "Ahap", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // Apple Haptics Pattern + { "Aifc", All }, // AIFF-C audio format + { "Aiff", All }, // Audio Interchange File + { "Aime", ApplePlatform.MacOSX }, // cashless payment brand + { "Aio", ApplePlatform.MacOSX }, // all-in-one abbreviation { "Alg", All }, // short for Algorithm - { "Aliasable", All }, - { "Allative", All }, - { "Amete", All }, - { "Amr", All }, - { "Ancs", All & ~ApplePlatform.MacOSX }, - { "Ane", All }, - { "Anglet", All }, - { "Apac", All }, - { "Apdu", All }, - { "Apl", All & ~ApplePlatform.TVOS }, + { "Alem", All }, // Ethiopic "Amete Alem" calendar + { "Aliasable", All }, // valid English derivative + { "Allative", All }, // linguistic case + { "Amete", All }, // Ethiopic calendar term + { "Amr", All }, // Adaptive Multi-Rate + { "Ancs", All & ~ApplePlatform.MacOSX }, // Apple Notification Center + { "Ane", All }, // Apple Neural Engine + { "Anglet", All }, // measurement term + { "Apac", All }, // Asia-Pacific abbreviation + { "Apdu", All }, // smart-card protocol unit + { "Apl", All & ~ApplePlatform.TVOS }, // APL language acronym { "Apng", All }, // Animated Portable Network Graphics - { "Apns", All & ~ApplePlatform.TVOS }, - { "Appactive", ApplePlatform.MacOSX }, - { "Applei", All }, - { "Aps", ApplePlatform.MacOSX }, - { "Apv", ApplePlatform.MacOSX }, - { "Arae", ApplePlatform.MacOSX }, - { "Arcball", All }, - { "Argb", All }, - { "Arraycollation", All & ~ApplePlatform.MacOSX }, - { "Asin", All }, - { "Asinh", All }, - { "Astc", All }, - { "Aswas", ApplePlatform.MacOSX }, - { "Atan", All }, - { "Atanh", All }, - { "Atm", All }, + { "Apns", All & ~ApplePlatform.TVOS }, // Apple Push Notification Service + { "Applei", All }, // Apple API selector fragment + { "Aps", ApplePlatform.MacOSX }, // Apple Push Service + { "Apv", ApplePlatform.MacOSX }, // Apple API abbreviation + { "Arcball", All }, // 3D rotation control + { "Argb", All }, // alpha-red-green-blue +#if !XAMCORE_5_0 + { "Arraycollation", All & ~ApplePlatform.MacOSX }, // SortedArrayFromArraycollationStringSelector - will be renamed in XAMCORE_5_0 +#endif + { "Asin", All }, // arc sine + { "Asinh", All }, // inverse hyperbolic sine + { "Astc", All }, // Adaptive Scalable Texture Compression + { "Atan", All }, // arc tangent + { "Atanh", All }, // inverse hyperbolic tangent + { "Atm", All }, // asynchronous transfer mode { "Atmos", All }, // Dolby Atmos - { "Atr", All }, + { "Atr", All }, // Answer To Reset { "Ats", All }, // App Transport Security - { "Atsc", All }, - { "Attr", ApplePlatform.MacOSX }, - { "Attrib", All }, - { "Attributesfor", ApplePlatform.MacOSX }, - { "Attributevalue", All }, + { "Atsc", All }, // TV broadcast standard + { "Attr", ApplePlatform.MacOSX }, // attribute abbreviation + { "Attrib", All }, // attribute abbreviation +#if !XAMCORE_5_0 + { "Attributevalue", All }, // ReplacementValueForAttributevalue - will be renamed in XAMCORE_5_0 +#endif { "Attrs", All }, // Attributes (used by Apple for keys) - { "Audiofile", All }, - { "Audiograph", ApplePlatform.MacOSX }, - { "Authenticatable", ApplePlatform.MacOSX }, - { "Automapping", All }, - { "Automatch", All }, - { "Automounted", All }, - { "Autoredirect", ApplePlatform.MacCatalyst | ApplePlatform.TVOS }, - { "Autospace", ApplePlatform.MacOSX }, - { "Autostarts", ApplePlatform.MacOSX }, + { "Audiofile", All }, // compound word + { "Audiograph", ApplePlatform.MacOSX }, // compound word + { "Authenticatable", ApplePlatform.MacOSX }, // valid English derivative + { "Automapping", All }, // compound word + { "Automatch", All }, // compound word + { "Automounted", All }, // compound word + { "Autoredirect", ApplePlatform.MacCatalyst | ApplePlatform.TVOS }, // compound word + { "Autospace", ApplePlatform.MacOSX }, // compound word + { "Autostarts", ApplePlatform.MacOSX }, // compound word { "Avb", All }, // acronym: Audio Video Bridging { "Avci", All }, // file type - { "Avg", All }, - { "Axept", All & ~ApplePlatform.TVOS }, - { "Bancomat", All & ~ApplePlatform.TVOS }, - { "Bary", All }, - { "Ber", All }, + { "Avg", All }, // average abbreviation + { "Axept", All & ~ApplePlatform.TVOS }, // payment terminal brand + { "Bancomat", All & ~ApplePlatform.TVOS }, // Italian payment network + { "Bancaires", All & ~ApplePlatform.TVOS }, // Cartes Bancaires payment network + { "Bary", All }, // barycentric abbreviation + { "Ber", All }, // Basic Encoding Rules { "Bggr", All }, // acronym for Blue, Green, Green, Red { "Bgra", All }, // acrnym for Blue, Green, Red, Alpha - { "Bgrx", All }, - { "Bim", All }, - { "Bitangent", All }, - { "Blinn", All }, - { "Blit", All }, - { "Blockmap", ApplePlatform.MacOSX }, - { "Blockquote", ApplePlatform.MacOSX }, - { "Brotli", All }, - { "Bsd", ApplePlatform.MacOSX }, - { "Bsln", All }, - { "Bssid", All & ~ApplePlatform.TVOS }, + { "Bgrx", All }, // blue-green-red-unused + { "Bim", All }, // BIM file/code term + { "Bitangent", All }, // geometry term + { "Blinn", All }, // Blinn shading model + { "Blit", All }, // graphics copy operation + { "Blockmap", ApplePlatform.MacOSX }, // compound word + { "Blockquote", ApplePlatform.MacOSX }, // HTML element name + { "Brotli", All }, // compression format + { "Bsd", ApplePlatform.MacOSX }, // Berkeley Software Distribution + { "Bsln", All }, // OpenType baseline tag + { "Bssid", All & ~ApplePlatform.TVOS }, // wireless network identifier { "Btle", ApplePlatform.MacOSX }, // Bluetooth Low Energy - { "Cabac", All }, + { "Cabac", All }, // video coding acronym { "Caf", All }, // acronym: Core Audio Format - { "Callables", All }, - { "Callpout", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, + { "Callables", All }, // valid English plural { "Cartes", All & ~ApplePlatform.TVOS }, // french - { "Catmull", All }, - { "Cavlc", All }, - { "Ccitt", ApplePlatform.MacOSX }, - { "Cct", All }, - { "Ccw", All }, + { "Catmull", All }, // Catmull-Rom term + { "Cavlc", All }, // video coding acronym + { "Ccitt", ApplePlatform.MacOSX }, // standards body acronym + { "Cbc", All }, // Cipher Block Chaining + { "Cct", All }, // correlated color temperature + { "Ccw", All }, // counterclockwise abbreviation { "Cda", All & ~ApplePlatform.TVOS }, // acronym: Clinical Document Architecture - { "Cdma", All }, - { "Cdrom", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Cea", All }, + { "Cdf", All }, // Cumulative Distribution Function + { "Cdma", All }, // cellular standard + { "Cdrom", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // Compact Disc Read-Only Memory + { "Cea", All }, // standards acronym { "Celp", All }, // MPEG4ObjectID { "Celu", All }, // Continuously Differentiable Exponential Linear Unit (ML) { "Cfa", All }, // acronym: Color Filter Array - { "Chacha", All }, - { "Chapv", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Characterteristic", All }, - { "Cholesky", All }, - { "Chromaticities", All }, - { "Chw", All }, - { "Ciexyz", ApplePlatform.MacOSX }, - { "Ciff", All }, - { "Cinemagraph", ApplePlatform.TVOS }, - { "Cinepak", All }, - { "Cla", All }, - { "Clearcoat", All }, - { "Clockstamp", All }, + { "Chacha", All }, // ChaCha cipher + { "Chapv", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // CHAP variant acronym +#if !XAMCORE_5_0 + { "Characterteristic", All }, // UpdatedCharacterteristicValue - will be renamed in XAMCORE_5_0 +#endif + { "Cholesky", All }, // matrix decomposition + { "Chromaticities", All }, // color science term + { "Chw", All }, // tensor layout abbrev + { "Ciexyz", ApplePlatform.MacOSX }, // CIE XYZ color space + { "Cif", All }, // Common Intermediate Format + { "Ciff", All }, // Canon image format + { "Cinemagraph", ApplePlatform.TVOS }, // still-photo animation + { "Cinepak", All }, // video codec + { "Ciphersuite", All }, // TLS cipher suite + { "Cla", All }, // command class byte + { "Clearcoat", All }, // material property + { "Clockstamp", All }, // timestamp variant { "Cmaf", All }, // Common Media Application Format (mpeg4) { "Cmy", ApplePlatform.MacOSX }, // acronym: Cyan, magenta, yellow { "Cmyk", All }, // acronym: Cyan, magenta, yellow and key - { "Cmyka", ApplePlatform.MacOSX }, + { "Cmyka", ApplePlatform.MacOSX }, // CMYK plus alpha { "Cnn", All }, // Convolutional Neural Network - { "Cns", ApplePlatform.MacOSX }, - { "Codabar", All }, - { "Commited", ApplePlatform.MacOSX }, - { "Conecs", All & ~ApplePlatform.TVOS }, - { "Constrainted", ApplePlatform.MacOSX }, - { "Conv", All }, - { "Copyback", All }, - { "Cose", All & ~ApplePlatform.TVOS }, - { "Crosstraining", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Csr", All }, - { "Ctm", ApplePlatform.MacOSX }, - { "Ctor", All }, - { "Cubemap", All }, - { "Cymk", ApplePlatform.MacOSX }, - { "Cymka", ApplePlatform.MacOSX }, - { "Daap", All }, - { "Dangi", All }, - { "Dankort", All & ~ApplePlatform.TVOS }, - { "Dav", All & ~ApplePlatform.TVOS }, + { "Cns", ApplePlatform.MacOSX }, // Chinese National Standard + { "Codabar", All }, // barcode symbology +#if !XAMCORE_5_0 + { "Commited", ApplePlatform.MacOSX }, // CommitedLoad - will be renamed in XAMCORE_5_0 +#endif + { "Conf", All }, // configuration abbreviation + { "Conecs", All & ~ApplePlatform.TVOS }, // card network acronym + { "Conv", All }, // convolution abbreviation + { "Cooldown", All & ~ApplePlatform.TVOS }, // compound word + { "Copyback", All }, // storage operation + { "Cose", All & ~ApplePlatform.TVOS }, // CBOR Object Signing and Encryption + { "Crosstraining", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // compound word + { "Csr", All }, // certificate signing request + { "Csc", All }, // cosecant + { "Ctm", ApplePlatform.MacOSX }, // current transform matrix + { "Ctor", All }, // constructor abbreviation + { "Cubemap", All }, // graphics texture type + { "Cymk", ApplePlatform.MacOSX }, // CMYK channel order + { "Cymka", ApplePlatform.MacOSX }, // CMYK plus alpha + { "Daap", All }, // Digital Audio Access Protocol + { "Dangi", All }, // Korean calendar + { "Dankort", All & ~ApplePlatform.TVOS }, // Danish payment card + { "Dav", All & ~ApplePlatform.TVOS }, // DAV protocol term { "Dcip", All }, // acronym: Digital Cinema Implementation Partners - { "Deca", All & ~ApplePlatform.TVOS }, - { "Decomposables", All }, - { "Deinterlace", All }, - { "Denimonator", All }, - { "Denoise", All }, - { "Denoised", All }, - { "Depthwise", All }, - { "Dequantize", All }, - { "Descendents", All }, - { "Descriptorat", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Descriptorfor", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Dfsi", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, + { "Deca", All & ~ApplePlatform.TVOS }, // metric prefix + { "Decomposables", All }, // valid English plural + { "Deinterlace", All }, // video processing term + { "Denoise", All }, // noise reduction term + { "Denoised", All }, // noise reduction term + { "Denoiser", All }, // noise reduction filter + { "Depthwise", All }, // ML convolution term + { "Dequantize", All }, // signal processing term + { "Dfsi", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation { "Dhe", All }, // Diffie–Hellman key exchange - { "Dhs", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Dhwio", All }, - { "Dicom", All }, - { "Diconnection", All }, + { "Dhs", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation + { "Dhwio", All }, // tensor layout abbrev + { "Dicom", All }, // medical imaging standard + { "Diconnection", All }, // Apple API spelling { "Diffable", All }, // that you can diff it.. made up word from apple - { "Differental", All }, - { "Diffie", All }, - { "Dirbursement", All & ~ApplePlatform.TVOS }, - { "Directionfor", All & ~ApplePlatform.MacOSX }, - { "Dirs", ApplePlatform.MacOSX }, - { "Dismissable", ApplePlatform.MacOSX }, - { "Dissapearing", ApplePlatform.MacOSX }, - { "Dist", All }, + { "Diffie", All }, // Diffie-Hellman name + { "Dirbursement", All & ~ApplePlatform.TVOS }, // Apple API spelling + { "Dirs", ApplePlatform.MacOSX }, // directories abbreviation + { "Dismissable", ApplePlatform.MacOSX }, // variant of dismissible + { "Dist", All }, // distance abbreviation { "Distinguised", ApplePlatform.MacOSX }, // ITLibPlaylistPropertyDistinguisedKind - { "dlclose", All }, - { "dlerror", All }, - { "Dlfcn", All }, - { "Dls", ApplePlatform.MacOSX }, - { "Dng", All }, - { "Dnssec", All }, - { "Dont", All }, - { "Dop", ApplePlatform.iOS }, - { "Dopesheet", All }, + { "dlclose", All }, // POSIX dynamic loader API + { "dlerror", All }, // POSIX dynamic loader API +#if !XAMCORE_5_0 + { "Directionfor", All & ~ApplePlatform.MacOSX }, // SetBaseWritingDirectionforRange - will be renamed in XAMCORE_5_0 +#endif + { "Dlfcn", All }, // dynamic loader header + { "Dls", ApplePlatform.MacOSX }, // Downloadable Sounds + { "Dng", All }, // Digital Negative format + { "Dnssec", All }, // DNS Security Extensions + { "Dont", All }, // selector fragment without apostrophe + { "Dop", All }, // data object parameter + { "Dopesheet", All }, // animation timeline view { "Downmix", All }, // Sound terminology that means making a stereo mix from a 5.1 surround mix. - { "Dpa", All }, + { "Dpa", All }, // Apple API abbreviation { "Dpad", All }, // Directional pad (D-pad) { "Dpads", All }, // plural of above { "Drm", ApplePlatform.MacOSX }, // MediaItemProperty.IsDrmProtected - { "Droste", All }, - { "Dsf", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Dsfi", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Dstu", All & ~ApplePlatform.TVOS }, - { "Dtls", All }, + { "Droste", All }, // Droste effect name + { "Dsf", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation + { "Dsfi", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation + { "Dstu", All & ~ApplePlatform.TVOS }, // Ukrainian standard acronym + { "Dtls", All }, // Datagram TLS { "Dtmf", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // DTMF - { "Dtss", ApplePlatform.MacOSX }, - { "dy", All }, - { "Eap", All }, - { "Ebu", All }, + { "Dtss", ApplePlatform.MacOSX }, // audio format acronym + { "dy", All }, // tensor dimension symbol + { "Eap", All }, // Extensible Authentication Protocol + { "Ean", All }, // European Article Number (barcode standard) + { "Ebu", All }, // European Broadcasting Union { "Ecc", All }, // Elliptic Curve Cryptography { "Ecdh", All }, // Elliptic Curve Diffie–Hellman { "Ecdhe", All }, // Elliptic Curve Diffie-Hellman Ephemeral { "Ecdsa", All }, // Elliptic Curve Digital Signature Algorithm - { "Ecg", All & ~ApplePlatform.TVOS }, + { "Ecg", All & ~ApplePlatform.TVOS }, // electrocardiogram + { "Echos", ApplePlatform.MacOSX }, // plural of echo { "Ecies", All }, // Elliptic Curve Integrated Encryption Scheme { "Ecn", All }, // Explicit Congestion Notification { "Ect", All }, // ECN Capable Transport - { "Editability", All & ~ApplePlatform.MacOSX }, - { "Edr", All }, + { "Editability", All & ~ApplePlatform.MacOSX }, // valid English noun + { "Edr", All }, // extended dynamic range { "Eftpos", All & ~ApplePlatform.TVOS }, // Electronic funds transfer at point of sale - { "Eisu", ApplePlatform.MacOSX }, - { "Elative", All }, - { "Elu", All }, - { "Emagic", All }, - { "Emaili", All & ~ApplePlatform.TVOS }, - { "Embd", All }, - { "Emebedding", All }, + { "Eisu", ApplePlatform.MacOSX }, // Japanese input mode + { "Elative", All }, // linguistic case + { "Elu", All }, // activation function + { "Emagic", All }, // audio software brand + { "Embd", All }, // embedded abbreviation +#if !XAMCORE_5_0 + { "Emebedding", All }, // NLContextualEmebeddingKey - will be renamed in XAMCORE_5_0 +#endif { "Emsg", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // 4cc - { "Enc", All }, - { "Endc", All }, + { "Enc", All }, // encoding abbreviation + { "Endc", All }, // 5G dual connectivity { "Eof", All }, // acronym End-Of-File - { "Eppc", All }, - { "Epub", All }, - { "Erf", All }, - { "Essive", All }, - { "Evdo", All }, - { "Evictable", ApplePlatform.MacOSX | ApplePlatform.iOS }, - { "Exabits", All }, - { "Exbibits", All }, - { "Exhange", All }, - { "Expr", All }, - { "Exr", All }, - { "Extrinsics", All }, - { "Feli", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, + { "Eppc", All }, // program-to-program comms + { "Epub", All }, // ebook file format + { "Erf", All }, // Ericsson Texture Compression format + { "Essive", All }, // linguistic case + { "Evdo", All }, // cellular data standard + { "Evictable", ApplePlatform.MacOSX | ApplePlatform.iOS }, // valid English derivative + { "Exabits", All }, // SI unit name + { "Exbibits", All }, // IEC unit name + { "Exbibytes", All }, // IEC unit name + { "Exp", All }, // exponent/exponential + { "Expr", All }, // expression abbreviation + { "Exr", All }, // OpenEXR image format + { "Extrinsics", All }, // computer vision term + { "Fcp", All }, // Apple ATS Forward Compatibility Policy + { "Feli", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // FeliCa term { "Felica", All & ~ApplePlatform.TVOS }, // Japanese contactless RFID smart card system - { "Femtowatts", All }, - { "Fft", All }, - { "Fhir", All & ~ApplePlatform.TVOS }, - { "Fieldset", All & ~ApplePlatform.MacCatalyst }, - { "Flipside", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Formati", All }, - { "Fourty", ApplePlatform.MacOSX }, - { "Fov", All }, - { "Fqdns", All }, - { "Framebuffer", All }, - { "Framesetter", All }, - { "Freq", All }, + { "Femtowatts", All }, // SI unit name + { "Fft", All }, // Fast Fourier Transform + { "Fhir", All & ~ApplePlatform.TVOS }, // healthcare data standard + { "Fieldset", All & ~ApplePlatform.MacCatalyst }, // HTML element name + { "Formati", All }, // FormatiTunesMetadata - word split of "Format" + "iTunes" + { "Fov", All }, // field of view + { "Fqdns", All }, // fully qualified domain names + { "Framebuffer", All }, // graphics buffer + { "Framesetter", All }, // Core Text type + { "Freq", All }, // frequency abbreviation { "Froms", ApplePlatform.MacOSX }, // NSMetadataItemWhereFromsKey - { "Ftps", All }, - { "Gadu", All & ~ApplePlatform.TVOS }, - { "Gainmap", All }, + { "Ftps", All }, // FTP over TLS + { "Func", All }, // function abbreviation + { "Gadu", All & ~ApplePlatform.TVOS }, // Gadu-Gadu messenger + { "Gainmap", All }, // HDR image term { "Gbrg", All }, // acronym for Green-Blue-Reg-Green - { "Gbtac", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Gbtdc", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Gcm", All }, + { "Gbtac", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation + { "Gbtdc", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation + { "Gcm", All }, // Galois/Counter Mode { "Gelu", All }, // Gaussian Error Linear Unit (ML) - { "Gibibits", All }, - { "Gid", ApplePlatform.MacOSX }, - { "Gigapascals", All }, - { "Girocard", All & ~ApplePlatform.TVOS }, - { "Gles", ApplePlatform.iOS | ApplePlatform.TVOS }, + { "Gen", All }, // generation (e.g. SiriRemote1stGen) + { "Gibibits", All }, // IEC unit name + { "Gid", ApplePlatform.MacOSX }, // group identifier + { "Gigapascals", All }, // SI unit name + { "Girocard", All & ~ApplePlatform.TVOS }, // German payment network + { "Gles", ApplePlatform.iOS | ApplePlatform.TVOS }, // OpenGL ES abbreviation { "Glorot", All }, // NN { "Gop", All }, // acronym for Group Of Pictures - { "Gpp", All }, - { "Gps", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Grammarl", ApplePlatform.MacOSX }, + { "Gpp", All }, // 3GPP standards acronym + { "Gps", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // Global Positioning System + { "Gsm", All }, // Global System for Mobile Communications { "Grbg", All }, // acronym for Green-Red-Blue-Green - { "Greeking", ApplePlatform.MacOSX }, - { "Groupless", All & ~ApplePlatform.TVOS }, - { "Gru", All }, - { "Gtin", All }, - { "Gui", All }, - { "Hardlink", ApplePlatform.MacOSX }, - { "Hdmi", All & ~ApplePlatform.MacOSX }, - { "Hdr", All }, + { "Groupless", All & ~ApplePlatform.TVOS }, // valid English derivative + { "Gru", All }, // gated recurrent unit + { "Gtin", All }, // Global Trade Item Number + { "Gui", All }, // graphical user interface + { "Handwashing", All & ~ApplePlatform.TVOS }, // compound word + { "Hankaku", All & ~ApplePlatform.MacOSX }, // Japanese half-width text + { "Hardlink", ApplePlatform.MacOSX }, // filesystem term + { "Hdmi", All & ~ApplePlatform.MacOSX }, // video connector standard + { "Hdr", All }, // high dynamic range { "Heic", All }, // file type { "Heics", All }, // High Efficiency Image File Format (Sequence) { "Heif", All }, // High Efficiency Image File Format - { "Hermitean", All }, + { "Hectopascals", All }, // SI unit name { "Hevc", All }, // CMVideoCodecType / High Efficiency Video Coding - { "Hfp", All & ~ApplePlatform.MacOSX }, - { "Hhr", All }, - { "Himyan", All & ~ApplePlatform.TVOS }, - { "Hindlegs", All }, - { "Hipass", All }, - { "Histogrammed", All & ~ApplePlatform.TVOS }, + { "Hfp", All & ~ApplePlatform.MacOSX }, // Bluetooth Hands-Free Profile + { "Hhr", All }, // Apple API abbreviation + { "Himyan", All & ~ApplePlatform.TVOS }, // South Arabian script + { "Hermitean", All }, // Apple's spelling of Hermitian in MPSGraph FFT methods + { "Hindlegs", All }, // compound word + { "Hipass", All }, // high-pass filter + { "Histogrammed", All & ~ApplePlatform.TVOS }, // valid technical term { "Hlg", All }, // Hybrid Log-Gamma - { "Hls", All }, - { "Hoa", All }, - { "Hpke", ApplePlatform.MacOSX }, + { "Hls", All }, // HTTP Live Streaming + { "Hoa", All }, // higher-order ambisonics + { "Hpke", ApplePlatform.MacOSX }, // Hybrid Public Key Encryption { "Hrtf", All }, // acronym used in AUSpatializationAlgorithm - { "Hsb", ApplePlatform.MacOSX }, - { "Hsba", ApplePlatform.MacOSX }, + { "Hsb", ApplePlatform.MacOSX }, // hue-saturation-brightness + { "Hsba", ApplePlatform.MacOSX }, // HSB plus alpha { "Hvxc", All }, // MPEG4ObjectID - { "Hwc", All }, - { "Hwio", All }, - { "Iap", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Ibss", ApplePlatform.MacOSX }, - { "Icns", All }, - { "Ico", All }, - { "Iconfor", ApplePlatform.MacOSX }, - { "Icq", All & ~ApplePlatform.TVOS }, - { "Identd", All }, - { "Iec", All }, - { "Ies", All }, - { "Imageblock", All }, - { "Imagefor", All & ~ApplePlatform.MacOSX }, - { "Imap", All }, - { "Imaps", All }, - { "Imei", All & ~ApplePlatform.MacOSX }, - { "Img", All }, + { "Hwc", All }, // tensor layout abbrev + { "Hwio", All }, // tensor layout abbrev + { "Iap", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // in-app purchase + { "Ibss", ApplePlatform.MacOSX }, // Wi-Fi ad hoc mode + { "Icns", All }, // Apple icon format + { "Ico", All }, // Windows icon format + { "Icq", All & ~ApplePlatform.TVOS }, // ICQ messenger name + { "Identd", All }, // ident daemon name + { "Ident", All }, // identifier abbreviation + { "Iec", All }, // International Electrotechnical Commission + { "Ies", All }, // lighting data format + { "Ikev", All }, // Internet Key Exchange v2 + { "Ima", All }, // Interactive Multimedia Association + { "Imageblock", All }, // compound word + { "Imap", All }, // Internet Message Access Protocol + { "Imaps", All }, // IMAP over TLS + { "Imei", All & ~ApplePlatform.MacOSX }, // device identifier acronym + { "Img", All }, // image abbreviation { "Impl", All }, // BindingImplAttribute - { "Incrementor", ApplePlatform.MacOSX }, - { "Indoorcycle", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Indoorrun", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Indoorwalk", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Inessive", All }, - { "Inklist", All }, - { "Inode", ApplePlatform.MacOSX }, - { "Inot", All }, - { "Inser", All }, - { "Instamatic", ApplePlatform.MacOSX }, - { "Interac", All & ~ApplePlatform.TVOS }, - { "Interactable", ApplePlatform.MacOSX }, - { "Interframe", All }, - { "Interitem", All }, - { "Intermenstrual", All & ~ApplePlatform.TVOS }, - { "Intoi", All & ~ApplePlatform.MacOSX }, - { "Intravaginal", All & ~ApplePlatform.TVOS }, - { "Inv", All }, - { "Invitable", All }, - { "Iou", All }, - { "Ipa", All }, - { "Ipp", All }, - { "Iptc", All }, - { "Ircs", All }, - { "Isrc", All }, - { "Itemto", ApplePlatform.MacOSX }, - { "Itf", All }, - { "Itt", All & ~ApplePlatform.TVOS }, - { "Itu", All }, + { "Incrementor", ApplePlatform.MacOSX }, // valid English noun + { "Indoorcycle", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // compound word + { "Indoorrun", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // compound word + { "Indoorwalk", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // compound word + { "Inessive", All }, // linguistic case + { "Ingles", All }, // El Corte Ingles = Spanish payment card + { "Inklist", All }, // compound word + { "Inode", ApplePlatform.MacOSX }, // filesystem metadata term + { "Inser", All }, // Apple API selector fragment + { "Instamatic", ApplePlatform.MacOSX }, // Kodak camera brand + { "Interac", All & ~ApplePlatform.TVOS }, // Canadian payment network + { "Interactable", ApplePlatform.MacOSX }, // valid English derivative + { "Interframe", All }, // video coding term + { "Interitem", All }, // compound word + { "Intermenstrual", All & ~ApplePlatform.TVOS }, // medical term + { "Intravaginal", All & ~ApplePlatform.TVOS }, // medical term + { "Inv", All }, // inverse abbreviation + { "Invitable", All }, // valid English derivative + { "Iou", All }, // IOU abbreviation + { "Ipa", All }, // International Phonetic Alphabet + { "Ipp", All }, // Internet Printing Protocol + { "Iptc", All }, // photo metadata standard + { "Ircs", All }, // remote control standard + { "Isrc", All }, // recording code standard + { "Itf", All }, // Interleaved 2 of 5 + { "Itt", All & ~ApplePlatform.TVOS }, // International Tape Association + { "Itu", All }, // International Telecommunication Union { "Itur", All }, // Itur_2020_Hlg - { "Jaywan", All & ~ApplePlatform.TVOS }, + { "Jaywan", All & ~ApplePlatform.TVOS }, // Jordanian payment network { "Jcb", All & ~ApplePlatform.TVOS }, // Japanese credit card company - { "Jfif", All }, - { "Jis", ApplePlatform.MacOSX }, - { "Jrts", All & ~ApplePlatform.TVOS }, - { "Jwks", ApplePlatform.MacOSX }, - { "Jws", All & ~ApplePlatform.TVOS }, - { "Jwt", ApplePlatform.MacOSX }, - { "Keepalive", All }, - { "Keycode", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Keyerror", All }, - { "Keyi", All }, - { "Keypath", ApplePlatform.MacOSX }, - { "Keypoint", All }, - { "Keypoints", All }, - { "Kibibits", All }, - { "Kickboard", All & ~ApplePlatform.TVOS }, - { "Kiloampere", All }, - { "Kiloamperes", All }, - { "Kiloohms", All }, - { "Kilopascals", All }, - { "ks", All }, + { "Jfif", All }, // JPEG File Interchange Format + { "Jis", ApplePlatform.MacOSX }, // Japanese Industrial Standards + { "Jrts", All & ~ApplePlatform.TVOS }, // Apple API abbreviation + { "Jws", ApplePlatform.MacOSX }, // JSON Web Signature + { "Jwks", ApplePlatform.MacOSX }, // JSON Web Key Set + { "Jwt", ApplePlatform.MacOSX }, // JSON Web Token + { "Keepalive", All }, // networking compound word + { "Keycode", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // compound word + { "Keyerror", All }, // compound word + { "Keyi", All }, // Apple API selector fragment + { "Keypath", ApplePlatform.MacOSX }, // compound word + { "Keypoint", All }, // computer vision term + { "Keypoints", All }, // computer vision term + { "Kibibits", All }, // IEC unit name + { "Kickboard", All & ~ApplePlatform.TVOS }, // compound word + { "Kiloampere", All }, // SI unit name + { "Kiloamperes", All }, // SI unit name + { "Kiloohms", All }, // SI unit name + { "Kilopascals", All }, // SI unit name + { "ks", All }, // word fragment from spell checker { "Kullback", All }, // Kullback-Leibler Divergence - { "Lacunarity", All }, - { "Langauges", All & ~ApplePlatform.MacOSX }, + { "Lacunarity", All }, // fractal geometry term { "Latm", All }, // Low Overhead Audio Transport Multiplex - { "Lbc", All }, - { "Ldaps", All }, - { "Lerp", All }, - { "libcompression", All }, - { "libdispatch", All }, - { "Lingustic", All }, - { "Lod", All }, - { "Lopass", All }, - { "Lowlevel", All }, - { "Lpcm", All }, - { "Lstm", All }, - { "Lte", All }, - { "Ltr", All }, - { "Lun", All }, - { "Lut", All }, + { "Lbc", All }, // audio codec acronym + { "Ldaps", All }, // LDAP over TLS + { "Leibler", All }, // Kullback-Leibler divergence + { "Lerp", All }, // linear interpolation + { "libcompression", All }, // Apple library name + { "libdispatch", All }, // Apple library name + { "Lingustic", All }, // Apple API spelling + { "Lite", All }, // lightweight variant + { "Loas", All }, // Low Overhead Audio Stream + { "Lod", All }, // level of detail + { "Lopass", All }, // low-pass filter + { "Lowlevel", All }, // compound word + { "Lpcm", All }, // Linear PCM audio + { "Lsb", All }, // Least Significant Bit + { "Lstm", All }, // long short-term memory + { "Lte", All }, // Long-Term Evolution + { "Ltp", All }, // AAC Long Term Prediction + { "Ltr", All }, // left-to-right abbreviation + { "Luma", All }, // luminance component in video + { "Lun", All }, // logical unit number + { "Lut", All }, // lookup table { "Lzfse", All }, // acronym { "Lzma", All }, // acronym - { "Lzw", ApplePlatform.MacOSX }, + { "Lzw", ApplePlatform.MacOSX }, // Lempel-Ziv-Welch { "Mada", All & ~ApplePlatform.TVOS }, // payment system - { "Matchingcoalesce", All }, { "Mcp", All }, // metacarpophalangeal (hand) - { "Mebibits", All }, - { "Mebx", All }, - { "Meeza", All & ~ApplePlatform.TVOS }, - { "Megaampere", All }, - { "Megaamperes", All }, - { "Megaliters", All }, - { "Megameters", All }, - { "Megaohms", All }, - { "Megapascals", All }, - { "Mennekes", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Metacharacters", All }, - { "Metadatas", All }, - { "Metalness", All }, - { "Mgmt", All }, - { "Microampere", All }, - { "Microamperes", All }, - { "Microohms", All }, - { "Microwatts", All }, - { "Mifare", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Millimoles", All }, - { "Milliohms", All }, - { "Minification", All }, - { "Mmw", All }, - { "Mncs", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, + { "Mebibits", All }, // IEC unit name + { "Mebx", All }, // image metadata box + { "Meeza", All & ~ApplePlatform.TVOS }, // Egyptian payment network + { "Megaampere", All }, // SI unit name + { "Megaamperes", All }, // SI unit name + { "Megaliters", All }, // SI unit name + { "Megameters", All }, // SI unit name + { "Megaohms", All }, // SI unit name + { "Megapascals", All }, // SI unit name + { "Mennekes", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // EV connector brand + { "Metacharacters", All }, // regex syntax term + { "Metadatas", All }, // valid technical plural + { "Metalness", All }, // PBR material property + { "Mgmt", All }, // management abbreviation + { "Microampere", All }, // SI unit name + { "Microamperes", All }, // SI unit name + { "Microohms", All }, // SI unit name + { "Microwatts", All }, // SI unit name + { "Mifare", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // NFC card brand + { "Mihret", All }, // Ethiopic "Amete Mihret" calendar + { "Millimoles", All }, // SI unit name + { "Milliohms", All }, // SI unit name + { "Minification", All }, // graphics scaling term + { "Mmw", All }, // millimeter wave + { "Mncs", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API abbreviation { "Mobike", All }, // acronym - { "Monoline", All & ~ApplePlatform.TVOS }, - { "Morpher", All }, + { "Monoline", All & ~ApplePlatform.TVOS }, // single-stroke design term + { "Morpher", All }, // graphics/animation term { "Mpe", All }, // acronym - { "Mps", All }, + { "Mps", All }, // metal performance shaders { "Msaa", All }, // multisample anti-aliasing - { "Msi", All }, + { "Msb", All }, // Most Significant Bit + { "Msi", All }, // installer package format { "Mtc", All }, // acronym - { "Mtgp", All }, - { "Mtl", All }, + { "Mtgp", All }, // PRNG algorithm name + { "Mtl", All }, // Metal framework prefix { "Mtu", All }, // acronym - { "Muid", All & ~ApplePlatform.TVOS }, - { "Mul", All }, - { "Mult", All }, - { "Multiary", All }, - { "Multipath", All }, - { "Multipeer", All }, - { "Multiscript", All }, - { "Multiselect", All & ~ApplePlatform.MacOSX }, - { "Multivariant", All }, - { "Multiview", All }, - { "Muxed", All }, - { "Nacs", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Nai", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Nanaco", All & ~ApplePlatform.TVOS }, - { "Nand", All }, - { "Nanograms", All }, - { "Nanowatts", All }, - { "Ncdhw", All }, - { "Nchw", All }, - { "nd", All }, - { "Ndef", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Ndhwc", All }, - { "Nesterov", All }, - { "Nestrov", All }, - { "Nfc", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Nfnt", All }, - { "Nhwc", All }, - { "Nntps", All }, - { "Nonenumerated", ApplePlatform.MacOSX }, - { "Noninteractive", All & ~ApplePlatform.TVOS }, - { "Noop", All }, - { "Nop", ApplePlatform.MacOSX }, - { "Nsa", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Nsevent", ApplePlatform.MacOSX }, + { "Muid", All & ~ApplePlatform.TVOS }, // MIDI universal identifier + { "Mul", All }, // multiply abbreviation + { "Mult", All }, // multiply abbreviation + { "Multiary", All }, // math/logic term + { "Multipath", All }, // networking term + { "Multipeer", All }, // Apple framework term + { "Multiscript", All }, // compound word + { "Multiselect", All & ~ApplePlatform.MacOSX }, // compound word + { "Multivariant", All }, // valid technical term + { "Multiview", All }, // graphics term + { "Muxed", All }, // multiplexed media term + { "Nacs", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // EV charging standard + { "Nai", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // network access identifier + { "Nal", All }, // Network Abstraction Layer (video coding) + { "Nanaco", All & ~ApplePlatform.TVOS }, // Japanese payment card + { "Nand", All }, // flash memory type + { "Nanograms", All }, // SI unit name + { "Nanowatts", All }, // SI unit name + { "Napas", All & ~ApplePlatform.TVOS }, // Vietnamese payment network + { "Ncdhw", All }, // tensor layout abbrev + { "Nchw", All }, // tensor layout abbrev + { "nd", All }, // tensor dimension symbol + { "Ndef", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // NFC data exchange format + { "Ndhwc", All }, // tensor layout abbrev + { "Nesterov", All }, // optimization method + { "Nestrov", All }, // Apple API spelling + { "Nfc", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // Near Field Communication + { "Nfnt", All }, // classic Mac font format + { "Nhwc", All }, // tensor layout abbrev + { "Nntps", All }, // NNTP over TLS + { "Nonenumerated", ApplePlatform.MacOSX }, // valid English derivative + { "Noninteractive", All & ~ApplePlatform.TVOS }, // valid English derivative + { "Noop", All }, // no-op abbreviation + { "Nop", ApplePlatform.MacOSX }, // no-op instruction + { "Nsa", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // network service access + { "Nsevent", ApplePlatform.MacOSX }, // Apple API class name { "Nsl", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // InternetLocationNslNeighborhoodIcon - { "Ntlm", All }, - { "Ntsc", All }, - { "Numberof", ApplePlatform.MacOSX }, - { "Nyquist", All & ~ApplePlatform.MacOSX }, - { "Objectness", All }, - { "Occlussion", All }, - { "Ocr", All }, + { "Ntlm", All }, // Windows auth protocol + { "Ntsc", All }, // video standard acronym + { "Nyquist", All & ~ApplePlatform.MacOSX }, // sampling theorem term + { "Oaep", All }, // Optimal Asymmetric Encryption Padding + { "Objectness", All }, // ML detection score + { "Ocr", All }, // optical character recognition { "Ocsp", All }, // Online Certificate Status Protocol - { "Octree", All }, - { "Ocurrences", All }, - { "Odia", All }, - { "Ohwi", All }, - { "Oid", All }, - { "Oidhw", All }, - { "Oihw", All }, - { "Onnx", All }, - { "Oper", All & ~ApplePlatform.MacOSX }, + { "Octree", All }, // spatial partition tree + { "Odia", All }, // Indic language name + { "Ohwi", All }, // tensor layout abbrev + { "Oid", All }, // object identifier + { "Oidhw", All }, // tensor layout abbrev + { "Oihw", All }, // tensor layout abbrev + { "Onnx", All }, // Open Neural Network Exchange + { "Ootf", All }, // Opto-Optical Transfer Function (HDR) + { "Oper", All & ~ApplePlatform.MacOSX }, // operator abbreviation { "Organisation", All }, // kCGImagePropertyIPTCExtRegistryOrganisationID in Xcode9.3-b1 - { "Orth", All }, + { "Orth", All }, // orthographic abbreviation { "Osa", All }, // Open Scripting Architecture { "Otsu", All }, // threshold for image binarization - { "ove", All }, - { "Overline", All & ~ApplePlatform.TVOS }, + { "ove", All }, // word fragment from spell checker + { "Overline", All & ~ApplePlatform.TVOS }, // typography term { "Paeth", All }, // PNG filter - { "Palettize", All }, - { "Parms", All }, - { "Pausable", All }, - { "Pbm", ApplePlatform.MacOSX }, - { "Pci", All & ~ApplePlatform.MacOSX }, - { "Pcl", All }, - { "Pcm", All }, - { "Pde", ApplePlatform.MacOSX }, - { "Pdu", All }, - { "Peap", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Pebibits", All }, - { "Performwith", ApplePlatform.MacOSX }, - { "Perlin", All }, - { "Persistable", All }, - { "Persistance", All }, - { "Petabits", All }, + { "Palettize", All }, // graphics term + { "Parms", All }, // parameters abbreviation + { "Pausable", All }, // valid English derivative + { "Pbm", ApplePlatform.MacOSX }, // Portable Bitmap format + { "Pci", All & ~ApplePlatform.MacOSX }, // Peripheral Component Interconnect + { "Pcl", All }, // Printer Command Language + { "Pcm", All }, // pulse-code modulation + { "Pde", ApplePlatform.MacOSX }, // partial differential equation + { "Pdu", All }, // protocol data unit + { "Peap", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Protected EAP + { "Pebibits", All }, // IEC unit name + { "Pebibytes", All }, // IEC unit name + { "Perlin", All }, // Perlin noise name + { "Persistable", All }, // valid English derivative + { "Petabits", All }, // SI unit name { "Pfs", All }, // acronym - { "Philox", All }, - { "Photoplethysmogram", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Phq", All & ~ApplePlatform.TVOS }, - { "Phy", ApplePlatform.MacOSX }, - { "Picometers", All }, - { "Picowatts", All }, - { "Pkcs", All }, - { "Placemark", All }, - { "Playout", All }, + { "Philox", All }, // PRNG algorithm name + { "Phong", All }, // Phong shading/reflection model + { "Photoplethysmogram", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // medical sensor term + { "Phq", All & ~ApplePlatform.TVOS }, // questionnaire acronym + { "Phy", ApplePlatform.MacOSX }, // physical layer term + { "Picometers", All }, // SI unit name + { "Pickleball", All & ~ApplePlatform.TVOS }, // sport name + { "Picowatts", All }, // SI unit name + { "Pkcs", All }, // crypto standard acronym + { "Placemark", All }, // mapping term + { "Playout", All }, // broadcasting term + { "Plessey", All }, // MSI/Plessey barcode symbology { "Pnc", All }, // MIDI - { "Pnorm", All }, - { "Polyline", All }, - { "Polylines", All }, - { "Popularimeter", All }, - { "Postback", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, + { "Pnorm", All }, // Lp norm notation + { "Polyline", All }, // graphics geometry term + { "Polylines", All }, // graphics geometry term + { "Popularimeter", All }, // ID3 metadata field + { "Postback", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // marketing/webhook term { "Ppd", ApplePlatform.MacOSX }, // PostScript Printer Description - { "Ppk", All }, - { "Preauthentication", ApplePlatform.MacOSX }, - { "Preds", All }, - { "Prefilter", All }, - { "Prereleased", All }, - { "Prerolls", All }, - { "Preseti", All }, - { "Previewable", ApplePlatform.MacOSX }, - { "Prf", All & ~ApplePlatform.TVOS }, - { "Propogate", All }, - { "Psec", All }, - { "Psk", All }, - { "Pskc", All & ~ApplePlatform.TVOS }, + { "Ppk", All }, // Apple API abbreviation + { "Preauthentication", ApplePlatform.MacOSX }, // compound word + { "Preds", All }, // predictions abbreviation + { "Prefilter", All }, // compound word + { "Prereleased", All }, // compound word + { "Prerolls", All }, // media playback term + { "Preseti", All }, // Apple API selector fragment + { "Prev", All }, // previous abbreviation + { "Previewable", ApplePlatform.MacOSX }, // valid English derivative + { "Prf", All & ~ApplePlatform.TVOS }, // pseudo-random function + { "Psec", All }, // picosecond abbreviation + { "Psk", All }, // pre-shared key + { "Pskc", All & ~ApplePlatform.TVOS }, // PSKC key container { "Psm", All }, // Protocol/Service Multiplexer - { "Ptp", ApplePlatform.MacOSX }, - { "Pvr", All }, + { "Privs", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // privileges abbreviation + { "Pss", All }, // Probabilistic Signature Scheme (RSA-PSS) + { "Ptp", ApplePlatform.MacOSX }, // Precision Time Protocol + { "Ptss", All & ~ApplePlatform.TVOS }, // Presentation Timestamps (plural) + { "Pvr", All }, // PowerVR graphics brand { "Pvrtc", All }, // MTLBlitOption - PowerVR Texture Compression - { "Qos", All }, - { "Quadding", All }, - { "Quaterniond", All }, - { "Quic", All }, - { "Qura", All }, - { "Qwac", All }, - { "Raycast", ApplePlatform.iOS }, - { "Raycasts", ApplePlatform.iOS }, - { "Reacquirer", All }, - { "Reassociation", ApplePlatform.MacOSX }, - { "Reauthentication", ApplePlatform.MacOSX }, - { "Rectfrom", ApplePlatform.MacOSX }, - { "Registeration", ApplePlatform.MacOSX }, - { "Reinvitation", All }, - { "Reinvite", All }, - { "Rel", All }, - { "Relocalization", ApplePlatform.iOS }, + { "Qos", All }, // quality of service + { "Quadding", All }, // typesetting term + { "Quaterniond", All }, // double quaternion type + { "Quic", All }, // transport protocol + { "Qura", All }, // payment network name + { "Qwac", All }, // qualified website cert + { "Raycast", ApplePlatform.iOS }, // graphics/AR term + { "Raycasts", ApplePlatform.iOS }, // graphics/AR term + { "Reacquirer", All }, // valid English noun + { "Reassociation", ApplePlatform.MacOSX }, // networking term + { "Reauthentication", ApplePlatform.MacOSX }, // compound word + { "Reinvitation", All }, // valid English noun + { "Reinvite", All }, // session protocol term + { "Rel", All }, // relation abbreviation + { "Relocalization", ApplePlatform.iOS }, // AR/vision term { "Relu", All }, // Rectified Linear Unit (ML) - { "Remmote", All }, - { "Replayable", All }, - { "Reprojection", All }, - { "Rgb", All }, - { "Rgba", All }, - { "Rgbaf", All }, - { "Rgbah", All }, - { "Rgbx", All }, + { "Replayable", All }, // valid English derivative + { "Reprojection", All }, // graphics/vision term + { "Rfc", All }, // Request for Comments + { "Rgb", All }, // red-green-blue + { "Rgba", All }, // red-green-blue-alpha + { "Rgbaf", All }, // RGBA float format + { "Rgbah", All }, // RGBA half-float format + { "Rgbx", All }, // RGB plus unused byte { "Rggb", All }, // acronym for Red, Green, Green, Blue - { "Rint", All }, - { "Rle", All }, - { "Rnn", All }, - { "Roi", All }, + { "Rint", All }, // round-to-integer function + { "Rle", All }, // run-length encoding + { "Rms", All }, // root mean square + { "Rnn", All }, // recurrent neural network + { "Roi", All }, // region of interest { "Romm", All }, // acronym: Reference Output Medium Metric - { "Rpa", All }, + { "Rpa", All }, // Resolvable Private Address { "Rpn", All }, // acronym { "Rsa", All }, // Rivest, Shamir and Adleman - { "Rsapss", All }, + { "Rsapss", All }, // RSA-PSS signature scheme { "Rsqrt", All }, // reciprocal square root - { "Rssi", All }, - { "Rtl", All }, - { "Rtp", All & ~ApplePlatform.MacOSX }, - { "Rtsp", All }, + { "Rssi", All }, // signal strength acronym + { "Rtl", All }, // right-to-left abbreviation + { "Rtp", All & ~ApplePlatform.MacOSX }, // Real-time Transport Protocol + { "Rtsp", All }, // streaming control protocol { "Saml", All & ~ApplePlatform.MacCatalyst }, // acronym - { "Scc", All }, - { "Scn", All }, - { "Sdh", ApplePlatform.TVOS }, - { "Sdk", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Sdnn", All & ~ApplePlatform.TVOS }, - { "Sdof", ApplePlatform.MacOSX }, - { "Sdr", All }, + { "Sbr", All }, // Spectral Band Replication (AAC) + { "Scc", All }, // subtitle/timing format + { "Scn", All }, // SceneKit prefix + { "Sdh", ApplePlatform.TVOS }, // subtitles for deaf/hard hearing + { "Sdk", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // software development kit + { "Sdnn", All & ~ApplePlatform.TVOS }, // Apple API abbreviation + { "Sdof", ApplePlatform.MacOSX }, // synthetic depth of field + { "Sdr", All }, // standard dynamic range { "Sdtv", ApplePlatform.TVOS }, // acronym: Standard Definition Tele Vision - { "Securit", ApplePlatform.iOS }, - { "Seekable", All }, - { "Sel", All & ~ApplePlatform.MacOSX }, + { "Securit", ApplePlatform.iOS }, // Apple API selector fragment + { "Seekable", All }, // valid English derivative + { "Sel", All & ~ApplePlatform.MacOSX }, // Objective-C selector { "Selu", All }, // Scaled Exponential Linear unit (ML) - { "Semitransient", ApplePlatform.MacOSX }, - { "Sensel", All }, - { "Shadable", All }, - { "Siemen", All & ~ApplePlatform.TVOS }, - { "Signbit", All }, + { "Semitransient", ApplePlatform.MacOSX }, // valid technical term + { "Sensel", All }, // pressure sensor brand + { "Sha", All }, // Secure Hash Algorithm + { "Shadable", All }, // graphics term + { "Siemen", All & ~ApplePlatform.TVOS }, // Apple API singular form + { "Signbit", All }, // math library term { "Sint", All }, // as in "Signed Integer" - { "Sixtyfour", ApplePlatform.MacOSX }, - { "Slerp", All }, - { "Slomo", All }, - { "Smpte", All }, - { "Snapshotter", All }, - { "Snn", All }, - { "Snorm", All }, - { "Sobel", All }, + { "Sixtyfour", ApplePlatform.MacOSX }, // compound number word + { "Slerp", All }, // spherical interpolation + { "Slomo", All }, // slow motion shorthand + { "Smpte", All }, // media standards body + { "Snapshotter", All }, // valid English noun + { "Snn", All }, // Apple API abbreviation + { "Snorm", All }, // signed normalized format + { "Sobel", All }, // image filter name { "Softmax", All }, // get_SoftmaxNormalization - { "Sopen", ApplePlatform.MacOSX }, - { "Spacei", All }, - { "Spl", All }, - { "Sqrt", All }, - { "Srgb", All }, - { "Ssid", All }, - { "Ssids", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Ssml", All }, - { "Sso", ApplePlatform.MacOSX }, - { "st", All }, - { "Sta", ApplePlatform.MacOSX }, - { "Standarize", All }, - { "Strided", All }, - { "Subband", All & ~ApplePlatform.TVOS }, - { "Subbeat", All }, - { "Subcaption", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Subcardioid", All & ~ApplePlatform.MacOSX }, - { "Subentities", All }, - { "Subfilter", All & ~ApplePlatform.TVOS }, - { "Subfilters", All & ~ApplePlatform.TVOS }, - { "Subheadline", All }, - { "Sublocality", All }, - { "Sublocation", All }, - { "Submesh", All }, - { "Submeshes", All }, - { "Subpixel", All }, - { "Subresources", All }, - { "Subsec", All }, + { "Sopen", ApplePlatform.MacOSX }, // Apple API abbreviation + { "Spacei", All }, // Apple API selector fragment + { "Spl", All }, // sound pressure level + { "Sqrt", All }, // square root function + { "Srgb", All }, // standard RGB color space + { "Ssid", All }, // Wi-Fi network identifier + { "Ssids", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // plural of SSID + { "Ssml", All }, // Speech Synthesis Markup Language + { "Sso", ApplePlatform.MacOSX }, // single sign-on + { "Ssr", All }, // Scalable Sample Rate (AAC) + { "st", All }, // ordinal suffix + { "Sta", ApplePlatform.MacOSX }, // station mode acronym + { "Strided", All }, // linear algebra term + { "Subband", All & ~ApplePlatform.TVOS }, // signal processing term + { "Subbeat", All }, // music timing term + { "Subcaption", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // compound word + { "Subcardioid", All & ~ApplePlatform.MacOSX }, // microphone polar pattern + { "Subentities", All }, // valid English plural + { "Subfilter", All & ~ApplePlatform.TVOS }, // compound word + { "Subfilters", All & ~ApplePlatform.TVOS }, // compound word + { "Subheadline", All }, // compound word + { "Sublocality", All }, // MapKit placemark term + { "Sublocation", All }, // compound word + { "Submesh", All }, // graphics geometry term + { "Submeshes", All }, // graphics geometry term + { "Subpixel", All }, // display/graphics term + { "Subresources", All }, // valid English plural + { "Subsec", All }, // subsecond abbreviation { "Suica", All & ~ApplePlatform.TVOS }, // Japanese contactless smart card type - { "Superentity", All }, - { "Supertype", All }, - { "Supertypes", All }, - { "Supression", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Svfg", All }, + { "Superentity", All }, // Core Data term + { "Supertype", All }, // type-system term + { "Supertypes", All }, // type-system term + { "Svfg", All }, // stochastic variance filtering { "Svg", All }, // Scalable Vector Graphics - { "Svgf", All }, - { "Swolf", All & ~ApplePlatform.TVOS }, - { "Sysex", All }, - { "Targetand", ApplePlatform.MacOSX }, - { "Tbgr", All }, - { "Tdoa", ApplePlatform.iOS }, - { "Tebibits", All }, - { "Tensorflow", All }, - { "Tessellator", All }, - { "Texcoord", All }, - { "Texel", All }, - { "Tga", All }, - { "th", All }, - { "Threadgroup", All }, - { "Threadgroups", All }, - { "Thumbnailing", All & ~ApplePlatform.TVOS }, - { "Thumbstick", All }, - { "Thumbsticks", ApplePlatform.iOS }, - { "Timecodes", All & ~ApplePlatform.TVOS }, - { "Tls", All }, - { "Tlv", All }, - { "Tmoney", All & ~ApplePlatform.TVOS }, - { "Toc", All }, - { "Toci", All }, - { "Tonemap", All }, - { "Transceive", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Trc", All }, - { "Tri", All }, - { "Ttls", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Tweening", All }, - { "Twentyfour", ApplePlatform.MacOSX }, - { "Twips", ApplePlatform.MacOSX }, - { "tx", All }, - { "ty", All }, - { "Udi", All & ~ApplePlatform.TVOS }, - { "Udp", All }, - { "Uid", All & ~ApplePlatform.TVOS }, - { "Unconfigured", All & ~ApplePlatform.MacOSX }, - { "Undecodable", All }, - { "Underrun", All }, - { "Unemphasized", ApplePlatform.MacOSX }, - { "Unentitled", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Unfetched", All }, - { "Unioning", All }, - { "Unmap", All }, - { "Unmatch", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Unorm", All }, - { "Unpremultiplied", All }, - { "Unpremultiplying", All }, - { "Unprepare", All }, - { "Unproject", All }, - { "Unpublish", All }, - { "Unsolo", All }, - { "Unsynced", ApplePlatform.MacOSX | ApplePlatform.iOS }, - { "Untrash", ApplePlatform.iOS }, - { "Upce", All }, - { "Upi", ApplePlatform.iOS }, - { "Uri", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, + { "Svgf", All }, // spatiotemporal variance-guided filter + { "Swolf", All & ~ApplePlatform.TVOS }, // swim efficiency score + { "Symbologies", All }, // plural of symbology (barcode) + { "Synchronizable", All }, // valid English derivative + { "Sysex", All }, // MIDI system exclusive + { "Tbgr", All }, // texture color format + { "Tdoa", ApplePlatform.iOS }, // time difference of arrival + { "Tebibits", All }, // IEC unit name + { "Tensorflow", All }, // machine learning framework + { "Tessellator", All }, // graphics term + { "Texcoord", All }, // texture coordinate + { "Texel", All }, // texture pixel term + { "Tga", All }, // Targa image format + { "th", All }, // ordinal suffix + { "Threadgroup", All }, // Metal compute term + { "Threadgroups", All }, // Metal compute term + { "Thumbnailing", All & ~ApplePlatform.TVOS }, // valid technical term + { "Thumbstick", All }, // game controller term + { "Thumbsticks", ApplePlatform.iOS }, // plural of thumbstick + { "Timecodes", All & ~ApplePlatform.TVOS }, // media timing term + { "Timelapse", All }, // compound word + { "Timelapses", All }, // plural of timelapse + { "Tls", All }, // Transport Layer Security + { "Tlv", All }, // tag-length-value + { "Tmoney", All & ~ApplePlatform.TVOS }, // Korean transit card + { "Toc", All }, // table of contents + { "Toci", All }, // Apple API selector fragment + { "Tonemap", All }, // image processing term + { "Touchpads", All }, // plural of touchpad + { "Transceive", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // communications verb + { "Trc", All }, // tone reproduction curve + { "Tri", All }, // triangle abbreviation + { "Ttls", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // tunneled TLS + { "Tweening", All }, // animation term + { "Twentyfour", ApplePlatform.MacOSX }, // compound number word + { "Twips", ApplePlatform.MacOSX }, // typography unit + { "tx", All }, // translation x axis + { "ty", All }, // translation y axis + { "Udi", All & ~ApplePlatform.TVOS }, // device identifier standard + { "Udp", All }, // User Datagram Protocol + { "Uid", All & ~ApplePlatform.TVOS }, // user identifier + { "Unconfigured", All & ~ApplePlatform.MacOSX }, // valid English derivative + { "Undecodable", All }, // valid English derivative + { "Underrun", All }, // audio/buffer term + { "Unemphasized", ApplePlatform.MacOSX }, // valid English derivative + { "Unentitled", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // security entitlement term + { "Unfetched", All }, // valid English derivative + { "Unfocus", All }, // valid UI verb + { "Unioning", All }, // set operation term + { "Unmap", All }, // memory mapping verb + { "Unmatch", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Apple API verb + { "Unorm", All }, // unsigned normalized format + { "Unpair", ApplePlatform.MacOSX }, // device pairing verb + { "Unpremultiplied", All }, // graphics term + { "Unpremultiplying", All }, // graphics term + { "Unprepare", All }, // API verb form + { "Unproject", All }, // graphics math term + { "Unpublish", All }, // content management verb + { "Unsend", All & ~ApplePlatform.TVOS }, // messaging verb + { "Unsolo", All }, // audio control verb + { "Unsynced", ApplePlatform.MacOSX | ApplePlatform.iOS }, // sync state adjective + { "Untrash", ApplePlatform.iOS }, // mail/files verb + { "Upce", All }, // UPC-E barcode + { "Upi", ApplePlatform.iOS }, // Unified Payments Interface + { "Uri", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // Uniform Resource Identifier { "Usac", All }, // Unified Speech and Audio Coding { "Usd", All }, // Universal Scene Description { "Usdz", All }, // USD zip - { "Usec", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Ussd", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Uterance", All }, - { "Utf", All }, - { "Uti", All & ~ApplePlatform.TVOS }, - { "Varispeed", All }, - { "Vbr", All }, - { "Vbv", All }, - { "Vergence", All }, - { "Vnode", All }, - { "Voip", ApplePlatform.MacCatalyst }, - { "Voronoi", All }, - { "Vpn", All }, - { "Vtt", All }, - { "Waon", All & ~ApplePlatform.TVOS }, - { "Warichu", All }, - { "Warpable", All }, - { "Wcdma", All }, - { "Wep", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, - { "Wifes", All & ~ApplePlatform.TVOS }, - { "Willl", All & ~ApplePlatform.TVOS }, - { "Wlan", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, - { "Wpa", All & ~ApplePlatform.TVOS }, - { "Writeability", All }, - { "Xattr", ApplePlatform.MacOSX }, - { "Xattrs", ApplePlatform.MacOSX }, - { "Xbgr", All }, - { "Xmp", All }, - { "Xnor", All }, - { "Xrgb", All }, - { "xy", All }, - { "Xyz", All }, - { "Xzy", All }, - { "Yobibits", All }, - { "Yottabits", All }, - { "Yuv", ApplePlatform.MacOSX }, - { "Yuvk", ApplePlatform.MacOSX }, - { "yuvs", All }, - { "yx", All }, - { "Yxz", All }, - { "yy", All }, - { "Yyy", All }, - { "Yzx", All }, - { "Zebibits", All }, - { "Zenkaku", All & ~ApplePlatform.MacOSX }, - { "Zettabits", All }, - { "Zlib", All }, - { "Zxy", All }, - { "Zyx", All }, + { "Usec", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // microsecond abbreviation + { "Ussd", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // cellular signaling code + { "Uterance", All }, // speech synthesis term + { "Utf", All }, // Unicode Transformation Format + { "Uti", All & ~ApplePlatform.TVOS }, // Uniform Type Identifier + { "Varispeed", All }, // audio playback effect + { "Vbr", All }, // variable bitrate + { "Vbv", All }, // video buffering verifier + { "Vergence", All }, // binocular vision term + { "Vnode", All }, // virtual node term + { "Voip", ApplePlatform.MacCatalyst }, // voice over IP + { "Voronoi", All }, // geometry diagram name + { "Vpn", All }, // virtual private network + { "Vtt", All }, // WebVTT subtitle format + { "Waon", All & ~ApplePlatform.TVOS }, // Japanese e-money card + { "Warichu", All }, // Japanese annotation term + { "Warpable", All }, // valid English derivative + { "Wcdma", All }, // cellular standard + { "Wep", ApplePlatform.iOS | ApplePlatform.MacCatalyst }, // Wi-Fi security protocol + { "Wlan", ApplePlatform.MacOSX | ApplePlatform.MacCatalyst }, // wireless LAN acronym + { "Wpa", All & ~ApplePlatform.TVOS }, // Wi-Fi security standard + { "Writeability", All }, // variant of writability + { "Xattr", ApplePlatform.MacOSX }, // extended attribute + { "Xattrs", ApplePlatform.MacOSX }, // plural of xattr + { "Xbgr", All }, // X-blue-green-red format + { "Xmp", All }, // Extensible Metadata Platform + { "Xnor", All }, // logic gate name + { "Xrgb", All }, // X-red-green-blue format + { "xy", All }, // coordinate axis pair + { "Xyz", All }, // axis order abbreviation + { "Xzy", All }, // axis order abbreviation + { "Yobibits", All }, // IEC unit name + { "Yobibytes", All }, // IEC unit name + { "Yottabits", All }, // SI unit name + { "Yuv", ApplePlatform.MacOSX }, // luma/chroma color space + { "Yuvk", ApplePlatform.MacOSX }, // YUV plus black channel + { "yuvs", All }, // packed YUV format + { "yx", All }, // coordinate axis pair + { "Yxz", All }, // axis order abbreviation + { "yy", All }, // coordinate repetition term + { "Yyy", All }, // axis placeholder term + { "Yzx", All }, // axis order abbreviation + { "Zebibits", All }, // IEC unit name + { "Zebibytes", All }, // IEC unit name + { "Zenkaku", All & ~ApplePlatform.MacOSX }, // Japanese full-width text + { "Zettabits", All }, // SI unit name + { "Zlib", All }, // compression library name + { "Zxy", All }, // axis order abbreviation + { "Zyx", All }, // axis order abbreviation }; - // tracks which allowed words were actually seen during TypoTest - HashSet used = new HashSet (); - - bool SkipAllowed (string? typeName, string? methodName, string typo) + // Check if any API name in the assembly contains the given word. + // This is used to avoid false "unnecessary allowed typo" reports caused + // by the spell checker not flagging the word on some machines (the spell + // checker is non-deterministic across machines/OS versions/locales). + bool IsWordInAnyApiName (Type [] types, string word) { - if (allowed.TryGetValue (typo, out var platforms) && platforms.HasFlag (TestRuntime.CurrentPlatform)) { - used.Add (typo); - return true; + foreach (var t in types) { + if (!t.IsPublic || IsObsolete (t)) + continue; + if (t.Name.Contains (word, StringComparison.OrdinalIgnoreCase)) + return true; + foreach (var f in t.GetFields ()) { + if ((!f.IsPublic && !f.IsFamily) || IsObsolete (f)) + continue; + if (f.Name.Contains (word, StringComparison.OrdinalIgnoreCase)) + return true; + } + foreach (var m in t.GetMethods ()) { + if ((!m.IsPublic && !m.IsFamily) || IsObsolete (m)) + continue; + if (m.Name.Contains (word, StringComparison.OrdinalIgnoreCase)) + return true; + } } return false; } @@ -833,6 +873,21 @@ bool IsObsolete (MemberInfo? mi) return true; if (MemberHasObsolete (mi)) return true; + if (MemberHasEditorBrowsableNever (mi)) + return true; + // Property accessors may not have [Obsolete] even if the property does + if (mi is MethodInfo method && method.IsSpecialName && mi.DeclaringType is not null) { + var name = mi.Name; + if (name.StartsWith ("get_", StringComparison.Ordinal) || name.StartsWith ("set_", StringComparison.Ordinal)) { + var propName = name.Substring (4); + foreach (var prop in mi.DeclaringType.GetProperties (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { + if (prop.Name != propName) + continue; + if (prop.GetCustomAttributes (true).Any () || MemberHasObsolete (prop)) + return true; + } + } + } return IsObsolete (mi.DeclaringType); } @@ -869,7 +924,6 @@ void AttributeTypo (Type t, ref int totalErrors) } [Test] - [Ignore ("https://github.com/dotnet/macios/issues/25397")] public virtual void TypoTest () { AssertMatchingOSVersionAndSdkVersion (); @@ -880,82 +934,89 @@ public virtual void TypoTest () using var checker = new SpellChecker (); + // Collect all unique words from public API names (split on uppercase boundaries) + var words = new HashSet (StringComparer.Ordinal); var types = Assembly.GetTypes (); - int totalErrors = 0; foreach (Type t in types) { - if (t.IsPublic) { - if (IsObsolete (t)) - continue; - - string txt = NameCleaner (t.Name); - var typo = GetCachedTypo (checker, txt); - if (typo.Length > 0) { - if (!Skip (t, typo)) { - ReportError ("Typo in TYPE: {0} - {1} ", t.Name, typo); - totalErrors++; - } - } - - var fields = t.GetFields (); - foreach (FieldInfo f in fields) { - if (!f.IsPublic && !f.IsFamily) - continue; - - if (IsObsolete (f)) - continue; + if (!t.IsPublic || IsObsolete (t)) + continue; - txt = NameCleaner (f.Name); - typo = GetCachedTypo (checker, txt); - if (typo.Length > 0) { - if (!Skip (f, typo)) { - ReportError ("Typo in FIELD name: {0} - {1}, Type: {2}", f.Name, typo, t.Name); - totalErrors++; - } - } - } + SplitIntoWords (words, t.Name); - var methods = t.GetMethods (); - foreach (MethodInfo m in methods) { - if (!m.IsPublic && !m.IsFamily) - continue; + foreach (FieldInfo f in t.GetFields ()) { + if ((!f.IsPublic && !f.IsFamily) || IsObsolete (f)) + continue; + SplitIntoWords (words, f.Name); + } - if (IsObsolete (m)) - continue; + foreach (MethodInfo m in t.GetMethods ()) { + if ((!m.IsPublic && !m.IsFamily) || IsObsolete (m)) + continue; + SplitIntoWords (words, m.Name); + } + } - txt = NameCleaner (m.Name); - typo = GetCachedTypo (checker, txt); - if (typo.Length > 0) { - if (!Skip (m, typo)) { - ReportError ("Typo in METHOD name: {0} - {1}, Type: {2}", m.Name, typo, t.Name); - totalErrors++; - } - } -#if false - var parameters = m.GetParameters (); - foreach (ParameterInfo p in parameters) { - txt = NameCleaner (p.Name); - typo = GetCachedTypo (checker, txt); - if (typo.Length > 0) { - ReportError ("Typo in PARAMETER Name: {0} - {1}, Method: {2}, Type: {3}", p.Name, typo, m.Name, t.Name); - totalErrors++; - } - } + // Check each unique word individually with the spell checker + var typos = new HashSet (StringComparer.Ordinal); + foreach (var word in words) { + var checkRange = new NSRange (0, word.Length); +#if MONOMAC + var typoRange = checker.CheckSpelling (word, 0, "en_US", false, 0, out var _); +#else + var typoRange = checker.RangeOfMisspelledWordInString (word, checkRange, checkRange.Location, false, "en_US"); #endif - } - } + if (typoRange.Length > 0) + typos.Add (word.Substring ((int) typoRange.Location, (int) typoRange.Length)); } - // verify that all allowed words for the current platform are still needed + + // Check each typo against allowed list + int totalErrors = 0; var currentPlatform = TestRuntime.CurrentPlatform; - var unused = allowed.Keys + var usedAllowed = new HashSet (); + foreach (var typo in typos) { + if (allowed.TryGetValue (typo, out var platforms) && platforms.HasFlag (currentPlatform)) { + usedAllowed.Add (typo); + continue; + } + ReportError ("Typo: {0}", typo); + totalErrors++; + } + + // Verify that all allowed words for the current platform are still needed + var unusedAllowed = allowed.Keys .Where (w => allowed [w].HasFlag (currentPlatform)) - .Except (used); - foreach (var typo in unused) { + .Except (usedAllowed); + foreach (var typo in unusedAllowed) { + if (IsWordInAnyApiName (types, typo)) + continue; ReportError ($"Unnecessary allowed typo \"{typo}\" is not present in any API name"); totalErrors++; } Assert.That (totalErrors, Is.EqualTo (0), "Typos!"); } + // Split an API name into words on uppercase/digit/symbol boundaries and add to the set + static void SplitIntoWords (HashSet words, string name) + { + int start = -1; + for (int i = 0; i < name.Length; i++) { + char c = name [i]; + if (Char.IsUpper (c)) { + if (start >= 0 && i > start) + words.Add (name.Substring (start, i - start)); + start = i; + } else if (Char.IsDigit (c) || c == '<' || c == '>' || c == '_') { + if (start >= 0 && i > start) + words.Add (name.Substring (start, i - start)); + start = -1; + } else if (start < 0) { + // lowercase char with no word start — skip + } + } + if (start >= 0 && name.Length > start) + words.Add (name.Substring (start)); + } + string? GetMessage (object attribute) { string? message = null; @@ -1023,55 +1084,6 @@ void AttributesMessageTypoRules (MemberInfo mi, string typeName, ref int totalEr } } - Dictionary cached_typoes = new Dictionary (); - string GetCachedTypo (SpellChecker checker, string txt) - { - if (!cached_typoes.TryGetValue (txt, out var rv)) - cached_typoes [txt] = rv = GetTypo (checker, txt); - return rv; - } - - string GetTypo (SpellChecker checker, string txt) - { - var checkRange = new NSRange (0, txt.Length); -#if MONOMAC - var typoRange = checker.CheckSpelling (txt, 0, "en_US", false, 0, out var _); -#else - var typoRange = checker.RangeOfMisspelledWordInString (txt, checkRange, checkRange.Location, false, "en_US"); -#endif - if (typoRange.Length == 0) - return String.Empty; - return txt.Substring ((int) typoRange.Location, (int) typoRange.Length); - } - - static StringBuilder clean = new StringBuilder (); - - static string NameCleaner (string name) - { - clean.Clear (); - foreach (char c in name) { - if (Char.IsUpper (c)) { - clean.Append (' ').Append (c); - continue; - } - if (Char.IsDigit (c)) { - clean.Append (' '); - continue; - } - switch (c) { - case '<': - case '>': - case '_': - clean.Append (' '); - break; - default: - clean.Append (c); - break; - } - } - return clean.ToString (); - } - bool CheckLibrary (string? lib) { #if MONOMAC diff --git a/tests/monotouch-test/CoreGraphics/PdfTagTypeTest.cs b/tests/monotouch-test/CoreGraphics/PdfTagTypeTest.cs index da9fdecbb354..078d1c45debe 100644 --- a/tests/monotouch-test/CoreGraphics/PdfTagTypeTest.cs +++ b/tests/monotouch-test/CoreGraphics/PdfTagTypeTest.cs @@ -60,7 +60,7 @@ public void EnumExtension () Assert.That (CGPdfTagType.RubyPunctuation.GetName (), Is.EqualTo ("/RP"), "RubyPunctuation"); Assert.That (CGPdfTagType.Warichu.GetName (), Is.EqualTo ("/Warichu"), "Warichu"); Assert.That (CGPdfTagType.WarichuText.GetName (), Is.EqualTo ("/WT"), "WarichuText"); - Assert.That (CGPdfTagType.WarichuPunctiation.GetName (), Is.EqualTo ("/WP"), "WarichuPunctiation"); + Assert.That (CGPdfTagType.WarichuPunctuation.GetName (), Is.EqualTo ("/WP"), "WarichuPunctuation"); Assert.That (CGPdfTagType.Figure.GetName (), Is.EqualTo ("/Figure"), "Figure"); Assert.That (CGPdfTagType.Formula.GetName (), Is.EqualTo ("/Formula"), "Formula"); Assert.That (CGPdfTagType.Form.GetName (), Is.EqualTo ("/Form"), "Form"); From a590a84ce873630d88783be2942b70b73f08e166 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Jun 2026 16:33:03 +0200 Subject: [PATCH 14/27] [mtouch] Guard RunRegistrar calls with platform ifdef checks (#25678) The no-xcode-build target (used on Linux) was unconditionally adding registrar generation targets for all platforms. When some platforms are disabled (e.g. only iOS is enabled), the prerequisite DLLs don't exist and make fails with 'No rule to make target'. Wrap each group of RunRegistrar eval calls with the corresponding INCLUDE_IOS/INCLUDE_TVOS/INCLUDE_MACCATALYST/INCLUDE_MAC guards so only enabled platforms have their registrar rules defined. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/mtouch/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/mtouch/Makefile b/tools/mtouch/Makefile index d48cc4bd1eaf..3a0d86f73ba1 100644 --- a/tools/mtouch/Makefile +++ b/tools/mtouch/Makefile @@ -51,16 +51,24 @@ define RunRegistrar no-xcode-build:: .libs/Microsoft.$(9).registrar.$(10).m .libs/Microsoft.$(9).registrar.$(10).h no-xcode-build:: .libs/Microsoft.$(9).registrar.coreclr.$(10).m .libs/Microsoft.$(9).registrar.$(10).h endef +ifdef INCLUDE_IOS $(eval $(call RunRegistrar,ios,x86_64,64,$(IOS_SDK_VERSION),iOS,$(iossimulator-x64_CFLAGS),,,iOS,iossimulator-x64)) $(eval $(call RunRegistrar,ios,arm64,64,$(IOS_SDK_VERSION),iOS,$(iossimulator-arm64_CFLAGS),,,iOS,iossimulator-arm64)) $(eval $(call RunRegistrar,ios,arm64,64,$(IOS_SDK_VERSION),iOS,$(ios-arm64_CFLAGS),,,iOS,ios-arm64)) +endif +ifdef INCLUDE_TVOS $(eval $(call RunRegistrar,tvos,x86_64,64,$(TVOS_SDK_VERSION),TVOS,$(tvossimulator-x64_CFLAGS),,,tvOS,tvossimulator-x64)) $(eval $(call RunRegistrar,tvos,arm64,64,$(TVOS_SDK_VERSION),TVOS,$(tvossimulator-arm64_CFLAGS),,,tvOS,tvossimulator-arm64)) $(eval $(call RunRegistrar,tvos,arm64,64,$(TVOS_SDK_VERSION),TVOS,$(tvos-arm64_CFLAGS),,,tvOS,tvos-arm64)) +endif +ifdef INCLUDE_MACCATALYST $(eval $(call RunRegistrar,maccatalyst,x86_64,64,$(MACCATALYST_SDK_VERSION),MacCatalyst,$(maccatalyst-x64_CFLAGS),,,MacCatalyst,maccatalyst-x64)) $(eval $(call RunRegistrar,maccatalyst,arm64,64,$(MACCATALYST_SDK_VERSION),MacCatalyst,$(maccatalyst-arm64_CFLAGS),,,MacCatalyst,maccatalyst-arm64)) +endif +ifdef INCLUDE_MAC $(eval $(call RunRegistrar,macos,arm64,64,$(MACOS_SDK_VERSION),macOS,$(osx-arm64_CFLAGS),,,macOS,osx-arm64)) $(eval $(call RunRegistrar,macos,x86_64,64,$(MACOS_SDK_VERSION),macOS,$(osx-x64_CFLAGS),,,macOS,osx-x64)) +endif TARGETS_DOTNET = \ $(foreach platform,$(DOTNET_PLATFORMS_MTOUCH),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(DOTNET_DESTDIR)/$($(rid)_NUGET_RUNTIME_NAME)/runtimes/$(rid)/native/Microsoft.$(platform).registrar.a)) \ From c603497270edc2c13a8024c03c0056b85600667a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 11:43:43 +0100 Subject: [PATCH 15/27] [tools] Move RegistrarMode to its own file. --- tools/common/Application.cs | 9 --------- tools/common/RegistrarMode.cs | 13 +++++++++++++ tools/dotnet-linker/dotnet-linker.csproj | 3 +++ tools/mtouch/mtouch.csproj | 3 +++ 4 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 tools/common/RegistrarMode.cs diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 5405b8eb0244..9d2115d4ea3e 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -59,15 +59,6 @@ public enum RegistrarOptions { Trace = 1, } - public enum RegistrarMode { - Default, - Dynamic, - PartialStatic, - Static, - ManagedStatic, - TrimmableStatic, - } - public partial class Application : IToolLog { public Cache? Cache; public string AppDirectory = "."; diff --git a/tools/common/RegistrarMode.cs b/tools/common/RegistrarMode.cs new file mode 100644 index 000000000000..9876623b0c9b --- /dev/null +++ b/tools/common/RegistrarMode.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Bundler; + +public enum RegistrarMode { + Default, + Dynamic, + PartialStatic, + Static, + ManagedStatic, + TrimmableStatic, +} diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index c3ff1f126681..456e1905a013 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -110,6 +110,9 @@ external/tools/common/CSToObjCMap.cs + + tools\common\RegistrarMode.cs + external/tools/common/Rewriter.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index d9ba0a1ea76e..88f535184fca 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -39,6 +39,9 @@ external/tools/common/cache.cs + + tools\common\RegistrarMode.cs + external/tools/linker/MonoTouch.Tuner/Extensions.cs From d1d410aa73dabeaf85a1b1de1b9b8316dfdfdf4f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 18:12:34 +0100 Subject: [PATCH 16/27] [tools] Move NormalizedStringComparer into its own file. --- tools/common/Assembly.cs | 27 ------------------- tools/common/NormalizedStringComparer.cs | 33 ++++++++++++++++++++++++ tools/dotnet-linker/dotnet-linker.csproj | 3 +++ tools/mtouch/mtouch.csproj | 3 +++ 4 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 tools/common/NormalizedStringComparer.cs diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index 6ad8d9141958..8e100d6d9609 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -600,33 +600,6 @@ public bool IsAOTCompiled { } } - public sealed class NormalizedStringComparer : IEqualityComparer { - public static readonly NormalizedStringComparer OrdinalIgnoreCase = new NormalizedStringComparer (StringComparer.OrdinalIgnoreCase); - - StringComparer comparer; - - public NormalizedStringComparer (StringComparer comparer) - { - this.comparer = comparer; - } - - public bool Equals (string? x, string? y) - { - // From what I gather it doesn't matter which normalization form - // is used, but I chose Form D because HFS normalizes to Form D. - if (x is not null) - x = x.Normalize (System.Text.NormalizationForm.FormD); - if (y is not null) - y = y.Normalize (System.Text.NormalizationForm.FormD); - return comparer.Equals (x, y); - } - - public int GetHashCode (string? obj) - { - return comparer.GetHashCode (obj?.Normalize (System.Text.NormalizationForm.FormD) ?? ""); - } - } - public class AssemblyCollection : IEnumerable { Dictionary HashedAssemblies = new Dictionary (NormalizedStringComparer.OrdinalIgnoreCase); diff --git a/tools/common/NormalizedStringComparer.cs b/tools/common/NormalizedStringComparer.cs new file mode 100644 index 000000000000..c6e1d884ffa5 --- /dev/null +++ b/tools/common/NormalizedStringComparer.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Bundler; + +#nullable enable + +public sealed class NormalizedStringComparer : IEqualityComparer { + public static readonly NormalizedStringComparer OrdinalIgnoreCase = new NormalizedStringComparer (StringComparer.OrdinalIgnoreCase); + + StringComparer comparer; + + public NormalizedStringComparer (StringComparer comparer) + { + this.comparer = comparer; + } + + public bool Equals (string? x, string? y) + { + // From what I gather it doesn't matter which normalization form + // is used, but I chose Form D because HFS normalizes to Form D. + if (x is not null) + x = x.Normalize (System.Text.NormalizationForm.FormD); + if (y is not null) + y = y.Normalize (System.Text.NormalizationForm.FormD); + return comparer.Equals (x, y); + } + + public int GetHashCode (string obj) + { + return comparer.GetHashCode (obj.Normalize (System.Text.NormalizationForm.FormD)); + } +} diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 456e1905a013..8c9fec13a15f 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -77,6 +77,9 @@ external/tools/common/LinkMode.cs + + tools\common\NormalizedStringComparer.cs + external/tools/common/SdkVersions.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index 88f535184fca..e0ebeca5ccf1 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -33,6 +33,9 @@ external/tools/common/AssemblyBuildTarget.cs + + tools\common\NormalizedStringComparer.cs + external/tools/common/PListExtensions.cs From 88539265f6e07388b439fe1f13661a5ded979a84 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 11:26:27 +0200 Subject: [PATCH 17/27] [xharness] Run the new assembly processing tests. --- tests/xharness/Harness.cs | 7 +++++++ tests/xharness/TestLabel.cs | 3 ++- tools/devops/automation/templates/common/configure.yml | 5 +++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/xharness/Harness.cs b/tests/xharness/Harness.cs index 33cf6b5ffb93..a8490b532f08 100644 --- a/tests/xharness/Harness.cs +++ b/tests/xharness/Harness.cs @@ -355,6 +355,13 @@ void PopulateUnitTestProjects () Timeout = (TimeSpan?) TimeSpan.FromMinutes (10), Filter = "", }, + new { + Label = TestLabel.AssemblyProcessing, + ProjectPath = Path.GetFullPath (Path.Combine (HarnessConfiguration.RootDirectory, "assembly-preparer", "assembly-preparer-tests.csproj")), + Name = "Assembly processing tests", + Timeout = (TimeSpan?) TimeSpan.FromMinutes (10), + Filter = "", + }, new { Label = TestLabel.Generator, ProjectPath = Path.GetFullPath (Path.Combine (HarnessConfiguration.RootDirectory, "bgen", "bgen-tests.csproj")), diff --git a/tests/xharness/TestLabel.cs b/tests/xharness/TestLabel.cs index 9085a1eb815f..0bc5e9d78910 100644 --- a/tests/xharness/TestLabel.cs +++ b/tests/xharness/TestLabel.cs @@ -44,7 +44,8 @@ public enum TestLabel : Int64 { Generator = 1 << 11, [Label ("interdependent-binding-projects")] InterdependentBindingProjects = 1 << 12, - // 1 << 13 is unused + [Label ("assembly-processing")] + AssemblyProcessing = 1 << 13, [Label ("introspection")] Introspection = 1 << 14, [Label ("linker")] diff --git a/tools/devops/automation/templates/common/configure.yml b/tools/devops/automation/templates/common/configure.yml index a7dd354368e7..377d7d4cde15 100644 --- a/tools/devops/automation/templates/common/configure.yml +++ b/tools/devops/automation/templates/common/configure.yml @@ -31,6 +31,11 @@ parameters: testPrefix: 'windows_integration', testStage: 'windows_integration' }, + { + label: assembly-processing, + splitByPlatforms: false, + testPrefix: 'simulator_tests', + }, { label: cecil, splitByPlatforms: false, From 93ba28286cea58b8273abf357f4065acf76a9db8 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 20:06:54 +0100 Subject: [PATCH 18/27] [tools] Remove some dead code --- tools/common/Application.cs | 1 - tools/dotnet-linker/LinkerConfiguration.cs | 5 ----- 2 files changed, 6 deletions(-) diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 9d2115d4ea3e..03f0d58bc48c 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -948,7 +948,6 @@ public void GetAotArguments (string filename, Abi abi, string outputDir, string bool enable_debug_symbols = app.PackageManagedDebugSymbols; bool interp = app.IsInterpreted (Assembly.GetIdentity (filename)) && !(isDedupAssembly.HasValue && isDedupAssembly.Value); bool interp_full = !interp && app.UseInterpreter; - bool is32bit = (abi & Abi.Arch32Mask) > 0; string arch = abi.AsArchString (); processArguments.Add ("--debug"); diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index e9375514f12c..c0152c375d5b 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -126,7 +126,6 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application = new Application (this); CompilerFlags = new CompilerFlags (Application); - var use_llvm = false; var lines = File.ReadAllLines (linker_file); var significantLines = new List (); // This is the input the cache uses to verify if the cache is still valid for (var i = 0; i < lines.Length; i++) { @@ -439,10 +438,6 @@ public static LinkerConfiguration GetInstance (LinkContext context) ErrorHelper.Show (Application, messages); } - if (use_llvm) { - Abi |= Abi.LLVM; - } - Application.CreateCache (significantLines.ToArray ()); if (Application.Cache is not null) Application.Cache.SetLocation (Application, CacheDirectory); From e0c4e45fb96095f74d705ebfa1447e1d0e26dae4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:08:12 +0200 Subject: [PATCH 19/27] [tools] Extract OptimizeGeneratedCode into its own class. Extract the static optimization methods from OptimizeGeneratedCodeHandler into a new standalone OptimizeGeneratedCode class. The handler class now only contains the linker-specific scaffolding and delegates to the new class. This makes the optimization logic reusable without depending on the linker's ExceptionalMarkHandler base class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../dotnet-linker/BackingFieldDelayHandler.cs | 6 +- .../OptimizeGeneratedCodeStep.cs | 4 +- tools/dotnet-linker/dotnet-linker.csproj | 3 + tools/linker/CoreOptimizeGeneratedCode.cs | 1207 +--------------- tools/linker/OptimizeGeneratedCode.cs | 1214 +++++++++++++++++ tools/linker/RegistrarRemovalTrackingStep.cs | 2 +- 6 files changed, 1225 insertions(+), 1211 deletions(-) create mode 100644 tools/linker/OptimizeGeneratedCode.cs diff --git a/tools/dotnet-linker/BackingFieldDelayHandler.cs b/tools/dotnet-linker/BackingFieldDelayHandler.cs index 9a61786f7c0d..970f5d4893d6 100644 --- a/tools/dotnet-linker/BackingFieldDelayHandler.cs +++ b/tools/dotnet-linker/BackingFieldDelayHandler.cs @@ -84,9 +84,9 @@ public static void ReapplyDisposedFields (DerivedLinkContext context, string ope var store_field = ins; var load_null = ins.Previous; var load_this = ins.Previous.Previous; - if (OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, store_field, operation, Code.Stfld) && - OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, load_null, operation, Code.Ldnull) && - OptimizeGeneratedCodeHandler.ValidateInstruction (app, method, load_this, operation, Code.Ldarg_0)) { + if (OptimizeGeneratedCode.ValidateInstruction (app, method, store_field, operation, Code.Stfld) && + OptimizeGeneratedCode.ValidateInstruction (app, method, load_null, operation, Code.Ldnull) && + OptimizeGeneratedCode.ValidateInstruction (app, method, load_this, operation, Code.Ldarg_0)) { store_field.OpCode = OpCodes.Nop; load_null.OpCode = OpCodes.Nop; load_this.OpCode = OpCodes.Nop; diff --git a/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs b/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs index d785f16c94af..a7b57042609f 100644 --- a/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs +++ b/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs @@ -14,7 +14,7 @@ public class OptimizeGeneratedCodeStep : AssemblyModifierStep { protected override bool IsActiveFor (AssemblyDefinition assembly) { - return OptimizeGeneratedCodeHandler.IsActiveFor (assembly, Configuration.Profile, DerivedLinkContext.Annotations); + return OptimizeGeneratedCode.IsActiveFor (assembly, Configuration.Profile, DerivedLinkContext.Annotations); } protected override bool ProcessType (TypeDefinition type) @@ -32,7 +32,7 @@ protected override bool ProcessMethod (MethodDefinition method) Device = App.IsDeviceBuild, }; } - return OptimizeGeneratedCodeHandler.OptimizeMethod (data, method); + return OptimizeGeneratedCode.OptimizeMethod (data, method); } } } diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 8c9fec13a15f..9a5d2966bc7b 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -140,6 +140,9 @@ external/tools/linker/CoreTypeMapStep.cs + + external/tools/linker/OptimizeGeneratedCode.cs + external/src/ObjCRuntime/Registrar.cs diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index c33d231aeb66..de6fabbec099 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -1,18 +1,8 @@ // Copyright 2012-2013, 2016 Xamarin Inc. All rights reserved. -using System; -using System.Collections.Generic; -using System.Linq; - -using ObjCRuntime; using Mono.Cecil; -using Mono.Cecil.Cil; using Mono.Linker; using Mono.Linker.Steps; -using Mono.Tuner; -using MonoTouch.Tuner; - -using Xamarin.Bundler; #nullable enable @@ -31,504 +21,7 @@ public override void Initialize (LinkContext context, MarkContext markContext) bool IsActiveFor (AssemblyDefinition assembly) { - return IsActiveFor (assembly, Profile, Annotations); - } - - public static bool IsActiveFor (AssemblyDefinition assembly, Profile profile, AnnotationStore annotations) - { - // Unless an assembly is or references our platform assembly, then it won't have anything we need to register - if (!profile.IsOrReferencesProductAssembly (assembly)) - return false; - - // We only care about assemblies that are being linked. - if (annotations.GetAction (assembly) != AssemblyAction.Link) - return false; - - return true; - } - - // [GeneratedCode] is not enough - e.g. it's used for anonymous delegates even if the - // code itself is not tool/compiler generated - static protected bool IsExport (ICustomAttributeProvider provider) - { - return provider.HasCustomAttribute (Namespaces.Foundation, "ExportAttribute"); - } - - // less risky to nop-ify if branches are pointing to this instruction - static protected void Nop (Instruction ins) - { - // Leave 'leave' instructions in place, they might be required for the resulting IL to be correct/verifiable. - switch (ins.OpCode.Code) { - case Code.Leave: - case Code.Leave_S: - return; - } - ins.OpCode = OpCodes.Nop; - ins.Operand = null; - } - - internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, Code expected) - { - return ValidateInstruction (data.App, caller, ins, operation, expected); - } - - internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, Code expected) - { - if (ins.OpCode.Code != expected) { - log.Log (1, "Could not {0} in {1} at offset {2}, expected {3} got {4}", operation, caller, ins.Offset, expected, ins); - return false; - } - - return true; - } - - internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, params Code [] expected) - { - foreach (var code in expected) { - if (ins.OpCode.Code == code) - return true; - } - - data.App.Log (1, "Could not {0} in {1} at offset {2}, expected any of [{3}] got {4}", operation, caller, ins.Offset, string.Join (", ", expected), ins); - return false; - } - - static int? GetConstantValue (OptimizeGeneratedCodeData data, Instruction? ins) - { - if (ins is null) - return null; - - switch (ins.OpCode.Code) { - case Code.Ldc_I4_0: - return 0; - case Code.Ldc_I4_1: - return 1; - case Code.Ldc_I4_2: - return 2; - case Code.Ldc_I4_3: - return 3; - case Code.Ldc_I4_4: - return 4; - case Code.Ldc_I4_5: - return 5; - case Code.Ldc_I4_6: - return 6; - case Code.Ldc_I4_7: - return 7; - case Code.Ldc_I4_8: - return 8; - case Code.Ldc_I4: - case Code.Ldc_I4_S: - if (ins.Operand is int) - return (int) ins.Operand; - if (ins.Operand is sbyte) - return (sbyte) ins.Operand; - return null; -#if DEBUG - case Code.Isinst: // We might be able to calculate a constant value here in the future - case Code.Ldloc: - case Code.Ldloca: - case Code.Ldloc_0: - case Code.Ldloc_1: - case Code.Ldloc_2: - case Code.Ldloc_3: - case Code.Ldloc_S: - case Code.Ldloca_S: - case Code.Ldarg: - case Code.Ldarg_0: - case Code.Ldarg_1: - case Code.Ldarg_2: - case Code.Ldarg_3: - case Code.Ldarg_S: - case Code.Ldarga: - case Code.Call: - case Code.Calli: - case Code.Callvirt: - case Code.Box: - case Code.Ldsfld: - case Code.Dup: // You might think we could get the constant of the previous instruction, but this instruction might be the target of a branch, in which case the question becomes: which instruction was the previous instruction? And that's not a question easily answered without a much more thorough analysis of the code. - case Code.Ldlen: - case Code.Ldind_U1: - case Code.Ldind_U2: - case Code.Ldind_U4: - case Code.Ldind_Ref: - case Code.Conv_I: - case Code.Conv_I1: - case Code.Conv_I2: - case Code.Conv_I4: - case Code.Conv_U: - case Code.Sizeof: - case Code.Ldfld: - case Code.Ldflda: - return null; // just to not hit the CWL below -#endif - default: -#if DEBUG - data.App.Log (9, "Unknown conditional instruction: {0}", ins); -#endif - return null; - } - } - - static bool MarkInstructions (OptimizeGeneratedCodeData data, MethodDefinition method, Mono.Collections.Generic.Collection instructions, bool [] reachable, int start, int end) - { - if (reachable [start]) - return true; // We've already marked this section of code - - for (int i = start; i < end; i++) { - reachable [i] = true; - - var ins = instructions [i]; - switch (ins.OpCode.FlowControl) { - case FlowControl.Branch: - // Unconditional branch, we continue marking from the instruction that we branch to. - var br_target = (Instruction) ins.Operand; - return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (br_target), instructions.Count); - case FlowControl.Cond_Branch: - // Conditional instruction, we need to check if we can calculate a constant value for the condition - var cond_target = ins.Operand as Instruction; - bool? branch = null; // did we get a constant value for the condition, and if so, did we branch or not? - var cond_instruction_count = 0; // The number of instructions that compose the condition - - if (ins.OpCode.Code == Code.Switch) { - // Treat all branches of the switch statement as reachable. - // FIXME: calculate the potential constant branch (currently there are no optimizable methods where the switch condition is constant, so this is not needed for now) - var targets = ins.Operand as Instruction []; - if (targets is null) { - data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); - return false; - } - foreach (var target in targets) { - // not constant, continue marking both this code sequence and the branched sequence - if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (target), end)) - return false; - } - return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (ins.Next), end); - } - - if (cond_target is null) { - data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); - return false; - } - - switch (ins.OpCode.Code) { - case Code.Brtrue: - case Code.Brtrue_S: { - var v = GetConstantValue (data, ins.Previous); - if (v.HasValue) - branch = v.Value != 0; - cond_instruction_count = 2; - break; - } - case Code.Brfalse: - case Code.Brfalse_S: { - var v = GetConstantValue (data, ins.Previous); - if (v.HasValue) - branch = v.Value == 0; - cond_instruction_count = 2; - break; - } - case Code.Beq: - case Code.Beq_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value == x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Bne_Un: - case Code.Bne_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value != x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Ble: - case Code.Ble_S: - case Code.Ble_Un: - case Code.Ble_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value <= x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Blt: - case Code.Blt_S: - case Code.Blt_Un: - case Code.Blt_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value < x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Bge: - case Code.Bge_S: - case Code.Bge_Un: - case Code.Bge_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value >= x2.Value; - cond_instruction_count = 3; - break; - } - case Code.Bgt: - case Code.Bgt_S: - case Code.Bgt_Un: - case Code.Bgt_Un_S: { - var x1 = GetConstantValue (data, ins.Previous?.Previous); - var x2 = GetConstantValue (data, ins.Previous); - if (x1.HasValue && x2.HasValue) - branch = x1.Value > x2.Value; - cond_instruction_count = 3; - break; - } - default: - data.App.Log ("Can't optimize {0} because of unknown branch instruction: {1}", method, ins); - break; - } - - if (branch.HasValue) { - // Make sure nothing else in the method branches into the middle of our supposedly constant condition, - // bypassing our constant calculation. Note that it's not a bad to branch to the _first_ instruction in - // the sequence (thus the +2 here), just into the middle of it. - if (AnyBranchTo (data, instructions, instructions [i - cond_instruction_count + 2], ins)) - branch = null; - } - - if (!branch.HasValue) { - // not constant, continue marking both this code sequence and the branched sequence - if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end)) - return false; - } else { - // we can remove the branch (and the code that loads the condition), so we mark those instructions as dead. - for (int a = 0; a < cond_instruction_count; a++) - reachable [i - a] = false; - - // Now continue marking according to whether we branched or not - if (branch.Value) { - // branch always taken - return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end); - } else { - // branch never taken - // continue looping - } - } - break; - case FlowControl.Call: - case FlowControl.Next: - // Nothing special, continue marking - break; - case FlowControl.Return: - case FlowControl.Throw: - // Control flow returns here, so stop marking - return true; - case FlowControl.Break: - case FlowControl.Meta: - case FlowControl.Phi: - default: - data.App.Log (4, "Can't optimize {0} because of unknown flow control for: {1}", method, ins); - return false; - } - } - - return true; - } - - // Check if there are any branches in the instructions that branch to anywhere between 'first' and 'last' instructions (both inclusive). - static bool AnyBranchTo (OptimizeGeneratedCodeData data, Mono.Collections.Generic.Collection instructions, Instruction first, Instruction last) - { - if (first.Offset > last.Offset) { - data.App.Log ($"Broken assumption: {first} is after {last}"); - return true; // This is the safe thing to do, since it will prevent inlining - } - - for (int i = 0; i < instructions.Count; i++) { - var ins = instructions [i]; - switch (ins.OpCode.FlowControl) { - case FlowControl.Branch: - case FlowControl.Cond_Branch: - var target = ins.Operand as Instruction; - if (target is not null && target.Offset >= first.Offset && target.Offset <= last.Offset) - return true; - break; - } - } - - return false; - } - - static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition caller) - { - var modified = false; - if (data.Optimizations.DeadCodeElimination != true) - return modified; - - var instructions = caller.Body.Instructions; - var reachable = new bool [instructions.Count]; - - // We walk the instructions in the method, starting with the first instruction, - // marking all reachable instructions. Any non-reachable instructions at the end - // can be removed. - - if (!MarkInstructions (data, caller, instructions, reachable, 0, instructions.Count)) - return modified; - - // Handle exception handlers specially, they do not follow normal code flow. - bool []? reachableExceptionHandlers = null; - if (caller.Body.HasExceptionHandlers) { - reachableExceptionHandlers = new bool [caller.Body.ExceptionHandlers.Count]; - for (var e = 0; e < reachableExceptionHandlers.Length; e++) { - var eh = caller.Body.ExceptionHandlers [e]; - - // First check if the protected region is reachable - var startI = instructions.IndexOf (eh.TryStart); - var endI = instructions.IndexOf (eh.TryEnd); - for (int i = startI; i < endI; i++) { - if (reachable [i]) { - reachableExceptionHandlers [e] = true; - break; - } - } - // The protected code isn't reachable, none of the handlers will be executed - if (!reachableExceptionHandlers [e]) - continue; - - switch (eh.HandlerType) { - case ExceptionHandlerType.Catch: - // We don't need catch handlers the reachable instructions are all nops - var allNops = true; - for (int i = startI; i < endI; i++) { - if (instructions [i].OpCode.Code != Code.Nop) { - allNops = false; - break; - } - } - if (!allNops) { - if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) - return modified; - } - break; - case ExceptionHandlerType.Finally: - // finally clauses are always executed, even if the protected region is empty - if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) - return modified; - break; - case ExceptionHandlerType.Fault: - case ExceptionHandlerType.Filter: - // FIXME: and until fixed, exit gracefully without doing anything - data.App.Log (4, "Unhandled exception handler: {0}, skipping dead code elimination for {1}", eh.HandlerType, caller); - return modified; - } - } - } - - if (Array.IndexOf (reachable, false) == -1) - return modified; // entire method is reachable - - // Kill branch instructions when there are only dead instructions between the branch instruction and the target of the branch - for (int i = 0; i < instructions.Count; i++) { - if (!reachable [i]) - continue; - - var ins = instructions [i]; - if (ins.OpCode.Code != Code.Br && ins.OpCode.Code != Code.Br_S) - continue; - var target = ins.Operand as Instruction; - if (target is null) - continue; - if (target.Offset < ins.Offset) - continue; // backwards branch, keep those - - var start = i + 1; - var end = instructions.IndexOf (target); - var any_reachable = false; - for (int k = start; k < end; k++) { - if (reachable [k]) { - any_reachable = true; - break; - } - } - if (any_reachable) - continue; - - // The branch instruction just branches over unreachable instructions, so it can be considered unreachable too. - reachable [i] = false; - } - - // Check if there are unreachable instructions at the end. - var last_reachable = Array.LastIndexOf (reachable, true); - if (last_reachable < reachable.Length - 1) { - // There are unreachable instructions at the end. - // We must verify that there are no branches into these instructions. - // In theory there shouldn't be any (if there are branches into these instructions, - // they're reachable), but let's still verify just in case. - var last_reachable_offset = instructions [last_reachable].Offset; - for (int i = 0; i < last_reachable; i++) { - if (!reachable [i]) - continue; // Unreachable instructions don't branch anywhere, because they'll be removed. - var ins = instructions [i]; - switch (ins.OpCode.FlowControl) { - case FlowControl.Break: - case FlowControl.Cond_Branch: - var target = (Instruction) ins.Operand; - if (target.Offset > last_reachable_offset) { - data.App.Log (4, "Can't optimize {0} because of branching beyond last instruction alive: {1}", caller, ins); - return modified; - } - break; - } - } - } -#if false - data.App.Log ($"{caller.FullName}:"); - for (int i = 0; i < reachable.Length; i++) { - data.App.Log ($"{(reachable [i] ? " " : "- ")} {instructions [i]}"); - if (!reachable [i]) - Nop (instructions [i]); - } - data.App.Log (""); -#endif - - // Exterminate, exterminate, exterminate - for (int i = 0; i < reachable.Length; i++) { - if (!reachable [i]) { - Nop (instructions [i]); - modified = true; - } - } - - // Remove exception handlers - if (reachableExceptionHandlers is not null) { - for (int i = reachableExceptionHandlers.Length - 1; i >= 0; i--) { - if (reachableExceptionHandlers [i]) - continue; - caller.Body.ExceptionHandlers.RemoveAt (i); - modified = true; - } - } - - // Remove unreachable instructions (nops) at the end, because the last instruction can only be ret/throw/backwards branch. - for (int i = last_reachable + 1; i < reachable.Length; i++) { - instructions.RemoveAt (last_reachable + 1); - modified = true; - } - - return modified; - } - - static bool GetIsExtensionType (TypeDefinition type) - { - // if 'type' inherits from NSObject inside an assembly that has [GeneratedCode] - // or for static types used for optional members (using extensions methods), they can be optimized too - return type.IsSealed && type.IsAbstract && type.Name.EndsWith ("_Extensions", StringComparison.Ordinal); + return OptimizeGeneratedCode.IsActiveFor (assembly, Profile, Annotations); } protected override void Process (MethodDefinition method) @@ -544,703 +37,7 @@ protected override void Process (MethodDefinition method) Device = LinkContext.App.IsDeviceBuild, }; } - OptimizeMethod (data, method); - } - - public static bool OptimizeMethod (OptimizeGeneratedCodeData data, MethodDefinition method) - { - var modified = false; - - if (!method.HasBody) - return modified; - - if (method.IsBindingImplOptimizableCode (data.LinkContext)) { - // We optimize all methods that have the [BindingImpl (BindingImplAttributes.Optimizable)] attribute. - } else if (method.IsGeneratedCode (data.LinkContext) && ( - GetIsExtensionType (method.DeclaringType) - || IsExport (method))) { - // We optimize methods that have the [GeneratedCodeAttribute] and is either an extension type or an exported method - } else { - // but it would be too risky to apply on user-generated code - return modified; - } - - if (data.Optimizations.InlineIsARM64CallingConvention == true && data.InlineIsArm64CallingConvention.HasValue && method.Name == "GetIsARM64CallingConvention" && method.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) { - // Rewrite to return the constant value - var instr = method.Body.Instructions; - instr.Clear (); - instr.Add (Instruction.Create (data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); - instr.Add (Instruction.Create (OpCodes.Ret)); - return true; // nothing else to do here. - } - - if (ProcessProtocolInterfaceStaticConstructor (data, method)) - return true; - - var instructions = method.Body.Instructions; - for (int i = 0; i < instructions.Count; i++) { - var ins = instructions [i]; - switch (ins.OpCode.Code) { - case Code.Newobj: - case Code.Call: - modified |= ProcessCalls (data, method, ins, out var instructionsAddedOrRemoved); - i += instructionsAddedOrRemoved; - break; - case Code.Ldsfld: - modified |= ProcessLoadStaticField (data, method, ins); - break; - } - } - - modified |= EliminateDeadCode (data, method); - return modified; - } - - // Returns the number of instructions added (or removed) in the 'instructionsAddedOrRemoved' parameter. - // Returns true if any modifications were done. - static bool ProcessCalls (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) - { - var modified = false; - instructionsAddedOrRemoved = 0; - var mr = ins.Operand as MethodReference; - switch (mr?.Name) { - case "EnsureUIThread": - modified |= ProcessEnsureUIThread (data, caller, ins); - break; - case "get_IsDirectBinding": - modified |= ProcessIsDirectBinding (data, caller, ins); - break; - case "SetupBlock": - case "SetupBlockUnsafe": - modified |= ProcessSetupBlock (data, caller, ins, out instructionsAddedOrRemoved); - break; - case ".ctor": - if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) - break; - return ProcessBlockLiteralConstructor (data, caller, ins, out instructionsAddedOrRemoved); - } - - return modified; - } - - static bool ProcessLoadStaticField (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - var modified = false; - var fr = ins.Operand as FieldReference; - switch (fr?.Name) { - case "IsARM64CallingConvention": - modified |= ProcessIsARM64CallingConvention (data, caller, ins); - break; - case "Arch": - // https://app.asana.com/0/77259014252/77812690163 - modified |= ProcessRuntimeArch (data, caller, ins); - break; - } - return modified; - } - - static bool ProcessEnsureUIThread (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - if (data.Optimizations.RemoveUIThreadChecks != true) - return false; - - // Verify we're checking the right get_EnsureUIThread call - var declaringTypeNamespace = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? Namespaces.AppKit : Namespaces.UIKit; - var declaringTypeName = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? "NSApplication" : "UIApplication"; - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (declaringTypeNamespace, declaringTypeName)) - return false; - - // Verify a few assumptions before doing anything - const string operation = "remove calls to [NS|UI]Application::EnsureUIThread"; - if (!ValidateInstruction (data, caller, ins, operation, Code.Call)) - return false; - - // This is simple: just remove the call - Nop (ins); // call void UIKit.UIApplication::EnsureUIThread() - return true; - } - - static bool? IsDirectBindingConstant (OptimizeGeneratedCodeData data, TypeDefinition type) - { - return type.IsNSObject (data.LinkContext) ? type.GetIsDirectBindingConstant (data.LinkContext) : null; - } - - static bool ProcessIsDirectBinding (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - const string operation = "inline IsDirectBinding"; - - if (data.Optimizations.InlineIsDirectBinding != true) - return false; - - bool? isdirectbinding_constant = IsDirectBindingConstant (data, caller.DeclaringType); - - // If we don't know the constant isdirectbinding value, then we can't inline anything - if (!isdirectbinding_constant.HasValue) - return false; - - // Verify we're checking the right get_IsDirectBinding call - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (Namespaces.Foundation, "NSObject")) - return false; - - // Verify a few assumptions before doing anything - if (!ValidateInstruction (data, caller, ins.Previous, operation, Code.Ldarg_0)) - return false; - - if (!ValidateInstruction (data, caller, ins, operation, Code.Call)) - return false; - - // Clearing the branch succeeded, so clear the condition too - // ldarg.0 - Nop (ins.Previous); - // call System.Boolean Foundation.NSObject::get_IsDirectBinding() - ins.OpCode = isdirectbinding_constant.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; - ins.Operand = null; - return true; - } - - static bool ProcessSetupBlock (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) - { - instructionsAddedOrRemoved = 0; - if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) - return false; - - // This will optimize calls to SetupBlock and SetupBlockUnsafe by calculating the signature for the block - // (which both SetupBlock and SetupBlockUnsafe do), and then rewrite the code to call SetupBlockImpl instead - // (which takes the block signature as an argument instead of calculating it). This is required to - // remove the dynamic registrar, because calculating the block signature is done in the dynamic registrar. - // - // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) - return false; - - if (caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) { - switch (caller.Name) { - case "GetBlockForDelegate": - case "CreateBlockForDelegate": - // These methods contain a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. - return false; - } - } - - string? signature = null; - try { - // We need to figure out the type of the first argument to the call to SetupBlock[Impl]. - // - // Example sequence: - // - // ldsfld ObjCRuntime.Trampolines/DJSContextExceptionHandler ObjCRuntime.Trampolines/SDJSContextExceptionHandler::Handler - // ldarg.1 - // call System.Void ObjCRuntime.BlockLiteral::SetupBlockUnsafe(System.Delegate, System.Delegate) - // - - // Locating the instruction that loads the first argument can be complicated, so we simplify by making a few assumptions: - // 1. The instruction immediately before the call instruction (which would load the last argument) is a Push1/Pop0 instruction. - // This avoids running into trouble when the instruction does something else (it could be a any other instruction, which would throw off the next calculations) - // 2. We have a approved list of instructions we know how to calculate the type for, and which we use on the second to last instruction before the call instruction - - // First verify the Push1/Pop0 behavior in point 1. - var prev = ins.Previous; - while (prev.OpCode.Code == Code.Nop) - prev = prev.Previous; // Skip any nops. - if (prev.OpCode.StackBehaviourPush != StackBehaviour.Push1) { - //todo: localize mmp error 2106 - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); - return false; - } else if (prev.OpCode.StackBehaviourPop != StackBehaviour.Pop0) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); - return false; - } - - var loadTrampolineInstruction = prev.Previous; - while (loadTrampolineInstruction.OpCode.Code == Code.Nop) - loadTrampolineInstruction = loadTrampolineInstruction.Previous; // Skip any nops. - - // Then find the type of the previous instruction (the first argument to SetupBlock[Unsafe]) - var trampolineDelegateType = GetPushedType (caller, loadTrampolineInstruction); - if (trampolineDelegateType is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_A, caller, ins.Offset, mr.Name, loadTrampolineInstruction)); - return false; - } - - if (trampolineDelegateType.Is ("System", "Delegate") || trampolineDelegateType.Is ("System", "MulticastDelegate")) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_B, caller, trampolineDelegateType.FullName, mr.Name)); - return false; - } - - if (!data.LinkContext.App.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); - return false; - - } - } catch (Exception e) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); - return false; - } - - // We got the information we need: rewrite the IL. - var instructions = caller.Body.Instructions; - var index = instructions.IndexOf (ins); - // Inject the extra arguments - instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); - instructions.Insert (index, Instruction.Create (mr.Name == "SetupBlockUnsafe" ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1)); - // Change the call to call SetupBlockImpl instead - ins.Operand = GetBlockSetupImpl (data, caller, ins); - - //Driver.Log (4, "Optimized call to BlockLiteral.SetupBlock in {0} at offset {1} with delegate type {2} and signature {3}", caller, ins.Offset, delegateType.FullName, signature); - instructionsAddedOrRemoved = 2; - return true; - } - - internal static bool IsBlockLiteralCtor_Type_String (MethodDefinition md) - { - if (!md.HasParameters) - return false; - - if (md.Parameters.Count != 4) - return false; - - if (!(md.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) - return false; - - if (!md.Parameters [1].ParameterType.Is ("System", "Object")) - return false; - - if (!md.Parameters [2].ParameterType.Is ("System", "Type")) - return false; - - if (!md.Parameters [3].ParameterType.Is ("System", "String")) - return false; - - return true; - } - - static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) - { - instructionsAddedOrRemoved = 0; - - if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) - return false; - - // This will optimize calls to this BlockLiteral constructor: - // (void* ptr, object context, Type trampolineType, string trampolineMethod) - // by calculating the signature for the block using the last two arguments, - // and then rewrite the code to call this constructor overload instead: - // (void* ptr, object context, string signature) - // This is required to remove the dynamic registrar, because calculating the block signature - // is done in the dynamic registrar. - // - // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). - var mr = ins.Operand as MethodReference; - if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) - return false; - - var md = mr.Resolve (); - if (md is null || !IsBlockLiteralCtor_Type_String (md)) - return false; - - string? signature = null; - Instruction? sequenceStart; - try { - // We need to figure out the last argument to the call to the ctor - // - // Example sequence: - // - // ldarg.0 - // ldarg.1 - // ldtoken ... - // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle) - // ldstr ... - // newobj BlockLiteral (void*, System.Object, System.Type, System.String) - // - - // Verify 'ldstr ...' - var loadString = GetPreviousSkippingNops (ins); - if (loadString.OpCode != OpCodes.Ldstr) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadString)); - return false; - } - - // Verify 'call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)' - var callGetTypeFromHandle = GetPreviousSkippingNops (loadString); - if (callGetTypeFromHandle.OpCode != OpCodes.Call || !(callGetTypeFromHandle.Operand is MethodReference methodOperand) || methodOperand.Name != "GetTypeFromHandle" || !methodOperand.DeclaringType.Is ("System", "Type")) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, callGetTypeFromHandle)); - return false; - } - - // Verify 'ldtoken ...' - var loadType = GetPreviousSkippingNops (callGetTypeFromHandle); - if (loadType.OpCode != OpCodes.Ldtoken) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType)); - return false; - } - - // Then find the type of the previous instruction - var trampolineContainerTypeReference = loadType.Operand as TypeReference; - if (trampolineContainerTypeReference is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType.Operand)); - return false; - } - - var trampolineContainerType = trampolineContainerTypeReference.Resolve (); - if (trampolineContainerType is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, trampolineContainerTypeReference)); - return false; - } - - // Find the trampoline method - var trampolineMethodName = (string) loadString.Operand; - var trampolineMethods = trampolineContainerType.Methods.Where (v => v.Name == trampolineMethodName).ToArray (); - if (!trampolineMethods.Any ()) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E1 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because no method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); - return false; - } else if (trampolineMethods.Count () > 1) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E2 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because more than one method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); - return false; - } - var trampolineMethod = trampolineMethods [0]; - if (!trampolineMethod.HasParameters || trampolineMethod.Parameters.Count < 1) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_F /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' must have at least one parameter. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); - return false; - } - - // Check that the method's first parameter is either IntPtr, void* or BlockLiteral* - var firstParameterType = trampolineMethod.Parameters [0].ParameterType; - if (firstParameterType.Is ("System", "IntPtr")) { - // ok - } else if (firstParameterType is PointerType ptrType) { - var ptrTargetType = ptrType.ElementType; - if (!(ptrTargetType.Is ("System", "Void") || ptrTargetType.Is ("ObjCRuntime", "BlockLiteral"))) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); - return false; - } - // ok - } else { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); - return false; - } - - // Check that the method has [UnmanagedCallersOnly] - if (!trampolineMethod.HasCustomAttributes || !trampolineMethod.CustomAttributes.Any (v => v.AttributeType.Is ("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"))) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_H /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' does not have an [UnmanagedCallersOnly] attribute. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); - return false; - } - - var userDelegateType = data.LinkContext.App.StaticRegistrar.GetUserDelegateType (trampolineMethod); - MethodReference? userMethod = null; - var blockSignature = true; - if (userDelegateType is not null) { - userMethod = data.LinkContext.App.StaticRegistrar.GetDelegateInvoke (userDelegateType); - } else { - userMethod = trampolineMethod; - } - - if (userMethod is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_D, caller, ins.Offset, "Could not find delegate invoke method")); - return false; - } - - // Calculate the block signature. - var parameters = new TypeReference [userMethod.Parameters.Count]; - for (int p = 0; p < parameters.Length; p++) - parameters [p] = userMethod.Parameters [p].ParameterType; - signature = data.LinkContext.App.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); - - sequenceStart = loadType; - } catch (Exception e) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); - return false; - } - - // We got the information we need: rewrite the IL. - var instructions = caller.Body.Instructions; - var index = instructions.IndexOf (sequenceStart); - int instructionDiff = 0; - while (instructions [index] != ins) { - instructions.RemoveAt (index); - instructionDiff--; - } - // Inject the extra arguments - instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); - instructionDiff++; - // Change the call to call the ctor with the string signature parameter instead - ins.Operand = GetBlockLiteralConstructor (data, caller, ins); - - data.App.Log (4, "Optimized call to BlockLiteral..ctor in {0} at offset {1} with signature {2}", caller, ins.Offset, signature); - instructionsAddedOrRemoved = instructionDiff; - return true; - } - - static Instruction GetPreviousSkippingNops (Instruction ins) - { - do { - ins = ins.Previous; - } while (ins.OpCode == OpCodes.Nop); - return ins; - } - - static Instruction? SkipNops (Instruction? ins) - { - if (ins is null) - return null; - - while (ins.OpCode == OpCodes.Nop) { - if (ins.Next is null) - return null; - ins = ins.Next; - } - return ins; - } - - static bool ProcessIsARM64CallingConvention (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - const string operation = "inline Runtime.IsARM64CallingConvention"; - - if (data.Optimizations.InlineIsARM64CallingConvention != true) - return false; - - if (!data.InlineIsArm64CallingConvention.HasValue) - return false; - - // Verify we're checking the right IsARM64CallingConvention field - var fr = ins.Operand as FieldReference; - if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) - return false; - - if (!ValidateInstruction (data, caller, ins, operation, Code.Ldsfld)) - return false; - - // We're fine, inline the Runtime.IsARM64CallingConvention value - ins.OpCode = data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; - ins.Operand = null; - - return true; - } - - static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - const string operation = "inline Runtime.Arch"; - - if (data.Optimizations.InlineRuntimeArch != true) - return false; - - // Verify we're checking the right Arch field - var fr = ins.Operand as FieldReference; - if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) - return false; - - // Verify a few assumptions before doing anything - if (!ValidateInstruction (data, caller, ins, operation, Code.Ldsfld)) - return false; - - // We're fine, inline the Runtime.Arch condition - // The enum values are Runtime.DEVICE = 0 and Runtime.SIMULATOR = 1, - ins.OpCode = data.Device ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1; - ins.Operand = null; - return true; - } - - // Returns the type of the value pushed on the stack by the given instruction. - // Returns null for unknown instructions, or for instructions that don't push anything on the stack. - static TypeReference? GetPushedType (MethodDefinition method, Instruction ins) - { - var index = 0; - switch (ins.OpCode.Code) { - case Code.Ldloc_0: - case Code.Ldarg_0: - index = 0; - break; - case Code.Ldloc_1: - case Code.Ldarg_1: - index = 1; - break; - case Code.Ldloc_2: - case Code.Ldarg_2: - index = 2; - break; - case Code.Ldloc_3: - case Code.Ldarg_3: - index = 3; - break; - case Code.Ldloc: - case Code.Ldloc_S: - return ((VariableDefinition) ins.Operand).VariableType; - case Code.Ldarg: - case Code.Ldarg_S: - return ((ParameterDefinition) ins.Operand).ParameterType; - case Code.Ldfld: - case Code.Ldsfld: - return ((FieldReference) ins.Operand).FieldType; - case Code.Call: - case Code.Calli: - case Code.Callvirt: - return ((MethodReference) ins.Operand).ReturnType; - default: - return null; - } - - switch (ins.OpCode.Code) { - case Code.Ldloc: - case Code.Ldloc_0: - case Code.Ldloc_1: - case Code.Ldloc_2: - case Code.Ldloc_3: - return method.Body.Variables [index].VariableType; - case Code.Ldarg: - case Code.Ldarg_0: - case Code.Ldarg_1: - case Code.Ldarg_2: - case Code.Ldarg_3: - if (method.IsStatic) { - return method.Parameters [index].ParameterType; - } else if (index == 0) { - return method.DeclaringType; - } else { - return method.Parameters [index - 1].ParameterType; - } - default: - return null; - } - } - - static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - if (data.SetupBlockImplDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); - foreach (var method in type.Methods) { - if (method.Name != "SetupBlockImpl") - continue; - data.SetupBlockImplDefinition = method; - data.SetupBlockImplDefinition.IsPublic = true; // Make sure the method is callable from the optimized code. - break; - } - if (data.SetupBlockImplDefinition is null) - throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the method {Namespaces.ObjCRuntime}.BlockLiteral.SetupBlockImpl"); - } - return caller.Module.ImportReference (data.SetupBlockImplDefinition); - } - - static MethodReference GetBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) - { - if (data.BlockCtorDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); - foreach (var method in type.Methods) { - if (!method.IsConstructor) - continue; - if (method.IsStatic) - continue; - if (!method.HasParameters || method.Parameters.Count != 3) - continue; - if (!(method.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) - continue; - if (!method.Parameters [1].ParameterType.Is ("System", "Object")) - continue; - if (!method.Parameters [2].ParameterType.Is ("System", "String")) - continue; - data.BlockCtorDefinition = method; - break; - } - if (data.BlockCtorDefinition is null) - throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the constructor ObjCRuntime.BlockLiteral (void*, object, string)"); - } - return caller.Module.ImportReference (data.BlockCtorDefinition); - } - - static bool ProcessProtocolInterfaceStaticConstructor (OptimizeGeneratedCodeData data, MethodDefinition method) - { - // The static cctor in protocol interfaces exists only to preserve the protocol's members, for inspection by the registrar(s). - // If we're registering protocols, then we don't need to preserve protocol members, because the registrar - // already knows everything about it => we can remove the static cctor. - - if (!(method.DeclaringType.IsInterface && method.IsStatic && method.IsConstructor && method.HasBody)) - return false; - - if (data.Optimizations.RegisterProtocols != true) { - data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName); - return false; - } - - if (!method.DeclaringType.HasCustomAttributes || !method.DeclaringType.CustomAttributes.Any (v => v.AttributeType.Is ("Foundation", "ProtocolAttribute"))) { - data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName); - return false; - } - - var ins = SkipNops (method.Body.Instructions.First ()); - if (ins is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); - return false; - } else if (ins.OpCode != OpCodes.Ldnull) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - ins = SkipNops (ins.Next); - if (ins is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); - return false; - } - var callGCKeepAlive = ins; - if (callGCKeepAlive.OpCode != OpCodes.Call || !(callGCKeepAlive.Operand is MethodReference methodOperand) || methodOperand.Name != "KeepAlive" || !methodOperand.DeclaringType.Is ("System", "GC")) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - ins = SkipNops (ins.Next); - if (ins is null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); - return false; - } else if (ins.OpCode != OpCodes.Ret) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - ins = SkipNops (ins.Next); - if (ins is not null) { - ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); - return false; - } - - // We can just remove the entire method, however that confuses the linker later on, so just empty it out and remove all the attributes. - data.App.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName); - method.Body.Instructions.Clear (); - method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret)); - - // Only remove DynamicDependency attributes that takes a single string argument. - // The generator generates other DynamicDependency attributes, and we don't want to remove those. - for (var i = method.CustomAttributes.Count - 1; i >= 0; i--) { - var ca = method.CustomAttributes [i]; - - if (!ca.AttributeType.Is ("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")) - continue; - - if (!ca.HasConstructorArguments) - continue; - - if (ca.ConstructorArguments.Count != 1) - continue; - - if (!ca.ConstructorArguments [0].Type.Is ("System", "String")) - continue; - - method.CustomAttributes.RemoveAt (i); - } - - return true; + OptimizeGeneratedCode.OptimizeMethod (data, method); } } - - public class OptimizeGeneratedCodeData { - public required Xamarin.Tuner.DerivedLinkContext LinkContext; - public required Optimizations Optimizations; - public required bool Device; - - public MethodDefinition? SetupBlockImplDefinition; - public MethodDefinition? BlockCtorDefinition; - public bool? InlineIsArm64CallingConvention; - - public Application App => LinkContext.App; - } - } diff --git a/tools/linker/OptimizeGeneratedCode.cs b/tools/linker/OptimizeGeneratedCode.cs new file mode 100644 index 000000000000..46af059779e8 --- /dev/null +++ b/tools/linker/OptimizeGeneratedCode.cs @@ -0,0 +1,1214 @@ +// Copyright 2012-2013, 2016 Xamarin Inc. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; + +using ObjCRuntime; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Linker.Steps; +using Mono.Tuner; +using MonoTouch.Tuner; + +using Xamarin.Bundler; + +#nullable enable + +namespace Xamarin.Linker { + public class OptimizeGeneratedCode { + public static bool IsActiveFor (AssemblyDefinition assembly, Profile profile, AnnotationStore annotations) + { + // Unless an assembly is or references our platform assembly, then it won't have anything we need to register + if (!profile.IsOrReferencesProductAssembly (assembly)) + return false; + + // We only care about assemblies that are being linked. + if (annotations.GetAction (assembly) != AssemblyAction.Link) + return false; + + return true; + } + + // [GeneratedCode] is not enough - e.g. it's used for anonymous delegates even if the + // code itself is not tool/compiler generated + static protected bool IsExport (ICustomAttributeProvider provider) + { + return provider.HasCustomAttribute (Namespaces.Foundation, "ExportAttribute"); + } + + // less risky to nop-ify if branches are pointing to this instruction + static protected void Nop (Instruction ins) + { + // Leave 'leave' instructions in place, they might be required for the resulting IL to be correct/verifiable. + switch (ins.OpCode.Code) { + case Code.Leave: + case Code.Leave_S: + return; + } + ins.OpCode = OpCodes.Nop; + ins.Operand = null; + } + + internal static bool ValidateInstruction (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, string operation, Code expected) + { + return ValidateInstruction (data.App, caller, ins, operation, expected); + } + + internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, Code expected) + { + if (ins.OpCode.Code != expected) { + log.Log (1, "Could not {0} in {1} at offset {2}, expected {3} got {4}", operation, caller, ins.Offset, expected, ins); + return false; + } + + return true; + } + + internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, Instruction ins, string operation, params Code [] expected) + { + foreach (var code in expected) { + if (ins.OpCode.Code == code) + return true; + } + + log.Log (1, "Could not {0} in {1} at offset {2}, expected any of [{3}] got {4}", operation, caller, ins.Offset, string.Join (", ", expected), ins); + return false; + } + + static int? GetConstantValue (OptimizeGeneratedCodeData data, Instruction? ins) + { + if (ins is null) + return null; + + switch (ins.OpCode.Code) { + case Code.Ldc_I4_0: + return 0; + case Code.Ldc_I4_1: + return 1; + case Code.Ldc_I4_2: + return 2; + case Code.Ldc_I4_3: + return 3; + case Code.Ldc_I4_4: + return 4; + case Code.Ldc_I4_5: + return 5; + case Code.Ldc_I4_6: + return 6; + case Code.Ldc_I4_7: + return 7; + case Code.Ldc_I4_8: + return 8; + case Code.Ldc_I4: + case Code.Ldc_I4_S: + if (ins.Operand is int) + return (int) ins.Operand; + if (ins.Operand is sbyte) + return (sbyte) ins.Operand; + return null; +#if DEBUG + case Code.Isinst: // We might be able to calculate a constant value here in the future + case Code.Ldloc: + case Code.Ldloca: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc_S: + case Code.Ldloca_S: + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg_S: + case Code.Ldarga: + case Code.Call: + case Code.Calli: + case Code.Callvirt: + case Code.Box: + case Code.Ldsfld: + case Code.Dup: // You might think we could get the constant of the previous instruction, but this instruction might be the target of a branch, in which case the question becomes: which instruction was the previous instruction? And that's not a question easily answered without a much more thorough analysis of the code. + case Code.Ldlen: + case Code.Ldind_U1: + case Code.Ldind_U2: + case Code.Ldind_U4: + case Code.Ldind_Ref: + case Code.Conv_I: + case Code.Conv_I1: + case Code.Conv_I2: + case Code.Conv_I4: + case Code.Conv_U: + case Code.Sizeof: + case Code.Ldfld: + case Code.Ldflda: + return null; // just to not hit the CWL below +#endif + default: +#if DEBUG + data.App.Log (9, "Unknown conditional instruction: {0}", ins); +#endif + return null; + } + } + + static bool MarkInstructions (OptimizeGeneratedCodeData data, MethodDefinition method, Mono.Collections.Generic.Collection instructions, bool [] reachable, int start, int end) + { + if (reachable [start]) + return true; // We've already marked this section of code + + for (int i = start; i < end; i++) { + reachable [i] = true; + + var ins = instructions [i]; + switch (ins.OpCode.FlowControl) { + case FlowControl.Branch: + // Unconditional branch, we continue marking from the instruction that we branch to. + var br_target = (Instruction) ins.Operand; + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (br_target), instructions.Count); + case FlowControl.Cond_Branch: + // Conditional instruction, we need to check if we can calculate a constant value for the condition + var cond_target = ins.Operand as Instruction; + bool? branch = null; // did we get a constant value for the condition, and if so, did we branch or not? + var cond_instruction_count = 0; // The number of instructions that compose the condition + + if (ins.OpCode.Code == Code.Switch) { + // Treat all branches of the switch statement as reachable. + // FIXME: calculate the potential constant branch (currently there are no optimizable methods where the switch condition is constant, so this is not needed for now) + var targets = ins.Operand as Instruction []; + if (targets is null) { + data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); + return false; + } + foreach (var target in targets) { + // not constant, continue marking both this code sequence and the branched sequence + if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (target), end)) + return false; + } + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (ins.Next), end); + } + + if (cond_target is null) { + data.App.Log (4, "Can't optimize {0} because of unknown target of branch instruction {1} {2}", method, ins, ins.Operand); + return false; + } + + switch (ins.OpCode.Code) { + case Code.Brtrue: + case Code.Brtrue_S: { + var v = GetConstantValue (data, ins.Previous); + if (v.HasValue) + branch = v.Value != 0; + cond_instruction_count = 2; + break; + } + case Code.Brfalse: + case Code.Brfalse_S: { + var v = GetConstantValue (data, ins.Previous); + if (v.HasValue) + branch = v.Value == 0; + cond_instruction_count = 2; + break; + } + case Code.Beq: + case Code.Beq_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value == x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Bne_Un: + case Code.Bne_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value != x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Ble: + case Code.Ble_S: + case Code.Ble_Un: + case Code.Ble_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value <= x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Blt: + case Code.Blt_S: + case Code.Blt_Un: + case Code.Blt_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value < x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Bge: + case Code.Bge_S: + case Code.Bge_Un: + case Code.Bge_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value >= x2.Value; + cond_instruction_count = 3; + break; + } + case Code.Bgt: + case Code.Bgt_S: + case Code.Bgt_Un: + case Code.Bgt_Un_S: { + var x1 = GetConstantValue (data, ins.Previous?.Previous); + var x2 = GetConstantValue (data, ins.Previous); + if (x1.HasValue && x2.HasValue) + branch = x1.Value > x2.Value; + cond_instruction_count = 3; + break; + } + default: + data.App.Log ("Can't optimize {0} because of unknown branch instruction: {1}", method, ins); + break; + } + + if (branch.HasValue) { + // Make sure nothing else in the method branches into the middle of our supposedly constant condition, + // bypassing our constant calculation. Note that it's not a bad to branch to the _first_ instruction in + // the sequence (thus the +2 here), just into the middle of it. + if (AnyBranchTo (data, instructions, instructions [i - cond_instruction_count + 2], ins)) + branch = null; + } + + if (!branch.HasValue) { + // not constant, continue marking both this code sequence and the branched sequence + if (!MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end)) + return false; + } else { + // we can remove the branch (and the code that loads the condition), so we mark those instructions as dead. + for (int a = 0; a < cond_instruction_count; a++) + reachable [i - a] = false; + + // Now continue marking according to whether we branched or not + if (branch.Value) { + // branch always taken + return MarkInstructions (data, method, instructions, reachable, instructions.IndexOf (cond_target), end); + } else { + // branch never taken + // continue looping + } + } + break; + case FlowControl.Call: + case FlowControl.Next: + // Nothing special, continue marking + break; + case FlowControl.Return: + case FlowControl.Throw: + // Control flow returns here, so stop marking + return true; + case FlowControl.Break: + case FlowControl.Meta: + case FlowControl.Phi: + default: + data.App.Log (4, "Can't optimize {0} because of unknown flow control for: {1}", method, ins); + return false; + } + } + + return true; + } + + // Check if there are any branches in the instructions that branch to anywhere between 'first' and 'last' instructions (both inclusive). + static bool AnyBranchTo (OptimizeGeneratedCodeData data, Mono.Collections.Generic.Collection instructions, Instruction first, Instruction last) + { + if (first.Offset > last.Offset) { + data.App.Log ($"Broken assumption: {first} is after {last}"); + return true; // This is the safe thing to do, since it will prevent inlining + } + + for (int i = 0; i < instructions.Count; i++) { + var ins = instructions [i]; + switch (ins.OpCode.FlowControl) { + case FlowControl.Branch: + case FlowControl.Cond_Branch: + var target = ins.Operand as Instruction; + if (target is not null && target.Offset >= first.Offset && target.Offset <= last.Offset) + return true; + break; + } + } + + return false; + } + + static bool EliminateDeadCode (OptimizeGeneratedCodeData data, MethodDefinition caller) + { + var modified = false; + if (data.Optimizations.DeadCodeElimination != true) + return modified; + + var instructions = caller.Body.Instructions; + var reachable = new bool [instructions.Count]; + + // We walk the instructions in the method, starting with the first instruction, + // marking all reachable instructions. Any non-reachable instructions at the end + // can be removed. + + if (!MarkInstructions (data, caller, instructions, reachable, 0, instructions.Count)) + return modified; + + // Handle exception handlers specially, they do not follow normal code flow. + bool []? reachableExceptionHandlers = null; + if (caller.Body.HasExceptionHandlers) { + reachableExceptionHandlers = new bool [caller.Body.ExceptionHandlers.Count]; + for (var e = 0; e < reachableExceptionHandlers.Length; e++) { + var eh = caller.Body.ExceptionHandlers [e]; + + // First check if the protected region is reachable + var startI = instructions.IndexOf (eh.TryStart); + var endI = instructions.IndexOf (eh.TryEnd); + for (int i = startI; i < endI; i++) { + if (reachable [i]) { + reachableExceptionHandlers [e] = true; + break; + } + } + // The protected code isn't reachable, none of the handlers will be executed + if (!reachableExceptionHandlers [e]) + continue; + + switch (eh.HandlerType) { + case ExceptionHandlerType.Catch: + // We don't need catch handlers the reachable instructions are all nops + var allNops = true; + for (int i = startI; i < endI; i++) { + if (instructions [i].OpCode.Code != Code.Nop) { + allNops = false; + break; + } + } + if (!allNops) { + if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) + return modified; + } + break; + case ExceptionHandlerType.Finally: + // finally clauses are always executed, even if the protected region is empty + if (!MarkInstructions (data, caller, instructions, reachable, instructions.IndexOf (eh.HandlerStart), instructions.IndexOf (eh.HandlerEnd))) + return modified; + break; + case ExceptionHandlerType.Fault: + case ExceptionHandlerType.Filter: + // FIXME: and until fixed, exit gracefully without doing anything + data.App.Log (4, "Unhandled exception handler: {0}, skipping dead code elimination for {1}", eh.HandlerType, caller); + return modified; + } + } + } + + if (Array.IndexOf (reachable, false) == -1) + return modified; // entire method is reachable + + // Kill branch instructions when there are only dead instructions between the branch instruction and the target of the branch + for (int i = 0; i < instructions.Count; i++) { + if (!reachable [i]) + continue; + + var ins = instructions [i]; + if (ins.OpCode.Code != Code.Br && ins.OpCode.Code != Code.Br_S) + continue; + var target = ins.Operand as Instruction; + if (target is null) + continue; + if (target.Offset < ins.Offset) + continue; // backwards branch, keep those + + var start = i + 1; + var end = instructions.IndexOf (target); + var any_reachable = false; + for (int k = start; k < end; k++) { + if (reachable [k]) { + any_reachable = true; + break; + } + } + if (any_reachable) + continue; + + // The branch instruction just branches over unreachable instructions, so it can be considered unreachable too. + reachable [i] = false; + } + + // Check if there are unreachable instructions at the end. + var last_reachable = Array.LastIndexOf (reachable, true); + if (last_reachable < reachable.Length - 1) { + // There are unreachable instructions at the end. + // We must verify that there are no branches into these instructions. + // In theory there shouldn't be any (if there are branches into these instructions, + // they're reachable), but let's still verify just in case. + var last_reachable_offset = instructions [last_reachable].Offset; + for (int i = 0; i < last_reachable; i++) { + if (!reachable [i]) + continue; // Unreachable instructions don't branch anywhere, because they'll be removed. + var ins = instructions [i]; + switch (ins.OpCode.FlowControl) { + case FlowControl.Break: + case FlowControl.Cond_Branch: + var target = (Instruction) ins.Operand; + if (target.Offset > last_reachable_offset) { + data.App.Log (4, "Can't optimize {0} because of branching beyond last instruction alive: {1}", caller, ins); + return modified; + } + break; + } + } + } +#if false + Console.WriteLine ($"{caller.FullName}:"); + for (int i = 0; i < reachable.Length; i++) { + Console.WriteLine ($"{(reachable [i] ? " " : "- ")} {instructions [i]}"); + if (!reachable [i]) + Nop (instructions [i]); + } + Console.WriteLine (); +#endif + + // Exterminate, exterminate, exterminate + for (int i = 0; i < reachable.Length; i++) { + if (!reachable [i]) { + Nop (instructions [i]); + modified = true; + } + } + + // Remove exception handlers + if (reachableExceptionHandlers is not null) { + for (int i = reachableExceptionHandlers.Length - 1; i >= 0; i--) { + if (reachableExceptionHandlers [i]) + continue; + caller.Body.ExceptionHandlers.RemoveAt (i); + modified = true; + } + } + + // Remove unreachable instructions (nops) at the end, because the last instruction can only be ret/throw/backwards branch. + for (int i = last_reachable + 1; i < reachable.Length; i++) { + instructions.RemoveAt (last_reachable + 1); + modified = true; + } + + return modified; + } + + static bool GetIsExtensionType (TypeDefinition type) + { + // if 'type' inherits from NSObject inside an assembly that has [GeneratedCode] + // or for static types used for optional members (using extensions methods), they can be optimized too + return type.IsSealed && type.IsAbstract && type.Name.EndsWith ("_Extensions", StringComparison.Ordinal); + } + + public static bool OptimizeMethod (OptimizeGeneratedCodeData data, MethodDefinition method) + { + var modified = false; + + if (!method.HasBody) + return modified; + + if (method.IsBindingImplOptimizableCode (data.LinkContext)) { + // We optimize all methods that have the [BindingImpl (BindingImplAttributes.Optimizable)] attribute. + } else if (method.IsGeneratedCode (data.LinkContext) && ( + GetIsExtensionType (method.DeclaringType) + || IsExport (method))) { + // We optimize methods that have the [GeneratedCodeAttribute] and is either an extension type or an exported method + } else { + // but it would be too risky to apply on user-generated code + return modified; + } + + if (data.Optimizations.InlineIsARM64CallingConvention == true && data.InlineIsArm64CallingConvention.HasValue && method.Name == "GetIsARM64CallingConvention" && method.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) { + // Rewrite to return the constant value + var instr = method.Body.Instructions; + instr.Clear (); + instr.Add (Instruction.Create (data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); + instr.Add (Instruction.Create (OpCodes.Ret)); + return true; // nothing else to do here. + } + + if (ProcessProtocolInterfaceStaticConstructor (data, method)) + return true; + + var instructions = method.Body.Instructions; + for (int i = 0; i < instructions.Count; i++) { + var ins = instructions [i]; + switch (ins.OpCode.Code) { + case Code.Newobj: + case Code.Call: + modified |= ProcessCalls (data, method, ins, out var instructionsAddedOrRemoved); + i += instructionsAddedOrRemoved; + break; + case Code.Ldsfld: + modified |= ProcessLoadStaticField (data, method, ins); + break; + } + } + + modified |= EliminateDeadCode (data, method); + return modified; + } + + // Returns the number of instructions added (or removed) in the 'instructionsAddedOrRemoved' parameter. + // Returns true if any modifications were done. + static bool ProcessCalls (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) + { + var modified = false; + instructionsAddedOrRemoved = 0; + var mr = ins.Operand as MethodReference; + switch (mr?.Name) { + case "EnsureUIThread": + modified |= ProcessEnsureUIThread (data, caller, ins); + break; + case "get_IsDirectBinding": + modified |= ProcessIsDirectBinding (data, caller, ins); + break; + case "SetupBlock": + case "SetupBlockUnsafe": + modified |= ProcessSetupBlock (data, caller, ins, out instructionsAddedOrRemoved); + break; + case ".ctor": + if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) + break; + return ProcessBlockLiteralConstructor (data, caller, ins, out instructionsAddedOrRemoved); + } + + return modified; + } + + static bool ProcessLoadStaticField (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + var modified = false; + var fr = ins.Operand as FieldReference; + switch (fr?.Name) { + case "IsARM64CallingConvention": + modified |= ProcessIsARM64CallingConvention (data, caller, ins); + break; + case "Arch": + // https://app.asana.com/0/77259014252/77812690163 + modified |= ProcessRuntimeArch (data, caller, ins); + break; + } + return modified; + } + + static bool ProcessEnsureUIThread (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + if (data.Optimizations.RemoveUIThreadChecks != true) + return false; + + // Verify we're checking the right get_EnsureUIThread call + var declaringTypeNamespace = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? Namespaces.AppKit : Namespaces.UIKit; + var declaringTypeName = data.LinkContext.App.Platform == Utils.ApplePlatform.MacOSX ? "NSApplication" : "UIApplication"; + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (declaringTypeNamespace, declaringTypeName)) + return false; + + // Verify a few assumptions before doing anything + const string operation = "remove calls to [NS|UI]Application::EnsureUIThread"; + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Call)) + return false; + + // This is simple: just remove the call + Nop (ins); // call void UIKit.UIApplication::EnsureUIThread() + return true; + } + + static bool? IsDirectBindingConstant (OptimizeGeneratedCodeData data, TypeDefinition type) + { + return type.IsNSObject (data.LinkContext) ? type.GetIsDirectBindingConstant (data.LinkContext) : null; + } + + static bool ProcessIsDirectBinding (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + const string operation = "inline IsDirectBinding"; + + if (data.Optimizations.InlineIsDirectBinding != true) + return false; + + bool? isdirectbinding_constant = IsDirectBindingConstant (data, caller.DeclaringType); + + // If we don't know the constant isdirectbinding value, then we can't inline anything + if (!isdirectbinding_constant.HasValue) + return false; + + // Verify we're checking the right get_IsDirectBinding call + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (Namespaces.Foundation, "NSObject")) + return false; + + // Verify a few assumptions before doing anything + if (!ValidateInstruction (data.App, caller, ins.Previous, operation, Code.Ldarg_0)) + return false; + + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Call)) + return false; + + // Clearing the branch succeeded, so clear the condition too + // ldarg.0 + Nop (ins.Previous); + // call System.Boolean Foundation.NSObject::get_IsDirectBinding() + ins.OpCode = isdirectbinding_constant.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; + ins.Operand = null; + return true; + } + + static bool ProcessSetupBlock (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) + { + instructionsAddedOrRemoved = 0; + if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) + return false; + + // This will optimize calls to SetupBlock and SetupBlockUnsafe by calculating the signature for the block + // (which both SetupBlock and SetupBlockUnsafe do), and then rewrite the code to call SetupBlockImpl instead + // (which takes the block signature as an argument instead of calculating it). This is required to + // remove the dynamic registrar, because calculating the block signature is done in the dynamic registrar. + // + // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) + return false; + + if (caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) { + switch (caller.Name) { + case "GetBlockForDelegate": + case "CreateBlockForDelegate": + // These methods contain a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. + return false; + } + } + + string? signature = null; + try { + // We need to figure out the type of the first argument to the call to SetupBlock[Impl]. + // + // Example sequence: + // + // ldsfld ObjCRuntime.Trampolines/DJSContextExceptionHandler ObjCRuntime.Trampolines/SDJSContextExceptionHandler::Handler + // ldarg.1 + // call System.Void ObjCRuntime.BlockLiteral::SetupBlockUnsafe(System.Delegate, System.Delegate) + // + + // Locating the instruction that loads the first argument can be complicated, so we simplify by making a few assumptions: + // 1. The instruction immediately before the call instruction (which would load the last argument) is a Push1/Pop0 instruction. + // This avoids running into trouble when the instruction does something else (it could be a any other instruction, which would throw off the next calculations) + // 2. We have a approved list of instructions we know how to calculate the type for, and which we use on the second to last instruction before the call instruction + + // First verify the Push1/Pop0 behavior in point 1. + var prev = ins.Previous; + while (prev.OpCode.Code == Code.Nop) + prev = prev.Previous; // Skip any nops. + if (prev.OpCode.StackBehaviourPush != StackBehaviour.Push1) { + //todo: localize mmp error 2106 + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); + return false; + } else if (prev.OpCode.StackBehaviourPop != StackBehaviour.Pop0) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106, caller, ins.Offset, mr.Name, prev)); + return false; + } + + var loadTrampolineInstruction = prev.Previous; + while (loadTrampolineInstruction.OpCode.Code == Code.Nop) + loadTrampolineInstruction = loadTrampolineInstruction.Previous; // Skip any nops. + + // Then find the type of the previous instruction (the first argument to SetupBlock[Unsafe]) + var trampolineDelegateType = GetPushedType (caller, loadTrampolineInstruction); + if (trampolineDelegateType is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_A, caller, ins.Offset, mr.Name, loadTrampolineInstruction)); + return false; + } + + if (trampolineDelegateType.Is ("System", "Delegate") || trampolineDelegateType.Is ("System", "MulticastDelegate")) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_B, caller, trampolineDelegateType.FullName, mr.Name)); + return false; + } + + if (!data.LinkContext.App.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); + return false; + + } + } catch (Exception e) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); + return false; + } + + // We got the information we need: rewrite the IL. + var instructions = caller.Body.Instructions; + var index = instructions.IndexOf (ins); + // Inject the extra arguments + instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); + instructions.Insert (index, Instruction.Create (mr.Name == "SetupBlockUnsafe" ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1)); + // Change the call to call SetupBlockImpl instead + ins.Operand = GetBlockSetupImpl (data, caller, ins); + + //data.App.Log (4, "Optimized call to BlockLiteral.SetupBlock in {0} at offset {1} with delegate type {2} and signature {3}", caller, ins.Offset, delegateType.FullName, signature); + instructionsAddedOrRemoved = 2; + return true; + } + + internal static bool IsBlockLiteralCtor_Type_String (MethodDefinition md) + { + if (!md.HasParameters) + return false; + + if (md.Parameters.Count != 4) + return false; + + if (!(md.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) + return false; + + if (!md.Parameters [1].ParameterType.Is ("System", "Object")) + return false; + + if (!md.Parameters [2].ParameterType.Is ("System", "Type")) + return false; + + if (!md.Parameters [3].ParameterType.Is ("System", "String")) + return false; + + return true; + } + + static bool ProcessBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins, out int instructionsAddedOrRemoved) + { + instructionsAddedOrRemoved = 0; + + if (data.Optimizations.OptimizeBlockLiteralSetupBlock != true) + return false; + + // This will optimize calls to this BlockLiteral constructor: + // (void* ptr, object context, Type trampolineType, string trampolineMethod) + // by calculating the signature for the block using the last two arguments, + // and then rewrite the code to call this constructor overload instead: + // (void* ptr, object context, string signature) + // This is required to remove the dynamic registrar, because calculating the block signature + // is done in the dynamic registrar. + // + // This code is a mirror of the code in BlockLiteral.SetupBlock (to calculate the block signature). + var mr = ins.Operand as MethodReference; + if (mr is null || !mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) + return false; + + var md = mr.Resolve (); + if (md is null || !IsBlockLiteralCtor_Type_String (md)) + return false; + + string? signature = null; + Instruction? sequenceStart; + try { + // We need to figure out the last argument to the call to the ctor + // + // Example sequence: + // + // ldarg.0 + // ldarg.1 + // ldtoken ... + // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle) + // ldstr ... + // newobj BlockLiteral (void*, System.Object, System.Type, System.String) + // + + // Verify 'ldstr ...' + var loadString = GetPreviousSkippingNops (ins); + if (loadString.OpCode != OpCodes.Ldstr) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadString)); + return false; + } + + // Verify 'call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)' + var callGetTypeFromHandle = GetPreviousSkippingNops (loadString); + if (callGetTypeFromHandle.OpCode != OpCodes.Call || !(callGetTypeFromHandle.Operand is MethodReference methodOperand) || methodOperand.Name != "GetTypeFromHandle" || !methodOperand.DeclaringType.Is ("System", "Type")) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, callGetTypeFromHandle)); + return false; + } + + // Verify 'ldtoken ...' + var loadType = GetPreviousSkippingNops (callGetTypeFromHandle); + if (loadType.OpCode != OpCodes.Ldtoken) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType)); + return false; + } + + // Then find the type of the previous instruction + var trampolineContainerTypeReference = loadType.Operand as TypeReference; + if (trampolineContainerTypeReference is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, loadType.Operand)); + return false; + } + + var trampolineContainerType = trampolineContainerTypeReference.Resolve (); + if (trampolineContainerType is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the previous instruction was unexpected ({3}) */, caller, ins.Offset, mr.Name, trampolineContainerTypeReference)); + return false; + } + + // Find the trampoline method + var trampolineMethodName = (string) loadString.Operand; + var trampolineMethods = trampolineContainerType.Methods.Where (v => v.Name == trampolineMethodName).ToArray (); + if (!trampolineMethods.Any ()) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E1 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because no method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); + return false; + } else if (trampolineMethods.Count () > 1) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_E2 /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because more than one method named '{3}' was found in the type '{4}'. */, caller, ins.Offset, mr.Name, trampolineMethodName, trampolineContainerType.FullName)); + return false; + } + var trampolineMethod = trampolineMethods [0]; + if (!trampolineMethod.HasParameters || trampolineMethod.Parameters.Count < 1) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_F /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' must have at least one parameter. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); + return false; + } + + // Check that the method's first parameter is either IntPtr, void* or BlockLiteral* + var firstParameterType = trampolineMethod.Parameters [0].ParameterType; + if (firstParameterType.Is ("System", "IntPtr")) { + // ok + } else if (firstParameterType is PointerType ptrType) { + var ptrTargetType = ptrType.ElementType; + if (!(ptrTargetType.Is ("System", "Void") || ptrTargetType.Is ("ObjCRuntime", "BlockLiteral"))) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); + return false; + } + // ok + } else { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_G /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the first parameter in the method '{3}' isn't 'System.IntPtr', 'void*' or 'ObjCRuntime.BlockLiteral*' (it's '{4}') */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName, firstParameterType.FullName)); + return false; + } + + // Check that the method has [UnmanagedCallersOnly] + if (!trampolineMethod.HasCustomAttributes || !trampolineMethod.CustomAttributes.Any (v => v.AttributeType.Is ("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"))) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MX2106_H /* Could not optimize the call to BlockLiteral.{2} in {0} at offset {1} because the method '{3}' does not have an [UnmanagedCallersOnly] attribute. */, caller, ins.Offset, mr.Name, trampolineContainerType.FullName + "::" + trampolineMethodName)); + return false; + } + + var userDelegateType = data.LinkContext.App.StaticRegistrar.GetUserDelegateType (trampolineMethod); + MethodReference? userMethod = null; + var blockSignature = true; + if (userDelegateType is not null) { + userMethod = data.LinkContext.App.StaticRegistrar.GetDelegateInvoke (userDelegateType); + } else { + userMethod = trampolineMethod; + } + + if (userMethod is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, caller, ins, Errors.MM2106_D, caller, ins.Offset, "Could not find delegate invoke method")); + return false; + } + + // Calculate the block signature. + var parameters = new TypeReference [userMethod.Parameters.Count]; + for (int p = 0; p < parameters.Length; p++) + parameters [p] = userMethod.Parameters [p].ParameterType; + signature = data.LinkContext.App.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); + + sequenceStart = loadType; + } catch (Exception e) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); + return false; + } + + // We got the information we need: rewrite the IL. + var instructions = caller.Body.Instructions; + var index = instructions.IndexOf (sequenceStart); + int instructionDiff = 0; + while (instructions [index] != ins) { + instructions.RemoveAt (index); + instructionDiff--; + } + // Inject the extra arguments + instructions.Insert (index, Instruction.Create (OpCodes.Ldstr, signature)); + instructionDiff++; + // Change the call to call the ctor with the string signature parameter instead + ins.Operand = GetBlockLiteralConstructor (data, caller, ins); + + data.App.Log (4, "Optimized call to BlockLiteral..ctor in {0} at offset {1} with signature {2}", caller, ins.Offset, signature); + instructionsAddedOrRemoved = instructionDiff; + return true; + } + + static Instruction GetPreviousSkippingNops (Instruction ins) + { + do { + ins = ins.Previous; + } while (ins.OpCode == OpCodes.Nop); + return ins; + } + + static Instruction? SkipNops (Instruction? ins) + { + if (ins is null) + return null; + + while (ins.OpCode == OpCodes.Nop) { + if (ins.Next is null) + return null; + ins = ins.Next; + } + return ins; + } + + static bool ProcessIsARM64CallingConvention (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + const string operation = "inline Runtime.IsARM64CallingConvention"; + + if (data.Optimizations.InlineIsARM64CallingConvention != true) + return false; + + if (!data.InlineIsArm64CallingConvention.HasValue) + return false; + + // Verify we're checking the right IsARM64CallingConvention field + var fr = ins.Operand as FieldReference; + if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) + return false; + + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Ldsfld)) + return false; + + // We're fine, inline the Runtime.IsARM64CallingConvention value + ins.OpCode = data.InlineIsArm64CallingConvention.Value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; + ins.Operand = null; + + return true; + } + + static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + const string operation = "inline Runtime.Arch"; + + if (data.Optimizations.InlineRuntimeArch != true) + return false; + + // Verify we're checking the right Arch field + var fr = ins.Operand as FieldReference; + if (fr is null || !fr.DeclaringType.Is (Namespaces.ObjCRuntime, "Runtime")) + return false; + + // Verify a few assumptions before doing anything + if (!ValidateInstruction (data.App, caller, ins, operation, Code.Ldsfld)) + return false; + + // We're fine, inline the Runtime.Arch condition + // The enum values are Runtime.DEVICE = 0 and Runtime.SIMULATOR = 1, + ins.OpCode = data.Device ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1; + ins.Operand = null; + return true; + } + + // Returns the type of the value pushed on the stack by the given instruction. + // Returns null for unknown instructions, or for instructions that don't push anything on the stack. + static TypeReference? GetPushedType (MethodDefinition method, Instruction ins) + { + var index = 0; + switch (ins.OpCode.Code) { + case Code.Ldloc_0: + case Code.Ldarg_0: + index = 0; + break; + case Code.Ldloc_1: + case Code.Ldarg_1: + index = 1; + break; + case Code.Ldloc_2: + case Code.Ldarg_2: + index = 2; + break; + case Code.Ldloc_3: + case Code.Ldarg_3: + index = 3; + break; + case Code.Ldloc: + case Code.Ldloc_S: + return ((VariableDefinition) ins.Operand).VariableType; + case Code.Ldarg: + case Code.Ldarg_S: + return ((ParameterDefinition) ins.Operand).ParameterType; + case Code.Ldfld: + case Code.Ldsfld: + return ((FieldReference) ins.Operand).FieldType; + case Code.Call: + case Code.Calli: + case Code.Callvirt: + return ((MethodReference) ins.Operand).ReturnType; + default: + return null; + } + + switch (ins.OpCode.Code) { + case Code.Ldloc: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + return method.Body.Variables [index].VariableType; + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + if (method.IsStatic) { + return method.Parameters [index].ParameterType; + } else if (index == 0) { + return method.DeclaringType; + } else { + return method.Parameters [index - 1].ParameterType; + } + default: + return null; + } + } + + static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + if (data.SetupBlockImplDefinition is null) { + var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + foreach (var method in type.Methods) { + if (method.Name != "SetupBlockImpl") + continue; + data.SetupBlockImplDefinition = method; + data.SetupBlockImplDefinition.IsPublic = true; // Make sure the method is callable from the optimized code. + break; + } + if (data.SetupBlockImplDefinition is null) + throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the method {Namespaces.ObjCRuntime}.BlockLiteral.SetupBlockImpl"); + } + return caller.Module.ImportReference (data.SetupBlockImplDefinition); + } + + static MethodReference GetBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) + { + if (data.BlockCtorDefinition is null) { + var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + foreach (var method in type.Methods) { + if (!method.IsConstructor) + continue; + if (method.IsStatic) + continue; + if (!method.HasParameters || method.Parameters.Count != 3) + continue; + if (!(method.Parameters [0].ParameterType is PointerType pt) || !pt.ElementType.Is ("System", "Void")) + continue; + if (!method.Parameters [1].ParameterType.Is ("System", "Object")) + continue; + if (!method.Parameters [2].ParameterType.Is ("System", "String")) + continue; + data.BlockCtorDefinition = method; + break; + } + if (data.BlockCtorDefinition is null) + throw ErrorHelper.CreateError (data.LinkContext.App, 99, caller, ins, Errors.MX0099, $"could not find the constructor ObjCRuntime.BlockLiteral (void*, object, string)"); + } + return caller.Module.ImportReference (data.BlockCtorDefinition); + } + + static bool ProcessProtocolInterfaceStaticConstructor (OptimizeGeneratedCodeData data, MethodDefinition method) + { + // The static cctor in protocol interfaces exists only to preserve the protocol's members, for inspection by the registrar(s). + // If we're registering protocols, then we don't need to preserve protocol members, because the registrar + // already knows everything about it => we can remove the static cctor. + + if (!(method.DeclaringType.IsInterface && method.IsStatic && method.IsConstructor && method.HasBody)) + return false; + + if (data.Optimizations.RegisterProtocols != true) { + data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName); + return false; + } + + if (!method.DeclaringType.HasCustomAttributes || !method.DeclaringType.CustomAttributes.Any (v => v.AttributeType.Is ("Foundation", "ProtocolAttribute"))) { + data.App.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName); + return false; + } + + var ins = SkipNops (method.Body.Instructions.First ()); + if (ins is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + return false; + } else if (ins.OpCode != OpCodes.Ldnull) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + ins = SkipNops (ins.Next); + if (ins is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + return false; + } + var callGCKeepAlive = ins; + if (callGCKeepAlive.OpCode != OpCodes.Call || !(callGCKeepAlive.Operand is MethodReference methodOperand) || methodOperand.Name != "KeepAlive" || !methodOperand.DeclaringType.Is ("System", "GC")) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + ins = SkipNops (ins.Next); + if (ins is null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName)); + return false; + } else if (ins.OpCode != OpCodes.Ret) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + ins = SkipNops (ins.Next); + if (ins is not null) { + ErrorHelper.Show (data.App, ErrorHelper.CreateWarning (data.LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset)); + return false; + } + + // We can just remove the entire method, however that confuses the linker later on, so just empty it out and remove all the attributes. + data.App.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName); + method.Body.Instructions.Clear (); + method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret)); + + // Only remove DynamicDependency attributes that takes a single string argument. + // The generator generates other DynamicDependency attributes, and we don't want to remove those. + for (var i = method.CustomAttributes.Count - 1; i >= 0; i--) { + var ca = method.CustomAttributes [i]; + + if (!ca.AttributeType.Is ("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")) + continue; + + if (!ca.HasConstructorArguments) + continue; + + if (ca.ConstructorArguments.Count != 1) + continue; + + if (!ca.ConstructorArguments [0].Type.Is ("System", "String")) + continue; + + method.CustomAttributes.RemoveAt (i); + } + + return true; + } + } + + public class OptimizeGeneratedCodeData { + public required Xamarin.Tuner.DerivedLinkContext LinkContext; + public required Optimizations Optimizations; + public required bool Device; + + public MethodDefinition? SetupBlockImplDefinition; + public MethodDefinition? BlockCtorDefinition; + public bool? InlineIsArm64CallingConvention; + + public Application App => LinkContext.App; + } + +} diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index b2cd10bc170c..c295752c86cc 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -127,7 +127,7 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) break; case ".ctor": if (mr.Resolve () is MethodDefinition md) - requires |= Xamarin.Linker.OptimizeGeneratedCodeHandler.IsBlockLiteralCtor_Type_String (md); + requires |= Xamarin.Linker.OptimizeGeneratedCode.IsBlockLiteralCtor_Type_String (md); if (requires && warnIfRequired) Warn (assembly, mr); break; From ba547a3e24368f4ca70628aa6781e07d4e7fb4e4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:08:36 +0200 Subject: [PATCH 20/27] [dotnet-linker] Refactor LinkerConfiguration to support read/write. Refactor the big switch statement in the LinkerConfiguration constructor into a GetConfigurator() method that returns a dictionary of (Load, Save) delegate pairs for each configuration key. This enables both reading configuration from file and writing/serializing the current configuration state. Also add a Save() method that uses the configurator to write out the current configuration state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/common/Application.cs | 2 + tools/dotnet-linker/LinkerConfiguration.cs | 770 +++++++++++++-------- 2 files changed, 486 insertions(+), 286 deletions(-) diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 03f0d58bc48c..05ffef60c8d9 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -152,6 +152,7 @@ public bool IsDefaultMarshalManagedExceptionMode { // How Mono should be embedded into the app. #if !LEGACY_TOOLS AssemblyBuildTarget? libmono_link_mode; + public bool HasLibMonoLinkMode => libmono_link_mode.HasValue; public AssemblyBuildTarget LibMonoLinkMode { get { if (!libmono_link_mode.HasValue) @@ -165,6 +166,7 @@ public AssemblyBuildTarget LibMonoLinkMode { // How libxamarin should be embedded into the app. AssemblyBuildTarget? libxamarin_link_mode; + public bool HasLibXamarinLinkMode => libxamarin_link_mode.HasValue; public AssemblyBuildTarget LibXamarinLinkMode { get { if (!libxamarin_link_mode.HasValue) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index c0152c375d5b..41d25c990fa0 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -115,6 +115,471 @@ public static LinkerConfiguration GetInstance (LinkContext context) return instance; } + public delegate void LoadValue (string key, string value); + public delegate void SaveValue (string key, List storage); + + delegate void LoadBool (string key, string value, out bool result); + delegate void LoadNullableBool (string key, string value, out bool? result); + + public class Configurator : Dictionary { } + + Configurator GetConfigurator (string linker_file) + { + var saveNonEmpty = new Action> ((key, value, storage) => { + if (string.IsNullOrEmpty (value)) + return; + storage.Add ($"{key}={value}"); + }); + var saveNullableBool = new Action> ((key, value, storage) => { + if (!value.HasValue) + return; + storage.Add ($"{key}={(value.Value ? "true" : "false")}"); + }); + var saveOptionalDefaultFalseBool = new Action> ((key, value, storage) => { + if (!value) + return; + storage.Add ($"{key}=true"); + }); + var loadBool = new LoadBool ((string key, string value, out bool result) => { + result = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); + }); + var loadNullableBool = new LoadNullableBool ((key, value, out result) => { + if (!TryParseOptionalBoolean (value, out result)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + }); + var loadWarningLevel = new Action ((key, value, level) => { + try { + ErrorHelper.ParseWarningLevel (Application, level, value); + } catch (Exception ex) { + throw new InvalidOperationException ($"Invalid {key} '{value}' in {linker_file}", ex); + } + }); + var saveWarningLevel = new Action, ErrorHelper.WarningLevel> ((key, storage, level) => { + var warningLevels = ErrorHelper.GetWarningLevels (Application); + if (warningLevels is null) + return; + foreach (var kvp in warningLevels.Where (v => v.Value == level).OrderBy (v => v.Key)) { + if (kvp.Key == -1) { + storage.Add (key); + } else { + storage.Add ($"{key}={kvp.Key}"); + } + } + }); + + var dict = new Configurator () { + { "AreAnyAssembliesTrimmed", ( + new LoadValue ((key, value) => Application.AreAnyAssembliesTrimmed = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.AreAnyAssembliesTrimmed ? "true" : "false")}")) + )}, + { "AssemblyName", ( + // This is the _AssemblyName MSBuild property for the main project (which is also the root/entry assembly) + new LoadValue ((key, value) => Application.RootAssemblies.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (Application.RootAssemblies.Select (v => $"{key}={v}"))) + )}, + { "AOTArgument", ( + new LoadValue ((key, value) => + { + if (!string.IsNullOrEmpty (value)) + Application.AotArguments.Add (value); + }), + new SaveValue ((key, storage) => + storage.AddRange (Application.AotArguments.Where (v => !string.IsNullOrEmpty (v)).Select (v => $"{key}={v}"))) + )}, + { "AOTCompiler", ( + new LoadValue ((key, value) => AOTCompiler = value), + new SaveValue ((key, storage) => saveNonEmpty (key, AOTCompiler, storage)) + )}, + { "AOTOutputDirectory", ( + new LoadValue ((key, value) => AOTOutputDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, AOTOutputDirectory, storage)) + )}, + { "AppBundleManifestPath", ( + new LoadValue ((key, value) => Application.InfoPListPath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.InfoPListPath, storage)) + )}, + { "CacheDirectory", ( + new LoadValue ((key, value) => CacheDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, CacheDirectory, storage)) + )}, + { "CustomLinkFlags", ( + new LoadValue ((key, value) => Application.ParseCustomLinkFlags (value, "gcc_flags")), + new SaveValue ((key, storage) => storage.AddRange (Application.CustomLinkFlags?.Select (v => $"{key}={v}") ?? [])) + )}, + { "Debug", ( + new LoadValue ((key, value) => Application.EnableDebug = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.EnableDebug ? "true" : "false")}")) + )}, + { "DedupAssembly", ( + new LoadValue ((key, value) => DedupAssembly = value), + new SaveValue ((key, storage) => saveNonEmpty (key, DedupAssembly, storage)) + )}, + { "DeploymentTarget", ( + new LoadValue ((key, value) => { + if (!Version.TryParse (value, out var deployment_target)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + DeploymentTarget = deployment_target; + }), + new SaveValue ((key, storage) => saveNonEmpty (key, DeploymentTarget?.ToString (), storage)) + )}, + { "Dlsym", ( + new LoadValue ((key, value) => Application.ParseDlsymOptions (value)), + new SaveValue ((key, storage) => { + switch (Application.DlsymOptions) { + case DlsymOptions.None: + storage.Add ($"Dlsym=false"); + break; + case DlsymOptions.All: + storage.Add ($"Dlsym=true"); + break; + case DlsymOptions.Custom: + if (Application.DlsymAssemblies is not null) + storage.Add ($"Dlsym={string.Join (",", Application.DlsymAssemblies.Select (v => (v.Item2 ? "+" : "-") + v.Item1 + ".dll"))}"); + break; + case DlsymOptions.Default: + // don't store default + break; + default: + throw new InvalidOperationException ($"Unknown DlsymOptions value: {Application.DlsymOptions}"); + } + }) + )}, + { "EnableSGenConc", ( + new LoadValue ((key, value) => Application.EnableSGenConc = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.EnableSGenConc ? "true" : "false")}")) + )}, + { "EnvironmentVariable", ( + // Format is either of: + // NAME=VALUE + // Overwrite=BOOL|NAME=VALUE + new LoadValue ((key, value) => { + var overwrite = true; + var needle = "Overwrite="; + if (value.StartsWith (needle, StringComparison.Ordinal)) { + var pipe = value.IndexOf ('|', needle.Length); + if (pipe > 0) { + var overwriteString = value [needle.Length..pipe]; + if (!TryParseOptionalBoolean (overwriteString, out var parsedOverwrite)) + throw new InvalidOperationException ($"Unable to parse the 'Overwrite' value '{overwriteString}' for the environment variable entry '{value}' in {linker_file}"); + overwrite = parsedOverwrite.Value; + value = value [(pipe + 1)..]; + } + } + var separators = new char [] { ':', '=' }; + var equals = value.IndexOfAny (separators); + var name = value.Substring (0, equals); + var val = value.Substring (equals + 1); + Application.EnvironmentVariables.Add (name, new (val, overwrite)); + }), + new SaveValue ((key, storage) => storage.AddRange (Application.EnvironmentVariables.Select (v => $"{key}=Overwrite={v.Value.Overwrite}|{v.Key}={v.Value.Value}").OrderBy (v => v))) + )}, + { "FrameworkAssembly", ( + new LoadValue ((key, value) => FrameworkAssemblies.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (FrameworkAssemblies.OrderBy (v => v).Select (v => $"{key}={v}"))) + )}, + { "InlineDlfcnMethods", ( + new LoadValue ((key, value) => { + if (Enum.TryParse (value, true, out var inlineDlfcnMode)) + InlineDlfcnMethods = inlineDlfcnMode; + else if (string.IsNullOrEmpty (value)) + InlineDlfcnMethods = InlineDlfcnMethodsMode.Disabled; + else + throw new InvalidOperationException ($"Unknown InlineDlfcnMethods value: {value}"); + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={InlineDlfcnMethods}")) + )}, + { "InlineClassGetHandle", ( + new LoadValue ((key, value) => { + if (Enum.TryParse (value, true, out var inlineClassGetHandleMode)) + InlineClassGetHandle = inlineClassGetHandleMode; + else if (string.IsNullOrEmpty (value)) + InlineClassGetHandle = InlineClassGetHandleMode.Disabled; + else + throw new InvalidOperationException ($"Unknown InlineClassGetHandle value: {value}"); + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={InlineClassGetHandle}")) + )}, + { "Interpreter", ( + new LoadValue ((key, value) => { + if (!string.IsNullOrEmpty (value)) + Application.ParseInterpreter (value); + }), + new SaveValue ((key, storage) => { + if (!Application.UseInterpreter) + return; + storage.Add ($"{key}={string.Join (",", Application.InterpretedAssemblies)}"); + }) + )}, + { "IntermediateLinkDir", ( + new LoadValue ((key, value) => IntermediateLinkDir = value), + new SaveValue ((key, storage) => saveNonEmpty (key, IntermediateLinkDir, storage)) + )}, + { "IntermediateOutputPath", ( + new LoadValue ((key, value) => IntermediateOutputPath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, IntermediateOutputPath, storage)) + )}, + { "IsAppExtension", ( + new LoadValue ((key, value) => Application.IsExtension = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.IsExtension ? "true" : "false")}")) + )}, + { "ItemsDirectory", ( + new LoadValue ((key, value) => ItemsDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, ItemsDirectory, storage)) + )}, + { "IsSimulatorBuild", ( + new LoadValue ((key, value) => IsSimulatorBuild = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(IsSimulatorBuild ? "true" : "false")}")) + )}, + { "LibMonoLinkMode", ( + new LoadValue ((key, value) => Application.LibMonoLinkMode = ParseLinkMode (value, key)), + new SaveValue ((key, storage) => { if (Application.HasLibMonoLinkMode) storage.Add ($"{key}={Application.LibMonoLinkMode}"); }) + )}, + { "LibXamarinLinkMode", ( + new LoadValue ((key, value) => Application.LibXamarinLinkMode = ParseLinkMode (value, key)), + new SaveValue ((key, storage) => { if (Application.HasLibXamarinLinkMode) storage.Add ($"{key}={Application.LibXamarinLinkMode}"); }) + )}, + { "MarshalManagedExceptionMode", ( + new LoadValue ((key, value) => { + if (!string.IsNullOrEmpty (value)) { + if (!Application.TryParseManagedExceptionMode (value, out var mode)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.MarshalManagedExceptions = mode; + } + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={Application.MarshalManagedExceptions.ToString ().ToLowerInvariant ()}")) + )}, + { "MarshalObjectiveCExceptionMode", ( + new LoadValue ((key, value) => { + if (!string.IsNullOrEmpty (value)) { + if (!Application.TryParseObjectiveCExceptionMode (value, out var mode)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.MarshalObjectiveCExceptions = mode; + } + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={Application.MarshalObjectiveCExceptions.ToString ().ToLowerInvariant ()}")) + )}, + { "MonoLibrary", ( + new LoadValue ((key, value) => Application.MonoLibraries.Add (value)), + new SaveValue ((key, storage) => storage.AddRange (Application.MonoLibraries.OrderBy (v => v).Select (v => $"{key}={v}"))) + )}, + { "MtouchFloat32", ( + new LoadValue ((key, value) => loadNullableBool (key, value, out Application.AotFloat32)), + new SaveValue ((key, storage) => saveNullableBool (key, Application.AotFloat32, storage)) + )}, + { "NoWarn", ( + new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Disable)), + new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Disable)) + )}, + { "Optimize", ( + new LoadValue ((key, value) => user_optimize_flags = value), + new SaveValue ((key, storage) => saveNonEmpty (key, user_optimize_flags, storage)) + )}, + { "PartialStaticRegistrarLibrary", ( + new LoadValue ((key, value) => PartialStaticRegistrarLibrary = value), + new SaveValue ((key, storage) => saveNonEmpty (key, PartialStaticRegistrarLibrary, storage)) + )}, + { "Platform", ( + new LoadValue ((key, value) => Platform = ApplePlatformExtensions.Parse (value)), + new SaveValue ((key, storage) => storage.Add ($"{key}={Platform.AsString ()}")) + )}, + { "PlatformAssembly", ( + new LoadValue ((key, value) => PlatformAssembly = Path.GetFileNameWithoutExtension (value)), + new SaveValue ((key, storage) => saveNonEmpty (key, string.IsNullOrEmpty (PlatformAssembly) ? PlatformAssembly : PlatformAssembly + ".dll", storage)) + )}, + { "PrepareAssemblies", ( + new LoadValue ((key, value) => loadBool (key, value, out Application.PrepareAssemblies)), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Application.PrepareAssemblies, storage)) + )}, + { "PublishTrimmed", ( + new LoadValue ((key, value) => PublishTrimmed = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => storage.Add ($"{key}={(PublishTrimmed ? "true" : "false")}")) + )}, + { "ReferenceNativeSymbol", ( + new LoadValue ((key, value) => { + (string symbolType, string symbolMode, string symbol) = SplitString3 (value, ':'); + var mode = SymbolMode.Default; + switch (symbolMode) { + case "Ignore": + mode = SymbolMode.Ignore; + break; + case "": + break; + default: + throw new InvalidOperationException ($"Unknown symbol mode '{symbolMode}' for symbol '{symbol}'. Expected 'Ignore' or nothing at all."); + } + switch (symbolType) { + case "Function": + DerivedLinkContext.RequiredSymbols.AddFunction (symbol, mode); + break; + case "ObjectiveCClass": + DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (symbol, mode); + break; + case "Field": + DerivedLinkContext.RequiredSymbols.AddField (symbol, mode); + break; + default: + throw new InvalidOperationException ($"Unknown symbol type '{symbolType}' for symbol '{symbol}'. Expected 'Function', 'ObjectiveCClass', or 'Field'."); + } + }), + new SaveValue ((key, storage) => { + foreach (var symbol in DerivedLinkContext.RequiredSymbols) { + var mode = symbol.Mode == SymbolMode.Ignore ? "Ignore" : ""; + switch (symbol.Type) { + case SymbolType.Function: + case SymbolType.ObjectiveCClass: + case SymbolType.Field: + storage.Add ($"{key}={symbol.Type}:{mode}:{symbol.Name}"); + break; + default: + throw new InvalidOperationException ($"Unknown symbol type '{symbol.Type}' for symbol '{symbol.Name}'. Expected 'Function', 'ObjectiveCClass', or 'Field'."); + } + } + }) + )}, + { "RelativeAppBundlePath", ( + new LoadValue ((key, value) => RelativeAppBundlePath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, RelativeAppBundlePath, storage)) + )}, + { "Registrar", ( + new LoadValue ((key, value) => Application.ParseRegistrar (value)), + new SaveValue ((key, storage) => { + if (Application.Registrar == RegistrarMode.Default) + return; + storage.Add ($"{key}={Application.Registrar}"); + }) + )}, + { "RequireLinkWithAttributeForObjectiveCClassSearch", ( + new LoadValue ((key, value) => { + if (!TryParseOptionalBoolean (value, out var require_link_with_attribute_for_objectivec_class_search, defaultValue: false)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.RequireLinkWithAttributeForObjectiveCClassSearch = require_link_with_attribute_for_objectivec_class_search.Value; + }), + new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.RequireLinkWithAttributeForObjectiveCClassSearch ? "true" : "false")}")) + )}, + { "RequirePInvokeWrappers", ( + new LoadValue ((key, value) => { + if (!TryParseOptionalBoolean (value, out var require_pinvoke_wrappers, defaultValue: false)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.RequiresPInvokeWrappers = require_pinvoke_wrappers.Value; + }), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Application.RequiresPInvokeWrappers, storage)) + )}, + { "RuntimeConfigurationFile", ( + new LoadValue ((key, value) => Application.RuntimeConfigurationFile = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.RuntimeConfigurationFile, storage)) + )}, + { "SdkDevPath", ( + new LoadValue ((key, value) => Application.SdkRoot = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.SdkRoot, storage)) + )}, + { "SdkRootDirectory", ( + new LoadValue ((key, value) => { + SdkRootDirectory = value; + Application.FrameworkCurrentDirectory = value; + }), + new SaveValue ((key, storage) => saveNonEmpty (key, SdkRootDirectory, storage)) + )}, + { "SdkVersion", ( + new LoadValue ((key, value) => { + if (!Version.TryParse (value, out var sdk_version)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + SdkVersion = sdk_version; + }), + new SaveValue ((key, storage) => saveNonEmpty (key, SdkVersion?.ToString (), storage)) + )}, + { "SkipMarkingNSObjectsInUserAssemblies", ( + new LoadValue ((key, value) => { + if (!TryParseOptionalBoolean (value, out var skip_marking_nsobjects_in_user_assemblies)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies.Value; + }), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Application.SkipMarkingNSObjectsInUserAssemblies, storage)) + )}, + { "TargetArchitectures", ( + new LoadValue ((key, value) => { + if (!Enum.TryParse (value, out var abi)) + throw new InvalidOperationException ($"Unknown target architectures: {value} in {linker_file}"); + Abi = abi | (Abi & Abi.LLVM); // Preserve the LLVM flag if it was set, since TargetArchitectures is orthogonal to LLVM + }), + new SaveValue ((key, storage) => saveNonEmpty (key, (Abi & ~Abi.LLVM).ToString (), storage)) + )}, + { "TargetFramework", ( + new LoadValue ((key, value) => { + if (!TargetFramework.TryParse (value, out var tf)) + throw new InvalidOperationException ($"Invalid TargetFramework '{value}' in {linker_file}"); + Application.TargetFramework = TargetFramework.Parse (value); + }), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.TargetFramework.ToString (), storage)) + )}, + { "TypeMapAssemblyName", ( + new LoadValue ((key, value) => Application.TypeMapAssemblyName = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapAssemblyName, storage)) + )}, + { "TypeMapFilePath", ( + new LoadValue ((key, value) => TypeMapFilePath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, TypeMapFilePath, storage)) + )}, + { "TypeMapOutputDirectory", ( + new LoadValue ((key, value) => Application.TypeMapOutputDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapOutputDirectory, storage)) + )}, + { "UseLlvm", ( + new LoadValue ((key, value) => { + var use_llvm = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); + if (use_llvm) { + Abi |= Abi.LLVM; + } else { + Abi &= ~Abi.LLVM; + } + }), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, Abi.HasFlag (Abi.LLVM), storage)) + )}, + { "Verbosity", ( + new LoadValue ((key, value) => { + if (!int.TryParse (value, out var verbosity)) + throw new InvalidOperationException ($"Invalid Verbosity '{value}' in {linker_file}"); + Application.Verbosity = verbosity; + }), + new SaveValue ((key, storage) => { + if (Application.Verbosity != 0) + storage.Add ($"{key}={Application.Verbosity}"); + }) + )}, + { "Warn", ( + new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Warning)), + new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Warning)) + )}, + { "WarnAsError", ( + new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Error)), + new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Error)) + )}, + { "XamarinRuntime", ( + new LoadValue ((key, value) => { + if (!Enum.TryParse (value, out var rv)) + throw new InvalidOperationException ($"Invalid XamarinRuntime '{value}' in {linker_file}"); + Application.XamarinRuntime = rv; + }), + new SaveValue ((key, storage) => { + storage.Add ($"{key}={Application.XamarinRuntime}"); + }) + )}, + { "InvariantGlobalization", ( + new LoadValue ((key, value) => InvariantGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, InvariantGlobalization, storage)) + )}, + { "HybridGlobalization", ( + new LoadValue ((key, value) => HybridGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), + new SaveValue ((key, storage) => saveOptionalDefaultFalseBool (key, HybridGlobalization, storage)) + )}, + { "XamarinNativeLibraryDirectory", ( + new LoadValue ((key, value) => XamarinNativeLibraryDirectory = value), + new SaveValue ((key, storage) => saveNonEmpty (key, XamarinNativeLibraryDirectory, storage)) + )}, + }; + + return dict; + } + LinkerConfiguration (string linker_file) { if (!File.Exists (linker_file)) @@ -126,6 +591,7 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application = new Application (this); CompilerFlags = new CompilerFlags (Application); + var configurator = GetConfigurator (linker_file); var lines = File.ReadAllLines (linker_file); var significantLines = new List (); // This is the input the cache uses to verify if the cache is still valid for (var i = 0; i < lines.Length; i++) { @@ -145,289 +611,10 @@ public static LinkerConfiguration GetInstance (LinkContext context) if (string.IsNullOrEmpty (value)) continue; - switch (key) { - case "AreAnyAssembliesTrimmed": - Application.AreAnyAssembliesTrimmed = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "AssemblyName": - // This is the AssemblyName MSBuild property for the main project (which is also the root/entry assembly) - Application.RootAssemblies.Add (value); - break; - case "AOTArgument": - if (!string.IsNullOrEmpty (value)) - Application.AotArguments.Add (value); - break; - case "AOTCompiler": - AOTCompiler = value; - break; - case "AOTOutputDirectory": - AOTOutputDirectory = value; - break; - case "DedupAssembly": - DedupAssembly = value; - break; - case "CacheDirectory": - CacheDirectory = value; - break; - case "AppBundleManifestPath": - Application.InfoPListPath = value; - break; - case "CustomLinkFlags": - Application.ParseCustomLinkFlags (value, "gcc_flags"); - break; - case "Debug": - Application.EnableDebug = string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); - break; - case "DeploymentTarget": - if (!Version.TryParse (value, out var deployment_target)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - DeploymentTarget = deployment_target; - break; - case "Dlsym": - Application.ParseDlsymOptions (value); - break; - case "EnableSGenConc": - Application.EnableSGenConc = string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); - break; - case "EnvironmentVariable": - var overwrite = true; - var needle = "Overwrite="; - if (value.StartsWith (needle, StringComparison.Ordinal)) { - var pipe = value.IndexOf ('|', needle.Length); - if (pipe > 0) { - var overwriteString = value [needle.Length..pipe]; - if (!TryParseOptionalBoolean (overwriteString, out var parsedOverwrite)) - throw new InvalidOperationException ($"Unable to parse the 'Overwrite' value '{overwriteString}' for the environment variable entry '{value}' in {linker_file}"); - overwrite = parsedOverwrite.Value; - value = value [(pipe + 1)..]; - } - } - var separators = new char [] { ':', '=' }; - var equals = value.IndexOfAny (separators); - var name = value.Substring (0, equals); - var val = value.Substring (equals + 1); - Application.EnvironmentVariables.Add (name, new (val, overwrite)); - break; - case "FrameworkAssembly": - FrameworkAssemblies.Add (value); - break; - case "InlineClassGetHandle": - if (Enum.TryParse (value, true, out var inlineClassGetHandleMode)) - InlineClassGetHandle = inlineClassGetHandleMode; - else if (string.IsNullOrEmpty (value)) - InlineClassGetHandle = InlineClassGetHandleMode.Disabled; - else - throw new InvalidOperationException ($"Unknown InlineClassGetHandle value: {value}"); - break; - case "InlineDlfcnMethods": - if (Enum.TryParse (value, true, out var inlineDlfcnMode)) - InlineDlfcnMethods = inlineDlfcnMode; - else if (string.IsNullOrEmpty (value)) - InlineDlfcnMethods = InlineDlfcnMethodsMode.Disabled; - else - throw new InvalidOperationException ($"Unknown InlineDlfcnMethods value: {value}"); - break; - case "IntermediateLinkDir": - IntermediateLinkDir = value; - break; - case "IntermediateOutputPath": - IntermediateOutputPath = value; - break; - case "Interpreter": - if (!string.IsNullOrEmpty (value)) - Application.ParseInterpreter (value); - break; - case "IsAppExtension": - Application.IsExtension = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "ItemsDirectory": - ItemsDirectory = value; - break; - case "IsSimulatorBuild": - IsSimulatorBuild = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "LibMonoLinkMode": - Application.LibMonoLinkMode = ParseLinkMode (value, key); - break; - case "LibXamarinLinkMode": - Application.LibXamarinLinkMode = ParseLinkMode (value, key); - break; - case "MarshalManagedExceptionMode": - if (!string.IsNullOrEmpty (value)) { - if (!Application.TryParseManagedExceptionMode (value, out var mode)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.MarshalManagedExceptions = mode; - } - break; - case "MarshalObjectiveCExceptionMode": - if (!string.IsNullOrEmpty (value)) { - if (!Application.TryParseObjectiveCExceptionMode (value, out var mode)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.MarshalObjectiveCExceptions = mode; - } - break; - case "MonoLibrary": - Application.MonoLibraries.Add (value); - break; - case "MtouchFloat32": - if (!TryParseOptionalBoolean (value, out Application.AotFloat32)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - break; - case "NoWarn": - try { - ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Disable, value); - } catch (Exception ex) { - throw new InvalidOperationException ($"Invalid WarnAsError '{value}' in {linker_file}", ex); - } - break; - case "Optimize": - user_optimize_flags = value; - break; - case "PartialStaticRegistrarLibrary": - PartialStaticRegistrarLibrary = value; - break; - case "Platform": - switch (value) { - case "iOS": - Platform = ApplePlatform.iOS; - break; - case "tvOS": - Platform = ApplePlatform.TVOS; - break; - case "macOS": - Platform = ApplePlatform.MacOSX; - break; - case "MacCatalyst": - Platform = ApplePlatform.MacCatalyst; - break; - default: - throw new InvalidOperationException ($"Unknown platform: {value} for the entry {line} in {linker_file}"); - } - break; - case "PlatformAssembly": - PlatformAssembly = Path.GetFileNameWithoutExtension (value); - break; - case "ReferenceNativeSymbol": { - (string symbolType, string symbolMode, string symbol) = SplitString3 (value, ':'); - var mode = SymbolMode.Default; - switch (symbolMode) { - case "Ignore": - mode = SymbolMode.Ignore; - break; - case "": - break; - default: - throw new InvalidOperationException ($"Unknown symbol mode '{symbolMode}' for symbol '{symbol}'. Expected 'Ignore' or nothing at all."); - } - switch (symbolType) { - case "Function": - DerivedLinkContext.RequiredSymbols.AddFunction (symbol, mode); - break; - case "ObjectiveCClass": - DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (symbol, mode); - break; - case "Field": - DerivedLinkContext.RequiredSymbols.AddField (symbol, mode); - break; - default: - throw new InvalidOperationException ($"Unknown symbol type '{symbolType}' for symbol '{symbol}'. Expected 'Function', 'ObjectiveCClass', or 'Field'."); - } - break; - } - case "RelativeAppBundlePath": - RelativeAppBundlePath = value; - break; - case "Registrar": - Application.ParseRegistrar (value); - break; - case "RequireLinkWithAttributeForObjectiveCClassSearch": - if (!string.IsNullOrEmpty (value)) { // The default is 'false' - if (!TryParseOptionalBoolean (value, out var require_link_with_attribute_for_objectivec_class_search)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.RequireLinkWithAttributeForObjectiveCClassSearch = require_link_with_attribute_for_objectivec_class_search.Value; - } - break; - case "RequirePInvokeWrappers": - if (!TryParseOptionalBoolean (value, out var require_pinvoke_wrappers)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.RequiresPInvokeWrappers = require_pinvoke_wrappers.Value; - break; - case "RuntimeConfigurationFile": - Application.RuntimeConfigurationFile = value; - break; - case "SdkDevPath": - Application.SdkRoot = value; - break; - case "SdkRootDirectory": - SdkRootDirectory = value; - Application.FrameworkCurrentDirectory = value; - break; - case "SdkVersion": - if (!Version.TryParse (value, out var sdk_version)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - SdkVersion = sdk_version; - break; - case "SkipMarkingNSObjectsInUserAssemblies": - if (!TryParseOptionalBoolean (value, out var skip_marking_nsobjects_in_user_assemblies)) - throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); - Application.SkipMarkingNSObjectsInUserAssemblies = skip_marking_nsobjects_in_user_assemblies.Value; - break; - case "TargetArchitectures": - if (!Enum.TryParse (value, out Abi)) - throw new InvalidOperationException ($"Unknown target architectures: {value} in {linker_file}"); - break; - case "TargetFramework": - if (!TargetFramework.TryParse (value, out var tf)) - throw new InvalidOperationException ($"Invalid TargetFramework '{value}' in {linker_file}"); - Application.TargetFramework = TargetFramework.Parse (value); - break; - case "TypeMapAssemblyName": - Application.TypeMapAssemblyName = value; - break; - case "TypeMapFilePath": - TypeMapFilePath = value; - break; - case "TypeMapOutputDirectory": - Application.TypeMapOutputDirectory = value; - break; - case "UseLlvm": - use_llvm = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "Verbosity": - if (!int.TryParse (value, out var verbosity)) - throw new InvalidOperationException ($"Invalid Verbosity '{value}' in {linker_file}"); - Application.Verbosity += verbosity; - break; - case "Warn": - try { - ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Warning, value); - } catch (Exception ex) { - throw new InvalidOperationException ($"Invalid Warn '{value}' in {linker_file}", ex); - } - break; - case "WarnAsError": - try { - ErrorHelper.ParseWarningLevel (Application, ErrorHelper.WarningLevel.Error, value); - } catch (Exception ex) { - throw new InvalidOperationException ($"Invalid WarnAsError '{value}' in {linker_file}", ex); - } - break; - case "XamarinRuntime": - if (!Enum.TryParse (value, out var rv)) - throw new InvalidOperationException ($"Invalid XamarinRuntime '{value}' in {linker_file}"); - Application.XamarinRuntime = rv; - break; - case "InvariantGlobalization": - InvariantGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "HybridGlobalization": - HybridGlobalization = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); - break; - case "XamarinNativeLibraryDirectory": - XamarinNativeLibraryDirectory = value; - break; - default: - throw new InvalidOperationException ($"Unknown key '{key}' in {linker_file}"); + if (configurator.TryGetValue (key, out var actions)) { + actions.Load (key, value); + } else { + throw new InvalidOperationException ($"Unknown configuration key '{key}' in {linker_file} at line {i + 1}."); } } @@ -457,8 +644,11 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.BuildTarget = IsSimulatorBuild ? BuildTarget.Simulator : BuildTarget.Device; break; case ApplePlatform.MacOSX: - default: + case ApplePlatform.MacCatalyst: break; + + default: + throw new System.InvalidOperationException ($"Unknown platform: {Platform}"); } if (Application.TargetFramework.Platform != Platform) @@ -475,6 +665,14 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.Initialize (); } + public void Save (List storage) + { + var configurator = GetConfigurator (LinkerFile); + foreach (var kvp in configurator.OrderBy (v => v.Key)) { + kvp.Value.Save (kvp.Key, storage); + } + } + // Splits a string in three based on the split character. // "a:b" => "a", "b", "" // "a:b:c" => "a", "b", "c" @@ -496,12 +694,12 @@ public static LinkerConfiguration GetInstance (LinkContext context) } - bool TryParseOptionalBoolean (string input, [NotNullWhen (true)] out bool? value) + bool TryParseOptionalBoolean (string input, [NotNullWhen (true)] out bool? value, bool defaultValue = true) { value = null; if (string.IsNullOrEmpty (input)) { - value = true; + value = defaultValue; return true; } From 61e1f04cd2907dee9f42ac3a53c62807074cb7c2 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:37:05 +0200 Subject: [PATCH 21/27] [tools] Add VSCode tasks. --- msbuild/.vscode/tasks.json | 17 +++++++++++++++++ tools/.vscode/tasks.json | 17 +++++++++++++++++ tools/ap-launcher/.vscode/tasks.json | 17 +++++++++++++++++ tools/assembly-preparer/.vscode/tasks.json | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 msbuild/.vscode/tasks.json create mode 100644 tools/.vscode/tasks.json create mode 100644 tools/ap-launcher/.vscode/tasks.json create mode 100644 tools/assembly-preparer/.vscode/tasks.json diff --git a/msbuild/.vscode/tasks.json b/msbuild/.vscode/tasks.json new file mode 100644 index 000000000000..45465f774797 --- /dev/null +++ b/msbuild/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "make" + } + ] +} diff --git a/tools/.vscode/tasks.json b/tools/.vscode/tasks.json new file mode 100644 index 000000000000..45465f774797 --- /dev/null +++ b/tools/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "make" + } + ] +} diff --git a/tools/ap-launcher/.vscode/tasks.json b/tools/ap-launcher/.vscode/tasks.json new file mode 100644 index 000000000000..69a9b5f361b2 --- /dev/null +++ b/tools/ap-launcher/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "build" + } + ] +} diff --git a/tools/assembly-preparer/.vscode/tasks.json b/tools/assembly-preparer/.vscode/tasks.json new file mode 100644 index 000000000000..69a9b5f361b2 --- /dev/null +++ b/tools/assembly-preparer/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ], + "label": "build" + } + ] +} From e194a4b2b0d3b430aa4b56c203556ca2081340a4 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Jun 2026 19:51:04 +0200 Subject: [PATCH 22/27] [tools] Add link to NoWarn issue --- tools/dotnet-linker/LinkerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 41d25c990fa0..1ad78a1a8313 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -366,7 +366,7 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => loadNullableBool (key, value, out Application.AotFloat32)), new SaveValue ((key, storage) => saveNullableBool (key, Application.AotFloat32, storage)) )}, - { "NoWarn", ( + { "NoWarn", ( // we should support '$(NoWarn)' at some point: https://github.com/dotnet/macios/issues/25645 new LoadValue ((key, value) => loadWarningLevel (key, value, ErrorHelper.WarningLevel.Disable)), new SaveValue ((key, storage) => saveWarningLevel (key, storage, ErrorHelper.WarningLevel.Disable)) )}, From 0a36fff27fe80f7df271f4037075006cdca32947 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 08:37:46 +0200 Subject: [PATCH 23/27] [tests] Create an opt-in test to capture build performance --- tests/dotnet/UnitTests/PerformanceTests.cs | 52 ++++++++++++++++++++++ tests/dotnet/UnitTests/TestBaseClass.cs | 15 +++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/dotnet/UnitTests/PerformanceTests.cs diff --git a/tests/dotnet/UnitTests/PerformanceTests.cs b/tests/dotnet/UnitTests/PerformanceTests.cs new file mode 100644 index 000000000000..73f88a7af72e --- /dev/null +++ b/tests/dotnet/UnitTests/PerformanceTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Xamarin.Tests { + [TestFixture] + public class PerformanceTests : TestBaseClass { + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64")] + public void PrepareAssemblies (ApplePlatform platform, string runtimeIdentifiers) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + if (string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ENABLE_PERFORMANCE_TESTS"))) + Assert.Ignore ("Test ignored because ENABLE_PERFORMANCE_TESTS is not set."); + + var projects = new string [] { "MySimpleApp", "monotouch-test" }; + var results = new List (); + var attempts = 3; + foreach (var project in projects) { + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + + var result = new PrepareAssembliesTestResult { + Project = project, + }; + foreach (var propertyValue in new bool [] { false, true }) { + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["PrepareAssemblies"] = propertyValue.ToString (); + + for (var i = 1; i <= attempts; i++) { + Clean (project_path); + var rv = DotNet.AssertBuild (project_path, properties); + (propertyValue ? result.EnabledBuildTimes : result.DisabledBuildTimes).Add (rv.Duration); + } + } + results.Add (result); + } + foreach (var result in results.OrderBy (v => v.Project)) { + Console.WriteLine ($"Results for: {result.Project}"); + Console.WriteLine ($" Enabled timings: {string.Join (", ", result.EnabledBuildTimes.Select (v => v.ToString ()))} average: {TimeSpan.FromTicks ((long) result.EnabledBuildTimes.Average (v => v.Ticks))}"); + Console.WriteLine ($" Disabled timings: {string.Join (", ", result.DisabledBuildTimes.Select (v => v.ToString ()))} average: {TimeSpan.FromTicks ((long) result.DisabledBuildTimes.Average (v => v.Ticks))}"); + } + } + + class PrepareAssembliesTestResult { + public required string Project; + public List EnabledBuildTimes = new (); + public List DisabledBuildTimes = new (); + } + } +} + diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index e421218252eb..fbe234b535a6 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -133,6 +133,9 @@ protected static string GetDefaultRuntimeIdentifier (ApplePlatform platform, str protected static string GetProjectPath (string project, string? subdir = null, ApplePlatform? platform = null) { + if (TryGetTestProjectPath (project, platform ?? ApplePlatform.None, out var testProjectPath)) + return testProjectPath; + var project_dir = Path.Combine (Configuration.SourceRoot, "tests", "dotnet", project); if (!string.IsNullOrEmpty (subdir)) project_dir = Path.Combine (project_dir, subdir); @@ -154,6 +157,18 @@ protected static string GetProjectPath (string project, string? subdir = null, A return project_path; } + static bool TryGetTestProjectPath (string project, ApplePlatform platform, [NotNullWhen (true)] out string? projectPath) + { + projectPath = null; + + switch (project) { + case "monotouch-test": + projectPath = Path.Combine (Configuration.SourceRoot, "tests", project, "dotnet", platform.AsString (), project + ".csproj"); + return true; + } + return false; + } + protected string GetPlugInsRelativePath (ApplePlatform platform) { switch (platform) { From 5da9d43238b63cd0d6a0288854919d77bfee6323 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Fri, 5 Jun 2026 08:48:48 +0200 Subject: [PATCH 24/27] [dotnet-linker] Move methods from ManagedRegistrarLookupTablesStep to AppBundleRewriter. Move FindNSObjectConstructor, FindINativeObjectConstructor, ImplementConstructNSObjectFactoryMethod, ImplementConstructINativeObjectFactoryMethod, and AddTypeInterfaceImplementation from ManagedRegistrarLookupTablesStep to AppBundleRewriter, and update all call sites accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/dotnet-linker/AppBundleRewriter.cs | 171 +++++++++++++++++ .../Steps/ManagedRegistrarLookupTablesStep.cs | 174 +----------------- .../Steps/ManagedRegistrarStep.cs | 10 +- .../Steps/TrimmableRegistrarStep.cs | 6 +- 4 files changed, 183 insertions(+), 178 deletions(-) diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 1b3908f009ea..1914b02994d7 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; @@ -1726,5 +1727,175 @@ public MethodDefinition CreateInternalPInvoke (ModuleDefinition module, string @ return rv; } + + internal static MethodDefinition? FindNSObjectConstructor (TypeDefinition type) + { + return FindConstructorWithOneParameter ("ObjCRuntime", "NativeHandle") + ?? FindConstructorWithOneParameter ("System", "IntPtr"); + + MethodDefinition? FindConstructorWithOneParameter (string ns, string cls) + => type.Methods.SingleOrDefault (method => + method.IsConstructor + && !method.IsStatic + && method.HasParameters + && method.Parameters.Count == 1 + && method.Parameters [0].ParameterType.Is (ns, cls)); + } + + internal static MethodDefinition? FindINativeObjectConstructor (TypeDefinition type) + { + return FindConstructorWithTwoParameters ("ObjCRuntime", "NativeHandle", "System", "Boolean") + ?? FindConstructorWithTwoParameters ("System", "IntPtr", "System", "Boolean"); + + MethodDefinition? FindConstructorWithTwoParameters (string ns1, string cls1, string ns2, string cls2) + => type.Methods.SingleOrDefault (method => + method.IsConstructor + && !method.IsStatic + && method.HasParameters + && method.Parameters.Count == 2 + && method.Parameters [0].ParameterType.Is (ns1, cls1) + && method.Parameters [1].ParameterType.Is (ns2, cls2)); + } + + internal void ImplementConstructNSObjectFactoryMethod (Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference ctor) + { + var abr = this; + + // skip creating the factory for NSObject itself + if (type.Is ("Foundation", "NSObject")) + return; + + // Make sure the type implements INSObjectFactory, otherwise we can't override the _Xamarin_ConstructNSObject method from it. + AddTypeInterfaceImplementation (abr, context, type, abr.Foundation_INSObjectFactory); + + var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructNSObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.Foundation_NSObject); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + abr.Foundation_INSObjectFactory.Resolve ().IsPublic = true; + createInstanceMethod.Overrides.Add (abr.INSObjectFactory__Xamarin_ConstructNSObject); + var body = createInstanceMethod.CreateBody (out var il); + + if (type.HasGenericParameters) { + ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); + } + + // return new TypeA (nativeHandle); // for NativeHandle ctor + // return new TypeA ((IntPtr) nativeHandle); // for IntPtr ctor + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + + body.GenerateILOffsets (); + + // make sure the trimmer doesn't trim it away if the type is kept + if (context.App.Registrar == RegistrarMode.TrimmableStatic) { + // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) + abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); + } else { + context.Annotations.Mark (createInstanceMethod); + } + } + + internal void ImplementConstructINativeObjectFactoryMethod (Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference? ctor) + { + var abr = this; + + // skip creating the factory for NSObject itself + if (type.Is ("Foundation", "NSObject")) + return; + + // If the type is a subclass of NSObject, we prefer the NSObject "IntPtr" constructor + MethodReference? nsobjectConstructor = type.IsNSObject (context) ? AppBundleRewriter.FindNSObjectConstructor (type) : null; + if (nsobjectConstructor is null && ctor is null) + return; + + // Make sure the type implements INativeObject, otherwise we can't override the _Xamarin_ConstructINativeObject method from it. + AddTypeInterfaceImplementation (abr, context, type, abr.ObjCRuntime_INativeObject); + + var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructINativeObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean); + abr.INativeObject__Xamarin_ConstructINativeObject.Resolve ().IsPublic = true; + createInstanceMethod.Overrides.Add (abr.INativeObject__Xamarin_ConstructINativeObject); + var body = createInstanceMethod.CreateBody (out var il); + + if (nsobjectConstructor is not null) { + // var instance = new TypeA (nativeHandle); + // // alternatively with a cast: new TypeA ((IntPtr) nativeHandle); + // if (instance is not null && owns) + // Runtime.TryReleaseINativeObject (instance); + // return instance; + + if (type.HasGenericParameters) { + nsobjectConstructor = type.CreateMethodReferenceOnGenericType (nsobjectConstructor, type.GenericParameters.ToArray ()); + } + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (nsobjectConstructor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, nsobjectConstructor); + + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Cgt_Un); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.And); + il.Emit (OpCodes.Brfalse_S, falseTarget); + + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Call, abr.Runtime_TryReleaseINativeObject); + + il.Append (falseTarget); + + il.Emit (OpCodes.Ret); + } else if (ctor is not null) { + // return new TypeA (nativeHandle, owns); // for NativeHandle ctor + // return new TypeA ((IntPtr) nativeHandle, owns); // IntPtr ctor + + if (type.HasGenericParameters) { + ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); + } + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + } else { + throw new UnreachableException (); + } + + body.GenerateILOffsets (); + + // make sure the trimmer doesn't trim it away if the type is kept + if (context.App.Registrar == RegistrarMode.TrimmableStatic) { + // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) + abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); + } else { + context.Annotations.Mark (createInstanceMethod); + } + } + + static void AddTypeInterfaceImplementation (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, TypeReference iface) + { + if (type.HasInterfaces && type.Interfaces.Any (v => v.InterfaceType == iface)) + return; + + var ifaceImplementation = new InterfaceImplementation (iface); + type.Interfaces.Add (ifaceImplementation); + + // make sure the trimmer doesn't trim it away if the type is kept + if (context.App.Registrar == RegistrarMode.TrimmableStatic) { + // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) + abr.AddAttributeToStaticConstructor (type, abr.CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes.Interfaces, type)); + } else { + context.Annotations.Mark (ifaceImplementation); + context.Annotations.Mark (ifaceImplementation.InterfaceType); + context.Annotations.Mark (ifaceImplementation.InterfaceType.Resolve ()); + } + } } } diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 68bbe468e009..4396c7319478 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -328,7 +328,7 @@ void GenerateConstructNSObject (TypeDefinition registrarType) var types = GetRelevantTypes (type => type.IsNSObject (DerivedLinkContext) && !type.IsAbstract && !type.IsInterface); foreach (var type in types) { - var ctorRef = FindNSObjectConstructor (type); + var ctorRef = AppBundleRewriter.FindNSObjectConstructor (type); if (ctorRef is null) { App.Log (9, $"Cannot include {type.FullName} in ConstructNSObject because it doesn't have a suitable constructor"); continue; @@ -357,7 +357,7 @@ void GenerateConstructNSObject (TypeDefinition registrarType) } // In addition to the big lookup method, implement the static factory method on the type: - ImplementConstructNSObjectFactoryMethod (abr, DerivedLinkContext, type, ctor); + abr.ImplementConstructNSObjectFactoryMethod (DerivedLinkContext, type, ctor); } // return default (NSObject); @@ -386,7 +386,7 @@ void GenerateConstructINativeObject (TypeDefinition registrarType) var types = GetRelevantTypes (type => type.IsNativeObject () && !type.IsAbstract && !type.IsInterface); foreach (var type in types) { - var ctorRef = FindINativeObjectConstructor (type); + var ctorRef = AppBundleRewriter.FindINativeObjectConstructor (type); if (ctorRef is not null) { var ctor = abr.CurrentAssembly.MainModule.ImportReference (ctorRef); @@ -415,7 +415,7 @@ void GenerateConstructINativeObject (TypeDefinition registrarType) } // In addition to the big lookup method, implement the static factory method on the type: - ImplementConstructINativeObjectFactoryMethod (abr, DerivedLinkContext, type, ctorRef); + abr.ImplementConstructINativeObjectFactoryMethod (DerivedLinkContext, type, ctorRef); } // return default (NSObject) @@ -444,172 +444,6 @@ void MarkConstructorIfTrimmed (MethodReference ctor) } } - static void AddTypeInterfaceImplementation (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, TypeReference iface) - { - if (type.HasInterfaces && type.Interfaces.Any (v => v.InterfaceType == iface)) - return; - - var ifaceImplementation = new InterfaceImplementation (iface); - type.Interfaces.Add (ifaceImplementation); - - // make sure the trimmer doesn't trim it away if the type is kept - if (context.App.Registrar == RegistrarMode.TrimmableStatic) { - // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) - abr.AddAttributeToStaticConstructor (type, abr.CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes.Interfaces, type)); - } else { - context.Annotations.Mark (ifaceImplementation); - context.Annotations.Mark (ifaceImplementation.InterfaceType); - context.Annotations.Mark (ifaceImplementation.InterfaceType.Resolve ()); - } - } - - internal static void ImplementConstructNSObjectFactoryMethod (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference ctor) - { - // skip creating the factory for NSObject itself - if (type.Is ("Foundation", "NSObject")) - return; - - // Make sure the type implements INSObjectFactory, otherwise we can't override the _Xamarin_ConstructNSObject method from it. - AddTypeInterfaceImplementation (abr, context, type, abr.Foundation_INSObjectFactory); - - var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructNSObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.Foundation_NSObject); - var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); - abr.Foundation_INSObjectFactory.Resolve ().IsPublic = true; - createInstanceMethod.Overrides.Add (abr.INSObjectFactory__Xamarin_ConstructNSObject); - var body = createInstanceMethod.CreateBody (out var il); - - if (type.HasGenericParameters) { - ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); - } - - // return new TypeA (nativeHandle); // for NativeHandle ctor - // return new TypeA ((IntPtr) nativeHandle); // for IntPtr ctor - il.Emit (OpCodes.Ldarg, nativeHandleParameter); - if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) - il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); - il.Emit (OpCodes.Newobj, ctor); - il.Emit (OpCodes.Ret); - - body.GenerateILOffsets (); - - // make sure the trimmer doesn't trim it away if the type is kept - if (context.App.Registrar == RegistrarMode.TrimmableStatic) { - // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) - abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); - } else { - context.Annotations.Mark (createInstanceMethod); - } - } - - internal static void ImplementConstructINativeObjectFactoryMethod (AppBundleRewriter abr, Tuner.DerivedLinkContext context, TypeDefinition type, MethodReference? ctor) - { - // skip creating the factory for NSObject itself - if (type.Is ("Foundation", "NSObject")) - return; - - // If the type is a subclass of NSObject, we prefer the NSObject "IntPtr" constructor - MethodReference? nsobjectConstructor = type.IsNSObject (context) ? FindNSObjectConstructor (type) : null; - if (nsobjectConstructor is null && ctor is null) - return; - - // Make sure the type implements INativeObject, otherwise we can't override the _Xamarin_ConstructINativeObject method from it. - AddTypeInterfaceImplementation (abr, context, type, abr.ObjCRuntime_INativeObject); - - var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructINativeObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); - var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); - var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean); - abr.INativeObject__Xamarin_ConstructINativeObject.Resolve ().IsPublic = true; - createInstanceMethod.Overrides.Add (abr.INativeObject__Xamarin_ConstructINativeObject); - var body = createInstanceMethod.CreateBody (out var il); - - if (nsobjectConstructor is not null) { - // var instance = new TypeA (nativeHandle); - // // alternatively with a cast: new TypeA ((IntPtr) nativeHandle); - // if (instance is not null && owns) - // Runtime.TryReleaseINativeObject (instance); - // return instance; - - if (type.HasGenericParameters) { - nsobjectConstructor = type.CreateMethodReferenceOnGenericType (nsobjectConstructor, type.GenericParameters.ToArray ()); - } - - il.Emit (OpCodes.Ldarg, nativeHandleParameter); - if (nsobjectConstructor.Parameters [0].ParameterType.Is ("System", "IntPtr")) - il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); - il.Emit (OpCodes.Newobj, nsobjectConstructor); - - var falseTarget = il.Create (OpCodes.Nop); - il.Emit (OpCodes.Dup); - il.Emit (OpCodes.Ldnull); - il.Emit (OpCodes.Cgt_Un); - il.Emit (OpCodes.Ldarg, ownsParameter); - il.Emit (OpCodes.And); - il.Emit (OpCodes.Brfalse_S, falseTarget); - - il.Emit (OpCodes.Dup); - il.Emit (OpCodes.Call, abr.Runtime_TryReleaseINativeObject); - - il.Append (falseTarget); - - il.Emit (OpCodes.Ret); - } else if (ctor is not null) { - // return new TypeA (nativeHandle, owns); // for NativeHandle ctor - // return new TypeA ((IntPtr) nativeHandle, owns); // IntPtr ctor - - if (type.HasGenericParameters) { - ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); - } - - il.Emit (OpCodes.Ldarg, nativeHandleParameter); - if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) - il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); - il.Emit (OpCodes.Ldarg, ownsParameter); - il.Emit (OpCodes.Newobj, ctor); - il.Emit (OpCodes.Ret); - } else { - throw new UnreachableException (); - } - - body.GenerateILOffsets (); - - // make sure the trimmer doesn't trim it away if the type is kept - if (context.App.Registrar == RegistrarMode.TrimmableStatic) { - // TODO: need to investigate why this is needed (https://github.com/dotnet/macios/issues/25232) - abr.AddDynamicDependencyAttributeToStaticConstructor (type, createInstanceMethod); - } else { - context.Annotations.Mark (createInstanceMethod); - } - } - - internal static MethodDefinition? FindNSObjectConstructor (TypeDefinition type) - { - return FindConstructorWithOneParameter ("ObjCRuntime", "NativeHandle") - ?? FindConstructorWithOneParameter ("System", "IntPtr"); - - MethodDefinition? FindConstructorWithOneParameter (string ns, string cls) - => type.Methods.SingleOrDefault (method => - method.IsConstructor - && !method.IsStatic - && method.HasParameters - && method.Parameters.Count == 1 - && method.Parameters [0].ParameterType.Is (ns, cls)); - } - - internal static MethodDefinition? FindINativeObjectConstructor (TypeDefinition type) - { - return FindConstructorWithTwoParameters ("ObjCRuntime", "NativeHandle", "System", "Boolean") - ?? FindConstructorWithTwoParameters ("System", "IntPtr", "System", "Boolean"); - - MethodDefinition? FindConstructorWithTwoParameters (string ns1, string cls1, string ns2, string cls2) - => type.Methods.SingleOrDefault (method => - method.IsConstructor - && !method.IsStatic - && method.HasParameters - && method.Parameters.Count == 2 - && method.Parameters [0].ParameterType.Is (ns1, cls1) - && method.Parameters [1].ParameterType.Is (ns2, cls2)); - } - void GenerateRegisterWrapperTypes (TypeDefinition type) { var method = type.AddMethod ("RegisterWrapperTypes", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_Void); diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 70c4cc72a7bf..28d906d981db 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -183,22 +183,22 @@ bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos, List acti // INativeObject subclasses var inativeObjectTypes = StaticRegistrar.GetAllTypes (assembly).Where (t => !t.IsInterface && !t.IsAbstract && t.IsNativeObject ()); foreach (var tr in inativeObjectTypes.OrderBy (v => v.FullName)) { - var inativeObjCtor = ManagedRegistrarLookupTablesStep.FindINativeObjectConstructor (tr); + var inativeObjCtor = AppBundleRewriter.FindINativeObjectConstructor (tr); if (inativeObjCtor is null) continue; @@ -373,7 +373,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti var createObjectMethod = proxyType.AddMethod ("CreateObject", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, abr.Foundation_NSObject); createObjectMethod.AddParameter ("handle", abr.System_IntPtr); il = createObjectMethod.Body.GetILProcessor (); - var nativeHandleCtor = ManagedRegistrarLookupTablesStep.FindNSObjectConstructor (td); + var nativeHandleCtor = AppBundleRewriter.FindNSObjectConstructor (td); if (nativeHandleCtor is not null) { il.Append (il.Create (OpCodes.Ldarg_1)); if (nativeHandleCtor.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle")) @@ -512,7 +512,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti createObjectMethod.AddParameter ("handle", abr.System_IntPtr); createObjectMethod.AddParameter ("owns", abr.System_Boolean); createObjectMethod.CreateBody (out il); - var nativeHandleCtor = ManagedRegistrarLookupTablesStep.FindINativeObjectConstructor (objcType.ProtocolWrapperType.Resolve ()); + var nativeHandleCtor = AppBundleRewriter.FindINativeObjectConstructor (objcType.ProtocolWrapperType.Resolve ()); if (nativeHandleCtor is not null) { il.Append (il.Create (OpCodes.Ldarg_1)); if (nativeHandleCtor.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle")) From 23cb3bc297a2aef60a4a0be7889a8911840d3e47 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 Jan 2026 11:45:54 +0100 Subject: [PATCH 25/27] [assembly-preparer] Create a new tool to replace pre-mark custom linker steps. Create an 'assembly-preparer' tool that performs assembly pre-processing (currently done by custom linker steps) as a standalone build step, gated behind the opt-in `PrepareAssemblies=true` MSBuild property. Key changes: * New `tools/assembly-preparer/` library with scaffolding that emulates a subset of ILLink's API (this makes it easy to reuse the code for existing custom trimmer step by just running the same steps in the new assembly-preparer tool instead). * New `PrepareAssemblies` MSBuild task and `_PrepareAssemblies` target in Xamarin.Shared.targets. * When `PrepareAssemblies=true`, most custom linker steps/mark handlers are disabled in the trimmer pipeline (conditions added to Xamarin.Shared.Sdk.targets). * Refactor `LinkerConfiguration.cs` with `#if ASSEMBLY_PREPARER` to support both linker and assembly-preparer contexts. * New test project `tests/assembly-preparer/` with tests for the assembly preparation steps. * Add xharness test variation for running with assembly-preparer enabled. * New `tools/ap-launcher/` to invoke the tool from the command line; this is useful during debugging. Contributes towards https://github.com/dotnet/macios/issues/17693. --- dotnet/targets/Xamarin.Shared.Sdk.targets | 82 ++-- msbuild/ILMerge.targets | 1 + .../MSBStrings.resx | 5 + msbuild/Xamarin.MacDev.Tasks.slnx | 4 + .../ConsoleToTaskWriter.cs | 103 +++++ .../ErrorHelper.msbuild.cs | 54 --- .../Xamarin.MacDev.Tasks/LoggingExtensions.cs | 23 ++ .../Tasks/PrepareAssemblies.cs | 110 +++++ .../Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs | 39 +- .../Xamarin.MacDev.Tasks.csproj | 36 +- msbuild/Xamarin.Shared/Xamarin.Shared.targets | 52 +++ src/ObjCRuntime/Registrar.core.cs | 8 + src/ObjCRuntime/Registrar.cs | 12 + src/bgen/BindingTouch.cs | 7 +- tests/assembly-preparer/BaseClass.cs | 98 +++++ tests/assembly-preparer/GlobalUsings.cs | 17 + .../InlineDlfcnMethodsStepTests.cs | 54 +++ tests/assembly-preparer/Makefile | 8 + .../MarkIProtocolHandlerTests.cs | 73 ++++ .../OptimizeGeneratedCodeHandlerTests.cs | 251 +++++++++++ .../PreserveBlockCodeHandlerTests.cs | 52 +++ .../PreserveSmartEnumConversionsTest.cs | 119 ++++++ tests/assembly-preparer/ReproTest.cs | 203 +++++++++ .../assembly-preparer-tests.csproj | 50 +++ .../assembly-preparer/assembly-preparer.slnx | 4 + tests/common/Configuration.cs | 57 +++ tests/common/DotNet.cs | 12 +- tests/common/test-variations.csproj | 6 + tests/linker/link all/dotnet/shared.csproj | 1 + .../xharness/Jenkins/TestVariationsFactory.cs | 34 ++ tools/Makefile | 1 + tools/ap-launcher/.gitignore | 2 + tools/ap-launcher/Makefile | 8 + tools/ap-launcher/Program.cs | 74 ++++ tools/ap-launcher/README.md | 2 + tools/ap-launcher/ap-launcher.csproj | 17 + tools/ap-launcher/ap-launcher.slnx | 5 + tools/assembly-preparer/.gitignore | 2 + tools/assembly-preparer/AssemblyPreparer.cs | 388 ++++++++++++++++++ .../DynamicallyAccessedMemberTypes.cs | 176 ++++++++ tools/assembly-preparer/GlobalUsings.cs | 16 + .../assembly-preparer/IAssemblyPreparerLog.cs | 26 ++ tools/assembly-preparer/Makefile | 25 ++ .../NetStandardExtensions.cs | 109 +++++ tools/assembly-preparer/README.md | 35 ++ .../Scaffolding/AnnotationStore.cs | 167 ++++++++ .../Scaffolding/AssemblyAction.cs | 15 + .../assembly-preparer/Scaffolding/BaseStep.cs | 85 ++++ tools/assembly-preparer/Scaffolding/IStep.cs | 38 ++ .../Scaffolding/LinkContext.cs | 48 +++ tools/assembly-preparer/Scaffolding/Target.cs | 11 + ...System_Diagnostics_UnreachableException.cs | 48 +++ tools/assembly-preparer/System_Index.cs | 170 ++++++++ tools/assembly-preparer/System_Range.cs | 141 +++++++ .../assembly-preparer.csproj | 341 +++++++++++++++ .../assembly-preparer/assembly-preparer.slnx | 5 + tools/common/Application.cs | 52 ++- tools/common/Assembly.cs | 9 +- tools/common/CoreResolver.cs | 2 + tools/common/DerivedLinkContext.cs | 17 +- tools/common/Driver.cs | 7 +- tools/common/ErrorHelper.tools.cs | 6 + tools/common/IToolLog.cs | 18 +- tools/common/Make.common | 3 + tools/common/Makefile | 5 + tools/common/NullableAttributes.cs | 18 + tools/common/Optimizations.cs | 2 +- tools/common/StaticRegistrar.cs | 10 +- tools/common/cache.cs | 2 + tools/common/error.cs | 7 + .../Program.cs | 126 +++++- tools/dotnet-linker/AppBundleRewriter.cs | 77 +++- .../ApplyPreserveAttributeBase.cs | 3 +- .../ApplyPreserveAttributeStep.cs | 6 + tools/dotnet-linker/Compat.cs | 2 +- tools/dotnet-linker/DotNetGlobals.cs | 1 + tools/dotnet-linker/DotNetResolver.cs | 6 + tools/dotnet-linker/LinkerConfiguration.cs | 143 ++++++- tools/dotnet-linker/MarkIProtocolHandler.cs | 20 +- .../Steps/ExceptionalMarkHandler.cs | 4 + .../Steps/InlineDlfcnMethodsStep.cs | 2 +- .../Steps/ManagedRegistrarLookupTablesStep.cs | 34 +- .../Steps/ManagedRegistrarStep.cs | 86 +++- .../Steps/PreserveBlockCodeHandler.cs | 3 +- .../Steps/SetBeforeFieldInitStep.cs | 33 +- .../Steps/TrimmableRegistrarStep.cs | 32 +- tools/linker/CoreTypeMapStep.cs | 6 +- tools/linker/MarkNSObjects.cs | 3 +- tools/linker/MobileExtensions.cs | 2 +- tools/linker/MonoTouch.Tuner/Extensions.cs | 4 + tools/linker/ObjCExtensions.cs | 6 +- tools/linker/OptimizeGeneratedCode.cs | 7 +- tools/mtouch/Errors.designer.cs | 9 + tools/mtouch/Errors.resx | 4 + tools/mtouch/mtouch.cs | 3 +- tools/tools.slnx | 1 + 96 files changed, 4092 insertions(+), 219 deletions(-) create mode 100644 msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs delete mode 100644 msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs create mode 100644 msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs create mode 100644 tests/assembly-preparer/BaseClass.cs create mode 100644 tests/assembly-preparer/GlobalUsings.cs create mode 100644 tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs create mode 100644 tests/assembly-preparer/Makefile create mode 100644 tests/assembly-preparer/MarkIProtocolHandlerTests.cs create mode 100644 tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs create mode 100644 tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs create mode 100644 tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs create mode 100644 tests/assembly-preparer/ReproTest.cs create mode 100644 tests/assembly-preparer/assembly-preparer-tests.csproj create mode 100644 tests/assembly-preparer/assembly-preparer.slnx create mode 100644 tools/ap-launcher/.gitignore create mode 100644 tools/ap-launcher/Makefile create mode 100644 tools/ap-launcher/Program.cs create mode 100644 tools/ap-launcher/README.md create mode 100644 tools/ap-launcher/ap-launcher.csproj create mode 100644 tools/ap-launcher/ap-launcher.slnx create mode 100644 tools/assembly-preparer/.gitignore create mode 100644 tools/assembly-preparer/AssemblyPreparer.cs create mode 100644 tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs create mode 100644 tools/assembly-preparer/GlobalUsings.cs create mode 100644 tools/assembly-preparer/IAssemblyPreparerLog.cs create mode 100644 tools/assembly-preparer/Makefile create mode 100644 tools/assembly-preparer/NetStandardExtensions.cs create mode 100644 tools/assembly-preparer/README.md create mode 100644 tools/assembly-preparer/Scaffolding/AnnotationStore.cs create mode 100644 tools/assembly-preparer/Scaffolding/AssemblyAction.cs create mode 100644 tools/assembly-preparer/Scaffolding/BaseStep.cs create mode 100644 tools/assembly-preparer/Scaffolding/IStep.cs create mode 100644 tools/assembly-preparer/Scaffolding/LinkContext.cs create mode 100644 tools/assembly-preparer/Scaffolding/Target.cs create mode 100644 tools/assembly-preparer/System_Diagnostics_UnreachableException.cs create mode 100644 tools/assembly-preparer/System_Index.cs create mode 100644 tools/assembly-preparer/System_Range.cs create mode 100644 tools/assembly-preparer/assembly-preparer.csproj create mode 100644 tools/assembly-preparer/assembly-preparer.slnx create mode 100644 tools/common/Makefile diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index ccbe646239a8..a40c8eeb6a41 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -271,6 +271,7 @@ _ResolveAppExtensionReferences; _ExtendAppExtensionReferences; _ComputeLinkerArguments; + _PrepareAssemblies; _ComputeFrameworkFilesToPublish; _ComputeDynamicLibrariesToPublish; ComputeFilesToPublish; @@ -579,6 +580,9 @@ true $(_TypeMapAssemblyName) + + <_ExtraTrimmerArgs Condition="'$(Registrar)' == 'trimmable-static' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '11.0'))">$(_ExtraTrimmerArgs) --typemap-entry-assembly "$(_TypeMapAssemblyName)" <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.CollectAssembliesStep" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.CoreTypeMapStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.ProcessExportedFields" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.OptimizeGeneratedCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseLinkDescriptionForApplyPreserveAttribute)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkForStaticRegistrarStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkStaticRegistrar)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkNSObjectsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkNSObjects)' == 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" Condition="'$(InlineDlfcnMethods)' != ''" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.OptimizeGeneratedCodeStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseLinkDescriptionForApplyPreserveAttribute)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkForStaticRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkStaticRegistrar)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkNSObjectsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkNSObjects)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(InlineDlfcnMethods)' != ''" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" Condition="'$(PrepareAssemblies)' != 'true'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreMarkDispatcher" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(Registrar)' == 'trimmable-static'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineClassGetHandleStep" Condition="'$(InlineClassGetHandle)' != '' And '$(InlineClassGetHandle)' != 'disabled'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreMarkDispatcher" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And ('$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static')" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(Registrar)' == 'trimmable-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineClassGetHandleStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(InlineClassGetHandle)' != '' And '$(InlineClassGetHandle)' != 'disabled'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' != 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' != 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkDispatcher)' != 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkDispatcher)' != 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' != 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="SweepStep" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(Registrar)' == 'managed-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="SweepStep" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(Registrar)' == 'managed-static'" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" AfterStep="SweepStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PostSweepDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" AfterStep="SweepStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PostSweepDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(PrepareAssemblies)' == 'true' And ('$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static')" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(PrepareAssemblies)' == 'true' And '$(Registrar)' == 'trimmable-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(PrepareAssemblies)' == 'true' And '$(Registrar)' == 'managed-static'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.RegistrarStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.GenerateMainStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.GenerateReferencesStep" /> @@ -1328,6 +1341,9 @@ <_TypeMapFilePath Condition="'$(_TypeMapFilePath)' == ''">$(_LinkerCacheDirectory)/type-map.txt + + + <_UnmanagedCallersOnlyMapPath Condition="'$(_UnmanagedCallersOnlyMapPath)' == ''">$(_LinkerCacheDirectory)/unmanaged_callers_only_map.txt @@ -1413,13 +1429,27 @@ - - - - + + + + + + + + + + + <_IntermediateAssemblyProperty>@(IntermediateAssembly) + <_PreparedIntermediateAssemblyProperty>@(PreparedIntermediateAssembly->WithMetadataValue('BeforePrepareAssembliesPath','$(_IntermediateAssemblyProperty)')) + + + <_PreparedRootedIntermediateAssembly Include="@(TrimmerRootAssembly->'$(_PreparedIntermediateAssemblyProperty)')" Condition="'%(Identity)' == '$(_IntermediateAssemblyProperty)'" /> + + + + diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index 5de6bb55b800..0741be004f37 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1661,4 +1661,9 @@ Shown when the tool reports a simulator runtime version mismatch. {0} - The platform name (e.g. "iOS" or "tvOS"). + + Console.StandardOutput or Console.StandardError was accessed during a build task. This should not happen, use the MSBuild logging infrastructure instead. Stack trace: {0} + Shown when an MSBuild task writes to the console instead of using MSBuild logging. +{0} - The stack trace of the offending call. + diff --git a/msbuild/Xamarin.MacDev.Tasks.slnx b/msbuild/Xamarin.MacDev.Tasks.slnx index e3c799a54619..1f17eb9aa95f 100644 --- a/msbuild/Xamarin.MacDev.Tasks.slnx +++ b/msbuild/Xamarin.MacDev.Tasks.slnx @@ -36,4 +36,8 @@ + + + + diff --git a/msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs b/msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs new file mode 100644 index 000000000000..1e97dc541697 --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/ConsoleToTaskWriter.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +#nullable enable + +namespace Xamarin.Utils; + +class ConsoleToTaskWriter : TextWriter { + TaskLoggingHelper helper; + bool errorShown; + + public ConsoleToTaskWriter (TaskLoggingHelper helper) + { + this.helper = helper; + } + + public override Encoding Encoding => Encoding.UTF8; + + public override void Write (char value) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, value.ToString ()); + } + + public override void Write (char [] buffer, int index, int count) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, new string (buffer, index, count)); + } + + public override void Write (string? value) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, value ?? string.Empty); + } + + public override void WriteLine () + { + ShowError (); + } + + public override void WriteLine (string? value) + { + ShowError (); + helper.LogMessage (MessageImportance.Low, value ?? string.Empty); + } + + void ShowError () + { + if (errorShown) + return; + errorShown = true; + + helper.LogError (null, "MT7178" /* Console.StandardOutput or Console.StandardError was accessed during a build task. This should not happen, use the MSBuild logging infrastructure instead. Stack trace: {0} */, null, null, 0, 0, 0, 0, Xamarin.Localization.MSBuild.MSBStrings.E7178, Environment.StackTrace); + } + + public static IDisposable EnsureNoConsoleUsage (TaskLoggingHelper log) + { + return new NoConsoleUsage (new ConsoleToTaskWriter (log)); + } + + class NoConsoleUsage : IDisposable { + TextWriter? originalStdout; + TextWriter? originalStderr; + + public NoConsoleUsage (ConsoleToTaskWriter redirector) + { + originalStdout = Console.Out; + originalStderr = Console.Error; + Console.SetOut (redirector); + Console.SetError (redirector); + } + + ~NoConsoleUsage () + { + Restore (); + } + + void IDisposable.Dispose () + { + Restore (); + GC.SuppressFinalize (this); + } + + void Restore () + { + if (originalStdout is not null) { + Console.SetOut (originalStdout); + originalStdout = null; + } + if (originalStderr is not null) { + Console.SetError (originalStderr); + originalStderr = null; + } + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs b/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs deleted file mode 100644 index 8bda5c48e690..000000000000 --- a/msbuild/Xamarin.MacDev.Tasks/ErrorHelper.msbuild.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021, Microsoft Corp. All rights reserved, - -using System.Collections.Generic; - -using Xamarin.Utils; - -#nullable enable - -namespace Xamarin.Bundler { - public static partial class ErrorHelper { - public static ApplePlatform Platform; - - internal static string GetPrefix (IToolLog? log) - { - return Xamarin.MacDev.Tasks.LoggingExtensions.ErrorPrefix; - } - - public enum WarningLevel { - Error = -1, - Warning = 0, - Disable = 1, - } - - static Dictionary? warning_levels; - - public static WarningLevel GetWarningLevel (IToolLog log, int code) - { - WarningLevel level; - - if (warning_levels is null) - return WarningLevel.Warning; - - // code -1: all codes - if (warning_levels.TryGetValue (-1, out level)) - return level; - - if (warning_levels.TryGetValue (code, out level)) - return level; - - return WarningLevel.Warning; - } - - public static void SetWarningLevel (WarningLevel level, int? code = null /* if null, apply to all warnings */) - { - if (warning_levels is null) - warning_levels = new Dictionary (); - if (code.HasValue) { - warning_levels [code.Value] = level; - } else { - warning_levels [-1] = level; // code -1: all codes. - } - } - } -} diff --git a/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs b/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs index 367f3f5bd46a..2f0f9b7a5538 100644 --- a/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs +++ b/msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs @@ -74,6 +74,19 @@ public static void LogTaskProperty (this TaskLoggingHelper log, string propertyN log.LogMessage (TaskPropertyImportance, " {0}: {1}", propertyName, value); } + /// + /// Creates an MSBuild error following our MTErrors convention. + /// + /// For every new error we need to update "docs/website/mtouch-errors.md" and "tools/mtouch/error.cs". + /// In the 7xxx range for MSBuild error. + /// The error's message to be displayed in the error pad. + /// Path to the known guilty file or null. + /// Line number in the file where the error was found, or 0 if unknown. + public static void LogError (this TaskLoggingHelper log, int errorCode, string? fileName, int lineNumber, string message, params object? [] args) + { + log.LogError (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", lineNumber, 0, 0, 0, message, args); + } + /// /// Creates an MSBuild error following our MTErrors convention. /// @@ -91,6 +104,16 @@ public static void LogWarning (this TaskLoggingHelper log, int errorCode, string log.LogWarning (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", 0, 0, 0, 0, message, args); } + public static void LogWarning (this TaskLoggingHelper log, int errorCode, string? fileName, int lineNumber, string message, params object? [] args) + { + log.LogWarning (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", lineNumber, 0, 0, 0, message, args); + } + + public static void LogMessage (this TaskLoggingHelper log, MessageImportance importance, int errorCode, string? fileName, int lineNumber, string message, params object? [] args) + { + log.LogMessage (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", lineNumber, 0, 0, 0, importance, message, args); + } + public static bool LogErrorsFromException (this TaskLoggingHelper log, Exception exception, bool showStackTrace = true, bool showDetail = true) { var exceptions = new List (); diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs new file mode 100644 index 000000000000..6c5b49233278 --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +using Xamarin.Build; +using Xamarin.Bundler; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.MacDev.Tasks { + // This task is not supposed to be remoted (it doesn't need to run on a Mac). + public class PrepareAssemblies : XamarinTask { + const string ErrorPrefix = "MX"; + + #region Inputs + [Required] + public ITaskItem [] InputAssemblies { get; set; } = []; + + public string MakeReproPath { get; set; } = ""; + + public string OutputDirectory { get; set; } = ""; + + [Required] + public ITaskItem? OptionsFile { get; set; } + #endregion + + #region Outputs + [Output] + public ITaskItem [] OutputAssemblies { get; set; } = []; + #endregion + + Dictionary map = new (); + + AssemblyPreparerInfo GetAssemblyInfo (ITaskItem item) + { + var inputPath = item.ItemSpec; + var outputPath = Path.Combine (OutputDirectory, Path.GetFileName (inputPath)); + var isTrimmableString = item.GetMetadata ("IsTrimmable"); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = item.GetMetadata ("TrimMode"); + var rv = new AssemblyPreparerInfo (inputPath, outputPath, isTrimmable, trimMode); + map [rv] = item; + return rv; + } + + public override bool Execute () + { + // Capture Console usage and show an error if anything uses Console.[Error.]Write* + using var consoleToLog = ConsoleToTaskWriter.EnsureNoConsoleUsage (Log); + + try { + var infos = InputAssemblies.Select (GetAssemblyInfo).ToArray (); + using var preparer = new AssemblyPreparer (this, infos, OptionsFile?.ItemSpec ?? ""); + preparer.MakeReproPath = MakeReproPath; + var rv = preparer.Prepare (out var exceptions); + + foreach (var pe in exceptions) { + if (pe.IsError (this)) { + ((IToolLog) this).LogError (pe); + } else { + ((IToolLog) this).LogWarning (pe); + } + } + + var outputAssemblies = preparer.Assemblies.Select (v => { + var item = new TaskItem (v.OutputPath); + map [v].CopyMetadataTo (item); + item.SetMetadata ("BeforePrepareAssembliesPath", v.InputPath); + return (ITaskItem) item; + }).ToList (); + + outputAssemblies.AddRange (preparer.AddedAssemblies.Select (v => { + var rv = new TaskItem (v.Path); + rv.SetMetadata ("PostprocessAssembly", "true"); + rv.SetMetadata ("RelativePath", preparer.Configuration.AssemblyPublishDir + Path.GetFileName (v.Path)); + if (v.OriginatingAssembly is not null) { + var originatingItem = map.SingleOrDefault (kvp => Path.GetFileName (kvp.Key.InputPath) == Path.GetFileName (v.OriginatingAssembly)).Value; + if (originatingItem is null) { + Log.LogMessage (MessageImportance.Low, $"Could not find originating assembly for {v.Path} with originating assembly name {v.OriginatingAssembly}"); + } else { + var metadata = originatingItem.MetadataNames.Cast ().ToList (); + if (metadata.Contains ("TrimMode")) + rv.SetMetadata ("TrimMode", originatingItem.GetMetadata ("TrimMode")); + if (metadata.Contains ("IsTrimmable")) + rv.SetMetadata ("IsTrimmable", originatingItem.GetMetadata ("IsTrimmable")); + } + } + return rv; + })); + + OutputAssemblies = outputAssemblies.ToArray (); + return rv && !Log.HasLoggedErrors; + } catch (Exception e) { + ((IToolLog) this).LogException (e); + return false; + } + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs index 3cd5a9386821..e50cee0ce94a 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs @@ -13,7 +13,6 @@ using Xamarin.Localization.MSBuild; using Xamarin.Messaging.Build.Client; using Xamarin.Utils; -using static Xamarin.Bundler.FileCopier; #nullable enable @@ -246,7 +245,7 @@ internal static bool ExecuteRemotely (T task) where T : Task, IHasSessionId } #if NET - internal static bool ExecuteRemotely (T task, [NotNullWhen (true)] out TaskRunner? taskRunner, Action? preprocessTaskRunner = null) where T: Task, IHasSessionId + internal static bool ExecuteRemotely (T task, [NotNullWhen (true)] out TaskRunner? taskRunner, Action? preprocessTaskRunner = null) where T : Task, IHasSessionId #else internal static bool ExecuteRemotely (T task, out TaskRunner taskRunner, Action? preprocessTaskRunner = null) where T : Task, IHasSessionId #endif @@ -371,8 +370,11 @@ void ICustomLogger.LogError (string message, Exception? ex) { if (!string.IsNullOrEmpty (message)) Log.LogError (message); - if (ex is not null) + if (ex is ProductException pe) { + LogDiagnostic (pe); + } else if (ex is not null) { Log.LogErrorFromException (ex); + } } void ICustomLogger.LogWarning (string messageFormat, params object? [] args) @@ -404,12 +406,37 @@ void IToolLog.LogError (string message) void IToolLog.LogException (Exception exception) { - ((ICustomLogger) this).LogError ("", exception); + if (exception is ProductException pe) { + LogDiagnostic (pe); + } else { + ((ICustomLogger) this).LogError ($"Unexpected exception '{GetType ().Name}': {exception.Message}", exception); + } + } + + void IToolLog.LogError (ProductException exception) + { + LogDiagnostic (exception); } - void IToolLog.LogError (Exception exception) + void IToolLog.LogWarning (ProductException exception) { - ((ICustomLogger) this).LogError ("", exception); + LogDiagnostic (exception); + } + + protected void LogDiagnostic (ProductException exception) + { + switch (exception.GetWarningLevel (this)) { + case ErrorHelper.WarningLevel.Warning: + Log.LogWarning (exception.Code, exception.FileName, exception.LineNumber, exception.Message); + break; + case ErrorHelper.WarningLevel.Error: + Log.LogError (exception.Code, exception.FileName, exception.LineNumber, exception.Message); + break; + case ErrorHelper.WarningLevel.Disable: + default: + Log.LogMessage (MessageImportance.Low, exception.Code, exception.FileName, exception.LineNumber, exception.Message); + break; + } } #endregion } diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj index 713db306ebef..cbd090125523 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj +++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj @@ -1,7 +1,8 @@ - netstandard2.0;net$(BundledNETCoreAppTargetFrameworkVersion) + net$(BundledNETCoreAppTargetFrameworkVersion);netstandard2.0 + net$(BundledNETCoreAppTargetFrameworkVersion) false compile true @@ -37,6 +38,7 @@ + ProjectReference @@ -69,45 +71,15 @@ - - ApplePlatform.cs - - JsonExtensions.cs - - - IToolLog.cs + external\JsonExtensions.cs StringUtils.cs - - Symbols.cs - - - FileCopier.cs - - - TargetFramework.cs - - - Execution.cs - - - PathUtils.cs - PListExtensions.cs - - MachO.cs - - - error.cs - - - ErrorHelper.cs - external\RuntimeException.cs diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 8307a85ebf48..38577d9196a7 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -84,6 +84,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. + @@ -3395,6 +3396,57 @@ Copyright (C) 2018 Microsoft. All rights reserved. + + false + + + + <_PrepareAssembliesForPreparationDependsOn> + _ComputeAssembliesToPostprocessOnPublish; + _ComputeLinkerInputs; + + + + + + + <_AssembliesToPrepare Include="@(ResolvedFileToPublish)" Condition="'%(ResolvedFileToPublish.Extension)' == '.dll' And '%(ResolvedFileToPublish.Culture)' == '' And !$([System.String]::Copy ('%(Filename)').EndsWith ('.resources'))" /> + + + $([MSBuild]::EnsureTrailingSlash('$(DeviceSpecificIntermediateOutputPath)prepared-assemblies')) + + + + + + + + + + + + + + + + diff --git a/src/ObjCRuntime/Registrar.core.cs b/src/ObjCRuntime/Registrar.core.cs index 958a17174874..86cd2436e8d0 100644 --- a/src/ObjCRuntime/Registrar.core.cs +++ b/src/ObjCRuntime/Registrar.core.cs @@ -8,7 +8,11 @@ abstract partial class Registrar { [return: NotNullIfNotNull (nameof (getterSelector))] internal static string? CreateSetterSelector (string? getterSelector) { +#if NET if (string.IsNullOrEmpty (getterSelector)) +#else + if (string.IsNullOrEmpty (getterSelector) || getterSelector is null) +#endif return getterSelector; var first = (int) getterSelector [0]; @@ -21,7 +25,11 @@ abstract partial class Registrar { [return: NotNullIfNotNull (nameof (name))] public static string? SanitizeObjectiveCName (string? name) { +#if NET if (string.IsNullOrEmpty (name)) +#else + if (string.IsNullOrEmpty (name) || name is null) +#endif return name; StringBuilder? sb = null; diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index d33f63746e84..303004248263 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -220,7 +220,11 @@ public void VerifyRegisterAttribute ([NotNullIfNotNull (nameof (exceptions))] re return; var name = RegisterAttribute.Name; +#if NET if (string.IsNullOrEmpty (name)) +#else + if (string.IsNullOrEmpty (name) || name is null) +#endif return; for (int i = 0; i < name.Length; i++) { @@ -540,7 +544,11 @@ abstract internal class ObjCMember { public bool SetExportAttribute (ExportAttribute ea, [NotNullIfNotNull (nameof (exceptions))] ref List? exceptions) { +#if NET if (string.IsNullOrEmpty (ea.Selector)) { +#else + if (string.IsNullOrEmpty (ea.Selector) || ea.Selector is null) { +#endif AddException (ref exceptions, Registrar.CreateException (4135, this, Errors.MT4135, FullName)); return false; } @@ -2181,7 +2189,11 @@ void FlattenInterfaces (TType? [] ifaces) objcType.Add (objcGetter, ref exceptions); +#if NET if (!string.IsNullOrEmpty (attrib.SetterSelector)) { +#else + if (!string.IsNullOrEmpty (attrib.SetterSelector) && attrib.SetterSelector is not null) { +#endif var objcSetter = new ObjCMethod (this, objcType, null) { Name = attrib.Name, Selector = attrib.SetterSelector, diff --git a/src/bgen/BindingTouch.cs b/src/bgen/BindingTouch.cs index 7b9e2d32dadb..2744ae3f2547 100644 --- a/src/bgen/BindingTouch.cs +++ b/src/bgen/BindingTouch.cs @@ -587,7 +587,12 @@ public void LogError (string message) Console.Error.WriteLine (message); } - public void LogError (Exception exception) + public void LogError (BindingException exception) + { + ErrorHelper.Show (exception); + } + + public void LogWarning (BindingException exception) { ErrorHelper.Show (exception); } diff --git a/tests/assembly-preparer/BaseClass.cs b/tests/assembly-preparer/BaseClass.cs new file mode 100644 index 000000000000..93e3f02a0011 --- /dev/null +++ b/tests/assembly-preparer/BaseClass.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AssemblyPreparerTests; + +public abstract class BaseClass { + public void AssertPrepare (AssemblyPreparer preparer) + { + if (!preparer.Prepare (out var exceptions)) { + foreach (var ex in exceptions) { + Console.WriteLine (ex.ToString ()); + if (ex.InnerException is not null) + Console.WriteLine ($" Inner: {ex.InnerException}"); + } + Assert.Fail ($"Prepare failed, exceptions:\n\t{string.Join ("\n\t", exceptions.Select (v => v.ToString ()))}"); + } + Assert.That (exceptions, Is.Empty, "Exceptions"); + } + + public bool AssertPrepare (ApplePlatform platform, bool isCoreCLR, string code, out AssemblyDefinition assemblyDefinition) + { + return AssertPrepare (platform, isCoreCLR, RegistrarMode.Dynamic, code, out assemblyDefinition); + } + + // returns true if the test assembly was modified + public bool AssertPrepare (ApplePlatform platform, bool isCoreCLR, RegistrarMode registrar, string code, out AssemblyDefinition assemblyDefinition) + { + AssemblyPreparer? preparer = null; + var rv = AssertPrepareCode (platform, isCoreCLR, p => { + p.Registrar = registrar; + preparer = p; + }, code, out string outputPath); + var resolver = new DefaultAssemblyResolver (); + var dirs = preparer!.Assemblies.Select (v => Path.GetDirectoryName (v.OutputPath)).Distinct ().ToList (); + dirs.ForEach (v => resolver.AddSearchDirectory (v)); + var readerParameters = new ReaderParameters { + ReadSymbols = true, + AssemblyResolver = resolver, + }; + assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath, readerParameters); + return rv; + } + + // returns true if the test assembly was modified + public bool AssertPrepareCode (ApplePlatform platform, bool isCoreCLR, Action? configure, string code, out string outputPath) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + var csproj = $@" + + + net$(BundledNETCoreAppTargetFrameworkVersion)-{platform.AsString ().ToLower ()} + false + true + + + "; + + var tmpdir = Xamarin.Cache.CreateTemporaryDirectory (); + + var config = $@" + AreAnyAssembliesTrimmed=true + PublishTrimmed=true + IntermediateOutputPath={Path.Combine (tmpdir, "intermediate")} + Platform={platform.AsString ()} + PlatformAssembly=Microsoft.{platform.AsString ()}.dll + SdkDevPath={Configuration.XcodeLocation} + SdkVersion={Configuration.GetSdkVersion (platform)} + TargetFramework={TargetFramework.GetTargetFramework (platform)} + "; + var configpath = Path.Combine (tmpdir, "config.txt"); + File.WriteAllText (configpath, config); + + File.WriteAllText (Path.Combine (tmpdir, "Test.cs"), code); + var csprojPath = Path.Combine (tmpdir, "Test.csproj"); + File.WriteAllText (csprojPath, csproj); + var properties = new Dictionary { + { "TreatWarningsAsErrors", "false" }, + }; + DotNet.AssertBuild (csprojPath, properties); + var assemblyDir = Path.Combine (tmpdir, "bin", "Debug"); + + var assemblies = Configuration.GetImplementationAssemblies (platform, isCoreCLR); + assemblies.Add (Path.Combine (assemblyDir, "Test.dll")); + var infos = assemblies.Select (v => new AssemblyPreparerInfo (v, Path.Combine (assemblyDir, "out", Path.GetFileName (v)), true, "link")).ToArray (); + var logger = new TestLogger () { Platform = platform }; + var preparer = new AssemblyPreparer (logger, infos, configpath); + if (configure is not null) + configure (preparer); + AssertPrepare (preparer); + + var testInfo = infos.Single (v => Path.GetFileNameWithoutExtension (v.InputPath) == "Test"); + outputPath = testInfo.OutputPath; + Console.WriteLine ("Output assembly: " + outputPath); + preparer.Dispose (); + return testInfo.InputPath != testInfo.OutputPath; + } +} diff --git a/tests/assembly-preparer/GlobalUsings.cs b/tests/assembly-preparer/GlobalUsings.cs new file mode 100644 index 000000000000..4e06615734f1 --- /dev/null +++ b/tests/assembly-preparer/GlobalUsings.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using System.IO; +global using System.Runtime.InteropServices; + +global using Mono.Cecil; +global using NUnit.Framework; + +global using Xamarin; +global using Xamarin.Bundler; +global using Xamarin.Build; +global using Xamarin.Tests; +global using Xamarin.Utils; + +// These tests are rather memory hungry, so running them in parallel really makes my machine crawl. +// [assembly: Parallelizable (ParallelScope.Children)] diff --git a/tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs b/tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs new file mode 100644 index 000000000000..fa0d1f66f6a4 --- /dev/null +++ b/tests/assembly-preparer/InlineDlfcnMethodsStepTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class InlineDlfcnMethodsStepTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void MarkedTest (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using CoreAnimation; + using Foundation; + using ObjCRuntime; + + class MyClass : NSObject { + void GetIntPtr () + { + Console.WriteLine (Dlfcn.GetIntPtr (0, ""NativeSymbol"")); + } + }"; + + AssertPrepare (platform, isCoreCLR, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var platformReference = assemblyDefinition.MainModule.AssemblyReferences.Single (v => v.Name == $"Microsoft.{platform.AsString ()}"); + var platformAssembly = assemblyDefinition.MainModule.AssemblyResolver.Resolve (platformReference); + var dlfcn = platformAssembly.MainModule.Types.Single (v => v.Name == "Dlfcn"); + + var cctor = type.GetStaticConstructor (); + Assert.That (cctor, Is.Null, "No static constructor should be needed."); + + void AssertHasDlfcnPInvokeCall (MethodDefinition method) + { + var instructions = method.Body.Instructions; + var call = instructions.FirstOrDefault (v => v.OpCode == OpCodes.Call && v.Operand is MethodReference mr && mr.DeclaringType.FullName == dlfcn.FullName); + Assert.That (call, Is.Not.Null, $"Expected a call to Dlfcn in {method}"); + var resolvedMethod = ((MethodReference) call.Operand).Resolve (); + Assert.That (resolvedMethod, Is.Not.Null, $"Expected the call to resolve to a method in Dlfcn for {method}"); + Assert.That (resolvedMethod.PInvokeInfo, Is.Null, $"Expected the method to not be a PInvoke method for {method}"); + } + + Assert.Multiple (() => { + AssertHasDlfcnPInvokeCall (type.Methods.Single (v => v.Name == "GetIntPtr")); + }); + } +} diff --git a/tests/assembly-preparer/Makefile b/tests/assembly-preparer/Makefile new file mode 100644 index 000000000000..b01174cb5734 --- /dev/null +++ b/tests/assembly-preparer/Makefile @@ -0,0 +1,8 @@ +TOP=../.. +include $(TOP)/Make.config + +build: + $(DOTNET) build *.csproj + +run-tests: + $(DOTNET) test *.csproj diff --git a/tests/assembly-preparer/MarkIProtocolHandlerTests.cs b/tests/assembly-preparer/MarkIProtocolHandlerTests.cs new file mode 100644 index 000000000000..fb7f0fcd41dd --- /dev/null +++ b/tests/assembly-preparer/MarkIProtocolHandlerTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class MarkIProtocolHandlerTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void DynamicRegistrar (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IProtocol { + } + + class MyClass : NSObject, IProtocol { + } + "; + + AssertPrepare (platform, isCoreCLR, RegistrarMode.Dynamic, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var cctor = type.GetStaticConstructor (); + Assert.That (cctor, Is.Not.Null, "Expected a static constructor to be added"); + var attribs = cctor.CustomAttributes?.Where (v => v.AttributeType.Name == "DynamicDependencyAttribute").OrderBy (v => string.Join (", ", v.ConstructorArguments.Select (v => v.Value?.ToString ()))).ToArray (); + Assert.That (attribs, Is.Not.Null, "Attributes"); + Assert.That (attribs.Count, Is.EqualTo (1), "Attribute count"); + // PreserveProtocolsStep adds DDA(DynamicallyAccessedMemberTypes.Interfaces, typeof(MyClass)) + Assert.That ((int) attribs [0].ConstructorArguments [0].Value, Is.EqualTo (0x2000), "First attribute's first argument (DynamicallyAccessedMemberTypes.Interfaces)"); + Assert.That (((TypeReference) attribs [0].ConstructorArguments [1].Value).FullName, Is.EqualTo ("MyClass"), "First attribute's second argument"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void ManagedStaticRegistrar (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IProtocol { + } + + class MyClass : NSObject, IProtocol { + } + "; + + // With ManagedStatic registrar, PreserveProtocolsStep doesn't run, + // so MyClass's cctor should not have any DDA for protocol preservation. + AssertPrepare (platform, isCoreCLR, RegistrarMode.ManagedStatic, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var cctor = type.GetStaticConstructor (); + if (cctor is not null) { + var ddaAttribs = cctor.CustomAttributes?.Where (v => v.AttributeType.Name == "DynamicDependencyAttribute").ToArray (); + Assert.That (ddaAttribs, Is.Empty, "No DynamicDependencyAttributes expected on MyClass's cctor"); + } + } +} diff --git a/tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs b/tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs new file mode 100644 index 000000000000..5a5ee83d47e4 --- /dev/null +++ b/tests/assembly-preparer/OptimizeGeneratedCodeHandlerTests.cs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class OptimizeGeneratedCodeHandlerTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void RemoveEnsureUIThread (ApplePlatform platform, bool isCoreCLR) + { + var ensureUIThreadCall = platform == ApplePlatform.MacOSX + ? "AppKit.NSApplication.EnsureUIThread ();" + : "UIKit.UIApplication.EnsureUIThread ();"; + + var usingDirective = platform == ApplePlatform.MacOSX + ? "using AppKit;" + : "using UIKit;"; + + var code = $@" + using System; + using Foundation; + using ObjCRuntime; + {usingDirective} + + class MyClass : NSObject {{ + [BindingImpl (BindingImplOptions.Optimizable)] + [Export (""myMethod"")] + public void MyMethod () {{ + {ensureUIThreadCall} + }} + }}"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RemoveUIThreadChecks = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + var hasEnsureUIThread = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Call && + (i.Operand as MethodReference)?.Name == "EnsureUIThread"); + Assert.That (hasEnsureUIThread, Is.False, "EnsureUIThread call should be removed"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void KeepEnsureUIThreadWhenOptimizationDisabled (ApplePlatform platform, bool isCoreCLR) + { + var ensureUIThreadCall = platform == ApplePlatform.MacOSX + ? "AppKit.NSApplication.EnsureUIThread ();" + : "UIKit.UIApplication.EnsureUIThread ();"; + + var usingDirective = platform == ApplePlatform.MacOSX + ? "using AppKit;" + : "using UIKit;"; + + var code = $@" + using System; + using Foundation; + using ObjCRuntime; + {usingDirective} + + class MyClass : NSObject {{ + [BindingImpl (BindingImplOptions.Optimizable)] + [Export (""myMethod"")] + public void MyMethod () {{ + {ensureUIThreadCall} + }} + }}"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RemoveUIThreadChecks = false; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + var hasEnsureUIThread = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Call && + (i.Operand as MethodReference)?.Name == "EnsureUIThread"); + Assert.That (hasEnsureUIThread, Is.True, "EnsureUIThread call should be preserved when optimization is disabled"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void OptimizeProtocolInterfaceStaticConstructor (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IMyProtocol { + [BindingImpl (BindingImplOptions.Optimizable)] + static IMyProtocol () { + GC.KeepAlive (null); + } + } + + class MyClass : NSObject, IMyProtocol { + }"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RegisterProtocols = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "IMyProtocol"); + var cctor = type.GetStaticConstructor (); + + Assert.That (cctor, Is.Not.Null, "Static constructor should still exist"); + Assert.That (cctor.Body.Instructions.Count, Is.EqualTo (1), "Static constructor should have only a ret instruction"); + Assert.That (cctor.Body.Instructions [0].OpCode.Code, Is.EqualTo (Code.Ret), "Static constructor should only contain ret"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void KeepProtocolStaticConstructorWhenOptimizationDisabled (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + [Protocol] + interface IMyProtocol { + [BindingImpl (BindingImplOptions.Optimizable)] + static IMyProtocol () { + GC.KeepAlive (null); + } + } + + class MyClass : NSObject, IMyProtocol { + }"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RegisterProtocols = false; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "IMyProtocol"); + var cctor = type.GetStaticConstructor (); + + Assert.That (cctor, Is.Not.Null, "Static constructor should still exist"); + Assert.That (cctor.Body.Instructions.Count, Is.GreaterThan (1), "Static constructor should not be optimized"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void NoOptimizationWithoutBindingAttributes (ApplePlatform platform, bool isCoreCLR) + { + var ensureUIThreadCall = platform == ApplePlatform.MacOSX + ? "AppKit.NSApplication.EnsureUIThread ();" + : "UIKit.UIApplication.EnsureUIThread ();"; + + var usingDirective = platform == ApplePlatform.MacOSX + ? "using AppKit;" + : "using UIKit;"; + + var code = $@" + using System; + using Foundation; + using ObjCRuntime; + {usingDirective} + + class MyClass : NSObject {{ + [Export (""myMethod"")] + public void MyMethod () {{ + {ensureUIThreadCall} + }} + }}"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.RemoveUIThreadChecks = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + var hasEnsureUIThread = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Call && + (i.Operand as MethodReference)?.Name == "EnsureUIThread"); + Assert.That (hasEnsureUIThread, Is.True, "EnsureUIThread call should be preserved without [BindingImpl(Optimizable)]"); + } + + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void DeadCodeElimination (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using Foundation; + using ObjCRuntime; + + class MyClass : NSObject { + [BindingImpl (BindingImplOptions.Optimizable)] + [Export (""myMethod"")] + public int MyMethod () { + if (true) { + return 1; + } + return 2; + } + }"; + + AssertPrepareCode (platform, isCoreCLR, preparer => { + preparer.Registrar = RegistrarMode.Dynamic; + preparer.Optimizations.DeadCodeElimination = true; + }, code, out var outputPath); + + using var assemblyDefinition = AssemblyDefinition.ReadAssembly (outputPath); + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var method = type.Methods.Single (v => v.Name == "MyMethod"); + + // After dead code elimination, there should be no ldc.i4.2 (the unreachable return 2) + var hasDeadCode = method.Body.Instructions.Any (i => + i.OpCode.Code == Code.Ldc_I4_2); + Assert.That (hasDeadCode, Is.False, "Dead code (return 2) should be eliminated"); + } +} diff --git a/tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs b/tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs new file mode 100644 index 000000000000..ad39cd811c5c --- /dev/null +++ b/tests/assembly-preparer/PreserveBlockCodeHandlerTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class PreserveBlockCodeHandlerTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void MarkedTest (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using ObjCRuntime; + namespace ObjCRuntime; + class Trampolines { + static internal class SDInnerBlock { + // this field is not preserved by other means, but it must not be linked away + static internal readonly DInnerBlock Handler = Invoke; + + [MonoPInvokeCallback (typeof (DInnerBlock))] + static internal void Invoke (IntPtr block, int magic_number) + { + } + + public delegate void DInnerBlock (IntPtr block, int magic_number); + } + }"; + + AssertPrepare (platform, isCoreCLR, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "Trampolines").NestedTypes.Single (v => v.Name == "SDInnerBlock"); + var cctor = type.GetStaticConstructor (); + var attribs = cctor.CustomAttributes?.OrderBy (v => string.Join (", ", v.ConstructorArguments.Select (v => v.Value?.ToString ()))).ToArray (); + Assert.That (attribs, Is.Not.Null, "Attributes"); + Assert.That (attribs.Count, Is.EqualTo (2), "Attribute count"); + Assert.That (attribs.All (v => v.AttributeType.Name == "DynamicDependencyAttribute"), Is.True, "Attribute name"); + // Handler field: DDA(string memberSignature, string typeName, string assemblyName) + Assert.That (attribs [0].ConstructorArguments.Count, Is.EqualTo (3), "First attribute's argument count"); + Assert.That ((string) attribs [0].ConstructorArguments [0].Value, Is.EqualTo ("Handler"), "First attribute's first argument"); + Assert.That ((string) attribs [0].ConstructorArguments [1].Value, Is.EqualTo ("ObjCRuntime.Trampolines.SDInnerBlock"), "First attribute's second argument"); + Assert.That ((string) attribs [0].ConstructorArguments [2].Value, Is.EqualTo ("Test"), "First attribute's third argument"); + + // Invoke method: DDA(string memberSignature) - same declaring type, so simpler constructor + Assert.That (attribs [1].ConstructorArguments.Count, Is.EqualTo (1), "Second attribute's argument count"); + Assert.That ((string) attribs [1].ConstructorArguments [0].Value, Is.EqualTo ("Invoke(System.IntPtr,System.Int32)"), "Second attribute's first argument"); + } +} diff --git a/tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs b/tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs new file mode 100644 index 000000000000..7c690caa9037 --- /dev/null +++ b/tests/assembly-preparer/PreserveSmartEnumConversionsTest.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Mono.Cecil.Rocks; + +namespace AssemblyPreparerTests; + +public class PreserveSmartEnumConversionsTests : BaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, false)] + [TestCase (ApplePlatform.iOS, false)] + [TestCase (ApplePlatform.TVOS, false)] + [TestCase (ApplePlatform.MacOSX, true)] + public void MarkedTest (ApplePlatform platform, bool isCoreCLR) + { + var code = @" + using System; + using CoreAnimation; + using Foundation; + using ObjCRuntime; + + class MyClass : NSObject { + [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode RWProperty { get; set; } + + [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode ROProperty { get { return default; } } + + [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode WOProperty { set { } } + + [return: BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode Method1 () { return default; } + + public void Method2 ([BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p1) {} + + public void Method3 ([BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p1, [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p2) {} + + [return: BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] + public CAToneMapMode Method4 ([BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p1, [BindAs (typeof (CAToneMapMode), OriginalType = typeof (NSString))] CAToneMapMode p2) { return default;} + }"; + + AssertPrepare (platform, isCoreCLR, code, out var assemblyDefinition); + + var type = assemblyDefinition.MainModule.Types.Single (v => v.Name == "MyClass"); + var cctor = type.GetStaticConstructor (); + Assert.That (cctor, Is.Null, "No static constructor should be needed."); + + void AssertHasDynamicDependency (ICustomAttributeProvider provider, string memberSignature, string typeName, string assemblyName) + { + var ddaAttributes = provider.CustomAttributes.Where (v => v.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute").ToArray (); + var found = 0; + foreach (var ca in ddaAttributes) { + if (ca.ConstructorArguments.Count != 3) + continue; + var attribMemberSignature = (string) ca.ConstructorArguments [0].Value!; + if (attribMemberSignature != memberSignature) + continue; + var attribTypeName = (string) ca.ConstructorArguments [1].Value!; + if (attribTypeName != typeName) + continue; + var attribAssemblyName = (string) ca.ConstructorArguments [2].Value!; + if (attribAssemblyName != assemblyName) + continue; + + found++; + } + + if (found == 1) + return; + + var attributesAsString = ddaAttributes + .Select (v => { + switch (v.ConstructorArguments.Count) { + case 3: + return $"[DynamicDependency (\"{v.ConstructorArguments [0].Value}\", \"{v.ConstructorArguments [1].Value}\", \"{v.ConstructorArguments [2].Value}\")]"; + case 2: + return $"[DynamicDependency (\"{v.ConstructorArguments [0].Value}\", \"{v.ConstructorArguments [1].Value}\")]"; + case 1: + return $"[DynamicDependency (\"{v.ConstructorArguments [0].Value}\")]"; + default: + return string.Join (", ", v.ConstructorArguments.Select (x => x.Value?.ToString () ?? "null")); + } + }) + .OrderBy (v => v) + .ToArray (); + + string msg; + if (found == 0) { + if (attributesAsString.Length == 0) { + msg = $"Expected [DynamicDependency (\"{memberSignature}\", \"{typeName}\", \"{assemblyName}\")] on {provider}, got no attributes."; + } else { + msg = $"Expected [DynamicDependency (\"{memberSignature}\", \"{typeName}\", \"{assemblyName}\")] on {provider}, got:\n\t{string.Join ("\n\t", attributesAsString)}"; + } + } else { + msg = $"Expected exactly one [DynamicDependency (\"{memberSignature}\", \"{typeName}\", \"{assemblyName}\")] on {provider}, got {found}:\n\t{string.Join ("\n\t", attributesAsString)}"; + } + Console.WriteLine (msg); + Assert.Fail (msg); + } + + void AssertHasDynamicDependencies (ICustomAttributeProvider provider) + { + AssertHasDynamicDependency (provider, "GetConstant(CoreAnimation.CAToneMapMode)", "CoreAnimation.CAToneMapModeExtensions", $"Microsoft.{platform.AsString ()}"); + AssertHasDynamicDependency (provider, "GetValue(Foundation.NSString)", "CoreAnimation.CAToneMapModeExtensions", $"Microsoft.{platform.AsString ()}"); + } + + Assert.Multiple (() => { + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "get_RWProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "set_RWProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "get_ROProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "set_WOProperty")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method1")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method2")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method3")); + AssertHasDynamicDependencies (type.Methods.Single (v => v.Name == "Method4")); + }); + } +} diff --git a/tests/assembly-preparer/ReproTest.cs b/tests/assembly-preparer/ReproTest.cs new file mode 100644 index 000000000000..267f7f417568 --- /dev/null +++ b/tests/assembly-preparer/ReproTest.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Build.Framework; +using Microsoft.Build.Logging.StructuredLogger; + +namespace AssemblyPreparerTests; + +public class ReproTest : BaseClass { + [TestCase (ApplePlatform.iOS, false)] + public void RoundTrip (ApplePlatform platform, bool isCoreCLR) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + + // build once with a repro path + // load everything from the repro path, prepare again (with a different repro path this time), + // and verify that the arguments.txt files from each preparation are identical + // + // this test can also be repurposed to run an existing repro by setting the _PrepareAssembliesMakeReproPath variable + + var reproPath = Environment.GetEnvironmentVariable ("_PrepareAssembliesMakeReproPath"); + var referenceAssemblies = Configuration.GetReferenceAssemblies (platform, false); + if (string.IsNullOrEmpty (reproPath)) { + var code = @"public class SomeLibrary {}"; + + reproPath = Xamarin.Cache.CreateTemporaryDirectory (); + Directory.Delete (reproPath); // the repro path can't exist prior to Prepare + AssertPrepareCode (platform, isCoreCLR, (preparer) => { + preparer.MakeReproPath = reproPath; + preparer.Registrar = RegistrarMode.Dynamic; + }, code, out string _); + } + + var lines = File.ReadAllLines (Path.Combine (reproPath, "arguments.txt")); + + var ap = AssemblyPreparer.LoadFromReproPath (reproPath); + ap.MakeReproPath = Xamarin.Cache.CreateTemporaryDirectory (); + Directory.Delete (ap.MakeReproPath); // the repro path can't exist prior to Prepare + AssertPrepare (ap); + + var lines2 = File.ReadAllLines (Path.Combine (ap.MakeReproPath, "arguments.txt")); + + // Normalize repro paths before comparison, since they will always differ + var normalizedLines = lines.Select (l => l.Replace (reproPath, "")).ToArray (); + var normalizedLines2 = lines2.Select (l => l.Replace (ap.MakeReproPath, "")).ToArray (); + Assert.That (normalizedLines, Is.EqualTo (normalizedLines2), "Repro arguments match"); + } + + // This test is here only to easily debug the PrepareAssemblies task from a build that produced a binlog. + // Just copy the binlog to /tmp/assembly-preparer.binlog and run this test. It will find the PrepareAssemblies + // task in the binlog, extract the relevant parameters, and run the preparation logic with those parameters. + [Test] + public void FromBinlog () + { + var binlogPath = "/tmp/assembly-preparer.binlog"; + if (!File.Exists (binlogPath)) + Assert.Ignore (); // The binlog doesn't exist, so nothing to do + + var reader = new BinLogReader (); + var records = reader.ReadRecords (binlogPath).ToArray (); + var originalBinlogPath = records + .Select (r => r.Args) + .OfType () + .Where (r => r.SenderName == "BinaryLogger" && r.Message?.StartsWith ("BinLogFilePath=") == true) + .Select (v => v.Message?.Substring ("BinLogFilePath=".Length) ?? "") + .SingleOrDefault (); + var originalBinlogDirectory = Path.GetDirectoryName (originalBinlogPath)!; + foreach (var record in records) { + if (record is null) + continue; + + if (record.Args is null) + continue; + + if (record.Args is TaskStartedEventArgs tsea && tsea.TaskName == "PrepareAssemblies") { + var taskId = tsea.BuildEventContext?.TaskId; + if (taskId is null) + continue; + var relevantRecords = records. + Select (v => v.Args). + Where (v => v is not null). + Where (v => v.BuildEventContext?.TaskId == taskId). + ToArray (); + + var taskParameters = relevantRecords.Where (v => v is TaskParameterEventArgs).Cast ().ToArray (); + + string? getProperty (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + if (param.Items.Count != 1) + return null; + var item = param.Items [0]; + if (item is null) + return null; + return ((ITaskItem) item).ItemSpec; + } + ITaskItem []? getItems (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + return param.Items.Cast ().ToArray (); + } + var outputDirectory = getProperty ("OutputDirectory"); + var optionsFile = getProperty ("OptionsFile"); + var targetFrameworkMoniker = getProperty ("TargetFrameworkMoniker"); + var makeReproPath = getProperty ("MakeReproPath"); + var inputAssemblies = getItems ("InputAssemblies"); + + if (string.IsNullOrEmpty (outputDirectory)) + throw new InvalidOperationException ("OutputDirectory is required"); + outputDirectory = Path.GetFullPath (outputDirectory, originalBinlogDirectory); + + if (string.IsNullOrEmpty (optionsFile)) + throw new InvalidOperationException ("OptionsFile is required"); + optionsFile = Path.GetFullPath (optionsFile, originalBinlogDirectory); + + if (string.IsNullOrEmpty (targetFrameworkMoniker)) + throw new InvalidOperationException ("TargetFrameworkMoniker is required"); + + if (inputAssemblies is null || inputAssemblies.Length == 0) + throw new InvalidOperationException ("InputAssemblies is required"); + + AssemblyPreparerInfo GetAssemblyInfo (ITaskItem item) + { + var inputPath = Path.GetFullPath (item.ItemSpec, originalBinlogDirectory); + var outputPath = Path.Combine (outputDirectory, Path.GetFileName (inputPath)); + var metadataNames = item.MetadataNames.Cast ().Select (v => v.ToLowerInvariant ()).ToHashSet (); + var isTrimmableString = item.GetMetadata ("IsTrimmable"); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = item.GetMetadata ("TrimMode"); + + var rv = new AssemblyPreparerInfo (inputPath, outputPath, isTrimmable, trimMode); + return rv; + } + + var platformString = File.ReadAllLines (optionsFile).Single (v => v.StartsWith ("Platform=")).Substring ("Platform=".Length); + var platform = ApplePlatformExtensions.Parse (platformString); + + var infos = inputAssemblies.Select (GetAssemblyInfo).ToArray (); + var logger = new TestLogger () { Platform = platform }; + using var preparer = new AssemblyPreparer (logger, infos, optionsFile); + preparer.MakeReproPath = makeReproPath ?? ""; + var rv = preparer.Prepare (out var exceptions); + return; + } + } + + Assert.Fail ("The task 'PrepareAssemblies' was not found in the provided binlog."); + } +} + + +class TestLogger : IToolLog { + public int Verbosity => 0; + public required ApplePlatform Platform { get; set; } + + public void Log (string value) + { + Console.WriteLine (value); + } + + public void Log (string format, params object? [] args) + { + Console.WriteLine (format, args); + } + + public void LogException (Exception ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogError (ProductException ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogError (Exception ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogError (string value) + { + Console.WriteLine (value); + } + + public void LogWarning (ProductException ex) + { + Console.WriteLine (ex.ToString ()); + } + + public void LogWarning (Exception ex) + { + Console.WriteLine (ex.ToString ()); + } +} diff --git a/tests/assembly-preparer/assembly-preparer-tests.csproj b/tests/assembly-preparer/assembly-preparer-tests.csproj new file mode 100644 index 000000000000..aab0b2d3d64e --- /dev/null +++ b/tests/assembly-preparer/assembly-preparer-tests.csproj @@ -0,0 +1,50 @@ + + + net$(BundledNETCoreAppTargetFrameworkVersion) + latest + enable + enable + false + true + true + + + + + + + + + + + + + + + + external/tests/common/BinLog.cs + + + external/tests/mtouch/Cache.cs + + + external/tests/common/Configuration.cs + + + external/tests/common/ConfigurationNUnit.cs + + + external/tests/common/DotNet.cs + + + external/tests/common/ExecutionHelper.cs + + + external/tools/common/SdkVersions.cs + + + external/tools/common/StringUtils.cs + + + + diff --git a/tests/assembly-preparer/assembly-preparer.slnx b/tests/assembly-preparer/assembly-preparer.slnx new file mode 100644 index 000000000000..990bc0a7a9fb --- /dev/null +++ b/tests/assembly-preparer/assembly-preparer.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/common/Configuration.cs b/tests/common/Configuration.cs index 1735ac8df815..20f92ed994ff 100644 --- a/tests/common/Configuration.cs +++ b/tests/common/Configuration.cs @@ -577,6 +577,63 @@ public static string GetRefLibrary (ApplePlatform platform) { return GetBaseLibrary (platform); } + + public static List GetBCLAssemblies (ApplePlatform platform, bool isCoreCLR) + { + var assemblies = new List (); + string rid; + string packageName; + switch (platform) { + case ApplePlatform.MacCatalyst: + rid = "maccatalyst-arm64"; + packageName = isCoreCLR ? $"microsoft.netcore.app.runtime.maccatalyst-arm64" : $"microsoft.netcore.app.runtime.mono.maccatalyst-arm64"; + break; + case ApplePlatform.iOS: + rid = "ios-arm64"; + packageName = isCoreCLR ? $"microsoft.netcore.app.runtime.ios-arm64" : $"microsoft.netcore.app.runtime.mono.ios-arm64"; + break; + case ApplePlatform.TVOS: + rid = "tvos-arm64"; + packageName = isCoreCLR ? $"microsoft.netcore.app.runtime.tvOS-arm64" : $"microsoft.netcore.app.runtime.mono.tvOS-arm64"; + break; + case ApplePlatform.MacOSX: + rid = "osx-arm64"; + packageName = "microsoft.netcore.app.runtime.osx-arm64"; + if (!isCoreCLR) + throw new InvalidOperationException ($"Mono doesn't support macOS, but was asked for the BCL assemblies for macOS"); + break; + default: + throw new NotSupportedException ($"Unsupported platform: {platform}"); + } + var microsoftNetCoreAppRefPackageVersion = File.ReadAllLines (Path.Combine (RootPath, "dotnet.config")).Single (v => v.StartsWith ("BUNDLED_NETCORE_PLATFORMS_PACKAGE_VERSION=", StringComparison.Ordinal)).Replace ("BUNDLED_NETCORE_PLATFORMS_PACKAGE_VERSION=", ""); + var bclDir = Path.Combine (RootPath, "packages", packageName, microsoftNetCoreAppRefPackageVersion, "runtimes", rid, "lib", DotNetTfm); + var nativeDir = Path.Combine (RootPath, "packages", packageName, microsoftNetCoreAppRefPackageVersion, "runtimes", rid, "native"); + + assemblies.AddRange (Directory.GetFiles (bclDir, "*.dll")); + assemblies.AddRange (Directory.GetFiles (nativeDir, "*.dll")); + + return assemblies; + } + + public static List GetReferenceAssemblies (ApplePlatform platform, bool isCoreCLR) + { + var assemblies = new List (); + + assemblies.AddRange (GetBCLAssemblies (platform, isCoreCLR)); + assemblies.AddRange (GetRefLibrary (platform)); + + return assemblies; + } + + public static List GetImplementationAssemblies (ApplePlatform platform, bool isCoreCLR) + { + var assemblies = new List (); + + assemblies.AddRange (GetBCLAssemblies (platform, isCoreCLR)); + assemblies.AddRange (GetBaseLibraryImplementations (platform).First ()); + + return assemblies; + } #endif // !XAMMAC_TESTS public static IEnumerable GetIncludedPlatforms () diff --git a/tests/common/DotNet.cs b/tests/common/DotNet.cs index 1f7a5c73e868..9e01afeebe45 100644 --- a/tests/common/DotNet.cs +++ b/tests/common/DotNet.cs @@ -109,7 +109,7 @@ public static ExecutionResult AssertNew (string outputDirectory, string template Console.WriteLine (output); Assert.That (rv.ExitCode, Is.EqualTo (0), $"Exit code: {Executable} {StringUtils.FormatArguments (args)}"); } - return new ExecutionResult (output, output, rv.ExitCode); + return new ExecutionResult (output, output, rv.ExitCode, rv.Duration); } public static ExecutionResult InstallWorkload (params string [] workloads) @@ -136,7 +136,7 @@ public static ExecutionResult InstallWorkload (params string [] workloads) Console.WriteLine (msg); Assert.Fail (msg.ToString ()); } - return new ExecutionResult (output, output, rv.ExitCode); + return new ExecutionResult (output, output, rv.ExitCode, rv.Duration); } public static ExecutionResult InstallTool (string tool, string path) @@ -206,7 +206,7 @@ public static ExecutionResult ExecuteCommand (string exe, Dictionary? properties, bool assert_success = true, string? target = null, bool? msbuildParallelism = null, TimeSpan? timeout = null, params string [] extraArguments) @@ -338,7 +338,7 @@ public static ExecutionResult Execute (string verb, string project, Dictionary + @@ -190,6 +191,11 @@ <_TestVariationApplied>true + + true + <_TestVariationApplied>true + + <_InvalidTestVariations Include="$(TestVariation.Split('|'))" Exclude="@(TestVariations)" /> diff --git a/tests/linker/link all/dotnet/shared.csproj b/tests/linker/link all/dotnet/shared.csproj index 6b75ec2a2d4e..33fc00ceb233 100644 --- a/tests/linker/link all/dotnet/shared.csproj +++ b/tests/linker/link all/dotnet/shared.csproj @@ -9,6 +9,7 @@ $(MtouchLink) --optimize=all,-remove-dynamic-registrar,-force-rejected-types-removal $(MtouchExtraArgs) --optimize=-remove-uithread-checks + $(MtouchExtraArgs) --nowarn:2003 $(MtouchExtraArgs) $(RootTestsDirectory)\linker\link all true diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 167d8f6247bb..a99809a50d28 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -33,6 +33,7 @@ IEnumerable GetTestData (RunTestTask test) var arm64_sim_runtime_identifier = string.Empty; var x64_sim_runtime_identifier = string.Empty; var supports_coreclr = test.Platform == TestPlatform.Mac || jenkins.Harness.DotNetVersion.Major >= 11; + var supports_mono = test.Platform != TestPlatform.Mac; var supports_x64 = string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ACES")); // x64 is not supported on ACES machines switch (test.Platform) { @@ -55,7 +56,39 @@ IEnumerable GetTestData (RunTestTask test) } switch (test.TestName) { + case "dont link": + if (supports_coreclr) { + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Dynamic Registrar)", TestVariation = "coreclr|prepare-assemblies|dynamic-registrar", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Managed Static Registrar)", TestVariation = "coreclr|prepare-assemblies|managed-static-registrar", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + if (supports_mono) { + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Dynamic Registrar)", TestVariation = "monovm|prepare-assemblies|dynamic-registrar", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Managed Static Registrar)", TestVariation = "monovm|prepare-assemblies|managed-static-registrar", Ignored = ignore }; + if (jenkins.Harness.DotNetVersion.Major >= 11) { // on Mono, the trimmable static registrar only works in .NET 11 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + } + break; + case "link sdk": + if (supports_coreclr) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which only works on CoreCLR in .NET 10 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + if (supports_mono && jenkins.Harness.DotNetVersion.Major >= 11) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which, on Mono, only works in .NET 11 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + break; case "link all": + if (supports_coreclr) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which only works on CoreCLR in .NET 10 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, CoreCLR, Trimmable Static Registrar)", TestVariation = "coreclr|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } + if (supports_mono && jenkins.Harness.DotNetVersion.Major >= 11) { + // if prepare-assemblies is enabled, then linking only works in any meaningful way when using the trimmable static registrar, which, on Mono, only works in .NET 11 + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies, MonoVM, Trimmable Static Registrar)", TestVariation = "monovm|prepare-assemblies|trimmable-static-registrar", Ignored = ignore }; + } if (test.ProjectConfiguration == "Debug") { yield return new TestData { Variation = "Debug (don't bundle original resources)", TestVariation = "do-not-bundle-original-resources" }; } @@ -63,6 +96,7 @@ IEnumerable GetTestData (RunTestTask test) case "monotouch-test": yield return new TestData { Variation = "Release (link sdk)", TestVariation = "release|linksdk", Ignored = ignore }; yield return new TestData { Variation = "Release (link all)", TestVariation = "release|linkall", Ignored = ignore }; + yield return new TestData { Variation = $"{test.ProjectConfiguration} (PrepareAssemblies)", TestVariation = "prepare-assemblies", Ignored = ignore }; break; } diff --git a/tools/Makefile b/tools/Makefile index 00d896286809..c4c969bf82b7 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -14,3 +14,4 @@ SUBDIRS+=mlaunch SUBDIRS += dotnet-linker +SUBDIRS += assembly-preparer diff --git a/tools/ap-launcher/.gitignore b/tools/ap-launcher/.gitignore new file mode 100644 index 000000000000..af1e7137432d --- /dev/null +++ b/tools/ap-launcher/.gitignore @@ -0,0 +1,2 @@ +.build-stamp + diff --git a/tools/ap-launcher/Makefile b/tools/ap-launcher/Makefile new file mode 100644 index 000000000000..adb26c8c4e3c --- /dev/null +++ b/tools/ap-launcher/Makefile @@ -0,0 +1,8 @@ +TOP=../.. +include $(TOP)/Make.config +include $(TOP)/mk/rules.mk + +build: + $(Q_BUILD) $(DOTNET) build /bl:$@.binlog *.csproj $(DOTNET_BUILD_VERBOSITY) + +all-local:: build diff --git a/tools/ap-launcher/Program.cs b/tools/ap-launcher/Program.cs new file mode 100644 index 000000000000..44da342c46a9 --- /dev/null +++ b/tools/ap-launcher/Program.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Xamarin.Build; +using Xamarin.Bundler; +using Xamarin.Utils; + +class Program { + public static int Main (string [] args) + { + var optionsFile = args.Single (v => v.StartsWith ("--options-file=")).Substring ("--options-file=".Length); + var makeReproPath = args.SingleOrDefault (v => v.StartsWith ("--make-repro-path="))?.Substring ("--make-repro-path=".Length) ?? ""; + + var api = new List (); + foreach (var inputArgs in args.Where (v => v.StartsWith ("--input-assembly=")).Select (v => v.Substring ("--input-assembly=".Length))) { + var ia = inputArgs.Split ('|'); + var inputPath = ia.Single (v => v.StartsWith ("InputPath="))?.Substring ("InputPath=".Length) ?? throw new InvalidOperationException ("InputPath is required"); + var outputPath = ia.Single (v => v.StartsWith ("OutputPath="))?.Substring ("OutputPath=".Length) ?? throw new InvalidOperationException ("OutputPath is required"); + var isTrimmableString = ia.Single (v => v.StartsWith ("IsTrimmable="))?.Substring ("IsTrimmable=".Length); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = ia.Single (v => v.StartsWith ("TrimMode="))?.Substring ("TrimMode=".Length) ?? ""; + + api.Add (new AssemblyPreparerInfo (inputPath, outputPath, isTrimmable, trimMode)); + } + + var platformString = File.ReadAllLines (optionsFile).Single (v => v.StartsWith ("Platform=")).Substring ("Platform=".Length); + var platform = ApplePlatformExtensions.Parse (platformString); + + var infos = api.ToArray (); + var logger = new TestLogger () { + Platform = platform, + }; + using var preparer = new AssemblyPreparer (logger, infos, optionsFile); + preparer.MakeReproPath = makeReproPath ?? ""; + var rv = preparer.Prepare (out var exceptions); + + return rv && exceptions.Count == 0 ? 0 : 1; + } +} + +class TestLogger : IToolLog { + public int Verbosity => 0; + public required ApplePlatform Platform { get; set; } + + public void Log (string value) + { + Console.WriteLine (value); + } + + public void LogError (string value) + { + Console.WriteLine (value); + } + + public void Log (string format, params object? [] args) + { + Console.WriteLine (format, args); + } + + public void LogException (Exception ex) + { + Console.Error.WriteLine (ex.ToString ()); + } + + public void LogError (ProductException ex) + { + Console.Error.WriteLine (ex.ToString ()); + } + + public void LogWarning (ProductException ex) + { + Console.WriteLine (ex.ToString ()); + } +} diff --git a/tools/ap-launcher/README.md b/tools/ap-launcher/README.md new file mode 100644 index 000000000000..5007c9cc4226 --- /dev/null +++ b/tools/ap-launcher/README.md @@ -0,0 +1,2 @@ +This is just a simple project that references the assembly-preparer library, to make it easy to debug the library in a debugger. + diff --git a/tools/ap-launcher/ap-launcher.csproj b/tools/ap-launcher/ap-launcher.csproj new file mode 100644 index 000000000000..ea7daa76d3f5 --- /dev/null +++ b/tools/ap-launcher/ap-launcher.csproj @@ -0,0 +1,17 @@ + + + + Exe + net$(BundledNETCoreAppTargetFrameworkVersion) + false + false + ap_launcher + enable + enable + + + + + + + diff --git a/tools/ap-launcher/ap-launcher.slnx b/tools/ap-launcher/ap-launcher.slnx new file mode 100644 index 000000000000..f543c9a91276 --- /dev/null +++ b/tools/ap-launcher/ap-launcher.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/tools/assembly-preparer/.gitignore b/tools/assembly-preparer/.gitignore new file mode 100644 index 000000000000..65bf06abaee5 --- /dev/null +++ b/tools/assembly-preparer/.gitignore @@ -0,0 +1,2 @@ +*.csproj.inc + diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs new file mode 100644 index 000000000000..f26d3c2f573a --- /dev/null +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -0,0 +1,388 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Runtime.Serialization; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.CompilerServices.SymbolWriter; +using Mono.Linker; +using Mono.Linker.Steps; +using Mono.Tuner; +using MonoTouch.Tuner; +using Xamarin.Bundler; +using Xamarin.Linker; +using Xamarin.Linker.Steps; +using Xamarin.Tuner; +using Xamarin.Utils; + +namespace Xamarin.Build; + +public class AssemblyPreparer : IDisposable { + AggregateLog log = new AggregateLog (); + + LinkerConfiguration configuration; + + public LinkerConfiguration Configuration => configuration; + + public string MakeReproPath { get; set; } = ""; + + public RegistrarMode Registrar { + get => configuration.Application.Registrar; + set => configuration.Application.Registrar = value; + } + + public string IntermediateOutputPath { + get => configuration.IntermediateOutputPath; + } + + public Optimizations Optimizations => configuration.Application.Optimizations; + + public List Assemblies { get; set; } = new List (); + + public IList<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> AddedAssemblies => configuration.AddedAssemblies; + + LinkerConfiguration.Configurator GetConfigurator (string? reproPath = null, Func? assemblyPreparerInfoFactory = null) + { + var dict = new LinkerConfiguration.Configurator () { + { "AssemblyPreparer", ( + new LinkerConfiguration.LoadValue ((key, value) => { + var split = value.Split ('|'); + var input = split[0]; + var output = split[1]; + var isTrimmableString = split[2]; + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = split[3]; + var apinfo = assemblyPreparerInfoFactory is not null ? assemblyPreparerInfoFactory (input, output) : new AssemblyPreparerInfo (input, output, isTrimmable, trimMode); + Assemblies.Add (apinfo); + }), + new LinkerConfiguration.SaveValue ((key, storage) => SaveAssemblies (key, storage, reproPath, Assemblies)) + )}, + }; + return dict; + } + + static void SaveAssemblies (string key, List storage, string? reproPath, IList assemblies) + { + foreach (var assembly in assemblies) { + var input = assembly.InputPath; + var output = assembly.OutputPath; + if (!string.IsNullOrEmpty (reproPath)) { + output = Path.Combine (reproPath, Path.GetFileName (output)); + File.Copy (input, output); + } + storage.Add ($"{key}={input}|{output}|{(assembly.IsTrimmable.HasValue ? (assembly.IsTrimmable.Value ? "true" : "false") : "")}|{assembly.TrimMode}"); + } + } + + public AssemblyPreparer (IToolLog log, AssemblyPreparerInfo [] assemblies, string linker_file) + { + var lines = File.ReadAllLines (linker_file).ToList (); + SaveAssemblies ("AssemblyPreparer", lines, null, assemblies); + configuration = new LinkerConfiguration (log, lines, linker_file, GetConfigurator (null, assemblies.Length == 0 ? null : (input, output) => assemblies.Single (a => a.InputPath == input && a.OutputPath == output))); + } + + public void AddLog (IAssemblyPreparerLog log) + { + if (log is null) + throw new ArgumentNullException (nameof (log)); + this.log.Add (log); + } + + bool SaveToReproPath (List exceptions) + { + if (File.Exists (MakeReproPath) || Directory.Exists (MakeReproPath)) { + exceptions.Add (ErrorHelper.CreateError (99, $"Repro location already exists: {MakeReproPath}")); + return false; + } + Directory.CreateDirectory (MakeReproPath); + var lines = new List (); + configuration.Save (lines, GetConfigurator (MakeReproPath)); + File.WriteAllLines (Path.Combine (MakeReproPath, "arguments.txt"), lines); + log.Log ($"Created repro in {MakeReproPath}"); + + return true; + } + + public static AssemblyPreparer LoadFromReproPath (string reproPath) + { + var file = Path.Combine (reproPath, "arguments.txt"); + if (!File.Exists (file)) + throw new FileNotFoundException ($"Repro arguments file not found: {file}"); + return new AssemblyPreparer (ConsoleLog.Instance, [], file); + } + + public bool Prepare (out List exceptions) + { + exceptions = configuration.Exceptions; + + if (Registrar == RegistrarMode.Default) { + exceptions.Add (ErrorHelper.CreateError (99, "RegistrarMode must be explicitly set.")); + return false; + } + + if (!string.IsNullOrEmpty (MakeReproPath) && !SaveToReproPath (exceptions)) + return false; + + var steps = new ConfigurationAwareStep [] { + // All the same steps as the custom trimmer steps that are run before MarkStep in Xamarin.Shared.Sdk.targets (and in the same order). + // CollectAssembliesStep + new CoreTypeMapStep (), + // ProcessExportedFields + new PreserveProtocolsStep (), + new PreserveSmartEnumConversionsStep (), + new PreserveBlockCodeStep (), + new OptimizeGeneratedCodeStep (), + new ApplyPreserveAttributeStep (), + new MarkForStaticRegistrarStep (), + new MarkNSObjectsStep (), + new InlineDlfcnMethodsStep (), + new RegistrarRemovalTrackingStep (), + // PreMarkDispatcher: I don't think we need this one + new ManagedRegistrarStep (), + new TrimmableRegistrarStep (), + new ManagedRegistrarLookupTablesStep (), + }; + + var linkContext = configuration.DerivedLinkContext; + + var parameters = new ReaderParameters { + AssemblyResolver = configuration.AssemblyResolver, + MetadataResolver = configuration.MetadataResolver, + ReadSymbols = true, + SymbolReaderProvider = new DefaultSymbolReaderProvider (throwIfNoSymbol: false), + }; + + var skippedAssemblies = new List (); + foreach (var assembly in Assemblies) { + AssemblyDefinition assemblyDefinition; + try { + assemblyDefinition = AssemblyDefinition.ReadAssembly (assembly.InputPath, parameters); + } catch (BadImageFormatException) { + // Not a managed assembly, skip it (pass it through unchanged). + log.Log ($"Skipping non-managed assembly: {assembly.InputPath}"); + assembly.OutputPath = assembly.InputPath; + skippedAssemblies.Add (assembly); + continue; + } + linkContext.Assemblies.Add (assemblyDefinition); + assembly.Assembly = assemblyDefinition; + configuration.Context.Annotations.SetAction (assemblyDefinition, ComputeAssemblyAction (assemblyDefinition, assembly)); + var assemblyName = assemblyDefinition.Name.Name; + if (configuration.AssemblyResolver.ResolverCache.ContainsKey (assemblyName)) + exceptions.Add (ErrorHelper.CreateWarning (99, $"Duplicate assembly name '{assemblyName}' in the list of assemblies to prepare (new: {assembly.InputPath}).")); + configuration.AssemblyResolver.ResolverCache [assemblyName] = assemblyDefinition; + } + + configuration.Context.Annotations.CollectOverrides (linkContext.Assemblies, linkContext); + + // Populate FieldSymbols for InlineDlfcnMethodsStep's compatibility mode. + // This is equivalent to what ProcessExportedFields does in the ILLink pipeline. + if (configuration.InlineDlfcnMethodsEnabled) { + foreach (var assembly in linkContext.Assemblies) { + if (!assembly.MainModule.HasTypeReference (Namespaces.Foundation + ".FieldAttribute")) + continue; + foreach (var type in assembly.MainModule.Types) + CollectFieldSymbols (configuration, type); + } + } + + foreach (var step in steps) { + step.Process (linkContext); + } + + // save assemblies + + foreach (var assembly in Assemblies) { + if (skippedAssemblies.Contains (assembly)) + continue; + + var assemblyDefinition = assembly.Assembly; + if (assemblyDefinition is null) { + exceptions.Add (ErrorHelper.CreateError (99, $"Assembly definition is null for {assembly.InputPath}")); + return false; + } + + var action = configuration.Context.Annotations.GetAction (assemblyDefinition); + switch (action) { + case AssemblyAction.Copy: + case AssemblyAction.CopyUsed: + assembly.OutputPath = assembly.InputPath; + continue; + case AssemblyAction.Link: + case AssemblyAction.Save: + log.Log ($"Saving {assembly.InputPath} to {assembly.OutputPath}"); + break; + default: + exceptions.Add (ErrorHelper.CreateError (99, $"Unknown link action: {action} for assembly {assemblyDefinition.Name}")); + return false; + } + + PathUtils.CreateDirectoryForFile (assembly.OutputPath); + var writerParameters = new WriterParameters (); + if (assemblyDefinition.MainModule.HasSymbols) { + var provider = new CustomSymbolWriterProvider (); + try { + using (var tmp = provider.GetSymbolWriter (assemblyDefinition.MainModule, Path.ChangeExtension (assembly.OutputPath, ".pdb"))) { } + File.Delete (Path.ChangeExtension (assembly.OutputPath, ".pdb")); + writerParameters.WriteSymbols = true; + writerParameters.SymbolWriterProvider = provider; + } catch (Exception e) { + log.Log ($"Failed to create symbol writer for {assembly.OutputPath}, not writing symbols: {e.Message}"); + } + } + + RemoveCrossGen (assemblyDefinition); + + try { + assemblyDefinition.Write (assembly.OutputPath, writerParameters); + ModuleAttributes m = assemblyDefinition.MainModule.Attributes; + } catch (Exception e) { + exceptions.Add (ErrorHelper.CreateError (99, e, $"Failed to write {assembly.OutputPath}: {e.Message}")); + log.Log ($"Failed to write {assembly.OutputPath}: {e}"); + return false; + } + } + + return exceptions.Count == 0; + } + + void RemoveCrossGen (AssemblyDefinition assemblyDefinition) + { + // Drop crossgened code from the assembly + // Ref: https://github.com/dotnet/runtime/blob/b86458593223f866effa63122b05bec37f83015e/src/tools/illink/src/linker/Linker.Steps/OutputStep.cs#L95-L105 + foreach (var module in assemblyDefinition.Modules) { + var moduleAttributes = module.Attributes; + var isCrossGened = (moduleAttributes & ModuleAttributes.ILOnly) == 0 && (moduleAttributes & ModuleAttributes.ILLibrary) == ModuleAttributes.ILLibrary; + if (isCrossGened) { + moduleAttributes |= ModuleAttributes.ILOnly; + moduleAttributes &= ~ModuleAttributes.ILLibrary; + module.Attributes = moduleAttributes; + module.Architecture = TargetArchitecture.I386; + module.Characteristics |= ModuleCharacteristics.NoSEH; + } + } + } + + // Figure out if an assembly is trimmed or not. + // This must be identical to how it's done for ILLink/ILC. + AssemblyAction ComputeAssemblyAction (AssemblyDefinition assembly, AssemblyPreparerInfo info) + { + // Unless 'PublishTrimmed=true', nothing is trimmed, because we won't run the trimmer. + if (!configuration.PublishTrimmed) + return AssemblyAction.Copy; + + // Then if 'TrimMode' is set on the assembly, then that takes precedence + switch (info.TrimMode?.ToLowerInvariant () ?? "") { + case "link": + return AssemblyAction.Link; + case "copy": + return AssemblyAction.Copy; + case "": + break; + default: + throw new ArgumentException ($"Unknown trim mode: {info.TrimMode} for assembly {assembly.Name}"); + } + + // Then if 'IsTrimmable' is set on the assembly, that takes precedence over the default for the platform. + if (info.IsTrimmable == false) + return AssemblyAction.CopyUsed; + else if (info.IsTrimmable == true) + return AssemblyAction.Link; + + // Check the global 'TrimMode' property, if it's not 'link', 'partial' or 'full', then we're not trimming anything + var globalTrimMode = configuration.TrimMode.ToLowerInvariant (); + switch (globalTrimMode) { + case "copy": + case "": + return AssemblyAction.Copy; + case "partial": + case "full": + case "link": + break; + default: + throw new ArgumentException ($"Unknown global trim mode: {configuration.TrimMode}"); + } + + // Check the [AssemblyMetadata] attribute + var isTrimmableAttribute = assembly.CustomAttributes + .Where (v => v.AttributeType.FullName == "System.Reflection.AssemblyMetadataAttribute") + .Where (v => v.HasConstructorArguments && v.ConstructorArguments.Count == 2 && v.ConstructorArguments [0].Type.Is ("System", "String") && v.ConstructorArguments [1].Type.Is ("System", "String")) + .Where (v => (v.ConstructorArguments [0].Value as string) == "IsTrimmable" && string.Equals (v.ConstructorArguments [1].Value as string, "true", StringComparison.OrdinalIgnoreCase)) + .SingleOrDefault (); + + if (isTrimmableAttribute is null) { + // If the attribute is not present, then we trim if the global 'TrimMode' is 'full' + return globalTrimMode switch { + "link" => AssemblyAction.Copy, + "partial" => AssemblyAction.Copy, + "full" => AssemblyAction.Link, + _ => throw new ArgumentException ($"Unknown global trim mode: {configuration.TrimMode}"), + }; + } + + // if the attribute is present, then we trim if the global 'TrimMode' is 'partial', 'full' or 'link', which are the only values it should have at this point + switch (globalTrimMode) { + case "partial": + case "full": + case "link": + break; + default: + // we shouldn't get here for any other trim mode value + throw new ArgumentException ($"Unexpected global trim mode: {configuration.TrimMode}"); + } + + return AssemblyAction.Link; + } + + public void Dispose () + { + foreach (var assembly in Assemblies) + assembly.Assembly?.Dispose (); + configuration.AssemblyResolver.ResolverCache.Clear (); + configuration.DerivedLinkContext.Assemblies.Clear (); + } + + static void CollectFieldSymbols (LinkerConfiguration configuration, TypeDefinition type) + { + if (type.HasNestedTypes) { + foreach (var nested in type.NestedTypes) + CollectFieldSymbols (configuration, nested); + } + + if (!type.HasProperties) + return; + + foreach (var property in type.Properties) { + if (!property.HasCustomAttributes) + continue; + + foreach (var attrib in property.CustomAttributes) { + var declaringType = attrib.Constructor.DeclaringType.Resolve (); + if (!declaringType.Is (Namespaces.Foundation, "FieldAttribute")) + continue; + if (attrib.ConstructorArguments.Count < 1) + continue; + configuration.FieldSymbols.Add ((string) attrib.ConstructorArguments [0].Value); + break; + } + } + } +} + +public class AssemblyPreparerInfo { + internal AssemblyDefinition? Assembly { get; set; } + + public string InputPath { get; private set; } + public bool? IsTrimmable { get; set; } + public string TrimMode { get; set; } + public string OutputPath { get; set; } + + public AssemblyPreparerInfo (string inputPath, string outputPath, bool? isTrimmable, string trimMode) + { + InputPath = inputPath; + OutputPath = outputPath; + IsTrimmable = isTrimmable; + TrimMode = trimMode; + } +} diff --git a/tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs b/tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 000000000000..4a42e76ab54c --- /dev/null +++ b/tools/assembly-preparer/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// This file is needed because we compile the current project under netstandard2.0, and this type doesn't exist there. + +#if !NET + +// From https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMemberTypes.cs + +global using DynamicallyAccessedMemberTypes = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes2; + +namespace System.Diagnostics.CodeAnalysis { + using System.ComponentModel; + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + enum DynamicallyAccessedMemberTypes2 { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all non-public constructors, including those inherited from base classes. + /// + NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000, + + /// + /// Specifies all non-public methods, including those inherited from base classes. + /// + NonPublicMethodsWithInherited = NonPublicMethods | 0x8000, + + /// + /// Specifies all non-public fields, including those inherited from base classes. + /// + NonPublicFieldsWithInherited = NonPublicFields | 0x10000, + + /// + /// Specifies all non-public nested types, including those inherited from base classes. + /// + NonPublicNestedTypesWithInherited = NonPublicNestedTypes | 0x20000, + + /// + /// Specifies all non-public properties, including those inherited from base classes. + /// + NonPublicPropertiesWithInherited = NonPublicProperties | 0x40000, + + /// + /// Specifies all non-public events, including those inherited from base classes. + /// + NonPublicEventsWithInherited = NonPublicEvents | 0x80000, + + /// + /// Specifies all public constructors, including those inherited from base classes. + /// + PublicConstructorsWithInherited = PublicConstructors | 0x100000, + + /// + /// Specifies all public nested types, including those inherited from base classes. + /// + PublicNestedTypesWithInherited = PublicNestedTypes | 0x200000, + + /// + /// Specifies all constructors, including those inherited from base classes. + /// + AllConstructors = PublicConstructorsWithInherited | NonPublicConstructorsWithInherited, + + /// + /// Specifies all methods, including those inherited from base classes. + /// + AllMethods = PublicMethods | NonPublicMethodsWithInherited, + + /// + /// Specifies all fields, including those inherited from base classes. + /// + AllFields = PublicFields | NonPublicFieldsWithInherited, + + /// + /// Specifies all nested types, including those inherited from base classes. + /// + AllNestedTypes = PublicNestedTypesWithInherited | NonPublicNestedTypesWithInherited, + + /// + /// Specifies all properties, including those inherited from base classes. + /// + AllProperties = PublicProperties | NonPublicPropertiesWithInherited, + + /// + /// Specifies all events, including those inherited from base classes. + /// + AllEvents = PublicEvents | NonPublicEventsWithInherited, + + /// + /// Specifies all members. + /// + [EditorBrowsable (EditorBrowsableState.Never)] + All = ~None + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/GlobalUsings.cs b/tools/assembly-preparer/GlobalUsings.cs new file mode 100644 index 000000000000..ba0a28435170 --- /dev/null +++ b/tools/assembly-preparer/GlobalUsings.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using System; +global using System.Collections.Generic; +global using System.Diagnostics.CodeAnalysis; +global using System.Linq; +global using System.Runtime.InteropServices; + +global using Foundation; +global using ObjCRuntime; + +global using Xamarin.Linker; + +namespace Xamarin.Tuner { } +namespace Mono.Linker.Steps { } diff --git a/tools/assembly-preparer/IAssemblyPreparerLog.cs b/tools/assembly-preparer/IAssemblyPreparerLog.cs new file mode 100644 index 000000000000..f41de7ca475e --- /dev/null +++ b/tools/assembly-preparer/IAssemblyPreparerLog.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Xamarin.Bundler; + +namespace Xamarin.Build; + +public interface IAssemblyPreparerLog { + void Log (string message); +} + +class AggregateLog : IAssemblyPreparerLog { + List logs = new (); + + public void Add (IAssemblyPreparerLog log) + { + logs.Add (log); + } + + public void Log (string message) + { + foreach (var log in logs) + log.Log (message); + } +} diff --git a/tools/assembly-preparer/Makefile b/tools/assembly-preparer/Makefile new file mode 100644 index 000000000000..587a1d6116f5 --- /dev/null +++ b/tools/assembly-preparer/Makefile @@ -0,0 +1,25 @@ +TOP=../.. +include $(TOP)/Make.config +include $(TOP)/mk/rules.mk +include ../common/Make.common + +# assembly-preparer.csproj.inc contains the $(assembly_preparer_dependencies) variable used to determine if assembly-preparer needs to be rebuilt or not. +assembly-preparer.csproj.inc: export BUILD_VERBOSITY=$(MSBUILD_VERBOSITY) +-include assembly-preparer.csproj.inc + +ASSEMBLY_PREPARER_NETCORE=bin/Debug/$(DOTNET_TFM)/assembly-preparer.dll +ASSEMBLY_PREPARER_NETSTD=bin/Debug/netstandard2.0/assembly-preparer.dll +ASSEMBLIES=$(ASSEMBLY_PREPARER_NETCORE) $(ASSEMBLY_PREPARER_NETSTD) + +.build-stamp: $(assembly_preparer_dependencies) + $(Q_BUILD) $(DOTNET) build /bl:$@.binlog *.csproj $(DOTNET_BUILD_VERBOSITY) + $(Q) touch $(ASSEMBLIES) + +$(ASSEMBLIES): .build-stamp + +all-local:: .build-stamp + +run-tests: + $(Q_BUILD) $(DOTNET) test $(TOP)/tests/assembly-preparer/assembly-preparer-tests.csproj --logger "console;verbosity=detailed" $(DOTNET_BUILD_VERBOSITY) -bl:$@.binlog + +generated-files: $(abspath ../common/SdkVersions.cs) $(abspath ../common/ProductConstants.cs) diff --git a/tools/assembly-preparer/NetStandardExtensions.cs b/tools/assembly-preparer/NetStandardExtensions.cs new file mode 100644 index 000000000000..5f81fd7f1b93 --- /dev/null +++ b/tools/assembly-preparer/NetStandardExtensions.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// This file is needed because we compile the current project under netstandard2.0, and the types here don't exist in netstandard2.0 + +#if !NET + +public static class QueueExtensions { + public static bool TryDequeue (this Queue queue, [MaybeNullWhen (false)] out T item) + { + if (queue.Count == 0) { + item = default; + return false; + } + item = queue.Dequeue (); + return true; + } + + public static bool TryAdd (this Dictionary dictionary, T key, V value) + { + if (dictionary.ContainsKey (key)) + return false; + dictionary.Add (key, value); + return true; + } +} + +public static class DictionaryExtensions { + public static bool Remove (this Dictionary dictionary, T key, [MaybeNullWhen (false)] out V value) + { + if (dictionary.TryGetValue (key, out value)) { + dictionary.Remove (key); + return true; + } + return false; + } +} + +public static class EnumerableExtensions { + public static IEnumerable SkipLast (this IEnumerable source, int count) + { + // very naive implementation, but it's only for netstandard2.0, which will go away soon, so no need to optimize it + var rv = source.ToList (); + if (rv.Count <= count) + return []; + rv.RemoveRange (rv.Count - count, count); + return rv; + } +} + +// From: https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RequiredMemberAttribute.cs +namespace System.Runtime.CompilerServices { + using System.ComponentModel; + + /// Specifies that a type has required members or that a member is required. + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + [EditorBrowsable (EditorBrowsableState.Never)] + internal sealed class RequiredMemberAttribute : Attribute { } +} + +// From: https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerFeatureRequiredAttribute.cs +namespace System.Runtime.CompilerServices { + using System.ComponentModel; + + /// + /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. + /// + [AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute { + public CompilerFeatureRequiredAttribute (string featureName) + { + FeatureName = featureName; + } + + /// + /// The name of the compiler feature. + /// + public string FeatureName { get; } + + /// + /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . + /// + public bool IsOptional { get; init; } + + /// + /// The used for the ref structs C# feature. + /// + public const string RefStructs = nameof (RefStructs); + + /// + /// The used for the required members C# feature. + /// + public const string RequiredMembers = nameof (RequiredMembers); + } +} + +// From: https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IsExternalInit.cs +namespace System.Runtime.CompilerServices { + using System.ComponentModel; + + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable (EditorBrowsableState.Never)] + internal static class IsExternalInit { + } +} +#endif diff --git a/tools/assembly-preparer/README.md b/tools/assembly-preparer/README.md new file mode 100644 index 000000000000..696974bf073f --- /dev/null +++ b/tools/assembly-preparer/README.md @@ -0,0 +1,35 @@ +# Assembly preparer + +This is a library that will modify assemblies when a project is built for a few reasons: + +* Collect required information for a successful build +* Transform some code patterns so that they can be properly recognized and handled correctly by trimmers. +* Optimize some code patterns we can easily recognize +* Precompute some things at build time to be able to make apps smaller and run faster. + +Currently it can: + +* PreserveCodeBlockHandler: in some cases user assemblies might contain code + created by the generator that's not trimmer safe; this handler will inject + code to ensure that trimmers don't trim way some things that shouldn't be + trimmed away. + +## Design principles + +* Easy to test (there's a unit test project, VSCode can run & debug its tests) +* Can be called from an MSBuild task (which means it currently needs to target `netstandard2.0`). +* Good error handling/reporting. +* We have two main scenarios: + * Debug loop, where we shouldn't do more than absolutely necessary to make + debug builds as fast as possible. In particular, we'll only do whatever + is necessary for a correct build, and if possible, no assembly + modification, only information gathering. + * Release builds, where we want to optimize as much as possible. +* To ease integration with existing custom linker steps, it's provides a + very simplified API of ILLink's custom linker step API - much of it is + stubbed out until it's needed. +* Until fully complete and both correctness and performance have been + validated, it should be possible to use either custom linker steps or the + assembly preparer, and as such any code that's used by both will have to + keep working in both modes. + diff --git a/tools/assembly-preparer/Scaffolding/AnnotationStore.cs b/tools/assembly-preparer/Scaffolding/AnnotationStore.cs new file mode 100644 index 000000000000..e1fc186bbf11 --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/AnnotationStore.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker; + +public class AnnotationStore { + Dictionary assemblyActions = new Dictionary (); + Dictionary> overrides = new Dictionary> (); + public AssemblyAction GetAction (AssemblyDefinition assembly) + { + if (assemblyActions.TryGetValue (assembly, out var action)) + return action; + throw new InvalidOperationException ($"Assembly {assembly.Name} not found in the annotation store"); + } + + public void SetAction (AssemblyDefinition assembly, AssemblyAction action) + { + assemblyActions [assembly] = action; + } + + public IEnumerable? GetOverrides (MethodDefinition method) + { + if (overrides.TryGetValue (method, out var list)) + return list; + return null; + } + + // Scan all types in the given assemblies and build the overrides map. + // For each method that overrides a base virtual method (or explicitly implements an interface method), + // record an OverrideInformation entry keyed by the base method. + public void CollectOverrides (IEnumerable assemblies, LinkContext context) + { + foreach (var assembly in assemblies) { + foreach (var module in assembly.Modules) { + foreach (var type in module.GetTypes ()) { + CollectOverridesForType (type, context); + } + } + } + } + + void CollectOverridesForType (TypeDefinition type, LinkContext context) + { + if (!type.HasMethods) + return; + + foreach (var method in type.Methods) { + // Handle explicit overrides (.override directive in IL / method.Overrides in Cecil) + if (method.HasOverrides) { + foreach (var overriddenRef in method.Overrides) { + var baseMethod = TryResolve (overriddenRef); + if (baseMethod is not null) + AddOverride (baseMethod, method); + } + } + + // Handle implicit virtual method overrides via the type hierarchy + if (method.IsVirtual && !method.IsNewSlot) { + var baseMethod = GetBaseMethodInTypeHierarchy (type, method, context); + if (baseMethod is not null) + AddOverride (baseMethod, method); + } + } + } + + void AddOverride (MethodDefinition baseMethod, MethodDefinition overridingMethod) + { + if (!overrides.TryGetValue (baseMethod, out var list)) { + list = new List (); + overrides [baseMethod] = list; + } + list.Add (new OverrideInformation (overridingMethod)); + } + + static MethodDefinition? GetBaseMethodInTypeHierarchy (TypeDefinition type, MethodDefinition method, LinkContext context) + { + var baseTypeRef = type.BaseType; + while (baseTypeRef is not null) { + TypeDefinition? baseType; + try { + baseType = context.Resolve (baseTypeRef); + } catch { + break; + } + + if (baseType.HasMethods) { + foreach (var candidate in baseType.Methods) { + if (candidate.IsVirtual && MethodMatch (candidate, method)) + return candidate; + } + } + + baseTypeRef = baseType.BaseType; + } + return null; + } + + static bool MethodMatch (MethodDefinition candidate, MethodDefinition method) + { + if (candidate.Name != method.Name) + return false; + if (candidate.HasGenericParameters != method.HasGenericParameters) + return false; + if (candidate.GenericParameters.Count != method.GenericParameters.Count) + return false; + if (candidate.ReturnType.FullName != method.ReturnType.FullName) + return false; + if (candidate.HasParameters != method.HasParameters) + return false; + if (!candidate.HasParameters) + return true; + if (candidate.Parameters.Count != method.Parameters.Count) + return false; + for (int i = 0; i < candidate.Parameters.Count; i++) { + if (candidate.Parameters [i].ParameterType.FullName != method.Parameters [i].ParameterType.FullName) + return false; + } + return true; + } + + static MethodDefinition? TryResolve (MethodReference methodRef) + { + try { + return methodRef.Resolve (); + } catch { + // FIXME: figure out a way that doesn't require throwing and catching exceptions. + return null; + } + } + + Dictionary> custom_annotations = new (); + + public void SetCustomAnnotation (object key, IMetadataTokenProvider item, object value) + { + if (!custom_annotations.TryGetValue (key, out var annotations)) + custom_annotations [key] = annotations = new Dictionary (); + annotations [item] = value; + } + + public object? GetCustomAnnotation (object key, IMetadataTokenProvider item) + { + if (custom_annotations.TryGetValue (key, out var annotations) && annotations.TryGetValue (item, out var value)) + return value; + + return null; + } + + // This should not be called; once closer to done, just remove this method. + public void Mark (object obj) + { + // Console.WriteLine ($"Annotations.Mark () called from {new StackTrace (1).GetFrame (0)?.GetMethod ()}"); + } +} + +[DebuggerDisplay ("{Override}")] +public class OverrideInformation { + + public MethodDefinition Override { get; } + + internal OverrideInformation (MethodDefinition @override) + { + Override = @override; + } +} diff --git a/tools/assembly-preparer/Scaffolding/AssemblyAction.cs b/tools/assembly-preparer/Scaffolding/AssemblyAction.cs new file mode 100644 index 000000000000..8a71e3197566 --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/AssemblyAction.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Mono.Linker; + +public enum AssemblyAction { + Copy, + Link, + Save, + Skip, + CopyUsed, + AddBypassNGen, + AddBypassNGenUsed, + Delete, +} diff --git a/tools/assembly-preparer/Scaffolding/BaseStep.cs b/tools/assembly-preparer/Scaffolding/BaseStep.cs new file mode 100644 index 000000000000..f0d31bf7e00b --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/BaseStep.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// +// BaseStep.cs +// +// Author: +// Jb Evain (jbevain@novell.com) +// +// (C) 2007 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Diagnostics; +using Mono.Cecil; + +using Xamarin.Tuner; + +namespace Mono.Linker.Steps; + +public abstract class BaseStep : IStep { + DerivedLinkContext? context; + + public DerivedLinkContext Context { + get { + Debug.Assert (context is not null); + return context!; + } + } + + public AnnotationStore Annotations { + get { return Context.Annotations; } + } + + public void Process (DerivedLinkContext context) + { + this.context = context; + + if (!ConditionToProcess ()) + return; + + Process (); + + foreach (var assembly in context.GetAssemblies ()) { + ProcessAssembly (assembly); + } + + EndProcess (); + } + + protected virtual bool ConditionToProcess () + { + return true; + } + + protected virtual void Process () + { + } + + protected virtual void EndProcess () + { + } + + protected virtual void ProcessAssembly (AssemblyDefinition assembly) + { + } +} diff --git a/tools/assembly-preparer/Scaffolding/IStep.cs b/tools/assembly-preparer/Scaffolding/IStep.cs new file mode 100644 index 000000000000..0a01c17d215b --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/IStep.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// +// IStep.cs +// +// Author: +// Jb Evain (jbevain@gmail.com) +// +// (C) 2006 Jb Evain +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using Xamarin.Tuner; + +namespace Mono.Linker.Steps; + +public interface IStep { + void Process (DerivedLinkContext context); +} diff --git a/tools/assembly-preparer/Scaffolding/LinkContext.cs b/tools/assembly-preparer/Scaffolding/LinkContext.cs new file mode 100644 index 000000000000..5921dbe364ca --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/LinkContext.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Mono.Cecil; +using Xamarin.Bundler; + +namespace Mono.Linker; + +public class LinkContext { + Dictionary custom_data = new Dictionary (); + + AnnotationStore annotations = new AnnotationStore (); + public AnnotationStore Annotations { get => annotations; } + + public List Assemblies = new List (); + + public AssemblyDefinition [] GetAssemblies () { return Assemblies.ToArray (); } + + public LinkerConfiguration Configuration { get; private set; } + + public LinkerConfiguration LinkerConfiguration { get => Configuration; } + + public LinkContext (LinkerConfiguration configuration) + { + Configuration = configuration; + } + + public TypeDefinition Resolve (TypeReference type) + { + return Configuration.MetadataResolver.Resolve (type); + } + + public AssemblyDefinition? GetLoadedAssembly (string name) + { + return Assemblies.SingleOrDefault (v => v.Name.Name == name); + } + + public void SetCustomData (string key, string value) + { + custom_data [key] = value; + } + + public bool TryGetCustomData (string key, [NotNullWhen (true)] out string? value) + { + return custom_data.TryGetValue (key, out value); + } +} diff --git a/tools/assembly-preparer/Scaffolding/Target.cs b/tools/assembly-preparer/Scaffolding/Target.cs new file mode 100644 index 000000000000..5d4e99fa509b --- /dev/null +++ b/tools/assembly-preparer/Scaffolding/Target.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Xamarin.Utils; + +namespace Xamarin.Bundler; + +public class Target { + Target () { } + public Application App { get => throw new NotImplementedException (); } +} diff --git a/tools/assembly-preparer/System_Diagnostics_UnreachableException.cs b/tools/assembly-preparer/System_Diagnostics_UnreachableException.cs new file mode 100644 index 000000000000..1f48b65b411c --- /dev/null +++ b/tools/assembly-preparer/System_Diagnostics_UnreachableException.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/UnreachableException.cs + +#if !NET + +namespace System.Diagnostics { + /// + /// Exception thrown when the program executes an instruction that was thought to be unreachable. + /// + public sealed class UnreachableException : Exception { + /// + /// Initializes a new instance of the class with the default error message. + /// + public UnreachableException () + : base ("SR.Arg_UnreachableException") + { + } + + /// + /// Initializes a new instance of the + /// class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public UnreachableException (string? message) + : base (message ?? "SR.Arg_UnreachableException") + { + } + + /// + /// Initializes a new instance of the + /// class with a specified error message and a reference to the inner exception that is the cause of + /// this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public UnreachableException (string? message, Exception? innerException) + : base (message ?? "SR.Arg_UnreachableException", innerException) + { + } + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/System_Index.cs b/tools/assembly-preparer/System_Index.cs new file mode 100644 index 000000000000..9dde742e6c73 --- /dev/null +++ b/tools/assembly-preparer/System_Index.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Index.cs +// to make newer compiler features compile in netstandard2.0 + +#if !NET + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System { + /// Represent a type can be used to index a collection either from the start or the end. + /// + /// Index is used by the C# compiler to support the new index syntax + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// + /// +#if SYSTEM_PRIVATE_CORELIB || MICROSOFT_BCL_MEMORY + public +#else + internal +#endif + readonly struct Index : IEquatable { + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public Index (int value, bool fromEnd = false) + { + if (value < 0) { + ThrowValueArgumentOutOfRange_NeedNonNegNumException (); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index (int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index (0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index (~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public static Index FromStart (int value) + { + if (value < 0) { + ThrowValueArgumentOutOfRange_NeedNonNegNumException (); + } + + return new Index (value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public static Index FromEnd (int value) + { + if (value < 0) { + ThrowValueArgumentOutOfRange_NeedNonNegNumException (); + } + + return new Index (~value); + } + + /// Returns the index value. + public int Value { + get { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public int GetOffset (int length) + { + int offset = _value; + if (IsFromEnd) { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals ([NotNullWhen (true)] object? value) => value is Index && _value == ((Index) value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals (Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode () => _value; + + /// Converts integer number to an Index. + public static implicit operator Index (int value) => FromStart (value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString () + { + if (IsFromEnd) + return ToStringFromEnd (); + + return ((uint) Value).ToString (); + } + + private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException () + { +#if SYSTEM_PRIVATE_CORELIB + throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); +#else + throw new ArgumentOutOfRangeException ("value", "value must be non-negative"); +#endif + } + + private string ToStringFromEnd () + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char [11]; // 1 for ^ and 10 for longest possible uint value + bool formatted = ((uint) Value).TryFormat (span.Slice (1), out int charsWritten); + Debug.Assert (formatted); + span [0] = '^'; + return new string (span.Slice (0, charsWritten + 1)); +#else + return '^' + Value.ToString(); +#endif + } + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/System_Range.cs b/tools/assembly-preparer/System_Range.cs new file mode 100644 index 000000000000..453dd1a4ccc7 --- /dev/null +++ b/tools/assembly-preparer/System_Range.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Copied from https://raw.githubusercontent.com/dotnet/dotnet/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Range.cs +// to make newer compiler features compile in netstandard2.0 + +#if !NET + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#if NETSTANDARD2_0 || NETFRAMEWORK +using System.Numerics.Hashing; +#endif + +namespace System { + /// Represent a range has start and end indexes. + /// + /// Range is used by the C# compiler to support the range syntax. + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; + /// int[] subArray1 = someArray[0..2]; // { 1, 2 } + /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } + /// + /// +#if SYSTEM_PRIVATE_CORELIB || MICROSOFT_BCL_MEMORY + public +#else + internal +#endif + readonly struct Range : IEquatable { + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range (Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals ([NotNullWhen (true)] object? value) => + value is Range r && + r.Start.Equals (Start) && + r.End.Equals (End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals (Range other) => other.Start.Equals (Start) && other.End.Equals (End); + + /// Returns the hash code for this instance. + public override int GetHashCode () + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + return HashCode.Combine (Start.GetHashCode (), End.GetHashCode ()); +#elif !NET + return Start.GetHashCode () ^ End.GetHashCode (); +#else + return HashHelpers.Combine(Start.GetHashCode(), End.GetHashCode()); +#endif + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString () + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char [2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint + int pos = 0; + + if (Start.IsFromEnd) { + span [0] = '^'; + pos = 1; + } + bool formatted = ((uint) Start.Value).TryFormat (span.Slice (pos), out int charsWritten); + Debug.Assert (formatted); + pos += charsWritten; + + span [pos++] = '.'; + span [pos++] = '.'; + + if (End.IsFromEnd) { + span [pos++] = '^'; + } + formatted = ((uint) End.Value).TryFormat (span.Slice (pos), out charsWritten); + Debug.Assert (formatted); + pos += charsWritten; + + return new string (span.Slice (0, pos)); +#else + return Start.ToString() + ".." + End.ToString(); +#endif + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt (Index start) => new Range (start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt (Index end) => new Range (Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new Range (Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength (int length) + { + int start = Start.GetOffset (length); + int end = End.GetOffset (length); + + if ((uint) end > (uint) length || (uint) start > (uint) end) { + ThrowArgumentOutOfRangeException (); + } + + return (start, end - start); + } + + private static void ThrowArgumentOutOfRangeException () + { + throw new ArgumentOutOfRangeException ("length"); + } + } +} + +#endif // !NET diff --git a/tools/assembly-preparer/assembly-preparer.csproj b/tools/assembly-preparer/assembly-preparer.csproj new file mode 100644 index 000000000000..89a35b82fccf --- /dev/null +++ b/tools/assembly-preparer/assembly-preparer.csproj @@ -0,0 +1,341 @@ + + + + assembly-preparer + net$(BundledNETCoreAppTargetFrameworkVersion);netstandard2.0 + net$(BundledNETCoreAppTargetFrameworkVersion) + $(DefineConstants);BUNDLER;ASSEMBLY_PREPARER + Library + true + latest + enable + + + + + + + + + + + + + + external/tools/common/ApplePlatform.cs + + + external/tools/common/Application.cs + + + external/tools/common/Assembly.cs + + + external/tools/common/AssemblyBuildTarget.cs + + + external/tools/common/cache.cs + + + external/tools/common/CoreResolver.cs + + + external/tools/common/DerivedLinkContext.cs + + + external/tools/common/DlsymOptions.cs + + + external/tools/common/Driver.cs + + + external/tools/common/Driver.execution.cs + + + external/tools/common/error.cs + + + external/tools/common/ErrorHelper.tools.cs + + + external/tools/common/Execution.cs + + + external/tools/common/FileCopier.cs + + + external/tools/common/FileUtils.cs + + + external/tools/common/Frameworks.cs + + + external/tools/common/MachO.cs + + + external/tools/common/NormalizedStringComparer.cs + + + external/tools/common/NullableAttributes.cs + + + external/tools/common/CSToObjCMap.cs + + + external/tools/common/ObjCNameIndex.cs + + + external/tools/common/Optimizations.cs + + + external/tools/common/OSPlatformAttributeExtensions.cs + + + external/tools/common/PInvokeWrapperGenerator.cs + + + external/tools/common/RegistrarMode.cs + + + external/tools/common/PathUtils.cs + + + external/tools/common/PListExtensions.cs + + + external/tools/common/ProductConstants.cs + + + external/tools/common/StaticRegistrar.cs + + + external/tools/common/StringUtils.cs + + + external/tools/common/Symbols.cs + + + external/tools/common/Target.cs + + + external/tools/common/TargetFramework.cs + + + external/tools/common/IToolLog.cs + + + external/tools/common/XamarinRuntime.cs + + + external/tools/dotnet-linker/AppBundleRewriter.cs + + + external/tools/dotnet-linker/ApplyPreserveAttributeBase.cs + + + external/tools/dotnet-linker/ApplyPreserveAttributeStep.cs + + + external/tools/dotnet-linker/Compat.cs + + + external/tools/dotnet-linker/DotNetResolver.cs + + + external/tools/dotnet-linker/Extensions.cs + + + external/tools/dotnet-linker/LinkerConfiguration.cs + + + external/tools/dotnet-linker/MarkForStaticRegistrarStep.cs + + + external/tools/dotnet-linker/MarkNSObjectsStep.cs + + + external/tools/dotnet-linker/OptimizeGeneratedCodeStep.cs + + + external/tools/dotnet-linker/PreserveProtocolsStep.cs + + + external/tools/dotnet-linker/PreserveSmartEnumConversionsStep.cs + + + external/tools/dotnet-linker/Steps/AssemblyModifierStep.cs + + + external/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs + + + external/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs + + + external/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs + + + external/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs + + + external/tools/dotnet-linker/Steps/PreserveBlockCodeStep.cs + + + external/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs + + + external/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs + + + external/tools/linker/CoreTypeMapStep.cs + + + external/tools/linker/CustomSymbolWriter.cs + + + external/tools/linker/MarkNSObjects.cs + + + external/tools/linker/MobileExtensions.cs + + + external/tools/linker/MonoTouch.Tuner/Extensions.cs + + + external/tools/linker/ObjCExtensions.cs + + + external/tools/linker/OptimizeGeneratedCode.cs + + + external/tools/linker/RegistrarRemovalTrackingStep.cs + + + external/src/Foundation/ExportAttribute.cs + + + external/src/Foundation/ConnectAttribute.cs + + + external/src/ObjCRuntime/ArgumentSemantic.cs + + + external/src/ObjCRuntime/BindingImplAttribute.cs + + + external/src/ObjCRuntime/Constants.cs + + + external/src/ObjCRuntime/ErrorHelper.cs + + + external/src/ObjCRuntime/ExceptionMode.cs + + + external/src/ObjCRuntime/LinkWithAttribute.cs + + + external/src/ObjCRuntime/NativeNameAttribute.cs + + + external/src/ObjCRuntime/Registrar.core.cs + + + external/src/ObjCRuntime/Registrar.cs + + + external/builds/mono-ios-sdk-destdir/ios-sources/external/linker/src/tuner/Mono.Tuner/Extensions.cs + + + external/builds/mono-ios-sdk-destdir/ios-sources/external/linker/src/linker/Linker/MethodDefinitionExtensions.cs + + + mono-archive/Linker/Linker/TypeReferenceExtensions.cs + + + external/builds/mono-ios-sdk-destdir/ios-sources/external/linker/src/tuner/Mono.Tuner/CecilRocks.cs + + + external/tools/dotnet-linker/CecilExtensions.cs + + + external/tools/dotnet-linker/DocumentionComments.cs + + + external/tools/common/SdkVersions.cs + + + + + + external/tools/mtouch/Errors.designer.cs + Errors.resx + + + external/tools/mtouch/Errors.resx + ResXFileCodeGenerator + Errors.designer.cs + Xamarin.Bundler + Xamarin.Bundler.Errors + + + external/tools/mtouch/Errors.cs.resx + Xamarin.Bundler.Errors.cs + + + external/tools/mtouch/Errors.de.resx + Xamarin.Bundler.Errors.de + + + external/tools/mtouch/Errors.es.resx + Xamarin.Bundler.Errors.es + + + external/tools/mtouch/Errors.fr.resx + Xamarin.Bundler.Errors.fr + + + external/tools/mtouch/Errors.it.resx + Xamarin.Bundler.Errors.it + + + external/tools/mtouch/Errors.ja.resx + Xamarin.Bundler.Errors.ja + + + external/tools/mtouch/Errors.ko.resx + Xamarin.Bundler.Errors.ko + + + external/tools/mtouch/Errors.pl.resx + Xamarin.Bundler.Errors.pl + + + external/tools/mtouch/Errors.pt-BR.resx + Xamarin.Bundler.Errors.pt-BR + + + external/tools/mtouch/Errors.ru.resx + Xamarin.Bundler.Errors.ru + + + external/tools/mtouch/Errors.tr.resx + Xamarin.Bundler.Errors.tr + + + external/tools/mtouch/Errors.zh-Hans.resx + Xamarin.Bundler.Errors.zh-Hans + + + external/tools/mtouch/Errors.zh-Hant.resx + Xamarin.Bundler.Errors.zh-Hant + + + + + + + diff --git a/tools/assembly-preparer/assembly-preparer.slnx b/tools/assembly-preparer/assembly-preparer.slnx new file mode 100644 index 000000000000..a2411d955a58 --- /dev/null +++ b/tools/assembly-preparer/assembly-preparer.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 05ffef60c8d9..8da7ac74c63a 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -20,16 +20,14 @@ using Registrar; -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER using ClassRedirector; #endif #if LEGACY_TOOLS using PlatformResolver = MonoTouch.Tuner.MonoTouchResolver; -#elif NET -using PlatformResolver = Xamarin.Linker.DotNetResolver; #else -#error Invalid defines +using PlatformResolver = Xamarin.Linker.DotNetResolver; #endif #nullable enable @@ -82,6 +80,13 @@ public partial class Application : IToolLog { public List AotArguments = new List (); public List? AotOtherArguments = null; public bool? AotFloat32 = null; + public bool PrepareAssemblies; // True if '$(PrepareAssemblies)' == 'true' +#if ASSEMBLY_PREPARER + public bool InCustomTrimmerStep = false; +#else + public bool InCustomTrimmerStep = true; +#endif + public bool IsPostProcessingAssemblies => PrepareAssemblies && InCustomTrimmerStep; #if !LEGACY_TOOLS public DlsymOptions DlsymOptions; @@ -273,7 +278,11 @@ public Version GetMacCatalystiOSVersion (Version macOSVersion) return value; } +#if !LEGACY_TOOLS public Application (LinkerConfiguration configuration) +#else + public Application () +#endif { #if !LEGACY_TOOLS this.configuration = configuration; @@ -557,6 +566,7 @@ void InitializeDeploymentTarget () } } +#if !ASSEMBLY_PREPARER public void RunRegistrar () { // The static registrar. @@ -623,6 +633,7 @@ public void RunRegistrar () registrar.Generate (resolver, resolvedAssemblies.Values, Path.ChangeExtension (registrar_m, "h"), registrar_m, out var _); } } +#endif // !ASSEMBLY_PREPARER public Abi Abi { get { return abi; } @@ -700,7 +711,7 @@ public void ParseAbi (string abi) } #if !LEGACY_TOOLS - public void ParseRegistrar (string v) + public void ParseRegistrar (string? v) { if (StringUtils.IsNullOrEmpty (v)) return; @@ -708,7 +719,7 @@ public void ParseRegistrar (string v) var split = v.Split ('='); var name = split [0]; var value = split.Length > 1 ? split [1] : string.Empty; - switch (name) { + switch (name.ToLowerInvariant ()) { case "static": Registrar = RegistrarMode.Static; break; @@ -720,12 +731,15 @@ public void ParseRegistrar (string v) break; case "partial": case "partial-static": + case "partialstatic": Registrar = RegistrarMode.PartialStatic; break; case "managed-static": + case "managedstatic": Registrar = RegistrarMode.ManagedStatic; break; case "trimmable-static": + case "trimmablestatic": Registrar = RegistrarMode.TrimmableStatic; break; default: @@ -1200,27 +1214,41 @@ public void SetDefaultHiddenWarnings () ErrorHelper.ParseWarningLevel (this, ErrorHelper.WarningLevel.Disable, "4190"); // The class '{0}' will not be registered because the {1} framework has been deprecated from the {2} SDK. } + IToolLog GetLog () + { +#if LEGACY_TOOLS + return ConsoleLog.Instance; +#else + return Configuration.Logger ?? ConsoleLog.Instance; +#endif + } + public void Log (string message) { - Console.WriteLine (message); + GetLog ().Log (message); } public void LogError (string message) { - Console.Error.WriteLine (message); + GetLog ().LogError (message); + } + + public void LogError (ProductException exception) + { + GetLog ().LogError (exception); } - public void LogError (Exception exception) + public void LogWarning (ProductException exception) { - ErrorHelper.Show (this, exception); + GetLog ().LogWarning (exception); } public void LogException (Exception exception) { - ErrorHelper.Show (this, exception); + GetLog ().LogException (exception); } - int verbosity = Driver.GetDefaultVerbosity (); + int verbosity = Driver.GetDefaultVerbosity (Driver.NAME); public int Verbosity { get => verbosity; set => verbosity = value; diff --git a/tools/common/Assembly.cs b/tools/common/Assembly.cs index 8e100d6d9609..c03e249d68d3 100644 --- a/tools/common/Assembly.cs +++ b/tools/common/Assembly.cs @@ -8,7 +8,6 @@ using System.Xml; using Mono.Cecil; using Mono.Tuner; -using MonoTouch.Tuner; using ObjCRuntime; using Xamarin; using Xamarin.Utils; @@ -58,6 +57,7 @@ public partial class Assembly { public AssemblyDefinition AssemblyDefinition; public bool? IsFrameworkAssembly { get { return is_framework_assembly; } } + public string FullPath { get { return full_path; @@ -66,7 +66,10 @@ public string FullPath { set { full_path = value; if (!is_framework_assembly.HasValue && !string.IsNullOrEmpty (full_path)) { -#if !LEGACY_TOOLS +#if ASSEMBLY_PREPARER + is_framework_assembly = false; // silence compiler warning + throw new InvalidOperationException (); +#elif !LEGACY_TOOLS is_framework_assembly = App.Configuration.FrameworkAssemblies.Contains (GetIdentity (full_path)); #else var real_full_path = Application.GetRealPath (App, full_path); @@ -126,7 +129,7 @@ public void LoadSymbols () symbols_loaded = false; try { var pdb = Path.ChangeExtension (FullPath, ".pdb"); - if (File.Exists (pdb)) { + if (File.Exists (pdb) && !string.IsNullOrEmpty (AssemblyDefinition.MainModule.FileName)) { AssemblyDefinition.MainModule.ReadSymbols (); symbols_loaded = true; } diff --git a/tools/common/CoreResolver.cs b/tools/common/CoreResolver.cs index b35f575c9fc1..d869736ee3ba 100644 --- a/tools/common/CoreResolver.cs +++ b/tools/common/CoreResolver.cs @@ -4,6 +4,8 @@ using Mono.Cecil; using Mono.Cecil.Cil; +using Xamarin.Utils; + #nullable enable namespace Xamarin.Bundler { diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index 0f639be2e7b5..e6f21acdc947 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -6,12 +6,13 @@ using Mono.Collections.Generic; using Registrar; + using Mono.Tuner; using Xamarin.Bundler; using Xamarin.Linker; using Xamarin.Utils; -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER using LinkContext = Xamarin.Bundler.DotNetLinkContext; #endif @@ -65,6 +66,11 @@ public DerivedLinkContext (LinkerConfiguration configuration, Application app) } AssemblyDefinition? corlib; + +#if !LEGACY_TOOLS + public RegistrarMode Registrar => App.Registrar; +#endif // !LEGACY_TOOLS + public AssemblyDefinition Corlib { get { if (corlib is null) { @@ -341,6 +347,15 @@ public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttri return false; } + public AssemblyDefinition GetProductAssembly () + { + var productAssemblyName = Driver.GetProductAssembly (App); + var rv = this.GetAssembly (productAssemblyName); + if (rv is null) + throw ErrorHelper.CreateError (1504, Errors.MX1504 /* Can not find the product assembly '{0}' in the list of loaded assemblies. */, productAssemblyName); + return rv; + } + class AttributeStorage : ICustomAttribute { public CustomAttribute Attribute { get; } public TypeReference AttributeType { get; set; } diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index a49cda6eb684..c1416fe5ff70 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Text; +using Xamarin.Bundler; using Xamarin.MacDev; using Xamarin.Utils; @@ -80,14 +81,14 @@ static void ParseOptions (Application app, Mono.Options.OptionSet options, strin } #endif // !LEGACY_TOOLS - public static int GetDefaultVerbosity () + public static int GetDefaultVerbosity (string toolName) { var v = 0; - var fn = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), $".{NAME}-verbosity"); + var fn = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), $".{toolName}-verbosity"); if (File.Exists (fn)) { v = (int) new FileInfo (fn).Length; if (v == 0) - v = 4; // this is the magic verbosity level we give everybody. + v = 4; // this is the magic verbosity level we give everybody if the file exists, but has no size. } return v; } diff --git a/tools/common/ErrorHelper.tools.cs b/tools/common/ErrorHelper.tools.cs index e930f95731f6..7f29a8980733 100644 --- a/tools/common/ErrorHelper.tools.cs +++ b/tools/common/ErrorHelper.tools.cs @@ -38,6 +38,12 @@ public enum WarningLevel { static ConditionalWeakTable> warning_levels = new (); + public static Dictionary? GetWarningLevels (IToolLog log) + { + warning_levels.TryGetValue (log, out var log_warning_levels); + return log_warning_levels; + } + public static WarningLevel GetWarningLevel (IToolLog log, int code) { if (warning_levels.TryGetValue (log, out var log_warning_levels)) { diff --git a/tools/common/IToolLog.cs b/tools/common/IToolLog.cs index 758cb136eaf4..2f9fd150a3ac 100644 --- a/tools/common/IToolLog.cs +++ b/tools/common/IToolLog.cs @@ -1,3 +1,7 @@ +#if BGENERATOR +using ProductException = BindingException; +#endif + using Xamarin.Utils; namespace Xamarin.Bundler; @@ -8,7 +12,8 @@ public interface IToolLog { void Log (string message); void LogError (string message); // Log an error we raise ourselves (through an exception) - void LogError (Exception exception); + void LogError (ProductException exception); + void LogWarning (ProductException exception); // Log an unexpected exception void LogException (Exception exception); } @@ -42,8 +47,10 @@ public class ConsoleLog : IToolLog { #if TESTS int verbosity = 0; -#else +#elif BGENERATOR int verbosity = Driver.GetDefaultVerbosity (); +#else + int verbosity = Driver.GetDefaultVerbosity (Driver.NAME); #endif public int Verbosity { get => verbosity; } @@ -60,11 +67,16 @@ public void LogError (string message) Console.Error.WriteLine (message); } - public void LogError (Exception exception) + public void LogError (ProductException exception) { Console.Error.WriteLine (exception); } + public void LogWarning (ProductException exception) + { + Console.WriteLine (exception); + } + public void LogException (Exception exception) { Console.Error.WriteLine (exception); diff --git a/tools/common/Make.common b/tools/common/Make.common index a83dd8a8c63b..d2dddec4ea22 100644 --- a/tools/common/Make.common +++ b/tools/common/Make.common @@ -3,6 +3,7 @@ LAST_XCODE_BUMP:=$(shell git blame -- $(TOP)/Make.config HEAD | grep " XCODE_VERSION=" | sed 's/ .*//') XCODE_BUMP_COMMIT_DISTANCE:=$(shell git log "$(LAST_XCODE_BUMP)..HEAD" --oneline | wc -l | sed -e 's/ //g') +all-local:: $(abspath ../common/SdkVersions.cs) $(abspath ../common/SdkVersions.cs): ../common/SdkVersions.in.cs Makefile $(TOP)/Make.config $(TOP)/Make.versions $(Q_GEN) sed \ -e 's/@IOS_SDK_VERSION@/$(IOS_SDK_VERSION)/g' -e 's/@TVOS_SDK_VERSION@/$(TVOS_SDK_VERSION)/' -e 's/@MACOS_SDK_VERSION@/$(MACOS_SDK_VERSION)/' \ @@ -49,6 +50,7 @@ $(abspath ../common/SdkVersions.cs): ../common/SdkVersions.in.cs Makefile $(TOP) $(Q) if ! diff $@ $@.tmp >/dev/null; then $(CP) $@.tmp $@; git diff "$@"; echo "The file $(TOP)/tools/common/SdkVersions.cs has been automatically re-generated; please commit the changes."; exit 1; fi $(Q) touch $@ +all-local:: $(abspath ../common/ProductConstants.cs) $(abspath ../common/ProductConstants.cs): ../common/ProductConstants.in.cs Makefile $(TOP)/Make.config $(GIT_DIRECTORY)/index $(Q_GEN) sed \ $(foreach platform,$(DOTNET_PLATFORMS_UPPERCASE),-e 's/@$(platform)_REVISION@/$($(platform)_COMMIT_DISTANCE) ($(CURRENT_BRANCH_SED_ESCAPED): $(CURRENT_HASH))/g') \ @@ -60,3 +62,4 @@ $(abspath ../common/ProductConstants.cs): ../common/ProductConstants.in.cs Makef -e "s/@XCODE_VERSION@/$(XCODE_VERSION)/g" \ -e "s/@XCODE_BUMP_COMMIT_DISTANCE@/$(XCODE_BUMP_COMMIT_DISTANCE)/g" \ $< > $@ + diff --git a/tools/common/Makefile b/tools/common/Makefile new file mode 100644 index 000000000000..6a27eba55f77 --- /dev/null +++ b/tools/common/Makefile @@ -0,0 +1,5 @@ +TOP=../.. + +include $(TOP)/Make.config +include $(TOP)/mk/rules.mk +include ../common/Make.common diff --git a/tools/common/NullableAttributes.cs b/tools/common/NullableAttributes.cs index dc8b0d8591ed..3f54b47e7c72 100644 --- a/tools/common/NullableAttributes.cs +++ b/tools/common/NullableAttributes.cs @@ -44,6 +44,24 @@ internal sealed class MemberNotNullAttribute : Attribute { /// Gets field or property member names. public string [] Members { get; } } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage (AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute (bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute { } + } #endif // !NET diff --git a/tools/common/Optimizations.cs b/tools/common/Optimizations.cs index d243a9b39415..133bb0406d44 100644 --- a/tools/common/Optimizations.cs +++ b/tools/common/Optimizations.cs @@ -172,7 +172,7 @@ public void Initialize (Application app, out List messages) continue; // The remove-dynamic-registrar optimization is required when using NativeAOT - if (app.XamarinRuntime == XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar && values [i] == false) { + if (app.XamarinRuntime == XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar && value == false) { messages.Add (ErrorHelper.CreateWarning (2016, Errors.MX2016 /* Keeping the dynamic registrar (by passing '--optimize=-remove-dynamic-registrar') is not possible, because the dynamic registrar is not supported when using NativeAOT. Support for dynamic registration will still be removed. */)); values [i] = true; continue; diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index f6b55ec0a015..fc2f5d3f7da3 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -2735,7 +2735,7 @@ public CSToObjCMap GetTypeMapDictionary (List exceptions) public void Rewrite () { -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER if (App.Optimizations.RedirectClassHandles == true) { var exceptions = new List (); var map_dict = GetTypeMapDictionary (exceptions); @@ -3964,7 +3964,7 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List nslog_start.AppendLine (");"); } -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER // Generate the native trampoline to call the generated UnmanagedCallersOnly method if we're using the managed static registrar. if (LinkContext.App.Registrar == RegistrarMode.ManagedStatic || LinkContext.App.Registrar == RegistrarMode.TrimmableStatic) { GenerateCallToUnmanagedCallersOnlyMethod (sb, method, isCtor, isVoid, num_arg, descriptiveMethodName, exceptions); @@ -4194,7 +4194,7 @@ void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List } } -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER void GenerateCallToUnmanagedCallersOnlyMethod (AutoIndentStringBuilder sb, ObjCMethod method, bool isCtor, bool isVoid, int num_arg, string descriptiveMethodName, List exceptions) { // Generate the native trampoline to call the generated UnmanagedCallersOnly method. @@ -5170,7 +5170,7 @@ bool TryCreateTokenReferenceUncached (MemberReference member, TokenType implied_ { var token = member.MetadataToken; -#if !LEGACY_TOOLS +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER if (App.Registrar == RegistrarMode.TrimmableStatic) throw ErrorHelper.CreateError (99, $"Can't create a token reference when using the trimmable static registrar (for: {member.FullName})"); @@ -5388,7 +5388,7 @@ public void Register (PlatformResolver? resolver, IEnumerable r.Args) + .OfType () + .Where (r => r.SenderName == "BinaryLogger" && r.Message?.StartsWith ("BinLogFilePath=") == true) + .Select (v => v.Message?.Substring ("BinLogFilePath=".Length) ?? "") + .SingleOrDefault (); + var originalBinlogDirectory = Path.GetDirectoryName (originalBinlogPath)!; + foreach (var record in records) { if (record is null) continue; @@ -60,14 +77,99 @@ static int Main (string [] args) if (record.Args is null) continue; - if (record.Args is TaskStartedEventArgs tsea && tsea.TaskName == "ILLink") { + if (record.Args is not TaskStartedEventArgs tsea) + continue; + + switch (tsea.TaskName) { + case "PrepareAssemblies": { + var taskId = tsea.BuildEventContext?.TaskId; + if (taskId is null) + continue; + var relevantRecords = records. + Select (v => v.Args). + Where (v => v is not null). + Where (v => v.BuildEventContext?.TaskId == taskId). + ToArray (); + + var taskParameters = relevantRecords.Where (v => v is TaskParameterEventArgs).Cast ().ToArray (); + + string? getProperty (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + if (param.Items.Count != 1) + return null; + var item = param.Items [0]; + if (item is null) + return null; + return ((ITaskItem) item).ItemSpec; + } + ITaskItem []? getItems (string name) + { + var param = taskParameters.SingleOrDefault (v => v.ItemType == name); + if (param is null) + return null; + if (param.Items is null) + return null; + return param.Items.Cast ().ToArray (); + } + var outputDirectory = getProperty ("OutputDirectory"); + var optionsFile = getProperty ("OptionsFile"); + var targetFrameworkMoniker = getProperty ("TargetFrameworkMoniker"); + var makeReproPath = getProperty ("MakeReproPath"); + var inputAssemblies = getItems ("InputAssemblies"); + + if (string.IsNullOrEmpty (outputDirectory)) + throw new InvalidOperationException ("OutputDirectory is required"); + outputDirectory = Path.GetFullPath (outputDirectory, originalBinlogDirectory); + + if (string.IsNullOrEmpty (optionsFile)) + throw new InvalidOperationException ("OptionsFile is required"); + optionsFile = Path.GetFullPath (optionsFile, originalBinlogDirectory); + + if (string.IsNullOrEmpty (targetFrameworkMoniker)) + throw new InvalidOperationException ("TargetFrameworkMoniker is required"); + + if (inputAssemblies is null || inputAssemblies.Length == 0) + throw new InvalidOperationException ("InputAssemblies is required"); + + string GetAssemblyInfo (ITaskItem item) + { + var inputPath = Path.GetFullPath (item.ItemSpec, originalBinlogDirectory); + var outputPath = Path.Combine (outputDirectory, Path.GetFileName (inputPath)); + var metadataNames = item.MetadataNames.Cast ().Select (v => v.ToLowerInvariant ()).ToHashSet (); + var isTrimmableString = item.GetMetadata ("IsTrimmable"); + var isTrimmable = string.IsNullOrEmpty (isTrimmableString) ? (bool?) null : string.Equals (isTrimmableString, "true", StringComparison.OrdinalIgnoreCase); + var trimMode = item.GetMetadata ("TrimMode"); + + return $"InputPath={inputPath}|OutputPath={outputPath}|IsTrimmable={isTrimmable}|TrimMode={trimMode}"; + } + + var launcherArgs = new List (); + launcherArgs.Add ("${workspaceFolder}/bin/Debug/ap-launcher.dll"); + if (!string.IsNullOrEmpty (makeReproPath)) + launcherArgs.Add ("--make-repro=" + makeReproPath); + if (!string.IsNullOrEmpty (optionsFile)) + launcherArgs.Add ("--options-file=" + optionsFile); + + foreach (var ia in inputAssemblies) { + launcherArgs.Add ("--input-assembly=" + GetAssemblyInfo (ia)); + } + + WriteApLauncherLaunchJson (CreateLaunchJson (rootDirectory, launcherArgs.ToArray ())); + + break; + } + case "ILLink": { if (skippedLinkerCommands > 0) { Console.WriteLine ($"Skipped an ILLink task invocation, {skippedLinkerCommands} left to skip..."); skippedLinkerCommands--; continue; } - var relevantRecords = records.Where (v => v?.Args?.BuildEventContext?.TaskId == tsea.BuildEventContext?.TaskId).Select (v => v.Args).ToArray (); var cla = relevantRecords.Where (v => v is BuildMessageEventArgs).Cast ().Where (v => v?.ToString ()?.Contains ("CommandLineArguments") == true).ToArray (); foreach (var rr in relevantRecords) { @@ -77,10 +179,12 @@ static int Main (string [] args) return 1; } - WriteLaunchJson (CreateLaunchJson (rootDirectory, arguments)); + WriteDotNetLinkerLaunchJson (CreateLaunchJson (rootDirectory, arguments)); return 0; } } + break; + } } } @@ -88,12 +192,22 @@ static int Main (string [] args) return 1; } - static void WriteLaunchJson (string contents) + static void WriteApLauncherLaunchJson (string contents) + { + WriteLaunchJson ("ap-launcher", contents); + } + + static void WriteDotNetLinkerLaunchJson (string contents) + { + WriteLaunchJson ("dotnet-linker", contents); + } + + static void WriteLaunchJson (string toolName, string contents) { var dir = Environment.CurrentDirectory!; - while (!Directory.Exists (Path.Combine (dir, "tools", "dotnet-linker"))) + while (!Directory.Exists (Path.Combine (dir, "tools", toolName))) dir = Path.GetDirectoryName (dir)!; - var path = Path.Combine (dir, "tools", "dotnet-linker", ".vscode", "launch.json"); + var path = Path.Combine (dir, "tools", toolName, ".vscode", "launch.json"); File.WriteAllText (path, contents); Console.WriteLine ($"Created {path}"); } diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 1914b02994d7..e9257f6727a5 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -80,6 +80,8 @@ public AppBundleRewriter (LinkerConfiguration configuration) // Find corlib and the platform assemblies foreach (var asm in configuration.Assemblies) { if (asm.Name.Name == Driver.CorlibName) { + if (corlib_assembly is not null) + throw new InvalidOperationException ($"Already have a corlib assembly named {corlib_assembly.Name}"); corlib_assembly = asm; } else if (asm.Name.Name == configuration.PlatformAssembly) { platform_assembly = asm; @@ -250,6 +252,13 @@ public TypeReference System_Exception { return GetTypeReference (CorlibAssembly, "System.Exception", out var _); } } + + public TypeReference System_GC { + get { + return GetTypeReference (CorlibAssembly, "System.GC", out var _); + } + } + public TypeReference System_Int32 { get { return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Int32); @@ -528,6 +537,17 @@ public MethodReference System_Console__WriteLine_String_Object { } } + public MethodReference System_GC__KeepAlive { + get { + return GetMethodReference (CorlibAssembly, System_GC, "KeepAlive", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && !v.HasGenericParameters); + } + } + public MethodReference System_Object__ctor { get { return GetMethodReference (CorlibAssembly, System_Object, ".ctor", (v) => v.IsDefaultConstructor ()); @@ -559,6 +579,12 @@ public MethodReference Nullable_Value { } } + public MethodReference Nullable_ctor { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, ".ctor", isStatic: false, System_Nullable_1.GenericParameters [0]); + } + } + public MethodReference Type_GetTypeFromHandle { get { return GetMethodReference (CorlibAssembly, System_Type, "GetTypeFromHandle", isStatic: true, System_RuntimeTypeHandle); @@ -1403,7 +1429,6 @@ public MethodReference Unsafe_AsRef { } } -#if NET public bool TryGet_NSObject_RegisterToggleRef ([NotNullWhen (true)] out MethodDefinition? md) { // the NSObject.RegisterToggleRef method isn't present on all platforms (for example on Mac) @@ -1415,7 +1440,6 @@ public bool TryGet_NSObject_RegisterToggleRef ([NotNullWhen (true)] out MethodDe return false; } } -#endif public void SetCurrentAssembly (AssemblyDefinition value) { @@ -1434,6 +1458,7 @@ void SaveAssembly (AssemblyDefinition assembly) var annotations = configuration.Context.Annotations; var action = annotations.GetAction (assembly); if (action == AssemblyAction.Copy) { +#if !ASSEMBLY_PREPARER // Preserve TypeForwardedTo which would the linker sweep otherwise // Note that the linker will sweep type forwarders even if the assembly isn't trimmed: // https://github.com/dotnet/runtime/blob/9dd59af3aee2f403e63887afef50d98022a2e575/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs#L191-L200 @@ -1442,6 +1467,7 @@ void SaveAssembly (AssemblyDefinition assembly) annotations.Mark (type); } } +#endif // !ASSEMBLY_PREPARER annotations.SetAction (assembly, AssemblyAction.Save); } } @@ -1457,9 +1483,11 @@ public void ClearCurrentAssembly () public CustomAttribute CreateAttribute (MethodReference constructor) { +#if !ASSEMBLY_PREPARER // For some reason the trimmer doesn't mark attribute constructors // This is probably only needed when running as a custom linker step. configuration.Context.Annotations.Mark (constructor.Resolve ()); +#endif // !ASSEMBLY_PREPARER return new CustomAttribute (constructor); } @@ -1478,8 +1506,7 @@ public bool AddDynamicDependencyAttribute (MethodDefinition addToMethod, MethodD return false; if (addToMethod.DeclaringType == dependsOn.DeclaringType) { - var attribute = CreateAttribute (DynamicDependencyAttribute_ctor__String); - attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, DocumentationComments.GetSignature (dependsOn))); + var attribute = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (dependsOn)); return AddAttributeOnlyOnce (addToMethod, attribute); } else if (addToMethod.DeclaringType.Module == dependsOn.DeclaringType.Module) { var attribute = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (dependsOn), dependsOn.DeclaringType); @@ -1501,11 +1528,23 @@ public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, return attribute; } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature) + { + var attribute = CreateAttribute (DynamicDependencyAttribute_ctor__String); + attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, memberSignature)); + return attribute; + } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, TypeDefinition type, AssemblyDefinition assembly) { return CreateDynamicDependencyAttribute (memberSignature, DocumentationComments.GetSignature (type), assembly.Name.Name); } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, string typeName, AssemblyDefinition assembly) + { + return CreateDynamicDependencyAttribute (memberSignature, typeName, assembly.Name.Name); + } + public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, string typeName, string assemblyName) { var attribute = CreateAttribute (DynamicDependencyAttribute_ctor__String_String_String); @@ -1531,7 +1570,16 @@ public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemb /// The method that is the target of the dynamic dependency. public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, MethodDefinition forMethod) { - var attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly); + CustomAttribute attrib; + + if (onType == forMethod.DeclaringType) { + attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod)); + } else if (onType.Module == forMethod.DeclaringType.Module) { + attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType); + } else { + attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly); + } + return AddAttributeToStaticConstructor (onType, attrib); } @@ -1580,13 +1628,6 @@ public bool AddAttributeToStaticConstructor (TypeDefinition onType, CustomAttrib { var cctor = GetOrCreateStaticConstructor (onType, out var modified); modified |= AddAttributeOnlyOnce (cctor, attribute); - - // Remove the BeforeFieldInit attribute from the type, otherwise the linker may trim away the static constructor, and taking our attributes with it. - if (onType.Attributes.HasFlag (TypeAttributes.BeforeFieldInit)) { - onType.Attributes &= ~TypeAttributes.BeforeFieldInit; - modified = true; - } - return modified; } @@ -1599,10 +1640,22 @@ public MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out b staticCtor = type.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, System_Void); staticCtor.CreateBody (out var il); il.Emit (OpCodes.Ret); + modified = true; + } + // Remove the BeforeFieldInit attribute from the type, otherwise the linker may trim away the static constructor, and taking our attributes with it. + if (type.Attributes.HasFlag (TypeAttributes.BeforeFieldInit)) { + type.Attributes &= ~TypeAttributes.BeforeFieldInit; modified = true; } + if (!staticCtor.Body.Instructions.Any (v => v.OpCode != OpCodes.Ret && v.OpCode != OpCodes.Nop)) { + // FIXME: improve workaround. + var body = staticCtor.Body; + body.Instructions.Insert (0, Instruction.Create (OpCodes.Call, this.System_GC__KeepAlive)); + body.Instructions.Insert (0, Instruction.Create (OpCodes.Ldnull)); + } + return staticCtor; } diff --git a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs index 1d4bc9520977..7135c70ffd90 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs @@ -12,7 +12,7 @@ #nullable enable namespace Xamarin.Linker.Steps { - +#if !ASSEMBLY_PREPARER public partial class ApplyPreserveAttribute : ConfigurationAwareSubStep, IApplyPreserveAttribute { ApplyPreserveAttributeImpl impl; @@ -64,6 +64,7 @@ bool IApplyPreserveAttribute.PreserveConditional (TypeDefinition onType, MethodD return true; } } +#endif public interface IApplyPreserveAttribute { bool PreserveType (TypeDefinition type, bool allMembers); diff --git a/tools/dotnet-linker/ApplyPreserveAttributeStep.cs b/tools/dotnet-linker/ApplyPreserveAttributeStep.cs index 18436ab2de88..f1d4025b7bfa 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeStep.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeStep.cs @@ -45,7 +45,11 @@ public bool CreateXmlDescriptionFile { } } +#if ASSEMBLY_PREPARER + public bool UseXmlDescriptionFile { get; set; } +#else public bool UseXmlDescriptionFile { get; set; } = true; +#endif public string XmlDescriptionPath { get; set; } = string.Empty; public ApplyPreserveAttributeStep () @@ -316,6 +320,7 @@ void WriteXmlDescription () Configuration.WriteOutputForMSBuild ("TrimmerRootDescriptor", items); } +#if !ASSEMBLY_PREPARER // The current linker run still needs these roots immediately. Writing the TrimmerRootDescriptor item only // makes the descriptor available to MSBuild after this step has already finished running. var applyXmlStepType = Context.GetType ().Assembly.GetType ("Mono.Linker.Steps.ResolveFromXmlStep"); @@ -326,6 +331,7 @@ void WriteXmlDescription () } else { throw ErrorHelper.CreateError (99, $"Unable to find Mono.Linker.Steps.ResolveFromXmlStep to apply the generated XML description file {xmlPath}"); } +#endif } } } diff --git a/tools/dotnet-linker/Compat.cs b/tools/dotnet-linker/Compat.cs index 491446a4a4b8..dfe1ab3f489d 100644 --- a/tools/dotnet-linker/Compat.cs +++ b/tools/dotnet-linker/Compat.cs @@ -100,7 +100,7 @@ public AnnotationStore Annotations { } } - public AssemblyDefinition GetAssembly (string name) + public AssemblyDefinition? GetAssembly (string name) { return LinkerConfiguration.Context.GetLoadedAssembly (name); } diff --git a/tools/dotnet-linker/DotNetGlobals.cs b/tools/dotnet-linker/DotNetGlobals.cs index 7642a580fb19..46c6a32dbe19 100644 --- a/tools/dotnet-linker/DotNetGlobals.cs +++ b/tools/dotnet-linker/DotNetGlobals.cs @@ -4,6 +4,7 @@ global using System; global using System.Collections.Generic; global using System.Diagnostics.CodeAnalysis; +global using System.Linq; global using System.Runtime.InteropServices; global using Foundation; diff --git a/tools/dotnet-linker/DotNetResolver.cs b/tools/dotnet-linker/DotNetResolver.cs index f71c2ec5b4cb..f989c68c5076 100644 --- a/tools/dotnet-linker/DotNetResolver.cs +++ b/tools/dotnet-linker/DotNetResolver.cs @@ -14,6 +14,12 @@ public DotNetResolver (Application app) public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters) { +#if ASSEMBLY_PREPARER + if (cache.TryGetValue (name.Name, out var assembly)) +#else + if (cache.TryGetValue (name.Name, out var assembly) && assembly.Name.FullName == name.FullName) +#endif + return assembly; throw new NotImplementedException ($"Unable to resolve the assembly reference {name}"); } } diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 1ad78a1a8313..8c7e6b148dd7 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -23,6 +23,7 @@ public class LinkerConfiguration { public Abi Abi = Abi.None; public string AOTCompiler = string.Empty; public string AOTOutputDirectory = string.Empty; + public string AssemblyPublishDir = string.Empty; public string DedupAssembly = string.Empty; public string CacheDirectory { get; private set; } = string.Empty; public Version? DeploymentTarget { get; private set; } @@ -43,10 +44,13 @@ public class LinkerConfiguration { public string PartialStaticRegistrarLibrary { get; set; } = string.Empty; public ApplePlatform Platform { get; private set; } public string PlatformAssembly { get; private set; } = string.Empty; + public bool PublishTrimmed { get; private set; } public string RelativeAppBundlePath { get; private set; } = string.Empty; public Version? SdkVersion { get; private set; } public string SdkRootDirectory { get; private set; } = string.Empty; public string TypeMapFilePath { get; set; } = string.Empty; + public string TrimMode { get; private set; } = string.Empty; + public string UnmanagedCallersOnlyMapPath { get; private set; } = string.Empty; public int Verbosity => Application.Verbosity; public string XamarinNativeLibraryDirectory { get; private set; } = string.Empty; @@ -54,17 +58,41 @@ public class LinkerConfiguration { public Application Application { get; private set; } + public IToolLog Logger { get; private set; } + public IList RegistrationMethods { get; set; } = new List (); public List NativeCodeToCompileAndLink { get; private set; } = new List (); +#if !ASSEMBLY_PREPARER public CompilerFlags CompilerFlags; +#endif + +#if ASSEMBLY_PREPARER + List exceptions = new List (); + public List Exceptions { + get { + return exceptions; + } + } + public DotNetResolver AssemblyResolver { get; private set; } + public IMetadataResolver MetadataResolver { get; private set; } +#endif +#if ASSEMBLY_PREPARER + public LinkContext Context { get => DerivedLinkContext; } +#else LinkContext? context; public LinkContext Context { get => context!; private set { context = value; } } +#endif public DerivedLinkContext DerivedLinkContext { get => Application.LinkContext; } public Profile Profile { get; private set; } +#if ASSEMBLY_PREPARER + public List Assemblies => Application.LinkContext.Assemblies; + public List<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> AddedAssemblies = new (); +#else // The list of assemblies is populated in CollectAssembliesStep. public List Assemblies = new List (); +#endif string? user_optimize_flags; @@ -93,23 +121,29 @@ public AssemblyDefinition EntryAssembly { // This dictionary contains information about the trampolines created for each assembly. public AssemblyTrampolineInfos AssemblyTrampolineInfos = new (); + // ASSEMBLY_PREPARER TODO move pinvoke wrapper generation out of ListExportedFields step (and remove the #pragma warning) +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null internal PInvokeWrapperGenerator? PInvokeWrapperGenerationState; +#pragma warning restore CS0649 public static bool TryGetInstance (LinkContext context, [NotNullWhen (true)] out LinkerConfiguration? configuration) { return configurations.TryGetValue (context, out configuration); } - public static LinkerConfiguration GetInstance (LinkContext context) { if (!TryGetInstance (context, out var instance)) { +#if ASSEMBLY_PREPARER + throw new InvalidOperationException ($"No LinkerConfiguration instance found for the given LinkContext."); +#else if (!context.TryGetCustomData ("LinkerOptionsFile", out var linker_options_file)) throw new Exception ($"No custom linker options file was passed to the linker (using --custom-data LinkerOptionsFile=..."); - instance = new LinkerConfiguration (linker_options_file) { + instance = new LinkerConfiguration (ConsoleLog.Instance, linker_options_file) { Context = context, }; configurations.Add (context, instance); +#endif } return instance; @@ -147,6 +181,7 @@ Configurator GetConfigurator (string linker_file) if (!TryParseOptionalBoolean (value, out result)) throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); }); + var loadWarningLevel = new Action ((key, value, level) => { try { ErrorHelper.ParseWarningLevel (Application, level, value); @@ -177,6 +212,11 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => Application.RootAssemblies.Add (value)), new SaveValue ((key, storage) => storage.AddRange (Application.RootAssemblies.Select (v => $"{key}={v}"))) )}, + { "AssemblyPublishDir", ( + // This is the AssemblyPublishDir MSBuild property for the main project + new LoadValue ((key, value) => AssemblyPublishDir = value), + new SaveValue ((key, storage) => saveNonEmpty (key, AssemblyPublishDir, storage)) + )}, { "AOTArgument", ( new LoadValue ((key, value) => { @@ -511,6 +551,10 @@ Configurator GetConfigurator (string linker_file) }), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TargetFramework.ToString (), storage)) )}, + { "TrimMode", ( + new LoadValue ((key, value) => TrimMode = value), + new SaveValue ((key, storage) => saveNonEmpty (key, TrimMode, storage)) + )}, { "TypeMapAssemblyName", ( new LoadValue ((key, value) => Application.TypeMapAssemblyName = value), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapAssemblyName, storage)) @@ -523,6 +567,10 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => Application.TypeMapOutputDirectory = value), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapOutputDirectory, storage)) )}, + { "UnmanagedCallersOnlyMapPath", ( + new LoadValue ((key, value) => UnmanagedCallersOnlyMapPath = value), + new SaveValue ((key, storage) => saveNonEmpty (key, UnmanagedCallersOnlyMapPath, storage)) + )}, { "UseLlvm", ( new LoadValue ((key, value) => { var use_llvm = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase); @@ -580,28 +628,42 @@ Configurator GetConfigurator (string linker_file) return dict; } - LinkerConfiguration (string linker_file) + public LinkerConfiguration (IToolLog log, string linker_file, Configurator? customConfigurator = null) + : this (log, File.ReadAllLines (linker_file).ToList (), linker_file, customConfigurator) { - if (!File.Exists (linker_file)) - throw new FileNotFoundException ($"The custom linker file {linker_file} does not exist."); + } + + public LinkerConfiguration (IToolLog log, List lines, string linker_file, Configurator? customConfigurator = null) + { + this.Logger = log; LinkerFile = linker_file; Profile = new BaseProfile (this); Application = new Application (this); + +#if ASSEMBLY_PREPARER + AssemblyResolver = new DotNetResolver (Application); + MetadataResolver = new MetadataResolver (AssemblyResolver); + + configurations.Add (this.Context, this); +#endif + +#if !ASSEMBLY_PREPARER CompilerFlags = new CompilerFlags (Application); +#endif var configurator = GetConfigurator (linker_file); - var lines = File.ReadAllLines (linker_file); + var significantLines = new List (); // This is the input the cache uses to verify if the cache is still valid - for (var i = 0; i < lines.Length; i++) { + for (var i = 0; i < lines.Count; i++) { var line = lines [i].TrimStart (); if (line.Length == 0 || line [0] == '#') continue; // Allow comments var eq = line.IndexOf ('='); if (eq == -1) - throw new InvalidOperationException ($"Invalid syntax for line {i + 1} in {linker_file}: No equals sign."); + throw new InvalidOperationException ($"Invalid syntax for line {i + 1} ('{line}') in {linker_file}:{i + 1} : No equals sign."); significantLines.Add (line); @@ -613,6 +675,8 @@ Configurator GetConfigurator (string linker_file) if (configurator.TryGetValue (key, out var actions)) { actions.Load (key, value); + } else if (customConfigurator?.TryGetValue (key, out var customActions) == true) { + customActions.Load (key, value); } else { throw new InvalidOperationException ($"Unknown configuration key '{key}' in {linker_file} at line {i + 1}."); } @@ -626,7 +690,7 @@ Configurator GetConfigurator (string linker_file) } Application.CreateCache (significantLines.ToArray ()); - if (Application.Cache is not null) + if (Application.Cache is not null && !string.IsNullOrEmpty (CacheDirectory)) Application.Cache.SetLocation (Application, CacheDirectory); if (DeploymentTarget is not null) Application.DeploymentTarget = DeploymentTarget; @@ -665,12 +729,18 @@ Configurator GetConfigurator (string linker_file) Application.Initialize (); } - public void Save (List storage) + public void Save (List storage, Configurator? customConfigurator = null) { var configurator = GetConfigurator (LinkerFile); foreach (var kvp in configurator.OrderBy (v => v.Key)) { kvp.Value.Save (kvp.Key, storage); } + + if (customConfigurator is not null) { + foreach (var kvp in customConfigurator.OrderBy (v => v.Key)) { + kvp.Value.Save (kvp.Key, storage); + } + } } // Splits a string in three based on the split character. @@ -771,6 +841,7 @@ public void Write () Application.Log ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); Application.Log ($" TypeMapFilePath: {TypeMapFilePath}"); Application.Log ($" TypeMapOutputDirectory: {Application.TypeMapOutputDirectory}"); + Application.Log ($" UnmanagedCallersOnlyMapPath: {UnmanagedCallersOnlyMapPath}"); Application.Log ($" UseInterpreter: {Application.UseInterpreter}"); Application.Log ($" UseLlvm: {Application.IsLLVM}"); Application.Log ($" Verbosity: {Verbosity}"); @@ -779,10 +850,12 @@ public void Write () } } +#if !ASSEMBLY_PREPARER public string GetAssemblyFileName (AssemblyDefinition assembly) { return Context.GetAssemblyLocation (assembly); } +#endif public void WriteOutputForMSBuild (string itemName, List items) { @@ -821,12 +894,27 @@ public static void Report (LinkContext Context, params Exception [] exceptions) public static void Report (LinkContext context, IList exceptions) { + // Unwrap aggregate exceptions, and collect all exceptions into a single list. + var list = ErrorHelper.CollectExceptions (exceptions); +#if ASSEMBLY_PREPARER + var log = context.Configuration.Logger; + foreach (var ex in list) { + if (ex is ProductException pe) { + if (pe.IsError (context.Configuration.Application)) { + log.LogError (pe); + } else { + log.LogWarning (pe); + } + } else { + log.LogException (ex); + } + } +#else // We can't really use the linker's reporting facilities and keep our own error codes, because we'll // end up re-using the same error codes the linker already uses for its own purposes. So instead show // a generic error using the linker's Context.LogMessage API, and then print our own errors to stderr. // Since we print using a standard message format, msbuild will parse those error messages and show // them as msbuild errors. - var list = ErrorHelper.CollectExceptions (exceptions); if (!TryGetInstance (context, out var instance)) { // Something went very wrong. Just dump out everything. context.LogMessage (MessageContainer.CreateCustomErrorMessage ("No linker configuration available.", 7000)); @@ -844,6 +932,7 @@ public static void Report (LinkContext context, IList exceptions) } // ErrorHelper.Show will print our errors and warnings to stderr. ErrorHelper.Show (instance.Application, list); +#endif } public IEnumerable GetNonDeletedAssemblies (BaseStep step) @@ -854,6 +943,38 @@ public IEnumerable GetNonDeletedAssemblies (BaseStep step) yield return assembly; } } + + public void Log (string value) + { + Log (0, value); + } + + public void Log (string format, params object? [] args) + { + Log (0, format, args); + } + + public void Log (int min_verbosity, string value) + { + if (min_verbosity > Verbosity) + return; + + if (Logger is not null) { + Logger.Log (value); + return; + } + + Console.WriteLine (value); + } + + public void Log (int min_verbosity, string format, params object? [] args) + { + if (min_verbosity > Verbosity) + return; + + var value = string.Format (format, args); + Log (min_verbosity, value); + } } } diff --git a/tools/dotnet-linker/MarkIProtocolHandler.cs b/tools/dotnet-linker/MarkIProtocolHandler.cs index b295ae6d385c..772bca9a3086 100644 --- a/tools/dotnet-linker/MarkIProtocolHandler.cs +++ b/tools/dotnet-linker/MarkIProtocolHandler.cs @@ -16,7 +16,7 @@ public override void Initialize (LinkContext context, MarkContext markContext) { base.Initialize (context); - if (LinkContext.App.Registrar == Bundler.RegistrarMode.Dynamic) { + if (LinkContext.Registrar == Bundler.RegistrarMode.Dynamic) { markContext.RegisterMarkTypeAction (ProcessType); } } @@ -26,19 +26,27 @@ protected override void Process (TypeDefinition type) if (!type.HasInterfaces) return; + var anyAdded = false; foreach (var iface in type.Interfaces) { var resolvedInterfaceType = iface.InterfaceType.Resolve (); // If we're using the dynamic registrar, we need to mark interfaces that represent protocols // even if it doesn't look like the interfaces are used, since we need them at runtime. var isProtocol = type.IsNSObject (LinkContext) && resolvedInterfaceType.HasCustomAttribute (LinkContext, Namespaces.Foundation, "ProtocolAttribute"); if (isProtocol) { - // Mark only if not already marked. - // otherwise we might enqueue something everytime and never get an empty queue - if (!LinkContext.Annotations.IsMarked (resolvedInterfaceType)) { - LinkContext.Annotations.Mark (resolvedInterfaceType); - } + // Preserve the method and field on the static constructor of the type. + abr.AddDynamicDependencyAttributeToStaticConstructor (type, resolvedInterfaceType); + anyAdded = true; } } + +#if ASSEMBLY_PREPARER + if (anyAdded) { + abr.SetCurrentAssembly (type.Module.Assembly); + abr.SaveCurrentAssembly (); + } +#else + _ = anyAdded; +#endif } } } diff --git a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs index 25153aca76fa..298a3d74a896 100644 --- a/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs +++ b/tools/dotnet-linker/Steps/ExceptionalMarkHandler.cs @@ -32,7 +32,11 @@ public virtual void Initialize (LinkContext context) protected AnnotationStore Annotations => Context.Annotations; protected LinkerConfiguration Configuration => LinkerConfiguration.GetInstance (Context); + private protected AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } + +#if !ASSEMBLY_PREPARER protected Profile Profile => Configuration.Profile; +#endif protected Application App => Configuration.Application; diff --git a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs index 95913f88ead9..5393008d2c41 100644 --- a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs +++ b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs @@ -101,7 +101,7 @@ attrib.ConstructorArguments [1].Value is string libraryName && TypeDefinition GetDlfcnType (ModuleDefinition module, string @namespace, string? fieldLibraryName = null) { var frameworkOverride = !string.IsNullOrEmpty (fieldLibraryName) ? fieldLibraryName : current_framework; - var ns = string.IsNullOrEmpty (frameworkOverride) ? @namespace : frameworkOverride; + var ns = frameworkOverride ?? @namespace; var rv = abr.GetOrCreateType (module, ns, "Dlfcn", out var created); if (created) { if (!string.IsNullOrEmpty (frameworkOverride)) { diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 4396c7319478..499ad0a43357 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -57,11 +57,28 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) return; abr.SetCurrentAssembly (assembly); + if (App.IsPostProcessingAssemblies) { + // We need to load what the PrepareAssemblies task did/produced + CollectRegistrarType (info, assembly); + } else { + CreateRegistrarType (info); + abr.SaveCurrentAssembly (); + } + abr.ClearCurrentAssembly (); + } - CreateRegistrarType (info); + void CollectRegistrarType (AssemblyTrampolineInfo info, AssemblyDefinition currentAssembly) + { + var registrarType = currentAssembly.MainModule.Types.SingleOrDefault (v => v.Is ("ObjCRuntime", "__Registrar__")); + if (registrarType is null) + throw ErrorHelper.CreateError (99, $"No __Registrar__ was found in the assembly {currentAssembly.Name.Name} after the PrepareAssemblies step, but none was found. This might be a sign that the PrepareAssemblies step didn't run, or didn't run correctly."); - abr.SaveCurrentAssembly (); - abr.ClearCurrentAssembly (); + info.RegistrarType = registrarType; + + // We don't care about getting the types, but we need the mapping to happen. + // None of the types in the generated table should be trimmed away by the trimmer, so sorting + // them when the table is generated, and then again after trimming (aka here), should result in the same order and thus the same mapping. + GetAndMapTypesToRegister (registrarType, info); } void CreateRegistrarType (AssemblyTrampolineInfo info) @@ -119,7 +136,7 @@ void CreateRegistrarType (AssemblyTrampolineInfo info) AddLoadTypeToModuleConstructor (registrarType); // Compute the list of types that we need to register - var types = GetTypesToRegister (registrarType, info); + var types = GetAndMapTypesToRegister (registrarType, info); GenerateLookupUnmanagedFunction (registrarType, sorted); GenerateLookupType (info, registrarType, types); @@ -164,7 +181,7 @@ void AddLoadTypeToModuleConstructor (TypeDefinition registrarType) Annotations.Mark (moduleConstructor); } - List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampolineInfo info) + List GetAndMapTypesToRegister (TypeDefinition registrarType, AssemblyTrampolineInfo info) { // Compute the list of types that we need to register var types = new List (); @@ -201,6 +218,9 @@ List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampol types.Add (new (wrapperType, wrapperType.Resolve ())); } + // Sort the types by their full name to make sure the generated code is deterministic. + types.Sort ((x, y) => string.Compare (x.Definition.FullName, y.Definition.FullName, StringComparison.Ordinal)); + // Now create a mapping from type to index for (var i = 0; i < types.Count; i++) info.RegisterType (types [i].Definition, (uint) i); @@ -228,7 +248,11 @@ IEnumerable GetRelevantTypes (Func isRelev bool IsTrimmed (MemberReference type) { +#if ASSEMBLY_PREPARER + return false; +#else return StaticRegistrar.IsTrimmed (type, Annotations); +#endif } void GenerateLookupTypeId (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List types) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 28d906d981db..9f737f9cc4ba 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -84,6 +84,8 @@ public class ManagedRegistrarStep : ConfigurationAwareStep { AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } List exceptions = new List (); + Dictionary unmanagedCallersOnlyMap = new (); + void AddException (Exception exception) { if (exceptions is null) @@ -98,6 +100,23 @@ protected override void TryProcess () if (App.Registrar != RegistrarMode.ManagedStatic && App.Registrar != RegistrarMode.TrimmableStatic) return; + if (App.IsPostProcessingAssemblies) { + var ucoMapPath = Configuration.UnmanagedCallersOnlyMapPath; + if (File.Exists (ucoMapPath)) { + foreach (var line in File.ReadAllLines (ucoMapPath)) { + var parts = line.Split ('|'); + if (parts.Length != 2) { + Console.WriteLine ($"Warning: Invalid line in unmanaged_callers_only_map.txt: {line}"); + continue; + } + var methodFullName = parts [0]; + var ucoEntryPoint = parts [1]; + unmanagedCallersOnlyMap.Add (methodFullName, ucoEntryPoint); + } + File.Delete (ucoMapPath); + } + } + Configuration.Application.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); } @@ -113,10 +132,23 @@ protected override void TryEndProcess (out List? exceptions) // Report back any exceptions that occurred during the processing. exceptions = this.exceptions; + if (App.PrepareAssemblies && !App.InCustomTrimmerStep) { + var ucoMapPath = Configuration.UnmanagedCallersOnlyMapPath; + using (var writer = new StreamWriter (ucoMapPath, false)) { + foreach (var entry in unmanagedCallersOnlyMap.Select (kvp => $"{kvp.Key}|{kvp.Value}").OrderBy (v => v)) { + writer.WriteLine (entry); + } + } + } + +#if !ASSEMBLY_PREPARER // Mark some stuff we use later on. - abr.SetCurrentAssembly (abr.PlatformAssembly); - Annotations.Mark (abr.RegistrarHelper_Register.Resolve ()); - abr.ClearCurrentAssembly (); + if (App.InCustomTrimmerStep && App.PrepareAssemblies == false) { + abr.SetCurrentAssembly (abr.PlatformAssembly); + Annotations.Mark (abr.RegistrarHelper_Register.Resolve ()); + abr.ClearCurrentAssembly (); + } +#endif } protected override void TryProcessAssembly (AssemblyDefinition assembly) @@ -181,7 +213,7 @@ bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos, List proxyInterfaces) + { + if (!unmanagedCallersOnlyMap.TryGetValue (method.FullName, out var ucoName)) { + AddException (ErrorHelper.CreateWarning (App, 99, method, $"Couldn't find an entry in the unmanaged_callers_only_map for method {method.FullName}.")); + return; + } + + var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); + if (callbackType is null) { + AddException (ErrorHelper.CreateWarning (App, 99, method, $"Couldn't find the __Registrar_Callbacks__ nested type for method {method.FullName}.")); + return; + } + + var candidates = callbackType.Methods.Where (v => v.Name == ucoName).ToArray (); + if (candidates.Length != 1) { + AddException (ErrorHelper.CreateWarning (App, 99, method, $"Didn't find exactly one matching callback method in __Registrar_Callbacks__ for method {method.FullName}, found {candidates.Length}")); + return; + } + var callback = candidates [0]; + + var info = new TrampolineInfo (callback, method, ucoName); + if (this.App.Registrar == RegistrarMode.TrimmableStatic) { + // Don't set Id here, it's not used. + } else if (int.TryParse (ucoName.Split ('_') [1], NumberStyles.None, CultureInfo.InvariantCulture, out var id)) { + info.Id = id; + } else { + AddException (ErrorHelper.CreateError (App, 99, method, $"Failed to parse the ID from the DynamicDependencyAttribute for method {method.FullName}, the trampoline won't be registered correctly. The member signature was: {ucoName}")); + } + infos.Add (info); + } + int counter; + AssemblyDefinition? last_assembly; void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineInfo infos, List proxyInterfaces) { + if (last_assembly != method.Module.Assembly) { + counter = 0; + last_assembly = method.Module.Assembly; + } + var baseMethod = StaticRegistrar.GetBaseMethodInTypeHierarchy (method); var placeholderType = abr.System_IntPtr; var name = $"callback_{counter++}_{Sanitize (method.DeclaringType.FullName)}_{Sanitize (method.Name)}"; + unmanagedCallersOnlyMap.Add (method.FullName, name); + var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); if (callbackType is null) { callbackType = new TypeDefinition (string.Empty, "__Registrar_Callbacks__", TypeAttributes.NestedPrivate | TypeAttributes.Sealed | TypeAttributes.Class); diff --git a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs index 98194cad5928..b406122d0f26 100644 --- a/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs +++ b/tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs @@ -2,7 +2,8 @@ using System.Linq; using Mono.Cecil; - +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; using Mono.Linker; using Mono.Linker.Steps; using Mono.Tuner; diff --git a/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs b/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs index 22f70f051ef0..e431f8e009bc 100644 --- a/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs +++ b/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs @@ -1,5 +1,6 @@ using Mono.Linker.Steps; using Xamarin.Linker; +using Xamarin.Linker.Steps; using Mono.Cecil; using Mono.Tuner; @@ -7,10 +8,27 @@ #nullable enable namespace Xamarin.Linker.Steps { +#if ASSEMBLY_PREPARER + public class SetBeforeFieldInitStep : AssemblyModifierStep { +#else public class SetBeforeFieldInitStep : ConfigurationAwareSubStep { +#endif protected override string Name { get; } = "Set BeforeFieldInit"; protected override int ErrorCode { get; } = 2380; +#if ASSEMBLY_PREPARER + protected override bool ModifyAssembly (AssemblyDefinition assembly) + { + if (Configuration.DerivedLinkContext.App.Optimizations.RegisterProtocols != true) + return false; + return base.ModifyAssembly (assembly); + } + + protected override bool ProcessType (TypeDefinition type) + { + return ProcessTypeImpl (type); + } +#else public override SubStepTargets Targets { get { return SubStepTargets.Type; @@ -19,6 +37,13 @@ public override SubStepTargets Targets { protected override void Process (TypeDefinition type) { + ProcessTypeImpl (type); + } +#endif + + bool ProcessTypeImpl (TypeDefinition type) + { + var modified = false; // If we're registering protocols, we want to remove the static // constructor on the protocol interface, because it's not needed // (because we've removing all the DynamicDependency attributes @@ -43,13 +68,17 @@ protected override void Process (TypeDefinition type) // the linker. if (Configuration.DerivedLinkContext.App.Optimizations.RegisterProtocols != true) - return; + return modified; if (!type.IsBeforeFieldInit && type.IsInterface && type.HasMethods) { var cctor = type.GetTypeConstructor (); - if (cctor is not null && cctor.IsBindingImplOptimizableCode (LinkContext)) + if (cctor is not null && cctor.IsBindingImplOptimizableCode (Configuration.DerivedLinkContext) && type.IsBeforeFieldInit == false) { type.IsBeforeFieldInit = true; + modified = true; + } } + + return modified; } } } diff --git a/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs b/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs index 3d0f5feb9281..7c3d4f6ab095 100644 --- a/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs @@ -22,7 +22,7 @@ public class TrimmableRegistrarStep : ConfigurationAwareStep { protected override int ErrorCode { get; } = 2470; AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } - List addedAssemblies = new List (); + List<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> addedAssemblies = new (); List exceptions = new List (); void AddException (Exception exception) @@ -39,6 +39,9 @@ protected override void TryProcess () if (App.Registrar != RegistrarMode.TrimmableStatic) return; + if (App.IsPostProcessingAssemblies) + return; + Configuration.Application.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); } @@ -48,6 +51,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, // .NET 10 doesn't support a separate root type map assembly, so we have to add these attributes to the entry assembly instead. var useEntryAssemblyAsRootTypeMapAssembly = App.TargetFramework.Version.Major <= 10; + var createdRootTypeMapAssemblyPath = Path.Combine (App.TypeMapOutputDirectory, App.TypeMapAssemblyName + ".dll"); if (useEntryAssemblyAsRootTypeMapAssembly) { rootTypeMapAssembly = Configuration.EntryAssembly; @@ -55,8 +59,9 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, var rootTypeMapAssemblyName = new AssemblyNameDefinition (App.TypeMapAssemblyName, new Version (1, 0, 0, 0)); rootTypeMapAssembly = AssemblyDefinition.CreateAssembly (rootTypeMapAssemblyName, rootTypeMapAssemblyName.Name, moduleParameters); Annotations.SetAction (rootTypeMapAssembly, AssemblyAction.Link); - addedAssemblies.Add (rootTypeMapAssembly); + addedAssemblies.Add ((createdRootTypeMapAssemblyPath, rootTypeMapAssembly, Configuration.PlatformAssembly + ".dll")); +#if !ASSEMBLY_PREPARER // We're running from inside the linker, but the TypeMapEntryAssembly property can only be set using a command-line // argument, so we need to cheat a bit here and use reflection to set it. This will go away once we're not running // as a custom linker step anymore. @@ -64,6 +69,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, if (typeMapEntryAssemblyProperty is null) throw ErrorHelper.CreateError (99, "Could not find the 'TypeMapEntryAssembly' property on the linker context."); typeMapEntryAssemblyProperty.SetValue (this.Context, App.TypeMapAssemblyName); +#endif } abr.SetCurrentAssembly (rootTypeMapAssembly); @@ -110,7 +116,7 @@ AssemblyDefinition CreateTypeMapRootAssembly (ModuleParameters moduleParameters, // We write the assembly here even if it hasn't changed, because otherwise we'll just end up re-creating // it again during the next incremental build. if (!useEntryAssemblyAsRootTypeMapAssembly) { - rootTypeMapAssembly.Write (Path.Combine (App.TypeMapOutputDirectory, rootTypeMapAssembly.Name.Name + ".dll")); + rootTypeMapAssembly.Write (createdRootTypeMapAssemblyPath); } return rootTypeMapAssembly; } @@ -154,6 +160,14 @@ protected override void TryEndProcess (out List? exceptions) return; } + if (App.IsPostProcessingAssemblies) { + // The assembly-preparer already created the type map assemblies, and the + // TypeMapEntryAssembly MSBuild property tells ILLink about the root type map + // assembly via --typemap-entry-assembly, so there's nothing to do here. + exceptions = null; + return; + } + abr.SetCurrentAssembly (abr.PlatformAssembly); abr.ObjCRuntime_NSObjectProxyAttribute.Resolve ().IsPublic = true; abr.ObjCRuntime_ProtocolProxyAttribute.Resolve ().IsPublic = true; @@ -239,9 +253,10 @@ void addPostAction (AssemblyDefinition assembly, Action acti var typeMapAssemblyName = new AssemblyNameDefinition ("_" + assembly.Name.Name + ".TypeMap", new Version (1, 0, 0, 0)); var typeMapAssembly = AssemblyDefinition.CreateAssembly (typeMapAssemblyName, typeMapAssemblyName.Name, assemblyParameters); + var typeMapAssemblyPath = Path.Combine (App.TypeMapOutputDirectory, typeMapAssembly.Name.Name + ".dll"); var existingAction = Annotations.GetAction (assembly); Annotations.SetAction (typeMapAssembly, existingAction); - addedAssemblies.Add (typeMapAssembly); + addedAssemblies.Add ((typeMapAssemblyPath, typeMapAssembly, assembly.MainModule.FileName)); var accessesAssemblies = new HashSet (); accessesAssemblies.Add (assembly); @@ -591,7 +606,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti // We write the assembly here even if it hasn't changed, because otherwise we'll just end up re-creating // it again during the next incremental build. - typeMapAssembly.Write (Path.Combine (App.TypeMapOutputDirectory, typeMapAssembly.Name.Name + ".dll")); + typeMapAssembly.Write (typeMapAssemblyPath); } foreach (var kvp in postActionsByAssembly) { @@ -604,13 +619,17 @@ void addPostAction (AssemblyDefinition assembly, Action acti abr.ClearCurrentAssembly (); } +#if ASSEMBLY_PREPARER + Configuration.AddedAssemblies.AddRange (addedAssemblies); +#else // Since we're running inside the trimmer, we need to make sure the trimmer knows about the assemblies we've created. // This will go away once we're running outside of the trimmer. var managedAssemblyToLinkItems = new List (); var resolver = abr.PlatformAssembly.MainModule.AssemblyResolver; var getAssembly = resolver.GetType ().GetMethod ("GetAssembly", new Type [] { typeof (string) })!; var cacheAssembly = resolver.GetType ().GetMethod ("CacheAssembly", new Type [] { typeof (AssemblyDefinition) })!; - foreach (var asm in addedAssemblies) { + foreach (var aa in addedAssemblies) { + var asm = aa.Assembly; var fn = Path.Combine (App.TypeMapOutputDirectory, asm.Name.Name + ".dll"); var asmDef = (AssemblyDefinition) getAssembly.Invoke (resolver, [fn])!; cacheAssembly.Invoke (resolver, [asmDef]); @@ -624,6 +643,7 @@ void addPostAction (AssemblyDefinition assembly, Action acti } Configuration.WriteOutputForMSBuild ("ManagedAssemblyToLink", managedAssemblyToLinkItems); +#endif // Report back any exceptions that occurred during the processing. exceptions = this.exceptions; diff --git a/tools/linker/CoreTypeMapStep.cs b/tools/linker/CoreTypeMapStep.cs index c180cf3b43fc..e73b6b9b56b9 100644 --- a/tools/linker/CoreTypeMapStep.cs +++ b/tools/linker/CoreTypeMapStep.cs @@ -168,14 +168,14 @@ bool IsWrapperType (TypeDefinition type) // Cache the results of the IsCIFilter check in a dictionary. It makes this method slightly faster // (total time spent in IsCIFilter when linking monotouch-test went from 11 ms to 3ms). Dictionary ci_filter_types = new Dictionary (); - bool IsCIFilter (TypeReference type) + bool IsCIFilter (TypeReference? type) { if (type is null) return false; bool rv; if (!ci_filter_types.TryGetValue (type, out rv)) { - rv = type.Is (Namespaces.CoreImage, "CIFilter") || IsCIFilter (Context.Resolve (type).BaseType); + rv = type.Is (Namespaces.CoreImage, "CIFilter") || IsCIFilter (Context.Resolve (type)?.BaseType); ci_filter_types [type] = rv; } return rv; @@ -198,7 +198,7 @@ void SetIsDirectBindingValue (TypeDefinition type) var base_type = Context.Resolve (type.BaseType); while (base_type is not null && IsNSObject (base_type)) { isdirectbinding_value [base_type] = null; - base_type = Context.Resolve (base_type.BaseType); + base_type = LinkContext.Resolve (base_type.BaseType); } return; } diff --git a/tools/linker/MarkNSObjects.cs b/tools/linker/MarkNSObjects.cs index 02502154fd10..2dc41e1e7539 100644 --- a/tools/linker/MarkNSObjects.cs +++ b/tools/linker/MarkNSObjects.cs @@ -41,7 +41,7 @@ #nullable enable namespace Xamarin.Linker.Steps { - +#if !ASSEMBLY_PREPARER public class MarkNSObjects : ExceptionalSubStep, IMarkNSObjects { protected override string Name { get; } = "MarkNSObjects"; protected override int ErrorCode { get; } = 2080; @@ -86,6 +86,7 @@ public bool PreserveMethod (TypeDefinition onType, MethodDefinition method) return true; } } +#endif public interface IMarkNSObjects { bool PreserveType (TypeDefinition type, bool allMembers); diff --git a/tools/linker/MobileExtensions.cs b/tools/linker/MobileExtensions.cs index cba0f7f9997e..d1458fdbf60c 100644 --- a/tools/linker/MobileExtensions.cs +++ b/tools/linker/MobileExtensions.cs @@ -44,7 +44,7 @@ public static bool HasCustomAttribute (this ICustomAttributeProvider? provider, if (provider?.HasCustomAttribute (@namespace, name) == true) return true; - return context?.GetCustomAttributes (provider, @namespace, name)?.Count > 0; + return context?.GetCustomAttributes (provider, @namespace, name)?.Any () == true; } public static bool HasCustomAttribute (this ICustomAttributeProvider? provider, string @namespace, string name) diff --git a/tools/linker/MonoTouch.Tuner/Extensions.cs b/tools/linker/MonoTouch.Tuner/Extensions.cs index e1eac2745c0a..d0764f92518d 100644 --- a/tools/linker/MonoTouch.Tuner/Extensions.cs +++ b/tools/linker/MonoTouch.Tuner/Extensions.cs @@ -7,6 +7,10 @@ using Xamarin.Tuner; +#if !LEGACY_TOOLS && !ASSEMBLY_PREPARER +using LinkContext = Xamarin.Bundler.DotNetLinkContext; +#endif + namespace MonoTouch.Tuner { public static class Extensions { diff --git a/tools/linker/ObjCExtensions.cs b/tools/linker/ObjCExtensions.cs index 5482602a5bdf..3d8ab04c91c0 100644 --- a/tools/linker/ObjCExtensions.cs +++ b/tools/linker/ObjCExtensions.cs @@ -117,8 +117,10 @@ public static bool IsNSObject (this TypeDefinition? type, DerivedLinkContext? li return link_context.CachedIsNSObject.Contains (type); return type.Inherits (Namespaces.Foundation, "NSObject" -#if !LEGACY_TOOLS - , link_context.LinkerConfiguration.Context +#if ASSEMBLY_PREPARER + , link_context!.Configuration.MetadataResolver +#elif !LEGACY_TOOLS + , link_context!.LinkerConfiguration.Context #endif ); } diff --git a/tools/linker/OptimizeGeneratedCode.cs b/tools/linker/OptimizeGeneratedCode.cs index 46af059779e8..43bd328b189d 100644 --- a/tools/linker/OptimizeGeneratedCode.cs +++ b/tools/linker/OptimizeGeneratedCode.cs @@ -140,10 +140,13 @@ internal static bool ValidateInstruction (IToolLog log, MethodDefinition caller, case Code.Conv_I1: case Code.Conv_I2: case Code.Conv_I4: + case Code.Conv_I8: case Code.Conv_U: case Code.Sizeof: case Code.Ldfld: case Code.Ldflda: + case Code.Mul: + case Code.And: return null; // just to not hit the CWL below #endif default: @@ -1076,7 +1079,7 @@ static bool ProcessRuntimeArch (OptimizeGeneratedCodeData data, MethodDefinition static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) { if (data.SetupBlockImplDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + var type = data.LinkContext.GetProductAssembly ().MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); foreach (var method in type.Methods) { if (method.Name != "SetupBlockImpl") continue; @@ -1093,7 +1096,7 @@ static MethodReference GetBlockSetupImpl (OptimizeGeneratedCodeData data, Method static MethodReference GetBlockLiteralConstructor (OptimizeGeneratedCodeData data, MethodDefinition caller, Instruction ins) { if (data.BlockCtorDefinition is null) { - var type = data.LinkContext.GetAssembly (Driver.GetProductAssembly (data.LinkContext.App)).MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); + var type = data.LinkContext.GetProductAssembly ().MainModule.GetType (Namespaces.ObjCRuntime, "BlockLiteral"); foreach (var method in type.Methods) { if (!method.IsConstructor) continue; diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index 7c3f1861b4ab..65858c29af01 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -3353,6 +3353,15 @@ public static string MX1503 { } } + /// + /// Looks up a localized string similar to Cannot find the product assembly '{0}' in the list of loaded assemblies.. + /// + public static string MX1504 { + get { + return ResourceManager.GetString("MX1504", resourceCulture); + } + } + /// /// Looks up a localized string similar to Not a Mach-O dynamic library (unknown header '0x{0}'): {1}.. /// diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index cc89125d1ff1..b25ab909b936 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -851,6 +851,10 @@ One or more reference(s) to type '{0}' still exists inside '{1}' after linking + + Cannot find the product assembly '{0}' in the list of loaded assemblies. + + Not a Mach-O dynamic library (unknown header '0x{0}'): {1}. diff --git a/tools/mtouch/mtouch.cs b/tools/mtouch/mtouch.cs index 42ae6901f1a0..2e61df769f2f 100644 --- a/tools/mtouch/mtouch.cs +++ b/tools/mtouch/mtouch.cs @@ -11,6 +11,7 @@ using Mono.Options; using Xamarin.Linker; +using Xamarin.Utils; namespace Xamarin.Bundler { public partial class Driver { @@ -18,7 +19,7 @@ public partial class Driver { static int Main2 (string [] args) { - var app = new Application (new LinkerConfiguration ()); + var app = new Application (); var os = new OptionSet (); ParseOptions (app, os, args); diff --git a/tools/tools.slnx b/tools/tools.slnx index 140d0af4708c..d4fd9bbf97a3 100644 --- a/tools/tools.slnx +++ b/tools/tools.slnx @@ -1,4 +1,5 @@ + From bacfada2de64ded2f8985988c2565a9c64608676 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Jun 2026 20:45:51 +0200 Subject: [PATCH 26/27] [docs] Improve XML docs for StatusCodeError (#25710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve XML documentation for the `StatusCodeError` type: - Remove "To be added." placeholder entries - Add meaningful `` and `` descriptions - Simplify `` references (remove redundant namespace qualifiers) - Reorder XML doc elements to standard order (summary, param, returns) - Remove empty `` nodes 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/SystemConfiguration/StatusCodeError.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/SystemConfiguration/StatusCodeError.cs b/src/SystemConfiguration/StatusCodeError.cs index cf35ba416c2c..06b74df94d3b 100644 --- a/src/SystemConfiguration/StatusCodeError.cs +++ b/src/SystemConfiguration/StatusCodeError.cs @@ -12,8 +12,7 @@ namespace SystemConfiguration { // https://developer.apple.com/library/mac/#documentation/SystemConfiguration/Reference/SystemConfiguration_Utilities/Reference/reference.html - /// Provides access to a text description associated with a . - /// To be added. + /// Provides access to a text description associated with a . public static class StatusCodeError { [DllImport (Constants.SystemConfigurationLibrary)] extern internal static StatusCode /* int */ SCError (); @@ -21,10 +20,9 @@ public static class StatusCodeError { [DllImport (Constants.SystemConfigurationLibrary)] extern static IntPtr /* const char* */ SCErrorString (int code); - /// To be added. - /// Description for the status code. - /// To be added. - /// To be added. + /// Returns the human-readable description for the specified status code. + /// The to describe. + /// A string containing the description of the status code, or if no description is available. public static string? GetErrorDescription (StatusCode statusCode) { var ptr = SCErrorString ((int) statusCode); From ab09468b42bad259b1b864b779be8ba2d6fad595 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Jun 2026 21:20:03 +0200 Subject: [PATCH 27/27] [tests] Replace httpbin.org with an in-proc HTTP server to improve reliability. (#25705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Replaces all httpbin.org dependencies in monotouch-test with an in-proc HTTP server (`HttpbinTestServer`), eliminating external network flakiness from these tests. ## Changes - **New file: `HttpbinTestServer.cs`** — A lightweight `HttpListener`-based server that implements all httpbin.org endpoints used by tests: `/get`, `/post`, `/cookies`, `/cookies/set`, `/redirect/{n}`, `/redirect-to`, `/basic-auth`, `/digest-auth`, `/gzip`, `/html`, `/status/{code}`. - **Updated `NetworkResources.cs`** — `Httpbin.Url` now points to the local server instead of `https://httpbin.org`. Removed httpbin from `HttpsUrls` array since the local server is HTTP-only. - **Removed CI-ignore patterns** — Tests that now hit the local server no longer need `IgnoreInCI`/`IgnoreInCIIfBadNetwork`/`Assert.Inconclusive` workarounds. Tests still using external URLs (microsoft.com, badssl.com) retain their CI-ignore patterns. - **SSL tests unchanged** — Tests requiring real HTTPS (SSL certificate validation) still use `NetworkResources.MicrosoftUrl`. 🤖 Pull request created by Copilot --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/linker/link all/dotnet/shared.csproj | 3 + tests/linker/link sdk/dotnet/shared.csproj | 3 + .../linker/trimmode link/dotnet/shared.csproj | 3 + .../Foundation/UrlSessionTest.cs | 25 +- .../System.Net.Http/HttpbinTestServer.cs | 406 ++++++++++++++++++ .../System.Net.Http/MessageHandlers.cs | 140 ++---- .../NSUrlSessionHandlerTest.cs | 36 +- .../System.Net.Http/NetworkResources.cs | 8 +- 8 files changed, 472 insertions(+), 152 deletions(-) create mode 100644 tests/monotouch-test/System.Net.Http/HttpbinTestServer.cs diff --git a/tests/linker/link all/dotnet/shared.csproj b/tests/linker/link all/dotnet/shared.csproj index 33fc00ceb233..bf4a46cecf39 100644 --- a/tests/linker/link all/dotnet/shared.csproj +++ b/tests/linker/link all/dotnet/shared.csproj @@ -73,6 +73,9 @@ NetworkResources.cs + + HttpbinTestServer.cs + diff --git a/tests/linker/link sdk/dotnet/shared.csproj b/tests/linker/link sdk/dotnet/shared.csproj index 464d06986f00..985c68f62ebe 100644 --- a/tests/linker/link sdk/dotnet/shared.csproj +++ b/tests/linker/link sdk/dotnet/shared.csproj @@ -66,6 +66,9 @@ NetworkResources.cs + + HttpbinTestServer.cs + diff --git a/tests/linker/trimmode link/dotnet/shared.csproj b/tests/linker/trimmode link/dotnet/shared.csproj index 48401810a36d..abe1e66a3c38 100644 --- a/tests/linker/trimmode link/dotnet/shared.csproj +++ b/tests/linker/trimmode link/dotnet/shared.csproj @@ -70,6 +70,9 @@ NetworkResources.cs + + HttpbinTestServer.cs + TestRuntime.cs diff --git a/tests/monotouch-test/Foundation/UrlSessionTest.cs b/tests/monotouch-test/Foundation/UrlSessionTest.cs index 6cd40f773c95..bca101c54e34 100644 --- a/tests/monotouch-test/Foundation/UrlSessionTest.cs +++ b/tests/monotouch-test/Foundation/UrlSessionTest.cs @@ -23,19 +23,12 @@ namespace MonoTouchFixtures.Foundation { [TestFixture] [Preserve (AllMembers = true)] public class UrlSessionTest { - void AssertTrueOrIgnoreInCI (Task task, string message) + void AssertCompleted (Task task, string message) { var value = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), task, out var ex); - if (value) { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - Assert.That (ex, Is.Null, message + " Exception"); - return; - } - - TestRuntime.IgnoreInCI ($"This test times out randomly in CI due to bad network: {message}"); - Assert.That (ex, Is.Null, $"Exception - {message}"); - Assert.Fail (message); + Assert.That (value, Is.True, $"Request timed out: {message}"); + Assert.That (ex, Is.Null, message + " Exception"); } [Test] @@ -54,16 +47,16 @@ public void CreateDataTaskAsync () uploadRequest.HttpMethod = "POST"; /* CreateDataTask */ - AssertTrueOrIgnoreInCI (session.CreateDataTaskAsync (request), "CreateDataTask a"); - AssertTrueOrIgnoreInCI (session.CreateDataTaskAsync (url), "CreateDataTask b"); + AssertCompleted (session.CreateDataTaskAsync (request), "CreateDataTask a"); + AssertCompleted (session.CreateDataTaskAsync (url), "CreateDataTask b"); /* CreateDownloadTask */ - AssertTrueOrIgnoreInCI (session.CreateDownloadTaskAsync (request), "CreateDownloadTask a"); - AssertTrueOrIgnoreInCI (session.CreateDownloadTaskAsync (url), "CreateDownloadTask b"); + AssertCompleted (session.CreateDownloadTaskAsync (request), "CreateDownloadTask a"); + AssertCompleted (session.CreateDownloadTaskAsync (url), "CreateDownloadTask b"); /* CreateUploadTask */ - AssertTrueOrIgnoreInCI (session.CreateUploadTaskAsync (uploadRequest, file_url), "CreateUploadTask a"); - AssertTrueOrIgnoreInCI (session.CreateUploadTaskAsync (uploadRequest, file_data), "CreateUploadTask b"); + AssertCompleted (session.CreateUploadTaskAsync (uploadRequest, file_url), "CreateUploadTask a"); + AssertCompleted (session.CreateUploadTaskAsync (uploadRequest, file_data), "CreateUploadTask b"); } [Test] diff --git a/tests/monotouch-test/System.Net.Http/HttpbinTestServer.cs b/tests/monotouch-test/System.Net.Http/HttpbinTestServer.cs new file mode 100644 index 000000000000..595999ae8729 --- /dev/null +++ b/tests/monotouch-test/System.Net.Http/HttpbinTestServer.cs @@ -0,0 +1,406 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// An in-process HTTP server that mimics httpbin.org endpoints for testing, +// eliminating external network dependencies and the associated flakiness. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace MonoTests.System.Net.Http { + [Preserve (AllMembers = true)] + static class HttpbinTestServer { + static readonly Lazy lazyBaseUrl = new Lazy (Start); + + public static string BaseUrl => lazyBaseUrl.Value; + + static string Start () + { + // IANA suggested range for dynamic/private ports + const int MinPort = 49152; + const int MaxPort = 65535; + + HttpListener? listener = null; + int port = -1; + + for (var p = MinPort; p < MaxPort; p++) { + listener = new HttpListener (); + listener.Prefixes.Add ($"http://127.0.0.1:{p}/"); + try { + listener.Start (); + port = p; + break; + } catch { + // port in use, try next + listener.Close (); + listener = null; + } + } + + if (listener is null || port == -1) + throw new InvalidOperationException ("HttpbinTestServer: Could not find an available port."); + + Task.Run (async () => { + try { + while (listener.IsListening) { + var context = await listener.GetContextAsync (); + _ = Task.Run (() => { + try { + HandleRequest (context); + } catch (Exception ex) { + try { + context.Response.StatusCode = 500; + var body = Encoding.UTF8.GetBytes (ex.ToString ()); + context.Response.OutputStream.Write (body, 0, body.Length); + } catch { + // nothing we can do + } + } finally { + try { + context.Response.Close (); + } catch { + // nothing we can do + } + } + }); + } + } catch (ObjectDisposedException) { + // listener was stopped + } catch (HttpListenerException) { + // listener was stopped + } + }); + + return $"http://127.0.0.1:{port}"; + } + + static void HandleRequest (HttpListenerContext context) + { + var path = context.Request.Url!.AbsolutePath; + + if (path == "/get" || path == "/") { + HandleGet (context); + } else if (path == "/post") { + HandlePost (context); + } else if (path == "/cookies") { + HandleCookies (context); + } else if (path.StartsWith ("/cookies/set", StringComparison.Ordinal)) { + HandleSetCookies (context); + } else if (path.StartsWith ("/redirect-to", StringComparison.Ordinal)) { + HandleRedirectTo (context); + } else if (path.StartsWith ("/redirect/", StringComparison.Ordinal)) { + HandleRedirect (context); + } else if (path.StartsWith ("/basic-auth/", StringComparison.Ordinal)) { + HandleBasicAuth (context); + } else if (path.StartsWith ("/digest-auth/", StringComparison.Ordinal)) { + HandleDigestAuth (context); + } else if (path == "/gzip") { + HandleGzip (context); + } else if (path == "/html") { + HandleHtml (context); + } else if (path.StartsWith ("/status/", StringComparison.Ordinal)) { + HandleStatus (context); + } else { + // Default: 200 OK with empty JSON + WriteJsonResponse (context, "{}"); + } + } + + // GET / or GET /get: return request info as JSON (including headers) + static void HandleGet (HttpListenerContext context) + { + var headerLines = new List (); + foreach (string key in context.Request.Headers) { + var value = context.Request.Headers [key]!; + headerLines.Add ($" \"{key}\": \"{EscapeJsonString (value)}\""); + } + + var json = "{\n \"headers\": {\n" + string.Join (",\n", headerLines) + "\n }\n}"; + WriteJsonResponse (context, json); + } + + // POST /post: echo the posted body and request info + static void HandlePost (HttpListenerContext context) + { + string data; + using (var reader = new StreamReader (context.Request.InputStream, context.Request.ContentEncoding)) { + data = reader.ReadToEnd (); + } + + var json = "{\n \"data\": \"" + EscapeJsonString (data) + "\",\n \"url\": \"" + EscapeJsonString (context.Request.Url!.ToString ()) + "\"\n}"; + WriteJsonResponse (context, json); + } + + // GET /cookies: echo cookies from request as JSON + static void HandleCookies (HttpListenerContext context) + { + var cookieLines = new List (); + var cookieHeader = context.Request.Headers ["Cookie"]; + if (cookieHeader is not null) { + var pairs = cookieHeader.Split (';'); + foreach (var pair in pairs) { + var trimmed = pair.Trim (); + var eqIdx = trimmed.IndexOf ('='); + if (eqIdx > 0) { + var name = trimmed.Substring (0, eqIdx); + var value = trimmed.Substring (eqIdx + 1); + cookieLines.Add ($" \"{name}\": \"{EscapeJsonString (value)}\""); + } + } + } + + var json = "{\n \"cookies\": {\n" + string.Join (",\n", cookieLines) + "\n }\n}"; + WriteJsonResponse (context, json); + } + + // GET /cookies/set?name=value: set cookie(s) via Set-Cookie header and redirect to /cookies + static void HandleSetCookies (HttpListenerContext context) + { + var queryString = context.Request.QueryString; + foreach (string? key in queryString) { + if (key is not null) { + context.Response.AppendHeader ("Set-Cookie", $"{key}={queryString [key]}; Path=/"); + } + } + + context.Response.StatusCode = 302; + context.Response.RedirectLocation = $"{BaseUrl}/cookies"; + } + + // GET /redirect/{n}: chain of redirects, ending at /get + static void HandleRedirect (HttpListenerContext context) + { + var path = context.Request.Url!.AbsolutePath; + var countStr = path.Substring ("/redirect/".Length); + if (!int.TryParse (countStr, out var count) || count < 1) { + context.Response.StatusCode = 400; + return; + } + + context.Response.StatusCode = 302; + if (count > 1) + context.Response.RedirectLocation = $"{BaseUrl}/redirect/{count - 1}"; + else + context.Response.RedirectLocation = $"{BaseUrl}/get"; + } + + // GET /redirect-to?url={url}: single redirect to the specified URL + static void HandleRedirectTo (HttpListenerContext context) + { + var url = context.Request.QueryString ["url"]; + if (url is null) { + context.Response.StatusCode = 400; + return; + } + + context.Response.StatusCode = 302; + context.Response.RedirectLocation = url; + } + + // GET /basic-auth/{user}/{pass}: HTTP Basic authentication + static void HandleBasicAuth (HttpListenerContext context) + { + var path = context.Request.Url!.AbsolutePath; + var parts = path.Substring ("/basic-auth/".Length).Split ('/'); + if (parts.Length != 2) { + context.Response.StatusCode = 400; + return; + } + + var validUser = Uri.UnescapeDataString (parts [0]); + var validPass = Uri.UnescapeDataString (parts [1]); + + var authHeader = context.Request.Headers ["Authorization"]; + if (authHeader is not null && authHeader.StartsWith ("Basic ", StringComparison.Ordinal)) { + try { + var credentials = Encoding.UTF8.GetString (Convert.FromBase64String (authHeader.Substring (6))); + var colonIdx = credentials.IndexOf (':'); + if (colonIdx > 0) { + var user = credentials.Substring (0, colonIdx); + var pass = credentials.Substring (colonIdx + 1); + if (user == validUser && pass == validPass) { + WriteJsonResponse (context, $"{{\"authenticated\": true, \"user\": \"{EscapeJsonString (user)}\"}}"); + return; + } + } + } catch { + // bad base64 or encoding + } + } + + context.Response.StatusCode = 401; + context.Response.AddHeader ("WWW-Authenticate", "Basic realm=\"Fake Realm\""); + } + + // GET /digest-auth/auth/{user}/{pass}: HTTP Digest authentication + static void HandleDigestAuth (HttpListenerContext context) + { + var path = context.Request.Url!.AbsolutePath; + var parts = path.Substring ("/digest-auth/auth/".Length).Split ('/'); + if (parts.Length != 2) { + context.Response.StatusCode = 400; + return; + } + + var validUser = Uri.UnescapeDataString (parts [0]); + var validPass = Uri.UnescapeDataString (parts [1]); + const string realm = "test@example.org"; + + var authHeader = context.Request.Headers ["Authorization"]; + if (authHeader is not null && authHeader.StartsWith ("Digest ", StringComparison.Ordinal)) { + var authParams = ParseDigestAuthHeader (authHeader); + + if (authParams.TryGetValue ("username", out var username) && + authParams.TryGetValue ("nonce", out var nonce) && + authParams.TryGetValue ("uri", out var uri) && + authParams.TryGetValue ("response", out var response)) { + + authParams.TryGetValue ("nc", out var nc); + authParams.TryGetValue ("cnonce", out var cnonce); + authParams.TryGetValue ("qop", out var qop); + + var ha1 = ComputeMD5 ($"{username}:{realm}:{validPass}"); + var ha2 = ComputeMD5 ($"GET:{uri}"); + + string expected; + if (!string.IsNullOrEmpty (qop)) + expected = ComputeMD5 ($"{ha1}:{nonce}:{nc}:{cnonce}:{qop}:{ha2}"); + else + expected = ComputeMD5 ($"{ha1}:{nonce}:{ha2}"); + + if (username == validUser && response == expected) { + WriteJsonResponse (context, $"{{\"authenticated\": true, \"user\": \"{EscapeJsonString (username)}\"}}"); + return; + } + } + } + + // Send digest challenge + var newNonce = Guid.NewGuid ().ToString ("N"); + context.Response.StatusCode = 401; + context.Response.AddHeader ("WWW-Authenticate", + $"Digest realm=\"{realm}\", nonce=\"{newNonce}\", qop=\"auth\", opaque=\"{Guid.NewGuid ():N}\", algorithm=MD5, stale=FALSE"); + } + + // GET /gzip: return gzip-compressed JSON response + static void HandleGzip (HttpListenerContext context) + { + var json = "{\"gzipped\": true, \"method\": \"GET\", \"origin\": \"127.0.0.1\"}"; + var jsonBytes = Encoding.UTF8.GetBytes (json); + + using var ms = new MemoryStream (); + using (var gzip = new GZipStream (ms, CompressionMode.Compress, leaveOpen: true)) { + gzip.Write (jsonBytes, 0, jsonBytes.Length); + } + + var compressed = ms.ToArray (); + context.Response.ContentType = "application/json"; + context.Response.AddHeader ("Content-Encoding", "gzip"); + context.Response.ContentLength64 = compressed.Length; + context.Response.OutputStream.Write (compressed, 0, compressed.Length); + } + + // GET /html: return an HTML response with Content-Length + static void HandleHtml (HttpListenerContext context) + { + const string html = "Test

Herman Melville - Moby Dick

"; + var bytes = Encoding.UTF8.GetBytes (html); + context.Response.ContentType = "text/html; charset=utf-8"; + context.Response.ContentLength64 = bytes.Length; + context.Response.OutputStream.Write (bytes, 0, bytes.Length); + } + + // GET /status/{code}: return a response with the specified HTTP status code + static void HandleStatus (HttpListenerContext context) + { + var path = context.Request.Url!.AbsolutePath; + var codeStr = path.Substring ("/status/".Length); + if (int.TryParse (codeStr, out var code)) + context.Response.StatusCode = code; + else + context.Response.StatusCode = 400; + } + + static void WriteJsonResponse (HttpListenerContext context, string json) + { + var bytes = Encoding.UTF8.GetBytes (json); + context.Response.ContentType = "application/json"; + context.Response.ContentLength64 = bytes.Length; + context.Response.StatusCode = 200; + context.Response.OutputStream.Write (bytes, 0, bytes.Length); + } + + static string EscapeJsonString (string value) + { + return value + .Replace ("\\", "\\\\") + .Replace ("\"", "\\\"") + .Replace ("\n", "\\n") + .Replace ("\r", "\\r") + .Replace ("\t", "\\t"); + } + + static Dictionary ParseDigestAuthHeader (string header) + { + var result = new Dictionary (StringComparer.OrdinalIgnoreCase); + var content = header.Substring ("Digest ".Length); + + var i = 0; + while (i < content.Length) { + // Skip whitespace and commas + while (i < content.Length && (content [i] == ' ' || content [i] == ',')) + i++; + + if (i >= content.Length) + break; + + // Read key + var keyStart = i; + while (i < content.Length && content [i] != '=') + i++; + + if (i >= content.Length) + break; + + var key = content.Substring (keyStart, i - keyStart).Trim (); + i++; // skip '=' + + // Read value (quoted or unquoted) + string value; + if (i < content.Length && content [i] == '"') { + i++; // skip opening quote + var valueStart = i; + while (i < content.Length && content [i] != '"') + i++; + value = content.Substring (valueStart, i - valueStart); + if (i < content.Length) + i++; // skip closing quote + } else { + var valueStart = i; + while (i < content.Length && content [i] != ',' && content [i] != ' ') + i++; + value = content.Substring (valueStart, i - valueStart); + } + + result [key] = value; + } + + return result; + } + + static string ComputeMD5 (string input) + { + var bytes = MD5.HashData (Encoding.UTF8.GetBytes (input)); + return Convert.ToHexStringLower (bytes); + } + } +} diff --git a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs index 2811dd23ee2c..75cf7a9b6125 100644 --- a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs +++ b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs @@ -116,8 +116,6 @@ void TestNSUrlSessionHandlerCookiesImpl (NSUrlSessionHandler nativeHandler) var managedHasExpectedCookie = managedCookies?.Any (v => v.StartsWith ("cookie=chocolate-chip;", StringComparison.Ordinal)) == true; var nativeHasExpectedCookie = nativeCookies?.Any (v => v.StartsWith ("cookie=chocolate-chip;", StringComparison.Ordinal)) == true; - if (!completed || !managedCookieResult || !nativeCookieResult || !managedHasExpectedCookie || !nativeHasExpectedCookie) - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.That (completed, Is.True, "Network request completed"); Assert.That (ex, Is.Null, "Exception"); Assert.That (managedCookieResult, Is.True, $"Failed to get managed cookies"); @@ -166,17 +164,6 @@ void TestNSUrlSessionHandlerCookieContainerImpl (NSUrlSessionHandler nativeHandl nativeCookieResult = await nativeResponse.Content.ReadAsStringAsync (); }, out var ex); - if (!completed) - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); - var intermittentFailures = new string [] { - "500 Internal Server Error", - "502 Bad Gateway", - "503 Service Temporarily Unavailable", - "504 Gateway Time-out", - }; - if (intermittentFailures.Any (v => managedCookieResult.Contains (v) || nativeCookieResult.Contains (v))) - TestRuntime.IgnoreInCI ("Intermittent network failure - ignore in CI"); - Assert.That (completed, Is.True, "Network request completed"); Assert.That (ex, Is.Null, "Exception"); Assert.That (managedCookieResult, Is.Not.Null, "Managed cookies result"); @@ -204,15 +191,11 @@ public void TestNSurlSessionHandlerCookieContainerSetCookie () nativeCookieResult = await nativeResponse.Content.ReadAsStringAsync (); }, out var ex); - if (!completed) - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.That (completed, Is.True, "Network request completed"); Assert.That (ex, Is.Null, "Exception"); Assert.That (nativeCookieResult, Is.Not.Null, "Native cookies result"); var cookiesFromServer = cookieContainer.GetCookies (new Uri (url)); var hasExpectedCookie = cookiesFromServer.Cast ().Any (v => v.Name == "cookie" && v.Value == "chocolate-chip"); - if (!hasExpectedCookie) - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.That (hasExpectedCookie, Is.True, "Cookies received from server."); } @@ -241,8 +224,6 @@ public void TestNSUrlSessionDefaultDisabledCookies () nativeCookieResult = await nativeResponse.Content.ReadAsStringAsync (); }, out var ex); - if (!completed) - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.That (completed, Is.True, "Network request completed"); Assert.That (ex, Is.Null, "Exception"); Assert.That (nativeSetCookieResult, Is.Not.Null, "Native set-cookies result"); @@ -276,8 +257,6 @@ public void TestNSUrlSessionDefaultDisableCookiesWithManagedContainer () nativeCookieResult = await nativeResponse.Content.ReadAsStringAsync (); }, out var ex); - if (!completed) - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); Assert.That (completed, Is.True, "Network request completed"); Assert.That (ex, Is.Null, "Exception"); Assert.That (nativeSetCookieResult, Is.Not.Null, "Native set-cookies result"); @@ -469,17 +448,10 @@ public void RedirectionWithAuthorizationHeaders (Type handlerType) containsHeaders = json.Contains ("headers"); // ensure we do have the headers in the response }, out var ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail - TestRuntime.IgnoreInCIIfHttpClientTimedOut (); - Assert.Inconclusive ("Request timedout."); - } else if (ex is not null) { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - Assert.That (ex, Is.Null, $"Exception {ex} for {json}"); - } else if (!containsHeaders) { - Assert.Inconclusive ("Response from httpbin does not contain headers, therefore we cannot ensure that if the authoriation is present."); - } else { - Assert.That (containsAuthorizarion, Is.False, $"Authorization header did reach the final destination. {json}"); - } + Assert.That (done, Is.True, "Request completed"); + Assert.That (ex, Is.Null, $"Exception {ex} for {json}"); + Assert.That (containsHeaders, Is.True, "Response does not contain headers"); + Assert.That (containsAuthorizarion, Is.False, $"Authorization header did reach the final destination. {json}"); } [TestCase (true, false, HttpStatusCode.Unauthorized, false, TestName = "NSUrlSessionHandlerOriginCredentialCacheNotSentToCrossOriginRedirectTarget")] @@ -668,10 +640,9 @@ public void RejectSslCertificatesServicePointManager (Type handlerType) var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { try { HttpClient client = new HttpClient (handler); - client.BaseAddress = NetworkResources.Httpbin.Uri; var byteArray = new UTF8Encoding ().GetBytes ("username:password"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ("Basic", Convert.ToBase64String (byteArray)); - result = await client.GetAsync (NetworkResources.Httpbin.GetRedirectUrl (3)); + result = await client.GetAsync (NetworkResources.MicrosoftUrl); } finally { #pragma warning disable SYSLIB0014 // 'ServicePointManager' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead. Settings on ServicePointManager no longer affect SslStream or HttpClient.' (https://aka.ms/dotnet-warnings/SYSLIB0014) ServicePointManager.ServerCertificateValidationCallback = null; @@ -720,10 +691,9 @@ public void AcceptSslCertificatesServicePointManager (Type handlerType) var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { try { HttpClient client = new HttpClient (handler); - client.BaseAddress = NetworkResources.Httpbin.Uri; var byteArray = new UTF8Encoding ().GetBytes ("username:password"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ("Basic", Convert.ToBase64String (byteArray)); - var result = await client.GetAsync (NetworkResources.Httpbin.GetRedirectUrl (3)); + var result = await client.GetAsync (NetworkResources.MicrosoftUrl); } finally { #pragma warning disable SYSLIB0014 // 'ServicePointManager' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead. Settings on ServicePointManager no longer affect SslStream or HttpClient.' (https://aka.ms/dotnet-warnings/SYSLIB0014) ServicePointManager.ServerCertificateValidationCallback = null; @@ -1194,14 +1164,9 @@ public void GHIssue8342 (HttpStatusCode expectedStatus, string validUsername, st httpStatus = result.StatusCode; }, out var ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail - Assert.Inconclusive ("Request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - TestRuntime.IgnoreInCIIfBadNetwork (httpStatus); - Assert.That (ex, Is.Null, "Exception not null"); - Assert.That (httpStatus, Is.EqualTo (expectedStatus), "Status not ok"); - } + Assert.That (done, Is.True, "Request completed"); + Assert.That (ex, Is.Null, "Exception not null"); + Assert.That (httpStatus, Is.EqualTo (expectedStatus), "Status not ok"); } [TestCase (HttpStatusCode.OK, "mandel", "12345678", "mandel", "12345678")] @@ -1221,14 +1186,9 @@ public void SupportsDigestAuthentication (HttpStatusCode expectedStatus, string httpStatus = result.StatusCode; }, out var ex); - if (!done) { - Assert.Inconclusive ("Request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - TestRuntime.IgnoreInCIIfBadNetwork (httpStatus); - Assert.That (ex, Is.Null, "Exception not null"); - Assert.That (httpStatus, Is.EqualTo (expectedStatus), "Status not ok"); - } + Assert.That (done, Is.True, "Request completed"); + Assert.That (ex, Is.Null, "Exception not null"); + Assert.That (httpStatus, Is.EqualTo (expectedStatus), "Status not ok"); } [TestCase] @@ -1252,13 +1212,10 @@ public void GHIssue8344 () httpStatus = result.StatusCode; }, out var ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail - Assert.Inconclusive ("First request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (httpStatus); - Assert.That (ex, Is.Null, "First request exception not null"); - Assert.That (httpStatus, Is.EqualTo (HttpStatusCode.OK), "First status not ok"); - } + Assert.That (done, Is.True, "First request completed"); + Assert.That (ex, Is.Null, "First request exception not null"); + Assert.That (httpStatus, Is.EqualTo (HttpStatusCode.OK), "First status not ok"); + // exactly same operation, diff handler, wrong password, should fail var secondHandler = new NSUrlSessionHandler () { @@ -1273,13 +1230,9 @@ public void GHIssue8344 () httpStatus = result.StatusCode; }, out ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail - Assert.Inconclusive ("Second request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (httpStatus); - Assert.That (ex, Is.Null, "Second request exception not null"); - Assert.That (httpStatus, Is.EqualTo (HttpStatusCode.Unauthorized), "Second status not ok"); - } + Assert.That (done, Is.True, "Second request completed"); + Assert.That (ex, Is.Null, "Second request exception not null"); + Assert.That (httpStatus, Is.EqualTo (HttpStatusCode.Unauthorized), "Second status not ok"); } class TestDelegateHandler : DelegatingHandler { @@ -1333,27 +1286,22 @@ public void GHIssue16339 () } }, out var ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc.. we do not want to fail - Assert.Inconclusive ("Request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - Assert.That (ex, Is.Null, "Exception"); + Assert.That (done, Is.True, "Request completed"); + Assert.That (ex, Is.Null, "Exception"); - for (var i = 0; i < iterations; i++) { - var rsp = delegatingHandler.Responses [i]; - TestRuntime.IgnoreInCIIfBadNetwork (rsp.StatusCode); - Assert.That (delegatingHandler.IsCompleted (i), Is.True, $"Completed #{i}"); - Assert.That (rsp.ReasonPhrase, Is.EqualTo ("OK"), $"ReasonPhrase #{i}"); - Assert.That (rsp.StatusCode, Is.EqualTo (HttpStatusCode.OK), $"StatusCode #{i}"); - - var body = bodies [i]; - // Poor-man's json parser - var data = body.Split ('\n', '\r').Single (v => v.Contains ("\"data\": \"")); - data = data.Trim ().Replace ("\"data\": \"", "").TrimEnd ('"', ','); - data = data.Replace ("\\\"", "\""); - - Assert.That (data, Is.EqualTo (json), $"Post data #{i}"); - } + for (var i = 0; i < iterations; i++) { + var rsp = delegatingHandler.Responses [i]; + Assert.That (delegatingHandler.IsCompleted (i), Is.True, $"Completed #{i}"); + Assert.That (rsp.ReasonPhrase, Is.EqualTo ("OK"), $"ReasonPhrase #{i}"); + Assert.That (rsp.StatusCode, Is.EqualTo (HttpStatusCode.OK), $"StatusCode #{i}"); + + var body = bodies [i]; + // Poor-man's json parser + var data = body.Split ('\n', '\r').Single (v => v.Contains ("\"data\": \"")); + data = data.Trim ().Replace ("\"data\": \"", "").TrimEnd ('"', ','); + data = data.Replace ("\\\"", "\""); + + Assert.That (data, Is.EqualTo (json), $"Post data #{i}"); } } @@ -1373,17 +1321,11 @@ public void UpdateRequestUriAfterRedirect (Type handlerType) using var request = new HttpRequestMessage (HttpMethod.Get, initialRequestUri); Assert.That (request.RequestUri.ToString (), Is.EqualTo (initialRequestUri), "Initial RequestUri"); using var response = await client.SendAsync (request); - TestRuntime.IgnoreInCIIfBadNetwork (response.StatusCode); Assert.That (request.RequestUri.ToString (), Is.EqualTo (postRequestUri), "Post RequestUri"); }, out var ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc. we do not want to fail - TestRuntime.IgnoreInCIIfHttpClientTimedOut (); - Assert.Inconclusive ("Request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - Assert.That (ex, Is.Null, "Exception"); - } + Assert.That (done, Is.True, "Request completed"); + Assert.That (ex, Is.Null, "Exception"); } [TestCase (typeof (NSUrlSessionHandler))] @@ -1401,17 +1343,11 @@ public void RequestUriNotUpdatedIfNotRedirect (Type handlerType) using var request = new HttpRequestMessage (HttpMethod.Get, requestUri); Assert.That (request.RequestUri.ToString (), Is.EqualTo (requestUri), "Initial RequestUri"); using var response = await client.SendAsync (request); - TestRuntime.IgnoreInCIIfBadNetwork (response.StatusCode); Assert.That (request.RequestUri.ToString (), Is.EqualTo (requestUri), "Post RequestUri"); }, out var ex); - if (!done) { // timeouts happen in the bots due to dns issues, connection issues etc. we do not want to fail - TestRuntime.IgnoreInCIIfHttpClientTimedOut (); - Assert.Inconclusive ("Request timedout."); - } else { - TestRuntime.IgnoreInCIIfBadNetwork (ex); - Assert.That (ex, Is.Null, "Exception"); - } + Assert.That (done, Is.True, "Request completed"); + Assert.That (ex, Is.Null, "Exception"); } // https://github.com/dotnet/macios/issues/23764 diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs index 79c787d387c1..e25ede8113e8 100644 --- a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs +++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs @@ -34,22 +34,14 @@ public void DecompressedResponseDoesNotHaveContentEncodingOrContentLength () // Use ResponseHeadersRead so that the response content is not buffered, // which would cause HttpContent to compute Content-Length from the buffer. var response = await client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead); - - if (!response.IsSuccessStatusCode) { - Assert.Inconclusive ($"Request failed with status {response.StatusCode}"); - return; - } + response.EnsureSuccessStatusCode (); noContentEncoding = response.Content.Headers.ContentEncoding.Count == 0; noContentLength = response.Content.Headers.ContentLength is null; body = await response.Content.ReadAsStringAsync (); }, out var ex); - if (!done) { - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); - Assert.Inconclusive ("Request timed out."); - } - TestRuntime.IgnoreInCIIfBadNetwork (ex); + Assert.That (done, Is.True, "Request completed"); Assert.That (ex, Is.Null, $"Exception: {ex}"); Assert.That (noContentEncoding, Is.True, "Content-Encoding header should be removed for decompressed content"); Assert.That (noContentLength, Is.True, "Content-Length header should be removed for decompressed content"); @@ -73,22 +65,14 @@ public void NonCompressedResponseHasContentLength () // Use ResponseHeadersRead so that the response content is not buffered, // which would cause HttpContent to compute Content-Length from the buffer. var response = await client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead); - - if (!response.IsSuccessStatusCode) { - Assert.Inconclusive ($"Request failed with status {response.StatusCode}"); - return; - } + response.EnsureSuccessStatusCode (); noContentEncoding = response.Content.Headers.ContentEncoding.Count == 0; contentLength = response.Content.Headers.ContentLength; body = await response.Content.ReadAsStringAsync (); }, out var ex); - if (!done) { - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); - Assert.Inconclusive ("Request timed out."); - } - TestRuntime.IgnoreInCIIfBadNetwork (ex); + Assert.That (done, Is.True, "Request completed"); Assert.That (ex, Is.Null, $"Exception: {ex}"); Assert.That (noContentEncoding, Is.True, "Content-Encoding should not be present for non-compressed content"); Assert.That (contentLength, Is.Not.Null, "Content-Length header should be present for non-compressed content"); @@ -112,22 +96,14 @@ public void KeepHeadersAfterDecompressionSwitch () using var request = new HttpRequestMessage (HttpMethod.Get, $"{NetworkResources.Httpbin.Url}/gzip"); request.Headers.TryAddWithoutValidation ("Accept-Encoding", "gzip"); var response = await client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead); - - if (!response.IsSuccessStatusCode) { - Assert.Inconclusive ($"Request failed with status {response.StatusCode}"); - return; - } + response.EnsureSuccessStatusCode (); hasContentEncoding = response.Content.Headers.ContentEncoding.Count > 0; hasContentLength = response.Content.Headers.ContentLength is not null; body = await response.Content.ReadAsStringAsync (); }, out var ex); - if (!done) { - TestRuntime.IgnoreInCI ("Transient network failure - ignore in CI"); - Assert.Inconclusive ("Request timed out."); - } - TestRuntime.IgnoreInCIIfBadNetwork (ex); + Assert.That (done, Is.True, "Request completed"); Assert.That (ex, Is.Null, $"Exception: {ex}"); Assert.That (hasContentEncoding, Is.True, "Content-Encoding header should be preserved when KeepHeadersAfterDecompression is enabled"); Assert.That (hasContentLength, Is.True, "Content-Length header should be preserved when KeepHeadersAfterDecompression is enabled"); diff --git a/tests/monotouch-test/System.Net.Http/NetworkResources.cs b/tests/monotouch-test/System.Net.Http/NetworkResources.cs index 2a887a52a3d6..26cf0f7ee030 100644 --- a/tests/monotouch-test/System.Net.Http/NetworkResources.cs +++ b/tests/monotouch-test/System.Net.Http/NetworkResources.cs @@ -20,7 +20,6 @@ public static class NetworkResources { public static string [] HttpsUrls => new [] { MicrosoftUrl, XamarinUrl, - Httpbin.Url, }; public static string [] HttpUrls => new [] { @@ -87,7 +86,8 @@ static string AssertNetworkConnection (string url) } public static class Httpbin { - public static string Url => AssertNetworkConnection ("https://httpbin.org"); + // Uses an in-proc HTTP server to avoid external network dependencies. + public static string Url => HttpbinTestServer.BaseUrl; public static Uri Uri => new Uri ($"{Url}"); public static string DeleteUrl => $"{Url}/delete"; public static string GetUrl => $"{Url}/get"; @@ -95,13 +95,13 @@ public static class Httpbin { public static string PostUrl => $"{Url}/post"; public static string PutUrl => $"{Url}/put"; public static string CookiesUrl => $"{Url}/cookies"; - public static string HttpUrl => AssertNetworkConnection ("http://httpbin.org"); + public static string HttpUrl => Url; public static string GetAbsoluteRedirectUrl (int count) => $"{Url}/absolute-redirect/{count}"; public static string GetRedirectUrl (int count) => $"{Url}/redirect/{count}"; public static string GetRelativeRedirectUrl (int count) => $"{Url}/relative-redirect/{count}"; public static string GetRedirectToUrl (string redirectTo) => $"{Url}/redirect-to?url={Uri.EscapeDataString (redirectTo)}"; - public static string GetStatusCodeUrl (HttpStatusCode status) => $"{HttpUrl}/status/{(int) status}"; + public static string GetStatusCodeUrl (HttpStatusCode status) => $"{Url}/status/{(int) status}"; public static string GetSetCookieUrl (string cookie, string value) => $"{Url}/cookies/set?{cookie}={value}"; public static string GetBasicAuthUrl (string username, string password) => $"{Url}/basic-auth/{username}/{password}"; public static string GetDigestAuthUrl (string username, string password) => $"{Url}/digest-auth/auth/{username}/{password}";