diff --git a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj index 3757ed34b68..92c92c0ee94 100644 --- a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj +++ b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs new file mode 100644 index 00000000000..5eb15079c14 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using Java.Interop.Tools.Cecil; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner +{ + static class AddKeepAlivesHelper + { + internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolver resolver, Func getCorlibAssembly, Action logMessage) + { + if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) + return false; + + // Anything that was built against .NET for Android will have + // keep-alives already compiled in. + if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly)) + return false; + + MethodDefinition? methodKeepAlive = null; + bool changed = false; + foreach (TypeDefinition type in assembly.MainModule.Types) + changed |= ProcessType (type, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage); + + return changed; + } + + static bool ProcessType (TypeDefinition type, IMetadataResolver resolver, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) + { + bool changed = false; + if (MightNeedFix (type, resolver)) + changed |= AddKeepAlives (type, ref methodKeepAlive, getCorlibAssembly, logMessage); + + if (type.HasNestedTypes) { + foreach (var t in type.NestedTypes) { + changed |= ProcessType (t, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage); + } + } + + return changed; + } + + static bool MightNeedFix (TypeDefinition type, IMetadataResolver resolver) + { + return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", resolver); + } + + static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) + { + bool changed = false; + foreach (MethodDefinition method in type.Methods) { + if (method.Parameters.Count == 0) + continue; + + if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute")) + continue; + + var instructions = method.Body.Instructions; + + var found = false; + for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) { + var current = instructions [off]; + if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) { + found = true; + break; + } + } + + if (found) + continue; + + var processor = method.Body.GetILProcessor (); + var module = method.DeclaringType.Module; + var end = instructions.Last (); + if (end.Previous.OpCode == OpCodes.Endfinally) + end = end.Previous; + + for (int i = 0; i < method.Parameters.Count; i++) { + if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String") + continue; + + if (methodKeepAlive == null) + methodKeepAlive = GetKeepAliveMethod (getCorlibAssembly, logMessage); + + if (methodKeepAlive == null) { + logMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method."); + break; + } + + processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i])); + processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive))); + changed = true; + } + } + return changed; + } + + static MethodDefinition? GetKeepAliveMethod (Func getCorlibAssembly, Action logMessage) + { + var corlibAssembly = getCorlibAssembly (); + if (corlibAssembly == null) + return null; + + var gcType = Extensions.GetType (corlibAssembly, "System.GC"); + if (gcType == null) + return null; + + return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" }); + } + + // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs + static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter) + { + switch (argNum) { + case 0: return Instruction.Create (OpCodes.Ldarg_0); + case 1: return Instruction.Create (OpCodes.Ldarg_1); + case 2: return Instruction.Create (OpCodes.Ldarg_2); + case 3: return Instruction.Create (OpCodes.Ldarg_3); + default: return Instruction.Create (OpCodes.Ldarg, parameter); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs index 0ca2fa7f597..32f69e5e70f 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs @@ -1,169 +1,23 @@ -using System; -using System.Linq; -using Java.Interop.Tools.Cecil; using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Linker; using Mono.Linker.Steps; using Xamarin.Android.Tasks; namespace MonoDroid.Tuner { - public class AddKeepAlivesStep : BaseStep -#if !ILLINK - , IAssemblyModifierPipelineStep -#endif // !ILLINK + public class AddKeepAlivesStep : BaseStep, IAssemblyModifierPipelineStep { - protected override void ProcessAssembly (AssemblyDefinition assembly) - { - var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip; - if (action == AssemblyAction.Delete) - return; - - if (AddKeepAlives (assembly)) { - if (action == AssemblyAction.Skip || action == AssemblyAction.Copy) - Annotations.SetAction (assembly, AssemblyAction.Save); - } - } - -#if !ILLINK public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) { // Only run this step on user Android assemblies if (!context.IsAndroidUserAssembly) return; - context.IsAssemblyModified |= AddKeepAlives (assembly); - } -#endif // !ILLINK - - internal bool AddKeepAlives (AssemblyDefinition assembly) - { - if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) - return false; - - // Anything that was built against .NET for Android will have - // keep-alives already compiled in. - if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly)) - return false; - - bool changed = false; - foreach (TypeDefinition type in assembly.MainModule.Types) - changed |= ProcessType (type); - - return changed; - } - - bool ProcessType (TypeDefinition type) - { - bool changed = false; - if (MightNeedFix (type)) - changed |= AddKeepAlives (type); - - if (type.HasNestedTypes) { - foreach (var t in type.NestedTypes) { - changed |= ProcessType (t); - } - } - - return changed; - } - - bool MightNeedFix (TypeDefinition type) - { - return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", Context); - } - - MethodDefinition? methodKeepAlive = null; - - bool AddKeepAlives (TypeDefinition type) - { - bool changed = false; - foreach (MethodDefinition method in type.Methods) { - if (method.Parameters.Count == 0) - continue; - - if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute")) - continue; - - var instructions = method.Body.Instructions; - - var found = false; - for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) { - var current = instructions [off]; - if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) { - found = true; - break; - } - } - - if (found) - continue; - - var processor = method.Body.GetILProcessor (); - var module = method.DeclaringType.Module; - var end = instructions.Last (); - if (end.Previous.OpCode == OpCodes.Endfinally) - end = end.Previous; - - for (int i = 0; i < method.Parameters.Count; i++) { - if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String") - continue; - - if (methodKeepAlive == null) - methodKeepAlive = GetKeepAliveMethod (); - - if (methodKeepAlive == null) { - LogMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method."); - break; - } - - processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i])); - processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive))); - changed = true; - } - } - return changed; - } - - protected virtual AssemblyDefinition GetCorlibAssembly () - { - return Context.GetAssembly ("System.Private.CoreLib"); - } - - MethodDefinition? GetKeepAliveMethod () - { - var corlibAssembly = GetCorlibAssembly (); - if (corlibAssembly == null) - return null; - - var gcType = Extensions.GetType (corlibAssembly, "System.GC"); - if (gcType == null) - return null; - - return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" }); - } - - public -#if !ILLINK - override -#endif - void LogMessage (string message) - { - Context.LogMessage (message); - } - - // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs - static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter) - { - switch (argNum) { - case 0: return Instruction.Create (OpCodes.Ldarg_0); - case 1: return Instruction.Create (OpCodes.Ldarg_1); - case 2: return Instruction.Create (OpCodes.Ldarg_2); - case 3: return Instruction.Create (OpCodes.Ldarg_3); - default: return Instruction.Create (OpCodes.Ldarg, parameter); - } + context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives ( + assembly, + Context, + () => Context.GetAssembly ("System.Private.CoreLib"), + (msg) => LogMessage (msg)); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs new file mode 100644 index 00000000000..e725b5714ec --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs @@ -0,0 +1,30 @@ +using System; +using Java.Interop.Tools.Cecil; +using Mono.Cecil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner; + +/// +/// Post-trimming version of AddKeepAlives that calls AddKeepAlivesHelper directly, +/// matching the original ILLink behavior (no IsAndroidUserAssembly pre-filter). +/// The helper has its own assembly-level guards (HasTypeReference, IsDotNetAndroidAssembly). +/// +class PostTrimmingAddKeepAlivesStep : IAssemblyModifierPipelineStep +{ + readonly IMetadataResolver cache; + readonly Func getCorlibAssembly; + readonly Action logMessage; + + public PostTrimmingAddKeepAlivesStep (IMetadataResolver cache, Func getCorlibAssembly, Action logMessage) + { + this.cache = cache; + this.getCorlibAssembly = getCorlibAssembly; + this.logMessage = logMessage; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives (assembly, cache, getCorlibAssembly, logMessage); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs new file mode 100644 index 00000000000..78f202b23c1 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -0,0 +1,65 @@ +#nullable enable + +using System; +using System.Linq; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; +using Mono.Cecil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner; + +class StripEmbeddedLibrariesStep : IAssemblyModifierPipelineStep +{ + readonly TaskLoggingHelper log; + + public StripEmbeddedLibrariesStep (TaskLoggingHelper log) + { + this.log = log; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) + return; + context.IsAssemblyModified |= StripEmbeddedLibraries (assembly, log); + } + + internal static bool StripEmbeddedLibraries (AssemblyDefinition assembly, TaskLoggingHelper log) + { + bool modified = false; + foreach (var module in assembly.Modules) { + foreach (var resource in module.Resources.ToArray ()) { + if (ShouldStripResource (resource)) { + log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); + module.Resources.Remove (resource); + modified = true; + } + } + } + return modified; + } + + /// + /// Determines whether a resource should be stripped from the assembly. + /// Matches the same criteria as the old ILLink StripEmbeddedLibraries step. + /// + internal static bool ShouldStripResource (Resource resource) + { + if (!(resource is EmbeddedResource)) + return false; + // Embedded jars + if (resource.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase)) + return true; + // Embedded AndroidNativeLibrary archive + if (resource.Name == "__AndroidNativeLibraries__.zip") + return true; + // Embedded AndroidResourceLibrary archive + if (resource.Name == "__AndroidLibraryProjects__.zip") + return true; + // Embedded AndroidEnvironment items + if (resource.Name.StartsWith ("__AndroidEnvironment__", StringComparison.Ordinal)) + return true; + return false; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets index 4be55630c91..84e68d8bf6f 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets @@ -7,7 +7,7 @@ - + <_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag @@ -197,12 +197,6 @@ <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" /> - <_TrimmerCustomSteps - Condition=" '$(AndroidAddKeepAlives)' == 'true' " - Include="$(_AndroidLinkerCustomStepAssembly)" - AfterStep="CleanStep" - Type="MonoDroid.Tuner.AddKeepAlivesStep" - /> <_TrimmerCustomSteps Condition=" '$(AndroidLinkResources)' == 'true' " @@ -251,18 +245,20 @@ - - <_StripEmbeddedLibrariesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> + <_PostTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> - diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs new file mode 100644 index 00000000000..7a33ea3c7be --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -0,0 +1,87 @@ +#nullable enable + +using System.Collections.Generic; +using System.IO; +using Java.Interop.Tools.Cecil; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Mono.Cecil; +using MonoDroid.Tuner; + +namespace Xamarin.Android.Tasks; + +/// +/// An MSBuild task that runs post-trimming assembly modifications in a single pass. +/// +/// This opens each assembly once (via DirectoryAssemblyResolver with ReadWrite) and +/// runs all registered steps on it, then writes modified assemblies in-place. Currently +/// runs StripEmbeddedLibrariesStep and (optionally) AddKeepAlivesStep. +/// +/// Runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, +/// so that R2R images are generated from the already-modified assemblies. +/// +public class PostTrimmingPipeline : AndroidTask +{ + public override string TaskPrefix => "PTP"; + + [Required] + public ITaskItem [] Assemblies { get; set; } = []; + + public bool AddKeepAlives { get; set; } + + public bool Deterministic { get; set; } + + public override bool RunTask () + { + using var resolver = new DirectoryAssemblyResolver ( + this.CreateTaskLogger (), loadDebugSymbols: true, + loadReaderParameters: new ReaderParameters { ReadWrite = true }); + var cache = new TypeDefinitionCache (); + + foreach (var assembly in Assemblies) { + var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); + if (!resolver.SearchDirectories.Contains (dir)) { + resolver.SearchDirectories.Add (dir); + } + } + + var steps = new List (); + steps.Add (new StripEmbeddedLibrariesStep (Log)); + if (AddKeepAlives) { + // Memoize the corlib resolution so the attempt (and any error logging) happens at most once, + // regardless of how many assemblies/methods need KeepAlive injection. + AssemblyDefinition? corlibAssembly = null; + bool corlibResolutionAttempted = false; + steps.Add (new PostTrimmingAddKeepAlivesStep (cache, + () => { + if (!corlibResolutionAttempted) { + corlibResolutionAttempted = true; + try { + corlibAssembly = resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")); + } catch (AssemblyResolutionException ex) { + Log.LogErrorFromException (ex, showStackTrace: false); + } + } + return corlibAssembly; + }, + (msg) => Log.LogDebugMessage (msg))); + } + + foreach (var item in Assemblies) { + var assembly = resolver.GetAssembly (item.ItemSpec); + var context = new StepContext (item, item); + foreach (var step in steps) { + step.ProcessAssembly (assembly, context); + } + if (context.IsAssemblyModified) { + Log.LogDebugMessage ($" Writing modified assembly: {item.ItemSpec}"); + assembly.Write (new WriterParameters { + WriteSymbols = assembly.MainModule.HasSymbols, + DeterministicMvid = Deterministic, + }); + } + } + + return !Log.HasLoggedErrors; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs deleted file mode 100644 index 7a65c4a8145..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs +++ /dev/null @@ -1,116 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Mono.Cecil; - -namespace Xamarin.Android.Tasks; - -/// -/// An MSBuild task that strips embedded Android resources (.jar, __AndroidNativeLibraries__.zip, -/// __AndroidLibraryProjects__.zip, __AndroidEnvironment__) from trimmed assemblies. -/// -/// This runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, -/// so that R2R images are generated from the already-stripped assemblies. -/// -public class StripEmbeddedLibraries : AndroidTask -{ - public override string TaskPrefix => "SEL"; - - [Required] - public ITaskItem [] Assemblies { get; set; } = []; - - public bool Deterministic { get; set; } - - public override bool RunTask () - { - var resolver = new DefaultAssemblyResolver (); - var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase); - - foreach (var assembly in Assemblies) { - var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); - if (searchDirectories.Add (dir)) { - resolver.AddSearchDirectory (dir); - } - } - - try { - foreach (var assembly in Assemblies) { - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { - continue; - } - - StripAssembly (assembly.ItemSpec, resolver); - } - } finally { - resolver.Dispose (); - } - - return !Log.HasLoggedErrors; - } - - void StripAssembly (string assemblyPath, IAssemblyResolver resolver) - { - string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb"); - bool havePdb = File.Exists (pdbPath); - - var readerParams = new ReaderParameters { - ReadSymbols = havePdb, - ReadWrite = true, - AssemblyResolver = resolver, - }; - - bool assembly_modified = false; - - using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { - foreach (var module in assembly.Modules) { - foreach (var resource in module.Resources.ToArray ()) { - if (ShouldStripResource (resource)) { - Log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); - module.Resources.Remove (resource); - assembly_modified = true; - } - } - } - - if (!assembly_modified) { - return; - } - - Log.LogDebugMessage ($" Writing stripped assembly: {assemblyPath}"); - assembly.Write (new WriterParameters { - WriteSymbols = havePdb, - DeterministicMvid = Deterministic, - }); - } - } - - /// - /// Determines whether a resource should be stripped from the assembly. - /// Matches the same criteria as the old ILLink StripEmbeddedLibraries step. - /// - internal static bool ShouldStripResource (Resource resource) - { - if (!(resource is EmbeddedResource)) - return false; - // Embedded jars - if (resource.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase)) - return true; - // Embedded AndroidNativeLibrary archive - if (resource.Name == "__AndroidNativeLibraries__.zip") - return true; - // Embedded AndroidResourceLibrary archive - if (resource.Name == "__AndroidLibraryProjects__.zip") - return true; - // Embedded AndroidEnvironment items - if (resource.Name.StartsWith ("__AndroidEnvironment__", StringComparison.Ordinal)) - return true; - return false; - } - -} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 577a607b4d7..c9157be3712 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -44,11 +44,15 @@ - + + + + +