Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Resource loading #815

Merged
merged 6 commits into from Mar 16, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 34 additions & 18 deletions Xamarin.Forms.Build.Tasks/XamlCTask.cs
Expand Up @@ -176,7 +176,7 @@ public override bool Execute(out IList<Exception> thrownExceptions)

Logger.LogString(2, " Replacing {0}.InitializeComponent ()... ", typeDef.Name);
Exception e;
if (!TryCoreCompile(initComp, initCompRuntime, rootnode, out e)) {
if (!TryCoreCompile(initComp, initCompRuntime, rootnode, resource.Name, out e)) {
success = false;
Logger.LogLine(2, "failed.");
(thrownExceptions = thrownExceptions ?? new List<Exception>()).Add(e);
Expand All @@ -195,6 +195,8 @@ public override bool Execute(out IList<Exception> thrownExceptions)
Logger.LogLine(2, "done");
}

Logger.LogLine(2, "");
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a debugging helper?

Copy link
Member Author

Choose a reason for hiding this comment

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

no, it's not. adding that white line makes the build output clearer


if (outputGeneratedILAsCode)
Logger.LogLine(2, " Decompiling option has been removed. Use a 3rd party decompiler to admire the beauty of the IL generated");

Expand Down Expand Up @@ -238,45 +240,59 @@ public override bool Execute(out IList<Exception> thrownExceptions)
return success;
}

bool TryCoreCompile(MethodDefinition initComp, MethodDefinition initCompRuntime, ILRootNode rootnode, out Exception exception)
bool TryCoreCompile(MethodDefinition initComp, MethodDefinition initCompRuntime, ILRootNode rootnode, string resourceId, out Exception exception)
{
try {
var body = new MethodBody(initComp);
var module = body.Method.Module;
var il = body.GetILProcessor();
il.Emit(OpCodes.Nop);

if (initCompRuntime != null) {
// Generating branching code for the Previewer
// IL_0007: call class [mscorlib]System.Func`2<class [mscorlib]System.Type,string> class [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider()
// IL_000c: brfalse IL_0031
// IL_0011: call class [mscorlib]System.Func`2<class [mscorlib]System.Type,string> class [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider()
// IL_0016: ldarg.0
// IL_0017: call instance class [mscorlib]System.Type object::GetType()
// IL_001c: callvirt instance !1 class [mscorlib]System.Func`2<class [mscorlib]System.Type, string>::Invoke(!0)
// IL_0021: brfalse IL_0031
// IL_0026: ldarg.0
// IL_0027: call instance void class Xamarin.Forms.Xaml.UnitTests.XamlLoaderGetXamlForTypeTests::__InitComponentRuntime()
// IL_002c: ret
// IL_0031: nop

//First using the ResourceLoader
var nop = Instruction.Create(OpCodes.Nop);
var getResourceProvider = module.ImportReference(module.ImportReference(typeof(Internals.ResourceLoader))
.Resolve()
.Properties.FirstOrDefault(pd => pd.Name == "ResourceProvider")
.GetMethod);
il.Emit(OpCodes.Call, getResourceProvider);
il.Emit(OpCodes.Brfalse, nop);
il.Emit(OpCodes.Call, getResourceProvider);
il.Emit(OpCodes.Ldstr, resourceId);
var func = module.ImportReference(module.ImportReference(typeof(Func<string, string>))
.Resolve()
.Methods.FirstOrDefault(md => md.Name == "Invoke"));
func = func.ResolveGenericParameters(module.ImportReference(typeof(Func<string, string>)), module);
il.Emit(OpCodes.Callvirt, func);
il.Emit(OpCodes.Brfalse, nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, initCompRuntime);
il.Emit(OpCodes.Ret);
il.Append(nop);

var getXamlFileProvider = body.Method.Module.ImportReference(body.Method.Module.ImportReference(typeof(Xamarin.Forms.Xaml.Internals.XamlLoader))
//Or using the deprecated XamlLoader
nop = Instruction.Create(OpCodes.Nop);
#pragma warning disable 0618
var getXamlFileProvider = module.ImportReference(module.ImportReference(typeof(Xaml.Internals.XamlLoader))
.Resolve()
.Properties.FirstOrDefault(pd => pd.Name == "XamlFileProvider")
.GetMethod);
#pragma warning restore 0618

il.Emit(OpCodes.Call, getXamlFileProvider);
il.Emit(OpCodes.Brfalse, nop);
il.Emit(OpCodes.Call, getXamlFileProvider);
il.Emit(OpCodes.Ldarg_0);
var getType = body.Method.Module.ImportReference(body.Method.Module.ImportReference(typeof(object))
var getType = module.ImportReference(module.ImportReference(typeof(object))
.Resolve()
.Methods.FirstOrDefault(md => md.Name == "GetType"));
il.Emit(OpCodes.Call, getType);
var func = body.Method.Module.ImportReference(body.Method.Module.ImportReference(typeof(Func<Type, string>))
func = module.ImportReference(module.ImportReference(typeof(Func<Type, string>))
.Resolve()
.Methods.FirstOrDefault(md => md.Name == "Invoke"));
func = func.ResolveGenericParameters(body.Method.Module.ImportReference(typeof(Func<Type, string>)), body.Method.Module);
func = func.ResolveGenericParameters(module.ImportReference(typeof(Func<Type, string>)), module);
il.Emit(OpCodes.Callvirt, func);
il.Emit(OpCodes.Brfalse, nop);
il.Emit(OpCodes.Ldarg_0);
Expand All @@ -285,7 +301,7 @@ bool TryCoreCompile(MethodDefinition initComp, MethodDefinition initCompRuntime,
il.Append(nop);
}

var visitorContext = new ILContext(il, body, body.Method.Module);
var visitorContext = new ILContext(il, body, module);

rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null);
rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null);
Expand Down
9 changes: 9 additions & 0 deletions Xamarin.Forms.Core/Internals/ResourceLoader.cs
@@ -0,0 +1,9 @@
using System;
namespace Xamarin.Forms.Internals
{
public static class ResourceLoader
{
public static Func<string, string> ResourceProvider { get; internal set; }
internal static Action<Exception> ExceptionHandler { get; set; }
}
}
3 changes: 2 additions & 1 deletion Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
Expand Up @@ -458,6 +458,7 @@
<Compile Include="PlatformConfiguration\AndroidSpecific\ListView.cs" />
<Compile Include="ITextElement.cs" />
<Compile Include="TextElement.cs" />
<Compile Include="Internals\ResourceLoader.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<ItemGroup>
Expand All @@ -473,4 +474,4 @@
</PropertyGroup>
<ItemGroup />
<ItemGroup />
</Project>
</Project>
5 changes: 5 additions & 0 deletions Xamarin.Forms.Xaml.UnitTests/DefaultCtorRouting2.xaml.cs
Expand Up @@ -26,7 +26,10 @@ public void Setup()
public void TearDown()
{
Device.PlatformServices = null;
#pragma warning disable 0618
Internals.XamlLoader.XamlFileProvider = null;
#pragma warning restore 0618

}

[Test]
Expand All @@ -39,7 +42,9 @@ public void ShouldBeCompiled()
[Test]
public void ShouldntBeCompiled()
{
#pragma warning disable 0618
Internals.XamlLoader.XamlFileProvider = (t) => {
#pragma warning restore 0618
if (t == typeof(DefaultCtorRouting2))
return @"<?xml version=""1.0"" encoding=""UTF-8""?>
<ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
Expand Down
Expand Up @@ -25,7 +25,10 @@ public class Tests
[SetUp]
public void SetUp()
{
#pragma warning disable 0618
Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider = null;
#pragma warning restore 0618

}

[TestCase(false)]
Expand All @@ -35,7 +38,9 @@ public void XamlContentIsReplaced(bool useCompiledXaml)
var layout = new XamlLoaderGetXamlForTypeTests(useCompiledXaml);
Assert.That(layout.Content, Is.TypeOf<Button>());

#pragma warning disable 0618
Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider = (t) => {
#pragma warning restore 0618
if (t == typeof(XamlLoaderGetXamlForTypeTests))
return @"
<ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
Expand Down
15 changes: 15 additions & 0 deletions Xamarin.Forms.Xaml.Xamlc/Xamarin.Forms.Xaml.Xamlc.csproj
Expand Up @@ -42,6 +42,18 @@
<Reference Include="System" />
<Reference Include="Microsoft.Build.Utilities.v4.0" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Mono.Cecil">
<HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Mdb">
<HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb">
<HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks">
<HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Xamarin.Forms.Xaml.Xamlg\Mono.Options\Options.cs">
Expand All @@ -59,4 +71,7 @@
<Name>Xamarin.Forms.Build.Tasks</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions Xamarin.Forms.Xaml.Xamlc/packages.config
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.Cecil" version="0.10.0-beta4" targetFramework="net451" />
</packages>
4 changes: 2 additions & 2 deletions Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Expand Up @@ -305,8 +305,8 @@ public static void SetPropertyValue(object xamlelement, XmlName propertyName, ob
return;

xpe = xpe ?? new XamlParseException($"Cannot assign property \"{localName}\": Property does not exists, or is not assignable, or mismatching type between value and property", lineInfo);
if (context.DoNotThrowOnExceptions)
System.Diagnostics.Debug.WriteLine(xpe.Message);
if (context.ExceptionHandler != null)
context.ExceptionHandler(xpe);
else
throw xpe;
}
Expand Down
6 changes: 3 additions & 3 deletions Xamarin.Forms.Xaml/HydratationContext.cs
Expand Up @@ -3,7 +3,7 @@

namespace Xamarin.Forms.Xaml
{
internal class HydratationContext
class HydratationContext
{
public HydratationContext()
{
Expand All @@ -17,8 +17,8 @@ public HydratationContext()

public HydratationContext ParentContext { get; set; }

public bool DoNotThrowOnExceptions { get; set; }
public Action<Exception> ExceptionHandler { get; set; }

public object RootElement { get; set; }
}
}
}
2 changes: 1 addition & 1 deletion Xamarin.Forms.Xaml/XamlFilePathAttribute.cs
Expand Up @@ -3,7 +3,7 @@

namespace Xamarin.Forms.Xaml
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class XamlFilePathAttribute : Attribute
{
public XamlFilePathAttribute([CallerFilePath] string filePath = "")
Expand Down
23 changes: 16 additions & 7 deletions Xamarin.Forms.Xaml/XamlLoader.cs
Expand Up @@ -32,9 +32,11 @@
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using Xamarin.Forms.Internals;

namespace Xamarin.Forms.Xaml.Internals
{
[Obsolete ("Replaced by ResourceLoader")]
public static class XamlLoader
{
public static Func<Type, string> XamlFileProvider { get; internal set; }
Expand All @@ -44,7 +46,7 @@ public static class XamlLoader

namespace Xamarin.Forms.Xaml
{
internal static class XamlLoader
static class XamlLoader
{
static readonly Dictionary<Type, string> XamlResources = new Dictionary<Type, string>();

Expand Down Expand Up @@ -75,7 +77,9 @@ public static void Load(object view, string xaml)
XamlParser.ParseXaml (rootnode, reader);
Visit (rootnode, new HydratationContext {
RootElement = view,
DoNotThrowOnExceptions = Xamarin.Forms.Xaml.Internals.XamlLoader.DoNotThrowOnExceptions
#pragma warning disable 0618
ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { }: (Action<Exception>)null)
#pragma warning restore 0618
});
break;
}
Expand All @@ -99,7 +103,7 @@ public static object Create (string xaml, bool doNotThrow = false)
var rootnode = new RuntimeRootNode (new XmlType (reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader);
XamlParser.ParseXaml (rootnode, reader);
var visitorContext = new HydratationContext {
DoNotThrowOnExceptions = doNotThrow,
ExceptionHandler = doNotThrow ? e => { } : (Action<Exception>)null,
};
var cvv = new CreateValuesVisitor (visitorContext);
cvv.Visit ((ElementNode)rootnode, null);
Expand Down Expand Up @@ -127,10 +131,14 @@ static void Visit (RootNode rootnode, HydratationContext visitorContext)

static string GetXamlForType(Type type)
{
string xaml = null;

//the Previewer might want to provide it's own xaml for this... let them do that
if (Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider != null && (xaml = Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider(type)) != null)
//the check at the end is preferred (using ResourceLoader). keep this until all the previewers are updated

#pragma warning disable 0618
var xaml = Internals.XamlLoader.XamlFileProvider?.Invoke(type);
#pragma warning restore 0618

if (xaml != null && ResourceLoader.ResourceProvider != null)
return xaml;

Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you checking that ResourceProvider != null here? I presume the idea is that we will either be using XamlFileProvider or we'll be using ResourceLoader.ResourceProvider which means if xaml is non-null then ResourceProvider will always be null.

Copy link
Member Author

Choose a reason for hiding this comment

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

this ResourceProvider != null check is not really required, it's only make sure that if you were foolish enough to set both, the ResourceProvider will prevail.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you've just hit the nail on the head - if you set both, and XamlFileProvider does give valid data, then ResourceProvider will not prevail. Was that expected?

Copy link
Member Author

Choose a reason for hiding this comment

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

🤦‍♂️

Copy link
Member Author

Choose a reason for hiding this comment

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

this is a case when I should rebase to hide my embarrassment

var assembly = type.GetTypeInfo().Assembly;
Expand Down Expand Up @@ -189,7 +197,8 @@ static string GetXamlForType(Type type)
return null;

XamlResources[type] = resourceName;
return xaml;
var alternateXaml = ResourceLoader.ResourceProvider?.Invoke(resourceName);
return alternateXaml ?? xaml;
}

static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
Expand Down
34 changes: 34 additions & 0 deletions docs/Xamarin.Forms.Core/Xamarin.Forms.Internals/ResourceLoader.xml
@@ -0,0 +1,34 @@
<Type Name="ResourceLoader" FullName="Xamarin.Forms.Internals.ResourceLoader">
<TypeSignature Language="C#" Value="public static class ResourceLoader" />
<TypeSignature Language="ILAsm" Value=".class public auto ansi abstract sealed beforefieldinit ResourceLoader extends System.Object" />
<AssemblyInfo>
<AssemblyName>Xamarin.Forms.Core</AssemblyName>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<Base>
<BaseTypeName>System.Object</BaseTypeName>
</Base>
<Interfaces />
<Docs>
<summary>To be added.</summary>
<remarks>To be added.</remarks>
</Docs>
<Members>
<Member MemberName="ResourceProvider">
<MemberSignature Language="C#" Value="public static Func&lt;string,string&gt; ResourceProvider { get; }" />
<MemberSignature Language="ILAsm" Value=".property class System.Func`2&lt;string, string&gt; ResourceProvider" />
<MemberType>Property</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.Func&lt;System.String,System.String&gt;</ReturnType>
</ReturnValue>
<Docs>
<summary>To be added.</summary>
<value>To be added.</value>
<remarks>To be added.</remarks>
</Docs>
</Member>
</Members>
</Type>
1 change: 1 addition & 0 deletions docs/Xamarin.Forms.Core/index.xml
Expand Up @@ -478,6 +478,7 @@
<Type Name="ReflectionExtensions" Kind="Class" />
<Type Name="Registrar" Kind="Class" />
<Type Name="Registrar`1" DisplayName="Registrar&lt;TRegistrable&gt;" Kind="Class" />
<Type Name="ResourceLoader" Kind="Class" />
<Type Name="ResourcesChangedEventArgs" Kind="Class" />
<Type Name="SetValueFlags" Kind="Enumeration" />
<Type Name="TableModel" Kind="Class" />
Expand Down