diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md index 7743863813b..afe06d94074 100644 --- a/Documentation/guides/building-apps/build-properties.md +++ b/Documentation/guides/building-apps/build-properties.md @@ -1303,6 +1303,20 @@ This is only used when building `system` applications. Support for this property was added in Xamarin.Android 11.3. +## AndroidStripILAfterAOT + +A bool property that specifies whether or not the *method bodies* of AOT compiled methods will be removed. + +The default value is `false`, and the method bodies of AOT compiled methods will *not* be removed. + +When set to `true`, [`$(AndroidEnableProfiledAot)`](#androidenableprofiledaot) is set to `false` by default. +This means that in Release configuration builds -- in which +[`$(RunAOTCompilation)`](#runaotcompilation) is `true` by default -- AOT is enabled for *everything*. +This can result in increased app sizes. This behavior can be overridden by explicitly setting +`$(AndroidEnableProfiledAot)` to `true` within your project file. + +Support for this property was added in .NET 8. + ## AndroidSupportedAbis A string property that contains a diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets index db621d58ecf..7e2c2fd97f8 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets @@ -123,11 +123,25 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). LLVMPath="$(_LLVMPath)" LdName="$(_LdName)" LdFlags="$(_LdFlags)" + CollectTrimmingEligibleMethods="$(AndroidStripILAfterAOT)" + TrimmingEligibleMethodsOutputDirectory="$(IntermediateOutputPath)tokens" WorkingDirectory="$(MSBuildProjectDirectory)" AotArguments="$(AndroidAotAdditionalArguments)"> + + + + true <_AndroidXA1030 Condition=" '$(RunAOTCompilation)' == 'true' and '$(PublishTrimmed)' == 'false' ">true $(RunAOTCompilation) - true + true diff --git a/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj b/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj index 8f6b1ecd0b8..70616562284 100644 --- a/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj +++ b/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj @@ -37,6 +37,9 @@ + + $(MicrosoftAndroidSdkOutDir)Xamarin.Android.Cecil.dll + diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index fc60f88a1b7..1672b36b7cb 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using System.Xml.Linq; using System.Xml.XPath; +using Mono.Cecil; using NUnit.Framework; using Xamarin.ProjectTools; @@ -1075,5 +1076,43 @@ public void SupportDesugaringStaticInterfaceMethods () ); } + [Test] + public void EnableAndroidStripILAfterAOT ([Values (false, true)] bool profiledAOT) + { + var proj = new XamarinAndroidApplicationProject { + ProjectName = nameof (EnableAndroidStripILAfterAOT), + RootNamespace = nameof (EnableAndroidStripILAfterAOT), + IsRelease = true, + EnableDefaultItems = true, + }; + proj.SetProperty("AndroidStripILAfterAOT", "true"); + proj.SetProperty("AndroidEnableProfiledAot", profiledAOT.ToString ()); + // So we can use Mono.Cecil to open assemblies directly + proj.SetProperty ("AndroidEnableAssemblyCompression", "false"); + + var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "`dotnet build` should succeed"); + + var apk = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk"); + FileAssert.Exists (apk); + var helper = new ArchiveAssemblyHelper (apk); + Assert.IsTrue (helper.Exists ($"assemblies/{proj.ProjectName}.dll"), $"{proj.ProjectName}.dll should exist in apk!"); + using (var stream = helper.ReadEntry ($"assemblies/{proj.ProjectName}.dll")) { + stream.Position = 0; + using var assembly = AssemblyDefinition.ReadAssembly (stream); + var type = assembly.MainModule.GetType ($"{proj.RootNamespace}.MainActivity"); + var method = type.Methods.FirstOrDefault (p => p.Name == "OnCreate"); + Assert.IsNotNull (method, $"{proj.RootNamespace}.MainActivity.OnCreate should exist!"); + Assert.IsTrue (!method.HasBody || method.Body.Instructions.Count == 0, $"{proj.RootNamespace}.MainActivity.OnCreate should have no body!"); + } + + RunProjectAndAssert (proj, builder); + + WaitForPermissionActivity (Path.Combine (Root, builder.ProjectDirectory, "permission-logcat.log")); + bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity", + Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30); + Assert.IsTrue(didLaunch, "Activity should have started."); + } + } }