Skip to content

Commit

Permalink
[Trimming] Add a feature flag to disable XAML loading at runtime (#19310
Browse files Browse the repository at this point in the history
)

* Add feature flag

* Add rudimentary docs for feature flags

* Add feature flag guards and attributes with warnings

* TMP: Add TODO comment to resolve issue with XamlC calling SetAndLoadSource

* Move feature flags to Core

* Fix typos

* List affected APIs in the docs

* Add summary comment for RuntimeFeature

* Improve ResourceDictionary.SetAndLoadSource

* Fix comment

* Rename feature switch property and name

* Remove comment

* Revisit ResourceDictionary

* Remove annotations from ResourcesLoader

* Remove fixed warnings from tests

* Remove unnecessary changes

* Update docs/design/FeatureSwitches.md

Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>

* Move feature switch setup code to Controls targets file

* Remove ILC System.Enum.GetValues warning

* Remove unnecessary Debug fallback

* Suppress trimming warnings in source-generated code

* Update docs/design/FeatureSwitches.md

Co-authored-by: MartyIX <203266+MartyIX@users.noreply.github.com>

---------

Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
Co-authored-by: MartyIX <203266+MartyIX@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 7, 2024
1 parent 6c50c5f commit a1096cc
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 113 deletions.
13 changes: 13 additions & 0 deletions docs/design/FeatureSwitches.md
@@ -0,0 +1,13 @@
# Feature Switches

Certain features of MAUI can be enabled or disabled using feature switches. The easiest way to control the features is by putting the corresponding MSBuild property into the app's project file. Disabling unnecessary features can help reducing the app size when combined with the [`full` trimming mode](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options).

| MSBuild Property Name | AppContext Setting | Description |
|-|-|-|
| MauiXamlRuntimeParsingSupport | Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported | When disabled, all XAML loading at runtime will throw an exception. This will affect usage of APIs such as the `LoadFromXaml` extension method. This feature can be safely turned off when all XAML resources are compiled using XamlC (see [XAML compilation](https://learn.microsoft.com/en-us/dotnet/maui/xaml/xamlc)). This feature is enabled by default for all configurations except for NativeAOT. |

## MauiXamlRuntimeParsingSupport

When this feature is disabled, the following APIs are affected:
- [`LoadFromXaml` extension methods](https://learn.microsoft.com/en-us/dotnet/maui/xaml/runtime-load) will throw runtime exceptions.
- [Disabling XAML compilation](https://learn.microsoft.com/en-us/dotnet/maui/xaml/xamlc#disable-xaml-compilation) using `[XamlCompilation(XamlCompilationOptions.Skip)]` on pages and controls or whole assemblies will cause runtime exceptions.
9 changes: 9 additions & 0 deletions src/Controls/src/Build.Tasks/XamlCTask.cs
Expand Up @@ -271,6 +271,15 @@ public override bool Execute(out IList<Exception> thrownExceptions)
LoggingHelper.LogMessage(Low, e.StackTrace);
continue;
}
if (initComp.HasCustomAttributes)
{
var suppressMessageAttribute = initComp.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute");
if (suppressMessageAttribute != null)
{
LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Removing UnconditionalSuppressMessageAttribute from InitializeComponent()");
initComp.CustomAttributes.Remove(suppressMessageAttribute);
}
}
if (Type != null)
InitCompForType = initComp;

Expand Down
Expand Up @@ -206,4 +206,16 @@
Text="The %24(TargetFrameworkVersion) for $(ProjectName) ($(TargetFrameworkVersion)) is less than the minimum required %24(TargetFrameworkVersion) for Microsoft.Maui ($(MinTargetFrameworkVersionForMaui)). You need to increase the %24(TargetFrameworkVersion) for $(ProjectName)." />
</Target>

<Target Name="_MauiPrepareForILLink" BeforeTargets="PrepareForILLink">
<PropertyGroup>
<MauiXamlRuntimeParsingSupport Condition="'$(MauiXamlRuntimeParsingSupport)' == '' and '$(PublishAot)' == 'true'">false</MauiXamlRuntimeParsingSupport>
</PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported"
Condition="'$(MauiXamlRuntimeParsingSupport)' != ''"
Value="$(MauiXamlRuntimeParsingSupport)"
Trim="true" />
</ItemGroup>
</Target>

</Project>
20 changes: 19 additions & 1 deletion src/Controls/src/Core/ResourceDictionary.cs
Expand Up @@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -47,9 +48,26 @@ public void SetAndLoadSource(Uri value, string resourcePath, Assembly assembly,
//this will return a type if the RD as an x:Class element, and codebehind
var type = XamlResourceIdAttribute.GetTypeForPath(assembly, resourcePath);
if (type != null)
{
_mergedInstance = s_instances.GetValue(type, _ => (ResourceDictionary)Activator.CreateInstance(type));
}
else
_mergedInstance = DependencyService.Get<IResourcesLoader>().CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
{
if (RuntimeFeature.IsXamlRuntimeParsingSupported)
{
_mergedInstance = DependencyService.Get<IResourcesLoader>().CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
}
else
{
// This codepath is only ever hit when XamlC is explicitly disabled for a given resource dictionary.
// The developer had to add [XamlCompilation(XamlCompilationOptions.Skip)] or <?xaml-comp compile="false" ?> to their code.
// XamlC will produce a warning in this case (MAUIG0070 or XC0010).
throw new InvalidOperationException(
$"The resource '{resourcePath}' has not been compiled using XamlC and parsing XAML resources at runtime is disabled. "
+ "Ensure the resource is compiled using XamlC. Alternatively, enable parsing XAML resources at runtime by setting "
+ "the MauiXamlRuntimeParsingSupport MSBuild property to true.");
}
}
OnValuesChanged(_mergedInstance.ToArray());
}

Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/TrimmerConstants.cs
Expand Up @@ -6,4 +6,6 @@ class TrimmerConstants
internal const string SerializerTrimmerWarning = "Data Contract Serialization and Deserialization might require types that cannot be statically analyzed. Make sure all of the required types are preserved.";

internal const string NativeBindingService = "This method properly handles missing properties, and there is not a way to preserve them from this method.";

internal const string XamlLoadingTrimmerWarning = "Loading XAML at runtime might require types that cannot be statically analyzed. Make sure all of the required types are preserved.";
}
1 change: 1 addition & 0 deletions src/Controls/src/SourceGen/CodeBehindGenerator.cs
Expand Up @@ -220,6 +220,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation

//initializeComponent
sb.AppendLine($"\t\t[global::System.CodeDom.Compiler.GeneratedCode(\"Microsoft.Maui.Controls.SourceGen\", \"1.0.0.0\")]");
sb.AppendLine($"\t\t[global::System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(\"Trimming\", \"IL2026\", Justification = \"Source-generated code will be replaced by XamlC in Release builds.\")]");

// add MemberNotNull attributes
if (namedFields != null)
Expand Down
4 changes: 4 additions & 0 deletions src/Controls/src/Xaml/ViewExtensions.cs
Expand Up @@ -26,24 +26,28 @@
// THE SOFTWARE.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Microsoft.Maui.Controls.Xaml
{
public static class Extensions
{
[RequiresUnreferencedCode(TrimmerConstants.XamlLoadingTrimmerWarning)]
public static TXaml LoadFromXaml<TXaml>(this TXaml view, Type callingType)
{
XamlLoader.Load(view, callingType);
return view;
}

[RequiresUnreferencedCode(TrimmerConstants.XamlLoadingTrimmerWarning)]
public static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml)
{
XamlLoader.Load(view, xaml);
return view;
}

[RequiresUnreferencedCode(TrimmerConstants.XamlLoadingTrimmerWarning)]
internal static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml, Assembly rootAssembly)
{
XamlLoader.Load(view, xaml, rootAssembly);
Expand Down
6 changes: 6 additions & 0 deletions src/Core/src/Core.csproj
Expand Up @@ -58,6 +58,12 @@
<None Include="nuget\**" Exclude="nuget\**\*.in.*" PackagePath="" Pack="true" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="ILLink.Substitutions.xml">
<LogicalName>ILLink.Substitutions.xml</LogicalName>
</EmbeddedResource>
</ItemGroup>

<Target Name="_CopyToBuildTasksDir" AfterTargets="Build">
<ItemGroup>
<_CopyItems Include="nuget\buildTransitive\**" Exclude="nuget\**\*.in.*" />
Expand Down
8 changes: 8 additions & 0 deletions src/Core/src/ILLink.Substitutions.xml
@@ -0,0 +1,8 @@
<linker>
<assembly fullname="Microsoft.Maui">
<type fullname="Microsoft.Maui.RuntimeFeature">
<method signature="System.Boolean get_IsXamlRuntimeParsingSupported()" body="stub" feature="Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported" value="false" featurevalue="false" />
<method signature="System.Boolean get_IsXamlRuntimeParsingSupported()" body="stub" feature="Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported" value="true" featurevalue="true" />
</type>
</assembly>
</linker>
24 changes: 24 additions & 0 deletions src/Core/src/RuntimeFeature.cs
@@ -0,0 +1,24 @@
using System;

namespace Microsoft.Maui
{
/// <summary>
/// Contains all the runtime feature switches that are used throught the MAUI codebase.
/// See <see href="https://github.com/dotnet/runtime/blob/main/docs/workflow/trimming/feature-switches.md" />
/// for examples of how to add new feature switches.
/// </summary>
/// <remarks>
/// Property names must be kept in sync with ILLink.Substitutions.xml for proper value substitutions.
/// Mapping of MSBuild properties to feature switches and the default values of feature switches
/// is defined in Microsoft.Maui.Sdk.Before.targets.
/// </remarks>
internal static class RuntimeFeature
{
private const bool IsXamlRuntimeParsingSupportedByDefault = true;

internal static bool IsXamlRuntimeParsingSupported
=> AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsXamlRuntimeParsingSupported", out bool isEnabled)
? isEnabled
: IsXamlRuntimeParsingSupportedByDefault;
}
}

0 comments on commit a1096cc

Please sign in to comment.