Skip to content
  • Jonathan Peppers's avatar
    [One .NET] new "greenfield" projects are trimmed by default (#8805) · 9a782d7f
    Jonathan Peppers authored
    Context: 5205a5f0
    Context: 8a5b2a0b
    Context: https://github.com/xamarin/xamarin-android/issues/8724
    Context: https://github.com/xamarin/xamarin-android/issues/8797
    
    As we have solved all trimming warnings (5205a5f0. 8a5b2a0b) in the
    Android workload, we can now go "all in" on trimming.
    
    Early in .NET 6 (maybe even 5?) we "hid" many trimming warnings as we
    did not yet plan to solve them:
    
    	<SuppressTrimAnalysisWarnings Condition=" '$(SuppressTrimAnalysisWarnings)' == '' ">true</SuppressTrimAnalysisWarnings>
    
    These warnings were not *actionable* at the time for customers, as
    many warnings were in `Mono.Android.dll`, `Java.Interop.dll`, etc.
    
    Going forward, let's stop suppressing these warnings for
    `$(TrimMode)`=full.
    
    We can also enable trimming for new projects:
    
      * `dotnet new android`
      * `dotnet new android-wear`
    
    New projects will have the [`$(TrimMode)`][0] property set to `Full`
    by default:
    
    	<!--
    	  Enable full trimming in Release mode.
    	  To learn more, see: https://learn.microsoft.com/dotnet/core/deploying/trimming/trimming-options#trimming-granularity
    	-->
    	<PropertyGroup Condition="'$(Configuration)' == 'Release'">
    	  <TrimMode>full</TrimMode>
    	</PropertyGroup>
    
    We wouldn't want to do this for existing projects *yet*, as they
    might have existing code, NuGet packages, etc. where trimming
    warnings might be present.
    
    We can also improve the templates for Android class libraries:
    
      * `dotnet new androidlib`
      * `dotnet new android-bindinglib`
    
    New class library projects will have the [`$(IsTrimmable)`][1]
    property set to `true` by default:
    
    	<!--
    	  Enable trim analyzers for Android class libraries.
    	  To learn more, see: https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming
    	-->
    	<IsTrimmable>true</IsTrimmable>
    
    This way, new class libraries will be "trimmable" by default and be
    able to react to trimming warnings.
    
    We can also use `$(TrimMode)=full` in many of our existing tests:
    
      * MSBuild tests that assert 0 warnings can use `$(TrimMode)=full`.
    
      * On-device tests can use `$(TrimMode)=full`.
    
    
    ~~ General trimming warnings ~~
    
    This was discovered through `Mono.Android-NET-Tests.csproj`, but
    there were a few trimmer warnings in the "layout bindings" feature:
    
    	…\dotnet\packs\Microsoft.Android.Sdk.Windows\…\tools\LayoutBinding.cs(79,56):
    	  warning IL2091: Xamarin.Android.Design.LayoutBinding.<>c__DisplayClass8_0<T>.<FindFragment>b__0(Activity):
    	  'T' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors',
    	    'DynamicallyAccessedMemberTypes.NonPublicConstructors' in 'Android.App.FragmentManager.FindFragmentById<T>(Int32)'.
    	  The generic parameter 'T' of 'Xamarin.Android.Design.LayoutBinding.<>c__DisplayClass8_0<T>' does not have matching annotations.
    	  The source value must declare at least the same requirements as those declared on the target location it is assigned to.
    	…\dotnet\packs\Microsoft.Android.Sdk.Windows\…\tools\LayoutBinding.cs(35,5):
    	  warning IL2091: Xamarin.Android.Design.LayoutBinding.FindView<T>(Int32, T&):
    	  'T' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors',
    	    'DynamicallyAccessedMemberTypes.NonPublicConstructors' in 'Android.App.Activity.FindViewById<T>(Int32)'.
    	  The generic parameter 'T' of 'Xamarin.Android.Design.LayoutBinding.FindView<T>(Int32, T&)' does not have matching annotations.
    	  The source value must declare at least the same requirements as those declared on the target location it is assigned to.
    	…\dotnet\packs\Microsoft.Android.Sdk.Windows\…\tools\LayoutBinding.cs(37,5):
    	  warning IL2091: Xamarin.Android.Design.LayoutBinding.FindView<T>(Int32, T&):
    	  'T' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors',
    	    'DynamicallyAccessedMemberTypes.NonPublicConstructors' in 'Android.Views.View.FindViewById<T>(Int32)'.
    	  The generic parameter 'T' of 'Xamarin.Android.Design.LayoutBinding.FindView<T>(Int32, T&)' does not have matching annotations.
    	  The source value must declare at least the same requirements as those declared on the target location it is assigned to.
    
    We can `[DynamicallyAccessedMembers(Constructors)]` to fix these.
    
    
    ~~ Trimming warnings in tests ~~
    
    Several tests that verify "trimming unsafe features" just specify:
    
    	[RequiresUnreferencedCode ("Tests trimming unsafe features")]
    
    If the test might have an issue under NativeAOT, I used:
    
    	// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
    	#pragma warning disable IL3050
    
    Places that use `Assembly.GetType()` can use `Type.GetType()` instead:
    
    	-var JavaProxyThrowable_type = typeof (Java.Lang.Object)
    	-    .Assembly
    	-    .GetType ("Android.Runtime.JavaProxyThrowable");
    	+var JavaProxyThrowable_type = Type.GetType ("Android.Runtime.JavaProxyThrowable, Mono.Android");
    
    `SystemTests.AppDomainTest` was just ignored (and had warnings).
    Update to just verify `PlatformNotSupportedException` is thrown.
    
    
    ~~ Test failures ~~
    
    `JsonSerializerTest` requires setting
    [`$(JsonSerializerIsReflectionEnabledByDefault)`][2]=true:
    
    	<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
    
    Otherwise, an exception is thrown:
    
    	System.InvalidOperationException : JsonSerializerIsReflectionDisabled
    
    `Java.Interop-Tests` were initially not loaded at all, with the log
    message:
    
    	W NUnit   : Failed to load tests from assembly 'Java.Interop-Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065
    
    If we make `Java.Interop-Tests.dll` a `@(TrimmerRootAssembly)`:
    
    	<TrimmerRootAssembly Include="Java.Interop-Tests" RootMode="All" />
    
    Then all the tests cases are preserved and can be run, in the same
    way the "main app assembly" is preserved.
    
    `Android.GraphicsTests.NinePatchTests` failed with:
    
    	The drawable created from resource tile should be a NinePatchDrawable.
    	Expected: not null
    	But was:  null
    
    The only usage of `NinePatchDrawable` was:
    
    	Assert.IsNotNull (d as NinePatchDrawable);
    
    `NinePatchDrawable` likely needs its interfaces and constructors
    preserved for this test to pass.  I added an attribute for just `All`
    members for the test to pass:
    
    	[DynamicDependency (DynamicallyAccessedMemberTypes.All, typeof (NinePatchDrawable))]
    
    `Xamarin.Android.RuntimeTests.CustomWidgetTests` failed with:
    
    	(Binary XML file line #1 in Mono.Android.NET_Tests:layout/uppercase_custom: Binary XML file line #1 in Mono.Android.NET_Tests:layout/uppercase_custom: Error inflating class Mono.Android_Test.Library.CustomTextView)
    	   at Java.Interop.JniEnvironment.InstanceMethods.CallObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
    	   at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualObjectMethod(String , IJavaPeerable , JniArgumentValue* )
    	   at Android.Views.LayoutInflater.Inflate(Int32 , ViewGroup )
    	   at Xamarin.Android.RuntimeTests.CustomWidgetTests.<>c.<UpperCaseCustomWidget_ShouldNotThrowInflateException>b__0_0()
    	   at NUnit.Framework.Constraints.VoidInvocationDescriptor.Invoke()
    	   at NUnit.Framework.Constraints.ExceptionInterceptor.Intercept(Object )
    	--- End of managed Java.Lang.RuntimeException stack trace ---
    	android.view.InflateException: Binary XML file line #1 in Mono.Android.NET_Tests:layout/uppercase_custom: Binary XML file line #1 in Mono.Android.NET_Tests:layout/uppercase_custom: Error inflating class Mono.Android_Test.Library.CustomTextView
    	Caused by: android.view.InflateException: Binary XML file line #1 in Mono.Android.NET_Tests:layout/uppercase_custom: Error inflating class Mono.Android_Test.Library.CustomTextView
    	Caused by: java.lang.ClassNotFoundException: Mono.Android_Test.Library.CustomTextView
    	   at java.lang.Class.classForName(Native Method)
    	   at java.lang.Class.forName(Class.java:454)
    	   at android.view.LayoutInflater.createView(LayoutInflater.java:815)
    	   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1006)
    	   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
    	   at android.view.LayoutInflater.rInflate(LayoutInflater.java:1123)
    	   at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1084)
    	   at android.view.LayoutInflater.inflate(LayoutInflater.java:682)
    	   at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
    	   at android.view.LayoutInflater.inflate(LayoutInflater.java:481)
    	   at crc643df67da7b13bb6b1.TestInstrumentation_1.n_onStart(Native Method)
    	   at crc643df67da7b13bb6b1.TestInstrumentation_1.onStart(TestInstrumentation_1.java:32)
    	   at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
    	Caused by: java.lang.ClassNotFoundException: Didn't find class "Mono.Android_Test.Library.CustomTextView" on path
    
    In this case, `Mono.Android_Test.Library.CustomTextView` was used
    from an Android layout, but not used anywhere in managed code.
    
    To fix, I added:
    
    	[DynamicDependency (DynamicallyAccessedMemberTypes.All, typeof (Mono.Android_Test.Library.CustomTextView))]
    
    I could have also made `Mono.Android_Test.Library` a `@(TrimmerRootAssembly)`.
    
    TODO: `View` subclasses used within Android Layout `.axml` files
    should be automatically preserved; see xamarin/xamarin-android#8797.
    
    [0]: https://learn.microsoft.com/dotnet/core/deploying/trimming/trimming-options?pivots=dotnet-8-0#trimming-granularity
    [1]: https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming?pivots=dotnet-8-0#enable-project-specific-trimming
    [2]: https://learn.microsoft.com/dotnet/core/compatibility/serialization/8.0/publishtrimmed
    9a782d7f