Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[X] Simplify OnPlatformExtension #4829

Merged
merged 1 commit into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .nuspec/Microsoft.Maui.Controls.targets
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,9 @@
OptimizeIL = "true"
DebugSymbols = "$(DebugSymbols)"
DebugType = "$(DebugType)"
DefaultCompile = "true"
DefaultCompile = "true"
ValidateOnly = "$(_MauiXamlCValidateOnly)"
TargetFramework = "$(TargetFramework)"
KeepXamlResources = "$(MauiKeepXamlResources)" />
<Touch Files="$(IntermediateOutputPath)XamlC.stamp" AlwaysCreate="True" />
<ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion src/Controls/src/Build.Tasks/XamlCTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ public class XamlCTask : XamlTask
{
bool hasCompiledXamlResources;
public bool KeepXamlResources { get; set; }
public bool OptimizeIL { get; set; }
public bool OptimizeIL { get; set; } = true;
public bool DefaultCompile { get; set; }
public bool ForceCompile { get; set; }
public string TargetFramework { get; set; }

public IAssemblyResolver DefaultAssemblyResolver { get; set; }

Expand Down Expand Up @@ -295,6 +296,7 @@ bool TryCoreCompile(MethodDefinition initComp, MethodDefinition initCompRuntime,
rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null);
rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null);
rootnode.Accept(new PruneIgnoredNodesVisitor(), null);
rootnode.Accept(new SimplifyOnPlatformVisitor(TargetFramework), null);
rootnode.Accept(new CreateObjectVisitor(visitorContext), null);
rootnode.Accept(new SetNamescopesAndRegisterNamesVisitor(visitorContext), null);
rootnode.Accept(new SetFieldVisitor(visitorContext), null);
Expand Down
16 changes: 8 additions & 8 deletions src/Controls/src/Xaml/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
[assembly: InternalsVisibleTo("CommunityToolkit.Maui.Markup.UnitTests")]
[assembly: Preserve]

[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/2021/maui", "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/2021/maui/design", "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "System", AssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "System", AssemblyName = "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2009/xaml", "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2009/xaml", "System", AssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2009/xaml", "System", AssemblyName = "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.MauiUri, "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.MauiDesignUri, "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.X2006Uri, "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.X2006Uri, "System", AssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.X2006Uri, "System", AssemblyName = "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.X2009Uri, "Microsoft.Maui.Controls.Xaml")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.X2009Uri, "System", AssemblyName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[assembly: XmlnsDefinition(Microsoft.Maui.Controls.Xaml.XamlParser.X2009Uri, "System", AssemblyName = "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
77 changes: 77 additions & 0 deletions src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#nullable disable

namespace Microsoft.Maui.Controls.Xaml
{
class SimplifyOnPlatformVisitor : IXamlNodeVisitor
{
public SimplifyOnPlatformVisitor(string targetFramework)
{
TargetFramework = targetFramework;
}

public string TargetFramework { get; }

public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
public bool StopOnDataTemplate => false;
public bool VisitNodeOnDataTemplate => true;
public bool StopOnResourceDictionary => false;
public bool IsResourceDictionary(ElementNode node) => false;
public bool SkipChildren(INode node, INode parentNode) => false;

public void Visit(ValueNode node, INode parentNode)
{
}

public void Visit(MarkupNode node, INode parentNode)
{
}

public void Visit(ElementNode node, INode parentNode)
{
if (node.XmlType.Name != nameof(OnPlatformExtension) || node.XmlType.NamespaceUri != XamlParser.MauiUri)
return;
if (string.IsNullOrEmpty(TargetFramework))
return;

string target = null;
if (TargetFramework.Contains("-android"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TargetFramework doesn't change for the lifetime of this object. Does it make sense to do this switch every time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

despite being in the Xaml project, this code is only ever executed by the compiler, and we're usually ok to sacrifice a few ms over code simplicity and readability. I'm 100% sure there's an alternative code that is as readable, as debuggable and as simple, but I'm not sure it will ever save the time we currently spend discussing it :)

target = nameof(OnPlatformExtension.Android);
if (TargetFramework.Contains("-ios"))
target = nameof(OnPlatformExtension.iOS);
if (TargetFramework.Contains("-macos"))
target = nameof(OnPlatformExtension.macOS);
if (TargetFramework.Contains("-maccatalyst"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no "Windows" platform extensions? Or do we not care in that case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of interesting since -windows could be wpf, or winforms or winappsdk in theory right? We only support winappsdk now, but who knows in the future... So perhaps target framework is not enough to check for -windows at some point...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no 1-1 mapping between .net TFM for windows and Maui targeted platform

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the "platform extensions" for windows? How does that switch happen at runtime?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/dotnet/maui/blob/main/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs#L13-L21
the platform for the running app is set by the IPlatformApplication scaffolding the whole Maui thing

Copy link
Member

@Redth Redth Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#4491 added WinUI as a platform in the OnPlatform markup extension for windows...

So, today we only have 1 windows target, perhaps we should optimize for the now. If we need to, in the future could we optimize such that at least -windows only has a limited number of options to check through and perhaps on -windows it's a slower path for this xamlc, but that's probably fine anyway because it's not android.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be a good time to review our current platform list, and names, and what defines a platform. should iPadOS be it's own platform, what about deprecating windows in favour of winui, wpf, ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I reading this correctly, in that if it's not windows, then there's just no optimization in the code path like there is for the other known tfm's? If that's the case I think this is fine as is and we should try and merge it in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, that's it, if the TFM is unlisted, we do nothing

target = nameof(OnPlatformExtension.MacCatalyst);

if (target is null)
return;

if ( node.Properties.TryGetValue(new XmlName("", target), out INode targetNode)
|| node.Properties.TryGetValue(new XmlName("", nameof(OnPlatformExtension.Default)), out targetNode))
{
if (!ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out XmlName name))
return;
if (parentNode is IElementNode parentEnode)
parentEnode.Properties[name] = targetNode;
}
else //no prop for target and no Default set
{
if (!ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out XmlName name))
return;
//if there's no value for the targetPlatform, ignore the node.
//this is slightly different than what OnPlatform does (return default(T))
if (parentNode is IElementNode parentEnode)
parentEnode.Properties.Remove(name);
}

}

public void Visit(RootNode node, INode parentNode)
{
}

public void Visit(ListNode node, INode parentNode)
{
}
}
}
2 changes: 1 addition & 1 deletion src/Controls/src/Xaml/XamlNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ public XamlNodeVisitor(Action<INode, INode> action, TreeVisitingMode visitingMod
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node) => false;
}
}
}
11 changes: 6 additions & 5 deletions src/Controls/tests/Xaml.UnitTests/MockCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests
{
public static class MockCompiler
{
public static void Compile(Type type)
public static void Compile(Type type, string targetFramework = null)
{
MethodDefinition _;
Compile(type, out _);
Compile(type, out _, targetFramework);
}

public static void Compile(Type type, out MethodDefinition methdoDefinition)
public static void Compile(Type type, out MethodDefinition methodDefinition, string targetFramework = null)
{
methdoDefinition = null;
methodDefinition = null;
var assembly = type.Assembly.Location;
var refs = from an in type.Assembly.GetReferencedAssemblies()
let a = System.Reflection.Assembly.Load(an)
Expand All @@ -32,12 +32,13 @@ public static void Compile(Type type, out MethodDefinition methdoDefinition)
DebugSymbols = false,
ValidateOnly = true,
Type = type.FullName,
TargetFramework = targetFramework,
BuildEngine = new MSBuild.UnitTests.DummyBuildEngine()
};

if (xamlc.Execute(out IList<Exception> exceptions) || exceptions == null || !exceptions.Any())
{
methdoDefinition = xamlc.InitCompForType;
methodDefinition = xamlc.InitCompForType;
return;
}
if (exceptions.Count > 1)
Expand Down
11 changes: 11 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/OnPlatformOptimization.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.OnPlatformOptimization">
<StackLayout>
<Label x:Name="label0"
Text="{OnPlatform Android='john', iOS='paul', Default='ringo' }"
Padding="{OnPlatform iOS='2,3,4,5'}" />

</StackLayout>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Core.UnitTests;
using Microsoft.Maui.Essentials;
using NUnit.Framework;
using Mono.Cecil.Cil;
using Mono.Cecil;

namespace Microsoft.Maui.Controls.Xaml.UnitTests
{
public partial class OnPlatformOptimization : ContentPage
{
public OnPlatformOptimization()
{
InitializeComponent();
}

public OnPlatformOptimization(bool useCompiledXaml)
{
//this stub will be replaced at compile time
}

[TestFixture]
public class Tests
{
[SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices();
[TearDown] public void TearDown() => Device.PlatformServices = null;

[Test]
public void OnPlatformExtensionsAreSimplified([Values("net6.0-ios", "net6.0-android")] string targetFramework)
{
MockCompiler.Compile(typeof(OnPlatformOptimization), out var methodDef, targetFramework);
Assert.That(!methodDef.Body.Instructions.Any(instr=>InstructionIsOnPlatformExtensionCtor(methodDef, instr)), "This Xaml still generates a new OnPlatformExtension()");
}

bool InstructionIsOnPlatformExtensionCtor(MethodDefinition methodDef, Mono.Cecil.Cil.Instruction instruction)
{
if (instruction.OpCode != OpCodes.Newobj)
return false;
if (!(instruction.Operand is MethodReference methodRef))
return false;
if (!Build.Tasks.TypeRefComparer.Default.Equals(methodRef.DeclaringType, methodDef.Module.ImportReference(typeof(OnPlatformExtension))))
return false;
return true;
}
}
}
}