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) 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/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 diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 02926aa4ed8c..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)" - <_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 @@ -633,6 +633,7 @@ AOTOutputDirectory=$(_AOTOutputDirectory) DedupAssembly=$(_DedupAssembly) AppBundleManifestPath=$(_AppBundleManifestPath) + AssemblyPublishDir=$(_AssemblyPublishDir) CacheDirectory=$(_LinkerCacheDirectory) @(_BundlerDlsym -> 'Dlsym=%(Identity)') Debug=$(_BundlerDebug) @@ -662,6 +663,8 @@ PartialStaticRegistrarLibrary=$(_LibPartialStaticRegistrar) Platform=$(_PlatformName) PlatformAssembly=$(_PlatformAssemblyName).dll + PrepareAssemblies=$(PrepareAssemblies) + PublishTrimmed=$(PublishTrimmed) RelativeAppBundlePath=$(_RelativeAppBundlePath) Registrar=$(Registrar) @(ReferenceNativeSymbol -> 'ReferenceNativeSymbol=%(SymbolType):%(SymbolMode):%(Identity)') @@ -674,9 +677,11 @@ SkipMarkingNSObjectsInUserAssemblies=$(_SkipMarkingNSObjectsInUserAssemblies) TargetArchitectures=$(TargetArchitectures) TargetFramework=$(_ComputedTargetFrameworkMoniker) + TrimMode=$(TrimMode) TypeMapAssemblyName=$(_TypeMapAssemblyName) TypeMapFilePath=$(_TypeMapFilePath) TypeMapOutputDirectory=$(_TypeMapOutputDirectory) + UnmanagedCallersOnlyMapPath=$(_UnmanagedCallersOnlyMapPath) UseLlvm=$(MtouchUseLlvm) Verbosity=$(_BundlerVerbosity) Warn=$(_BundlerWarn) @@ -784,44 +789,45 @@ <_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" /> @@ -1290,6 +1299,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 + + + <_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)'" /> + + + - 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 @@ -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..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 - 51ca379106cfd749a498cb0822210ef1aa926e41 + 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" } } 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 @@ + + + + + + + + + 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/msbuild/ILMerge.targets b/msbuild/ILMerge.targets index d8c87c68fccf..cb30898bcba4 100644 --- a/msbuild/ILMerge.targets +++ b/msbuild/ILMerge.targets @@ -18,6 +18,7 @@ + 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/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/AppKit/Enums.cs b/src/AppKit/Enums.cs index bc8a31913034..8656ba155c77 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 05b4c69ff335..814065f997e3 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 d89f9c464287..02e76f13e9a1 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 { @@ -215,7 +217,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 eb3d0e52ebd8..5e0f20492175 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 { @@ -167,7 +168,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 4f773aabac45..5fbc1c320ba5 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 243b2adfc4a8..8aeb5f5e80d1 100644 --- a/src/NaturalLanguage/Enums.cs +++ b/src/NaturalLanguage/Enums.cs @@ -259,7 +259,7 @@ public enum NLLanguage { [Field ("NLLanguageVietnamese")] Vietnamese, - [iOS (16, 0), TV (16, 0), MacCatalyst (16, 0)] + [iOS (16, 0), Mac (13, 0), TV (16, 0), MacCatalyst (16, 0)] [Field ("NLLanguageKazakh")] Kazakh, } @@ -288,12 +288,13 @@ public enum NLTagScheme { /// Indicates that tokens will be tagged with the script in which they were written. [Field ("NLTagSchemeScript")] Script, + [iOS (13, 0), TV (13, 0)] [MacCatalyst (13, 1)] [Field ("NLTagSchemeSentimentScore")] SentimentScore, } - [TV (17, 0), iOS (17, 0), MacCatalyst (17, 0)] + [TV (17, 0), Mac (14, 0), iOS (17, 0), MacCatalyst (17, 0)] public enum NLScript { [Field ("NLScriptUndetermined")] Undetermined, @@ -359,7 +360,7 @@ public enum NLScript { TraditionalChinese, } - [TV (17, 0), iOS (17, 0), MacCatalyst (17, 0)] + [TV (17, 0), Mac (14, 0), iOS (17, 0), MacCatalyst (17, 0)] [Native] public enum NLContextualEmbeddingAssetsResult : long { Available, @@ -367,8 +368,12 @@ public enum NLContextualEmbeddingAssetsResult : long { Error, } - [TV (17, 0), iOS (17, 0), MacCatalyst (17, 0)] + [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/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/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/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); 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 () diff --git a/src/accessibility.cs b/src/accessibility.cs index f6c4cd8520bc..db551e87df39 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 f3fa730c79dd..3992382d93ba 100644 --- a/src/appkit.cs +++ b/src/appkit.cs @@ -3882,8 +3882,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. @@ -8575,8 +8584,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; } @@ -14530,8 +14548,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); @@ -15597,8 +15624,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); @@ -16183,8 +16219,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); @@ -17414,8 +17459,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; } @@ -26810,8 +26864,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 6f24ea8c0327..8000f4d46cc0 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; @@ -1313,7 +1314,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 fd36a24e0610..ad14eb0f6688 100644 --- a/src/avfoundation.cs +++ b/src/avfoundation.cs @@ -22471,8 +22471,18 @@ interface AVAssetDownloadDelegate : NSUrlSessionTaskDelegate { [MacCatalyst (18, 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/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/src/corebluetooth.cs b/src/corebluetooth.cs index 56ee620ce7a4..11f9a99b29e5 100644 --- a/src/corebluetooth.cs +++ b/src/corebluetooth.cs @@ -1003,7 +1003,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 18060e955e99..bf5dfae5b996 100644 --- a/src/coreimage.cs +++ b/src/coreimage.cs @@ -2693,8 +2693,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 a06651792445..5ffae02dde82 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 eb9795f2e0cf..33701bf60950 100644 --- a/src/foundation.cs +++ b/src/foundation.cs @@ -4705,7 +4705,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. @@ -5438,7 +5445,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))] @@ -10041,7 +10052,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; } @@ -12299,8 +12318,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 (); @@ -12536,8 +12564,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:")] @@ -15690,7 +15727,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))] @@ -18675,8 +18721,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")] @@ -20395,8 +20450,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); @@ -20405,8 +20469,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); @@ -20419,8 +20492,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. @@ -20436,8 +20518,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 ed2a9c4f654e..011d8274b11a 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 @@ -3021,8 +3023,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 1f911ab838fb..5bb76f52546c 100644 --- a/src/imageio.cs +++ b/src/imageio.cs @@ -369,11 +369,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. @@ -830,11 +833,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 829a1f8e63c1..6d46ec29e2c5 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 a06bb87a45cd..9c9a49e725a2 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/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/src/uikit.cs b/src/uikit.cs index badf39507942..689655e14e47 100644 --- a/src/uikit.cs +++ b/src/uikit.cs @@ -10256,7 +10256,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. @@ -16588,8 +16597,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. @@ -16644,8 +16662,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); @@ -24414,7 +24441,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 3307429c8ab5..fd06672beaea 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/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/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/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/common/Configuration.cs b/tests/common/Configuration.cs index 8154d4e9b100..20f92ed994ff 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 { @@ -598,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 () @@ -694,7 +730,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 +768,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/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/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/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"); } } 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/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 231f95d534eb..b9b3d0813dce 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); 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) { 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/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/linker/link all/dotnet/shared.csproj b/tests/linker/link all/dotnet/shared.csproj index 6b75ec2a2d4e..bf4a46cecf39 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 @@ -72,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/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"); 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}"; 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}"); } 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/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/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/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/.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/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/.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/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/.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" + } + ] +} 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 5405b8eb0244..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 @@ -59,15 +57,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 = "."; @@ -91,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; @@ -161,6 +157,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) @@ -174,6 +171,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) @@ -280,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; @@ -564,6 +566,7 @@ void InitializeDeploymentTarget () } } +#if !ASSEMBLY_PREPARER public void RunRegistrar () { // The static registrar. @@ -630,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; } @@ -707,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; @@ -715,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; @@ -727,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: @@ -957,7 +964,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"); @@ -1208,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 6ad8d9141958..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; } @@ -600,33 +603,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/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 fe912e00e4a8..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; } @@ -394,8 +395,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/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/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; 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/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/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/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/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/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 diff --git a/tools/devops/automation/templates/common/configure.yml b/tools/devops/automation/templates/common/configure.yml index 53e0b1ebaeb9..4c8bb18dc802 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, diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 1b3908f009ea..e9257f6727a5 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; @@ -79,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; @@ -249,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); @@ -527,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 ()); @@ -558,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); @@ -1402,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) @@ -1414,7 +1440,6 @@ public bool TryGet_NSObject_RegisterToggleRef ([NotNullWhen (true)] out MethodDe return false; } } -#endif public void SetCurrentAssembly (AssemblyDefinition value) { @@ -1433,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 @@ -1441,6 +1467,7 @@ void SaveAssembly (AssemblyDefinition assembly) annotations.Mark (type); } } +#endif // !ASSEMBLY_PREPARER annotations.SetAction (assembly, AssemblyAction.Save); } } @@ -1456,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); } @@ -1477,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); @@ -1500,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); @@ -1530,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); } @@ -1579,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; } @@ -1598,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; } @@ -1726,5 +1780,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/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/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/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 e9375514f12c..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,50 +121,549 @@ 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; } - LinkerConfiguration (string linker_file) + 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) { - if (!File.Exists (linker_file)) - throw new FileNotFoundException ($"The custom linker file {linker_file} does not exist."); + 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}"))) + )}, + { "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) => + { + 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", ( // 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)) + )}, + { "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)) + )}, + { "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)) + )}, + { "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)) + )}, + { "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); + 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; + } + + public LinkerConfiguration (IToolLog log, string linker_file, Configurator? customConfigurator = null) + : this (log, File.ReadAllLines (linker_file).ToList (), linker_file, customConfigurator) + { + } + + 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 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++) { + 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); @@ -146,289 +673,12 @@ 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 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}."); } } @@ -439,12 +689,8 @@ 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) + if (Application.Cache is not null && !string.IsNullOrEmpty (CacheDirectory)) Application.Cache.SetLocation (Application, CacheDirectory); if (DeploymentTarget is not null) Application.DeploymentTarget = DeploymentTarget; @@ -462,8 +708,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) @@ -480,6 +729,20 @@ public static LinkerConfiguration GetInstance (LinkContext context) Application.Initialize (); } + 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. // "a:b" => "a", "b", "" // "a:b:c" => "a", "b", "c" @@ -501,12 +764,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; } @@ -578,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}"); @@ -586,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) { @@ -628,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)); @@ -651,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) @@ -661,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/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/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 68bbe468e009..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 (); + } + + 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."); - CreateRegistrarType (info); + info.RegistrarType = registrarType; - abr.SaveCurrentAssembly (); - abr.ClearCurrentAssembly (); + // 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) @@ -328,7 +352,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 +381,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 +410,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 +439,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 +468,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..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,24 +213,24 @@ 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 e494cec5a972..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); @@ -264,7 +279,7 @@ void addPostAction (AssemblyDefinition assembly, Action 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 +388,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 +527,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")) @@ -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/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index c3ff1f126681..9a5d2966bc7b 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 @@ -110,6 +113,9 @@ external/tools/common/CSToObjCMap.cs + + tools\common\RegistrarMode.cs + external/tools/common/Rewriter.cs @@ -134,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/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 new file mode 100644 index 000000000000..43bd328b189d --- /dev/null +++ b/tools/linker/OptimizeGeneratedCode.cs @@ -0,0 +1,1217 @@ +// 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_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: +#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.GetProductAssembly ().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.GetProductAssembly ().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; 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/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)) \ 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/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index d9ba0a1ea76e..e0ebeca5ccf1 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -33,12 +33,18 @@ external/tools/common/AssemblyBuildTarget.cs + + tools\common\NormalizedStringComparer.cs + external/tools/common/PListExtensions.cs external/tools/common/cache.cs + + tools\common\RegistrarMode.cs + external/tools/linker/MonoTouch.Tuner/Extensions.cs 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 @@ +