diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs index f9a688ebdc9..b54c2f535ec 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs @@ -135,7 +135,7 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha pe.EmitIgnoresAccessChecksToAttribute (accessTargets); // Emit TypeMapLoader class with Initialize() method - EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse, maxArrayRank); + EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse, maxArrayRank, assemblyName); pe.WritePE (stream); } @@ -201,7 +201,7 @@ static void EmitAssemblyTargetAttribute (PEAssemblyBuilder pe, MemberReferenceHa pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorRef, blobHandle); } - static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank) + static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank, string assemblyName) { var metadata = pe.Metadata; @@ -243,21 +243,21 @@ static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHand if (maxArrayRank > 0) { var initializeRef = AddInitializeSingleWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, externalDictArrayTypeSpec, perAssemblyTypeMapNames, maxArrayRank); + initializeRef, externalDictTypeSpec, externalDictArrayTypeSpec, perAssemblyTypeMapNames, maxArrayRank, assemblyName); } else { var initializeRef = AddInitializeSingleNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); - EmitInitializeWithSingleTypeMapNoArrays (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef); + EmitInitializeWithSingleTypeMapNoArrays (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef, assemblyName); } } else { var proxyDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); if (maxArrayRank > 0) { var initializeRef = AddInitializeAggregateWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank); + initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank, assemblyName); } else { var initializeRef = AddInitializeAggregateNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); EmitInitializeWithAggregateTypeMapNoArrays (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef); + initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, assemblyName); } } } @@ -274,7 +274,8 @@ static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe, TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, - int maxArrayRank) + int maxArrayRank, + string assemblyName) { var count = perAssemblyTypeMapNames.Count; @@ -292,6 +293,7 @@ static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), encoder => { + EmitSetTypeMappingEntryAssembly (pe, encoder, assemblyName); // var typeMaps = new IReadOnlyDictionary[N]; (loc 0) EmitNewArrayLocal (encoder, count, externalDictTypeSpec, slot: 0); EmitFillArrayLocal (encoder, count, getExternalSpecs, slot: 0); @@ -345,7 +347,8 @@ static void EmitInitializeWithAggregateTypeMapNoArrays (PEAssemblyBuilder pe, MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, MemberReferenceHandle initializeRef, TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec, - TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) + TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, + string assemblyName) { var count = perAssemblyTypeMapNames.Count; @@ -363,6 +366,7 @@ static void EmitInitializeWithAggregateTypeMapNoArrays (PEAssemblyBuilder pe, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), encoder => { + EmitSetTypeMappingEntryAssembly (pe, encoder, assemblyName); EmitNewArrayLocal (encoder, count, externalDictTypeSpec, slot: 0); EmitFillArrayLocal (encoder, count, getExternalSpecs, slot: 0); @@ -432,7 +436,8 @@ static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle MemberReferenceHandle initializeRef, TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec, IReadOnlyList perAssemblyTypeMapNames, - int maxArrayRank) + int maxArrayRank, + string assemblyName) { var getExternalSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, anchorTypeHandle); var getProxySpec = MakeGenericMethodSpec (pe, getProxyMemberRef, anchorTypeHandle); @@ -441,6 +446,7 @@ static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), encoder => { + EmitSetTypeMappingEntryAssembly (pe, encoder, assemblyName); // TrimmableTypeMap.Initialize(GetExternal(), GetProxy(), arrayMapsByAssemblyAndRank-or-null) encoder.Call (getExternalSpec, parameterCount: 0, returnsValue: true); encoder.Call (getProxySpec, parameterCount: 0, returnsValue: true); @@ -456,7 +462,8 @@ static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle /// static void EmitInitializeWithSingleTypeMapNoArrays (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, - MemberReferenceHandle initializeRef) + MemberReferenceHandle initializeRef, + string assemblyName) { var getExternalSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, anchorTypeHandle); var getProxySpec = MakeGenericMethodSpec (pe, getProxyMemberRef, anchorTypeHandle); @@ -465,6 +472,7 @@ static void EmitInitializeWithSingleTypeMapNoArrays (PEAssemblyBuilder pe, Entit MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), encoder => { + EmitSetTypeMappingEntryAssembly (pe, encoder, assemblyName); encoder.Call (getExternalSpec, parameterCount: 0, returnsValue: true); encoder.Call (getProxySpec, parameterCount: 0, returnsValue: true); encoder.Call (initializeRef, parameterCount: 2); @@ -486,6 +494,22 @@ static MemberReferenceHandle AddInitializeSingleNoArraysRef (PEAssemblyBuilder p pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob)); } + static void EmitSetTypeMappingEntryAssembly (PEAssemblyBuilder pe, TrackedInstructionEncoder encoder, string assemblyName) + { + var appContextRef = pe.Metadata.AddTypeReference (pe.SystemRuntimeRef, + pe.Metadata.GetOrAddString ("System"), pe.Metadata.GetOrAddString ("AppContext")); + var setDataRef = pe.AddMemberRef (appContextRef, "SetData", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().String (); + p.AddParameter ().Type ().Object (); + })); + encoder.LoadString (pe.Metadata.GetOrAddUserString ("System.Runtime.InteropServices.TypeMappingEntryAssembly")); + encoder.LoadString (pe.Metadata.GetOrAddUserString (assemblyName)); + encoder.Call (setDataRef, parameterCount: 2); + } + /// MemberRef for TrimmableTypeMap.Initialize(typeMap, proxyMap, arrayMapsByAssemblyAndRank[][]). static MemberReferenceHandle AddInitializeSingleWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs index 85fe427feb2..752e1083157 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -44,7 +44,9 @@ public sealed record JavaPeerInfo public required string AssemblyName { get; init; } /// - /// True when the type belongs to a framework assembly. + /// True when the type belongs to a framework assembly supplied by the Android SDK. + /// Framework ACWs are generated by the SDK and can be trimmed like bindings unless + /// another rule explicitly roots them. /// public bool IsFrameworkAssembly { get; init; } diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets index 706a73ae31e..a76819a61a1 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets @@ -5,6 +5,10 @@ <_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">mono.MonoRuntimeProvider + + <_GenerateProguardAfterTargets Condition=" '$(_GenerateProguardAfterTargets)' == '' ">ILLink + + %(Filename)%(Extension) + true @@ -21,10 +26,28 @@ BeforeTargets="PrepareForILLink;_RunILLink" DependsOnTargets="_GenerateTrimmableTypeMap"> - <_ExtraTrimmerArgs>--typemap-entry-assembly $(_TypeMapAssemblyName) $(_ExtraTrimmerArgs) + <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --typemap-entry-assembly "$(_TypeMapAssemblyName)" + + + <_LinkedAssemblyForProguard Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> + + + + + + + + @@ -23,10 +24,10 @@ <_TypeMapOutputDirectory>$(_TypeMapBaseOutputDir)typemap/ <_TypeMapJavaOutputDirectory>$(_TypeMapBaseOutputDir)typemap/java <_TypeMapAssembliesListFile>$(_TypeMapOutputDirectory)typemap-assemblies.txt - - <_AndroidTrimmableTypeMapMaxArrayRank Condition=" '$(_AndroidTrimmableTypeMapMaxArrayRank)' == '' and '$(PublishAot)' == 'true' ">3 + <_AndroidTrimmableTypeMapMaxArrayRank Condition=" '$(_AndroidTrimmableTypeMapMaxArrayRank)' == '' and ('$(PublishAot)' == 'true' or '$(DynamicCodeSupport)' == 'false') ">3 <_AndroidTrimmableTypeMapMaxArrayRank Condition=" '$(_AndroidTrimmableTypeMapMaxArrayRank)' == '' ">0 _RecordTrimmableTypeMapFileWrites; @@ -88,6 +89,7 @@ @@ -46,6 +52,7 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN [Required] public ITaskItem [] ResolvedAssemblies { get; set; } = []; public ITaskItem [] ResolvedFrameworkAssemblies { get; set; } = []; + public string [] FrameworkAssemblyNames { get; set; } = []; [Required] public string OutputDirectory { get; set; } = ""; [Required] @@ -105,7 +112,10 @@ public override bool RunTask () Path: g.Key, IsFrameworkAssembly: frameworkAssemblyPaths.Contains (g.Key) || g.Any (IsFrameworkAssemblyItem))) .ToList (); - var frameworkAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); + var frameworkAssemblyNames = new HashSet (DefaultFrameworkAssemblyNames, StringComparer.OrdinalIgnoreCase); + foreach (var assemblyName in FrameworkAssemblyNames) { + frameworkAssemblyNames.Add (assemblyName); + } Directory.CreateDirectory (OutputDirectory); Directory.CreateDirectory (JavaSourceOutputDirectory); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs index 4b29bc38e76..148641dc855 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using System.Text.RegularExpressions; +using Mono.Cecil; using NUnit.Framework; using Xamarin.Android.Tasks; using Xamarin.Android.Tools; @@ -259,6 +261,33 @@ public void CoreClrTrimmableTypeMap_PackagesReadyToRunTypeMap () } } + [Test] + public void ReleaseCoreClrTrimmableTypeMap_SupportsExplicitDynamicCodeSupportOff () + { + if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) { + return; + } + + var dynamicCodeDisabledTrimmable = BuildDynamicCodeSupportProfile ("trimmable", dynamicCodeSupport: false); + + using var runtimeConfigJson = JsonDocument.Parse (dynamicCodeDisabledTrimmable.RuntimeConfig); + Assert.IsTrue ( + runtimeConfigJson.RootElement.TryGetProperty ("runtimeOptions", out var runtimeOptions), + "runtimeconfig.json should include runtimeOptions."); + Assert.IsTrue ( + runtimeOptions.TryGetProperty ("configProperties", out var configProperties), + "runtimeconfig.json should include runtimeOptions.configProperties."); + Assert.IsTrue ( + configProperties.TryGetProperty ("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", out var dynamicCodeSupportProperty), + "runtimeconfig.json should include RuntimeFeature.IsDynamicCodeSupported."); + Assert.IsFalse ( + dynamicCodeSupportProperty.GetBoolean (), + "trimmable typemap builds should honor explicit DynamicCodeSupport=false."); + Assert.IsTrue ( + dynamicCodeDisabledTrimmable.LinkedTypeMapAssembliesContainArrayRankSentinels, + "trimmable typemap builds should emit array typemap sentinels when dynamic code is disabled."); + } + [Test] public void TrimmableTypeMap_PreserveLists_ArePackagedInSdk () { @@ -290,7 +319,6 @@ public void TrimmableTypeMap_RuntimeArtifacts_ArePackagedInSdk () }) { FileAssert.Exists (Path.Combine (toolsDir, file), $"{file} should exist in the SDK pack."); } - } // T1: end-to-end build coverage for [Export] and [ExportField] under trimmable. @@ -500,5 +528,69 @@ static void AssertTrimmableTypeMapOutputs (string typemapDir) var javaFiles = Directory.GetFiles (javaDir, "*.java", SearchOption.AllDirectories); Assert.IsNotEmpty (javaFiles, "At least one trimmable JCW Java source file should be generated."); } + DynamicCodeSupportProfile BuildDynamicCodeSupportProfile (string typemapImplementation, bool? dynamicCodeSupport) + { + var dynamicCodeSuffix = dynamicCodeSupport.HasValue ? $"_{dynamicCodeSupport.Value.ToString ().ToLowerInvariant ()}" : ""; + var projectName = $"DynamicCodeSupport_{typemapImplementation.Replace ("-", "_")}{dynamicCodeSuffix}"; + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + PackageName = "com.xamarin.dynamiccodesupport", + ProjectName = projectName, + }; + proj.SetRuntime (AndroidRuntime.CoreCLR); + proj.SetProperty (KnownProperties.RuntimeIdentifier, "android-arm64"); + proj.SetProperty ("AndroidPackageFormat", "apk"); + proj.SetProperty (KnownProperties.AndroidLinkTool, "r8"); + proj.SetProperty ("TrimMode", "full"); + proj.SetProperty ("PublishReadyToRun", "false"); + proj.SetProperty ("_AndroidTypeMapImplementation", typemapImplementation); + if (dynamicCodeSupport.HasValue) { + proj.SetProperty ("DynamicCodeSupport", dynamicCodeSupport.Value.ToString ().ToLowerInvariant ()); + } + + using var builder = CreateApkBuilder (Path.Combine ("temp", $"{projectName}_{Guid.NewGuid ():N}")); + Assert.IsTrue (builder.Build (proj), $"{typemapImplementation} build should have succeeded."); + + var runtimeConfigPath = FindOutputFile (builder, proj, $"{proj.ProjectName}.runtimeconfig.json"); + var linkedAssemblyDirectory = builder.Output.GetIntermediaryPath (Path.Combine ("android-arm64", "linked")); + return new DynamicCodeSupportProfile ( + File.ReadAllText (runtimeConfigPath), + TypeMapAssembliesContainType (linkedAssemblyDirectory, "__ArrayMapRank1")); + } + + string FindOutputFile (ProjectBuilder builder, XamarinAndroidApplicationProject proj, string fileName) + { + var outputDirectory = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath); + var files = Directory.GetFiles (outputDirectory, fileName, SearchOption.AllDirectories); + Assert.AreEqual (1, files.Length, $"{outputDirectory} should contain one {fileName}."); + return files [0]; + } + + bool TypeMapAssembliesContainType (string directory, string typeName) + { + if (!Directory.Exists (directory)) { + return false; + } + + foreach (var file in Directory.EnumerateFiles (directory, "*.dll", SearchOption.TopDirectoryOnly).Where (IsTypeMapAssemblyPath)) { + using var assembly = AssemblyDefinition.ReadAssembly (file); + if (assembly.Modules.SelectMany (m => m.Types).Any (type => type.Name == typeName)) { + return true; + } + } + + return false; + } + + bool IsTypeMapAssemblyPath (string file) + { + var fileName = Path.GetFileName (file); + return fileName.EndsWith (".TypeMap.dll", StringComparison.Ordinal) || + fileName.StartsWith ("_Microsoft.Android.TypeMap", StringComparison.Ordinal); + } + + sealed record DynamicCodeSupportProfile ( + string RuntimeConfig, + bool LinkedTypeMapAssembliesContainArrayRankSentinels); } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs index 2905ee9eed5..2ad242d096a 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs @@ -61,6 +61,31 @@ public void Generate_AssemblyName_MatchesExpected (string? assemblyName, string Assert.Equal (expectedName, reader.GetString (asmDef.Name)); } + [Fact] + public void Generate_InitializeConfiguresTypeMappingEntryAssembly () + { + using var stream = GenerateRootAssembly (new [] { "_App.TypeMap" }, assemblyName: "MyRoot"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var typeRefs = reader.TypeReferences + .Select (h => reader.GetTypeReference (h)) + .ToList (); + Assert.Contains (typeRefs, t => + reader.GetString (t.Name) == "AppContext" && + reader.GetString (t.Namespace) == "System"); + + var setDataMemberRefs = reader.MemberReferences + .Select (h => reader.GetMemberReference (h)) + .Where (m => reader.GetString (m.Name) == "SetData") + .ToList (); + Assert.NotEmpty (setDataMemberRefs); + + var loadedStrings = GetLoadStringOperands (pe, reader, "Initialize"); + Assert.Contains ("System.Runtime.InteropServices.TypeMappingEntryAssembly", loadedStrings); + Assert.Contains ("MyRoot", loadedStrings); + } + [Fact] public void Generate_ReferencesGenericTypeMapAssemblyTargetAttribute () { @@ -392,6 +417,26 @@ static List GetIgnoresAccessChecksToValues (MetadataReader reader) return result; } + static List GetLoadStringOperands (PEReader pe, MetadataReader reader, string methodName) + { + var result = new List (); + var method = reader.GetMethodDefinition (FindMethodDefinition (reader, methodName)); + var body = pe.GetMethodBody (method.RelativeVirtualAddress); + var il = body.GetILBytes (); + if (il is null) { + throw new InvalidOperationException ($"{methodName} has no IL body."); + } + for (int i = 0; i + 4 < il.Length; i++) { + if (il [i] != 0x72) { + continue; + } + var token = BitConverter.ToInt32 (il, i + 1); + result.Add (reader.GetUserString (MetadataTokens.UserStringHandle (token))); + i += 4; + } + return result; + } + [Fact] public void Generate_MergedMode_WithArrays_ProducesValidPEAssembly () { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 5355347ded5..0a19aa073ac 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -216,6 +216,32 @@ public void Build_UserAcwType_IsUnconditional () Assert.Null (mainEntry.TargetTypeReference); } + [Fact] + public void Build_FrameworkAcwType_IsTrimmable () + { + var peer = MakeAcwPeer ("mono/android/view/View_OnClickListenerImplementor", "Android.Views.View+IOnClickListenerImplementor", "Mono.Android") with { + IsFrameworkAssembly = true, + }; + var model = BuildModel (new [] { peer }); + + var entry = model.Entries.First (e => e.JniName == "mono/android/view/View_OnClickListenerImplementor"); + Assert.False (entry.IsUnconditional); + Assert.Equal ("Android.Views.View+IOnClickListenerImplementor, Mono.Android", entry.TargetTypeReference); + } + + [Fact] + public void Build_FrameworkAcwType_MarkedUnconditional_IsUnconditional () + { + var peer = MakeAcwPeer ("mono/android/app/ApplicationRegistration", "Android.App.ApplicationRegistration", "Mono.Android") with { + IsFrameworkAssembly = true, + IsUnconditional = true, + }; + var model = BuildModel (new [] { peer }); + + Assert.True (model.Entries [0].IsUnconditional); + Assert.Null (model.Entries [0].TargetTypeReference); + } + [Fact] public void Build_McwBinding_IsTrimmable () {