diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Build.Tasks | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Build.Tasks')
25 files changed, 3899 insertions, 0 deletions
diff --git a/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs new file mode 100644 index 00000000..0dabb5d9 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs @@ -0,0 +1,544 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class CreateObjectVisitor : IXamlNodeVisitor + { + public CreateObjectVisitor(ILContext context) + { + Context = context; + Module = context.Body.Method.Module; + } + + public ILContext Context { get; } + + ModuleDefinition Module { get; } + + public bool VisitChildrenFirst + { + get { return true; } + } + + public bool StopOnDataTemplate + { + get { return true; } + } + + public bool StopOnResourceDictionary + { + get { return false; } + } + + public void Visit(ValueNode node, INode parentNode) + { + Context.Values[node] = node.Value; + + XmlName propertyName; + if (SetPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName)) + { + if (propertyName.NamespaceURI == "http://schemas.openxmlformats.org/markup-compatibility/2006" && + propertyName.LocalName == "Ignorable") + { + (parentNode.IgnorablePrefixes ?? (parentNode.IgnorablePrefixes = new List<string>())).AddRange( + (node.Value as string).Split(',')); + } + } + } + + public void Visit(MarkupNode node, INode parentNode) + { + //At this point, all MarkupNodes are expanded to ElementNodes + } + + public void Visit(ElementNode node, INode parentNode) + { + if (node.SkipPrefix((node.NamespaceResolver ?? parentNode.NamespaceResolver).LookupPrefix(node.NamespaceURI))) + return; + + var typeref = node.XmlType.GetTypeReference(Module, node); + TypeDefinition typedef = typeref.Resolve(); + + if (IsXaml2009LanguagePrimitive(node)) + { + var vardef = new VariableDefinition(typeref); + Context.Variables[node] = vardef; + Context.Body.Variables.Add(vardef); + + Context.IL.Append(PushValueFromLanguagePrimitive(typedef, node)); + Context.IL.Emit(OpCodes.Stloc, vardef); + } + else + { + MethodDefinition factoryCtorInfo = null; + MethodDefinition factoryMethodInfo = null; + MethodDefinition parameterizedCtorInfo = null; + MethodDefinition ctorInfo = null; + + if (node.Properties.ContainsKey(XmlName.xArguments) && !node.Properties.ContainsKey(XmlName.xFactoryMethod)) + { + factoryCtorInfo = typedef.AllMethods().FirstOrDefault(md => md.IsConstructor && + !md.IsStatic && + md.HasParameters && + md.MatchXArguments(node, Module)); + if (factoryCtorInfo == null) + { + throw new XamlParseException( + string.Format("No constructors found for {0} with matching x:Arguments", typedef.FullName), node); + } + ctorInfo = factoryCtorInfo; + if (!typedef.IsValueType) //for ctor'ing typedefs, we first have to ldloca before the params + Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node)); + } + else if (node.Properties.ContainsKey(XmlName.xFactoryMethod)) + { + var factoryMethod = (string)(node.Properties[XmlName.xFactoryMethod] as ValueNode).Value; + factoryMethodInfo = typedef.AllMethods().FirstOrDefault(md => !md.IsConstructor && + md.Name == factoryMethod && + md.IsStatic && + md.MatchXArguments(node, Module)); + if (factoryMethodInfo == null) + { + throw new XamlParseException( + String.Format("No static method found for {0}::{1} ({2})", typedef.FullName, factoryMethod, null), node); + } + Context.IL.Append(PushCtorXArguments(factoryMethodInfo, node)); + } + if (ctorInfo == null && factoryMethodInfo == null) + { + parameterizedCtorInfo = typedef.Methods.FirstOrDefault(md => md.IsConstructor && + !md.IsStatic && + md.HasParameters && + md.Parameters.All( + pd => + pd.CustomAttributes.Any( + ca => + ca.AttributeType.FullName == + "Xamarin.Forms.ParameterAttribute"))); + } + if (parameterizedCtorInfo != null && ValidateCtorArguments(parameterizedCtorInfo, node)) + { + ctorInfo = parameterizedCtorInfo; + // IL_0000: ldstr "foo" + Context.IL.Append(PushCtorArguments(parameterizedCtorInfo, node)); + } + ctorInfo = ctorInfo ?? typedef.Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters && !md.IsStatic); + + var ctorinforef = ctorInfo?.ResolveGenericParameters(typeref, Module); + var factorymethodinforef = factoryMethodInfo?.ResolveGenericParameters(typeref, Module); + var implicitOperatorref = typedef.Methods.FirstOrDefault(md => + md.IsPublic && + md.IsStatic && + md.IsSpecialName && + md.Name == "op_Implicit" && md.Parameters[0].ParameterType.FullName == "System.String"); + + if (ctorinforef != null || factorymethodinforef != null || typedef.IsValueType) + { + VariableDefinition vardef = new VariableDefinition(typeref); + Context.Variables[node] = vardef; + Context.Body.Variables.Add(vardef); + + ValueNode vnode = null; + if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && + vardef.VariableType.IsValueType) + { + //<Color>Purple</Color> + Context.IL.Append(vnode.PushConvertedValue(Context, typeref, new ICustomAttributeProvider[] { typedef }, + node.PushServiceProvider(Context), false, true)); + Context.IL.Emit(OpCodes.Stloc, vardef); + } + else if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null && + implicitOperatorref != null) + { + //<FileImageSource>path.png</FileImageSource> + var implicitOperator = Module.Import(implicitOperatorref); + Context.IL.Emit(OpCodes.Ldstr, ((ValueNode)(node.CollectionItems.First())).Value as string); + Context.IL.Emit(OpCodes.Call, implicitOperator); + Context.IL.Emit(OpCodes.Stloc, vardef); + } + else if (factorymethodinforef != null) + { + var factory = Module.Import(factorymethodinforef); + Context.IL.Emit(OpCodes.Call, factory); + Context.IL.Emit(OpCodes.Stloc, vardef); + } + else if (!typedef.IsValueType) + { + var ctor = Module.Import(ctorinforef); + // IL_0001: newobj instance void class [Xamarin.Forms.Core]Xamarin.Forms.Button::'.ctor'() + // IL_0006: stloc.0 + Context.IL.Emit(OpCodes.Newobj, ctor); + Context.IL.Emit(OpCodes.Stloc, vardef); + } + else if (ctorInfo != null && node.Properties.ContainsKey(XmlName.xArguments) && + !node.Properties.ContainsKey(XmlName.xFactoryMethod) && ctorInfo.MatchXArguments(node, Module)) + { + // IL_0008: ldloca.s 1 + // IL_000a: ldc.i4.1 + // IL_000b: call instance void valuetype Test/Foo::'.ctor'(bool) + + var ctor = Module.Import(ctorinforef); + Context.IL.Emit(OpCodes.Ldloca, vardef); + Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node)); + Context.IL.Emit(OpCodes.Call, ctor); + } + else + { + // IL_0000: ldloca.s 0 + // IL_0002: initobj Test/Foo + Context.IL.Emit(OpCodes.Ldloca, vardef); + Context.IL.Emit(OpCodes.Initobj, Module.Import(typedef)); + } + + if (typeref.FullName == "Xamarin.Forms.Xaml.TypeExtension") + { + var visitor = new SetPropertiesVisitor(Context); + foreach (var cnode in node.Properties.Values.ToList()) + cnode.Accept(visitor, node); + foreach (var cnode in node.CollectionItems) + cnode.Accept(visitor, node); + + //As we're stripping the TypeExtension bare, keep the type if we need it later (hint: we do need it) + INode ntype; + if (!node.Properties.TryGetValue(new XmlName("", "TypeName"), out ntype)) + ntype = node.CollectionItems[0]; + + var type = ((ValueNode)ntype).Value as string; + var namespaceuri = type.Contains(":") ? node.NamespaceResolver.LookupNamespace(type.Split(':')[0].Trim()) : ""; + type = type.Contains(":") ? type.Split(':')[1].Trim() : type; + Context.TypeExtensions[node] = new XmlType(namespaceuri, type, null).GetTypeReference(Module, node); + + node.Properties.Clear(); + node.CollectionItems.Clear(); + + var vardefref = new VariableDefinitionReference(vardef); + Context.IL.Append(SetPropertiesVisitor.ProvideValue(vardefref, Context, Module, node)); + if (vardef != vardefref.VariableDefinition) + { + Context.Variables[node] = vardefref.VariableDefinition; + Context.Body.Variables.Add(vardefref.VariableDefinition); + } + } + } + } + } + + public void Visit(RootNode node, INode parentNode) + { + // IL_0013: ldarg.0 + // IL_0014: stloc.3 + + var ilnode = (ILRootNode)node; + var typeref = ilnode.TypeReference; + var vardef = new VariableDefinition(typeref); + Context.Variables[node] = vardef; + Context.Root = vardef; + Context.Body.Variables.Add(vardef); + Context.IL.Emit(OpCodes.Ldarg_0); + Context.IL.Emit(OpCodes.Stloc, vardef); + } + + public void Visit(ListNode node, INode parentNode) + { + XmlName name; + if (SetPropertiesVisitor.TryGetPropertyName(node, parentNode, out name)) + node.XmlName = name; + } + + bool ValidateCtorArguments(MethodDefinition ctorinfo, ElementNode enode) + { + foreach (var parameter in ctorinfo.Parameters) + { + var propname = + parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Xamarin.Forms.ParameterAttribute") + .ConstructorArguments.First() + .Value as string; + if (!enode.Properties.ContainsKey(new XmlName("", propname))) + return false; + } + return true; + } + + IEnumerable<Instruction> PushCtorArguments(MethodDefinition ctorinfo, ElementNode enode) + { + foreach (var parameter in ctorinfo.Parameters) + { + var propname = + parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Xamarin.Forms.ParameterAttribute") + .ConstructorArguments.First() + .Value as string; + var node = enode.Properties[new XmlName("", propname)]; + enode.Properties.Remove(new XmlName("", propname)); + VariableDefinition vardef; + ValueNode vnode = null; + + if (node is IElementNode && (vardef = Context.Variables[node as IElementNode]) != null) + yield return Instruction.Create(OpCodes.Ldloc, vardef); + else if ((vnode = node as ValueNode) != null) + { + foreach (var instruction in vnode.PushConvertedValue(Context, + parameter.ParameterType, + new ICustomAttributeProvider[] { parameter, parameter.ParameterType.Resolve() }, + enode.PushServiceProvider(Context), false, true)) + yield return instruction; + } + } + } + + IEnumerable<Instruction> PushCtorXArguments(MethodDefinition factoryCtorInfo, ElementNode enode) + { + if (!enode.Properties.ContainsKey(XmlName.xArguments)) + yield break; + + var arguments = new List<INode>(); + var node = enode.Properties[XmlName.xArguments] as ElementNode; + if (node != null) + arguments.Add(node); + var list = enode.Properties[XmlName.xArguments] as ListNode; + if (list != null) + { + foreach (var n in list.CollectionItems) + arguments.Add(n); + } + + for (var i = 0; i < factoryCtorInfo.Parameters.Count; i++) + { + var parameter = factoryCtorInfo.Parameters[i]; + var arg = arguments[i]; + VariableDefinition vardef; + ValueNode vnode = null; + + if (arg is IElementNode && (vardef = Context.Variables[arg as IElementNode]) != null) + yield return Instruction.Create(OpCodes.Ldloc, vardef); + else if ((vnode = arg as ValueNode) != null) + { + foreach (var instruction in vnode.PushConvertedValue(Context, + parameter.ParameterType, + new ICustomAttributeProvider[] { parameter, parameter.ParameterType.Resolve() }, + enode.PushServiceProvider(Context), false, true)) + yield return instruction; + } + } + } + + static bool IsXaml2009LanguagePrimitive(IElementNode node) + { + if (node.NamespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml") + return true; + if (node.NamespaceURI != "clr-namespace:System;assembly=mscorlib") + return false; + var name = node.XmlType.Name.Split(':')[1]; + if (name == "Boolean" || + name == "String" || + name == "Char" || + name == "Decimal" || + name == "Single" || + name == "Double" || + name == "Byte" || + name == "Int16" || + name == "Int32" || + name == "Int64" || + name == "TimeSpan" || + name == "Uri") + return true; + return false; + } + + IEnumerable<Instruction> PushValueFromLanguagePrimitive(TypeDefinition typedef, ElementNode node) + { + var hasValue = node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode && + ((ValueNode)node.CollectionItems[0]).Value is string; + var valueString = hasValue ? ((ValueNode)node.CollectionItems[0]).Value as string : string.Empty; + switch (typedef.FullName) + { + case "System.Boolean": + bool outbool; + if (hasValue && bool.TryParse(valueString, out outbool)) + yield return Instruction.Create(outbool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + else + yield return Instruction.Create(OpCodes.Ldc_I4_0); + break; + case "System.String": + yield return Instruction.Create(OpCodes.Ldstr, valueString); + break; + case "System.Object": + var ctorinfo = + Context.Body.Method.Module.TypeSystem.Object.Resolve() + .Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters); + var ctor = Context.Body.Method.Module.Import(ctorinfo); + yield return Instruction.Create(OpCodes.Newobj, ctor); + break; + case "System.Char": + char outchar; + if (hasValue && char.TryParse(valueString, out outchar)) + yield return Instruction.Create(OpCodes.Ldc_I4, outchar); + else + yield return Instruction.Create(OpCodes.Ldc_I4, 0x00); + break; + case "System.Decimal": + decimal outdecimal; + if (hasValue && decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outdecimal)) + { + var vardef = new VariableDefinition(Context.Body.Method.Module.Import(typeof (decimal))); + Context.Body.Variables.Add(vardef); + //Use an extra temp var so we can push the value to the stack, just like other cases + // IL_0003: ldstr "adecimal" + // IL_0008: ldc.i4.s 0x6f + // IL_000a: call class [mscorlib]System.Globalization.CultureInfo class [mscorlib]System.Globalization.CultureInfo::get_InvariantCulture() + // IL_000f: ldloca.s 0 + // IL_0011: call bool valuetype [mscorlib]System.Decimal::TryParse(string, valuetype [mscorlib]System.Globalization.NumberStyles, class [mscorlib]System.IFormatProvider, [out] valuetype [mscorlib]System.Decimal&) + // IL_0016: pop + yield return Instruction.Create(OpCodes.Ldstr, valueString); + yield return Instruction.Create(OpCodes.Ldc_I4, 0x6f); //NumberStyles.Number + var getInvariantInfo = + Context.Body.Method.Module.Import(typeof (CultureInfo)) + .Resolve() + .Properties.FirstOrDefault(pd => pd.Name == "InvariantCulture") + .GetMethod; + var getInvariant = Context.Body.Method.Module.Import(getInvariantInfo); + yield return Instruction.Create(OpCodes.Call, getInvariant); + yield return Instruction.Create(OpCodes.Ldloca, vardef); + var tryParseInfo = + Context.Body.Method.Module.Import(typeof (decimal)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "TryParse" && md.Parameters.Count == 4); + var tryParse = Context.Body.Method.Module.Import(tryParseInfo); + yield return Instruction.Create(OpCodes.Call, tryParse); + yield return Instruction.Create(OpCodes.Pop); + yield return Instruction.Create(OpCodes.Ldloc, vardef); + } + else + { + yield return Instruction.Create(OpCodes.Ldc_I4_0); + var decimalctorinfo = + Context.Body.Method.Module.Import(typeof (decimal)) + .Resolve() + .Methods.FirstOrDefault( + md => md.IsConstructor && md.Parameters.Count == 1 && md.Parameters[0].ParameterType.FullName == "System.Int32"); + var decimalctor = Context.Body.Method.Module.Import(decimalctorinfo); + yield return Instruction.Create(OpCodes.Newobj, decimalctor); + } + break; + case "System.Single": + float outfloat; + if (hasValue && float.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outfloat)) + yield return Instruction.Create(OpCodes.Ldc_R4, outfloat); + else + yield return Instruction.Create(OpCodes.Ldc_R4, 0f); + break; + case "System.Double": + double outdouble; + if (hasValue && double.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outdouble)) + yield return Instruction.Create(OpCodes.Ldc_R8, outdouble); + else + yield return Instruction.Create(OpCodes.Ldc_R8, 0d); + break; + case "System.Byte": + byte outbyte; + if (hasValue && byte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outbyte)) + yield return Instruction.Create(OpCodes.Ldc_I4, (int)outbyte); + else + yield return Instruction.Create(OpCodes.Ldc_I4, 0x00); + break; + case "System.Int16": + short outshort; + if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outshort)) + yield return Instruction.Create(OpCodes.Ldc_I4, outshort); + else + yield return Instruction.Create(OpCodes.Ldc_I4, 0x00); + break; + case "System.Int32": + int outint; + if (hasValue && int.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outint)) + yield return Instruction.Create(OpCodes.Ldc_I4, outint); + else + yield return Instruction.Create(OpCodes.Ldc_I4, 0x00); + break; + case "System.Int64": + long outlong; + if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outlong)) + yield return Instruction.Create(OpCodes.Ldc_I8, outlong); + else + yield return Instruction.Create(OpCodes.Ldc_I8, 0L); + break; + case "System.TimeSpan": + TimeSpan outspan; + if (hasValue && TimeSpan.TryParse(valueString, CultureInfo.InvariantCulture, out outspan)) + { + var vardef = new VariableDefinition(Context.Body.Method.Module.Import(typeof (TimeSpan))); + Context.Body.Variables.Add(vardef); + //Use an extra temp var so we can push the value to the stack, just like other cases + yield return Instruction.Create(OpCodes.Ldstr, valueString); + var getInvariantInfo = + Context.Body.Method.Module.Import(typeof (CultureInfo)) + .Resolve() + .Properties.FirstOrDefault(pd => pd.Name == "InvariantCulture") + .GetMethod; + var getInvariant = Context.Body.Method.Module.Import(getInvariantInfo); + yield return Instruction.Create(OpCodes.Call, getInvariant); + yield return Instruction.Create(OpCodes.Ldloca, vardef); + var tryParseInfo = + Context.Body.Method.Module.Import(typeof (TimeSpan)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "TryParse" && md.Parameters.Count == 3); + var tryParse = Context.Body.Method.Module.Import(tryParseInfo); + yield return Instruction.Create(OpCodes.Call, tryParse); + yield return Instruction.Create(OpCodes.Pop); + yield return Instruction.Create(OpCodes.Ldloc, vardef); + } + else + { + yield return Instruction.Create(OpCodes.Ldc_I8, 0L); + var timespanctorinfo = + Context.Body.Method.Module.Import(typeof (TimeSpan)) + .Resolve() + .Methods.FirstOrDefault( + md => md.IsConstructor && md.Parameters.Count == 1 && md.Parameters[0].ParameterType.FullName == "System.Int64"); + var timespanctor = Context.Body.Method.Module.Import(timespanctorinfo); + yield return Instruction.Create(OpCodes.Newobj, timespanctor); + } + break; + case "System.Uri": + Uri outuri; + if (hasValue && Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out outuri)) + { + var vardef = new VariableDefinition(Context.Body.Method.Module.Import(typeof (Uri))); + Context.Body.Variables.Add(vardef); + //Use an extra temp var so we can push the value to the stack, just like other cases + yield return Instruction.Create(OpCodes.Ldstr, valueString); + yield return Instruction.Create(OpCodes.Ldc_I4, (int)UriKind.RelativeOrAbsolute); + yield return Instruction.Create(OpCodes.Ldloca, vardef); + var tryCreateInfo = + Context.Body.Method.Module.Import(typeof (Uri)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "TryCreate" && md.Parameters.Count == 3); + var tryCreate = Context.Body.Method.Module.Import(tryCreateInfo); + yield return Instruction.Create(OpCodes.Call, tryCreate); + yield return Instruction.Create(OpCodes.Pop); + yield return Instruction.Create(OpCodes.Ldloc, vardef); + } + else + yield return Instruction.Create(OpCodes.Ldnull); + break; + default: + var defaultctorinfo = typedef.Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters); + if (defaultctorinfo != null) + { + var defaultctor = Context.Body.Method.Module.Import(defaultctorinfo); + yield return Instruction.Create(OpCodes.Newobj, defaultctor); + } + else + { + //should never happen. but if it does, this prevents corrupting the IL stack + yield return Instruction.Create(OpCodes.Ldnull); + } + break; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs new file mode 100644 index 00000000..f4b2f52c --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs @@ -0,0 +1,152 @@ +using System; +using System.IO; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; + +namespace Xamarin.Forms.Build.Tasks +{ + public class DebugXamlCTask : XamlCTask + { + public override bool Execute() + { + InMsBuild = true; + Verbosity = Int32.MaxValue; + LogLine(1, "Preparing debug code for xamlc"); + LogLine(1, "\nAssembly: {0}", Assembly); + + var resolver = new DefaultAssemblyResolver(); + if (!string.IsNullOrEmpty(DependencyPaths)) + { + foreach (var dep in DependencyPaths.Split(';')) + { + LogLine(3, "Adding searchpath {0}", dep); + resolver.AddSearchDirectory(dep); + } + } + if (!string.IsNullOrEmpty(ReferencePath)) + { + var paths = ReferencePath.Replace("//", "/").Split(';'); + foreach (var p in paths) + { + var searchpath = Path.GetDirectoryName(p); + LogLine(3, "Adding searchpath {0}", searchpath); + resolver.AddSearchDirectory(searchpath); + // LogLine (3, "Referencing {0}", p); + // resolver.AddAssembly (p); + } + } + var assemblyDefinition = AssemblyDefinition.ReadAssembly(Assembly, new ReaderParameters + { + //ReadSymbols = DebugSymbols, + AssemblyResolver = resolver + }); + + foreach (var module in assemblyDefinition.Modules) + { + LogLine(2, " Module: {0}", module.Name); + foreach (var resource in module.Resources.OfType<EmbeddedResource>()) + { + Log(2, " Resource: {0}... ", resource.Name); + string classname; + if (!resource.IsXaml(out classname)) + { + LogLine(2, "skipped."); + continue; + } + TypeDefinition typeDef = module.GetType(classname); + if (typeDef == null) + { + LogLine(2, "no type found... skipped."); + continue; + } + var initComp = typeDef.Methods.FirstOrDefault(md => md.Name == "InitializeComponent"); + if (initComp == null) + { + LogLine(2, "no InitializeComponent found... skipped."); + continue; + } + if (typeDef.Methods.FirstOrDefault(md => md.Name == "InitCompRuntime") != null) + { + LogLine(2, "InitCompRuntime already exists... skipped"); + continue; + } + LogLine(2, ""); + + Log(2, " Duplicating {0}.InitializeComponent () into {0}.InitCompRuntime ... ", typeDef.Name); + var initCompRuntime = new MethodDefinition("InitCompRuntime", initComp.Attributes, initComp.ReturnType); + initCompRuntime.Body = initComp.Body; + typeDef.Methods.Add(initCompRuntime); + LogLine(2, "done."); + + // IL_0000: ldarg.0 + // IL_0001: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ContentPage::'.ctor'() + // + // IL_0006: nop + // IL_0007: ldarg.1 + // IL_0008: brfalse IL_0018 + // + // IL_000d: ldarg.0 + // IL_000e: callvirt instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::InitializeComponent() + // IL_0013: br IL_001e + // + // IL_0018: ldarg.0 + // IL_0019: callvirt instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::InitCompRuntime() + // IL_001e: ret + + var altCtor = + typeDef.Methods.Where( + md => md.IsConstructor && md.Parameters.Count == 1 && md.Parameters[0].ParameterType == module.TypeSystem.Boolean) + .FirstOrDefault(); + if (altCtor != null) + Log(2, " Replacing body of {0}.{0} (bool {1}) ... ", typeDef.Name, altCtor.Parameters[0].Name); + else + { + Log(2, " Adding {0}.{0} (bool useCompiledXaml) ... ", typeDef.Name); + altCtor = new MethodDefinition(".ctor", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName, module.TypeSystem.Void); + altCtor.Parameters.Add(new ParameterDefinition("useCompiledXaml", ParameterAttributes.None, + module.TypeSystem.Boolean)); + } + + var body = new MethodBody(altCtor); + var il = body.GetILProcessor(); + var br2 = Instruction.Create(OpCodes.Ldarg_0); + var ret = Instruction.Create(OpCodes.Ret); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Callvirt, + module.Import(typeDef.BaseType.Resolve().GetConstructors().First(c => c.HasParameters == false))); + + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Brfalse, br2); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Callvirt, initComp); + il.Emit(OpCodes.Br, ret); + + il.Append(br2); + il.Emit(OpCodes.Callvirt, initCompRuntime); + il.Append(ret); + + altCtor.Body = body; + if (!typeDef.Methods.Contains(altCtor)) + typeDef.Methods.Add(altCtor); + LogLine(2, "done."); + } + + LogLine(2, ""); + } + Log(1, "Writing the assembly... "); + assemblyDefinition.Write(Assembly, new WriterParameters + { + WriteSymbols = DebugSymbols + }); + LogLine(1, "done."); + + return true; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs new file mode 100644 index 00000000..e63ae426 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Xamarin.Forms.Xaml; +using Xamarin.Forms.Xaml.Internals; + +namespace Xamarin.Forms.Build.Tasks +{ + class ExpandMarkupsVisitor : IXamlNodeVisitor + { + readonly IList<XmlName> skips = new List<XmlName> + { + XmlName.xKey, + XmlName.xTypeArguments, + XmlName.xArguments, + XmlName.xFactoryMethod, + XmlName.xName + }; + + public ExpandMarkupsVisitor(ILContext context) + { + Context = context; + } + + ILContext Context { get; } + + public bool VisitChildrenFirst + { + get { return true; } + } + + public bool StopOnDataTemplate + { + get { return false; } + } + + public bool StopOnResourceDictionary + { + get { return false; } + } + + public void Visit(ValueNode node, INode parentNode) + { + } + + public void Visit(MarkupNode markupnode, INode parentNode) + { + XmlName propertyName; + if (!TryGetProperyName(markupnode, parentNode, out propertyName)) + return; + if (skips.Contains(propertyName)) + return; + var markupString = markupnode.MarkupString; + var node = ParseExpression(ref markupString, Context, markupnode.NamespaceResolver, markupnode) as IElementNode; + if (node != null) + { + ((IElementNode)parentNode).Properties[propertyName] = node; + node.Accept(new XamlNodeVisitor((n, parent) => n.Parent = parent), parentNode); + } + } + + public void Visit(ElementNode node, INode parentNode) + { + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + public static bool TryGetProperyName(INode node, INode parentNode, out XmlName name) + { + name = default(XmlName); + var parentElement = parentNode as IElementNode; + if (parentElement == null) + return false; + foreach (var kvp in parentElement.Properties) + { + if (kvp.Value != node) + continue; + name = kvp.Key; + return true; + } + return false; + } + + static INode ParseExpression(ref string expression, ILContext context, IXmlNamespaceResolver nsResolver, + IXmlLineInfo xmlLineInfo) + { + if (expression.StartsWith("{}", StringComparison.Ordinal)) + return new ValueNode(expression.Substring(2), null); + + if (expression[expression.Length - 1] != '}') + throw new XamlParseException("Markup expression missing its closing tag", xmlLineInfo); + + int len; + string match; + if (!MarkupExpressionParser.MatchMarkup(out match, expression, out len)) + throw new XamlParseException("Error while parsing markup expression", xmlLineInfo); + expression = expression.Substring(len).TrimStart(); + if (expression.Length == 0) + throw new XamlParseException("Markup expression not closed", xmlLineInfo); + + var provider = new XamlServiceProvider(null, null); + provider.Add(typeof (ILContextProvider), new ILContextProvider(context)); + provider.Add(typeof (IXmlNamespaceResolver), nsResolver); + provider.Add(typeof (IXmlLineInfoProvider), new XmlLineInfoProvider(xmlLineInfo)); + + return new MarkupExpansionParser().Parse(match, ref expression, provider); + } + + class ILContextProvider + { + public ILContextProvider(ILContext context) + { + Context = context; + } + + public ILContext Context { get; } + } + + class MarkupExpansionParser : MarkupExpressionParser, IExpressionParser<INode> + { + IElementNode node; + + object IExpressionParser.Parse(string match, ref string remaining, IServiceProvider serviceProvider) + { + return Parse(match, ref remaining, serviceProvider); + } + + public INode Parse(string match, ref string remaining, IServiceProvider serviceProvider) + { + var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver; + if (nsResolver == null) + throw new ArgumentException(); + IXmlLineInfo xmlLineInfo = null; + var xmlLineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider; + if (xmlLineInfoProvider != null) + xmlLineInfo = xmlLineInfoProvider.XmlLineInfo; + var contextProvider = serviceProvider.GetService(typeof (ILContextProvider)) as ILContextProvider; + + var split = match.Split(':'); + if (split.Length > 2) + throw new ArgumentException(); + + string prefix, name; + if (split.Length == 2) + { + prefix = split[0]; + name = split[1]; + } + else + { + prefix = ""; + name = split[0]; + } + + var namespaceuri = nsResolver.LookupNamespace(prefix) ?? ""; + //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix. + XmlType type; + try + { + type = new XmlType(namespaceuri, name + "Extension", null); + var typeref = type.GetTypeReference(contextProvider.Context.Body.Method.Module, null); + } + catch (XamlParseException) + { + type = new XmlType(namespaceuri, name, null); + } + + if (type == null) + throw new NotSupportedException(); + + node = xmlLineInfo == null + ? new ElementNode(type, null, nsResolver) + : new ElementNode(type, null, nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); + + if (remaining.StartsWith("}", StringComparison.Ordinal)) + { + remaining = remaining.Substring(1); + return node; + } + + char next; + string piece; + while ((piece = GetNextPiece(ref remaining, out next)) != null) + HandleProperty(piece, serviceProvider, ref remaining, next != '='); + + return node; + } + + protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) + { + if (prop != null) + { + var name = new XmlName(node.NamespaceURI, prop); + node.Properties[name] = value as INode ?? new ValueNode(strValue, null); + } + else //ContentProperty + node.CollectionItems.Add(value as INode ?? new ValueNode(strValue, null)); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/FieldReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/FieldReferenceExtensions.cs new file mode 100644 index 00000000..3876b9c8 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/FieldReferenceExtensions.cs @@ -0,0 +1,22 @@ +using Mono.Cecil; + +namespace Xamarin.Forms.Build.Tasks +{ + static class FieldReferenceExtensions + { + public static FieldReference ResolveGenericParameters(this FieldReference self, TypeReference declaringTypeRef) + { + var fieldType = self.FieldType; + if (fieldType.IsGenericParameter) + { + var genericParameter = (GenericParameter)fieldType; + fieldType = ((GenericInstanceType)declaringTypeRef).GenericArguments[genericParameter.Position]; + } + var fieldReference = new FieldReference(self.Name, fieldType) + { + DeclaringType = declaringTypeRef + }; + return fieldReference; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/FixedCreateCSharpManifestResourceName.cs b/Xamarin.Forms.Build.Tasks/FixedCreateCSharpManifestResourceName.cs new file mode 100644 index 00000000..a508a558 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/FixedCreateCSharpManifestResourceName.cs @@ -0,0 +1,27 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using Microsoft.Build.Utilities; + +namespace Xamarin.Forms.Build.Tasks +{ + public class FixedCreateCSharpManifestResourceName : CreateCSharpManifestResourceName + { + [Output] + public ITaskItem[] ResourceFilesWithManifestResourceNames { get; set; } + + public override bool Execute() + { + var ret = base.Execute(); + + ResourceFilesWithManifestResourceNames = new TaskItem[ResourceFiles.Length]; + + for (var i = 0; i < ResourceFiles.Length; i++) + { + var copy = new TaskItem(ResourceFiles[i]); + copy.SetMetadata("ManifestResourceName", ManifestResourceNames[i].ItemSpec); + ResourceFilesWithManifestResourceNames[i] = copy; + } + return ret; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/ILContext.cs b/Xamarin.Forms.Build.Tasks/ILContext.cs new file mode 100644 index 00000000..14ade391 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/ILContext.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class ILContext + { + public ILContext(ILProcessor il, MethodBody body, FieldDefinition parentContextValues = null) + { + IL = il; + Body = body; + Values = new Dictionary<IValueNode, object>(); + Variables = new Dictionary<IElementNode, VariableDefinition>(); + Scopes = new Dictionary<INode, VariableDefinition>(); + TypeExtensions = new Dictionary<INode, TypeReference>(); + ParentContextValues = parentContextValues; + } + + public Dictionary<IValueNode, object> Values { get; private set; } + + public Dictionary<IElementNode, VariableDefinition> Variables { get; private set; } + + public Dictionary<INode, VariableDefinition> Scopes { get; private set; } + + public Dictionary<INode, TypeReference> TypeExtensions { get; } + + public FieldDefinition ParentContextValues { get; private set; } + + public object Root { get; set; } //FieldDefinition or VariableDefinition + + public ILProcessor IL { get; private set; } + + public MethodBody Body { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/ILProcessorExtensions.cs b/Xamarin.Forms.Build.Tasks/ILProcessorExtensions.cs new file mode 100644 index 00000000..6bcedc78 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/ILProcessorExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Mono.Cecil.Cil; + +namespace Xamarin.Forms.Build.Tasks +{ + static class ILProcessorExtensions + { + public static void Append(this ILProcessor processor, IEnumerable<Instruction> instructions) + { + foreach (var instruction in instructions) + processor.Append(instruction); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/ILRootNode.cs b/Xamarin.Forms.Build.Tasks/ILRootNode.cs new file mode 100644 index 00000000..b50d6204 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/ILRootNode.cs @@ -0,0 +1,15 @@ +using Mono.Cecil; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class ILRootNode : RootNode + { + public ILRootNode(XmlType xmlType, TypeReference typeReference) : base(xmlType) + { + TypeReference = typeReference; + } + + public TypeReference TypeReference { get; private set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs new file mode 100644 index 00000000..41d7cb20 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/MethodDefinitionExtensions.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Mono.Cecil; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + static class MethodDefinitionExtensions + { + public static bool MatchXArguments(this MethodDefinition methodDefinition, ElementNode enode, ModuleDefinition module) + { + if (!enode.Properties.ContainsKey(XmlName.xArguments)) + return !methodDefinition.HasParameters; + + var arguments = new List<INode>(); + var node = enode.Properties[XmlName.xArguments] as ElementNode; + if (node != null) + arguments.Add(node); + var list = enode.Properties[XmlName.xArguments] as ListNode; + if (list != null) + { + foreach (var n in list.CollectionItems) + arguments.Add(n); + } + + if (methodDefinition.Parameters.Count != arguments.Count) + return false; + + for (var i = 0; i < methodDefinition.Parameters.Count; i++) + { + var paramType = methodDefinition.Parameters[i].ParameterType; + var argType = ((IElementNode)arguments[i]).XmlType.GetTypeReference(module, null); + if (!argType.InheritsFromOrImplements(paramType)) + return false; + } + return true; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs new file mode 100644 index 00000000..e377a0c6 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs @@ -0,0 +1,63 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Rocks; + +namespace Xamarin.Forms.Build.Tasks +{ + static class MethodReferenceExtensions + { + [Obsolete] + public static MethodReference MakeGeneric(this MethodReference self, params TypeReference[] arguments) + { + var reference = new MethodReference(self.Name, self.ReturnType) + { + DeclaringType = self.DeclaringType.MakeGenericInstanceType(arguments), + HasThis = self.HasThis, + ExplicitThis = self.ExplicitThis, + CallingConvention = self.CallingConvention + }; + + foreach (var parameter in self.Parameters) + reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); + + foreach (var generic_parameter in self.GenericParameters) + reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference)); + + return reference; + } + + public static MethodReference ResolveGenericParameters(this MethodReference self, TypeReference declaringTypeRef, + ModuleDefinition module) + { + if (self == null) + throw new ArgumentNullException("self"); + if (declaringTypeRef == null) + throw new ArgumentNullException("declaringTypeRef"); + + var reference = new MethodReference(self.Name, self.ReturnType) + { + DeclaringType = declaringTypeRef, + HasThis = self.HasThis, + ExplicitThis = self.ExplicitThis, + CallingConvention = self.CallingConvention + }; + + foreach (var parameter in self.Parameters) + reference.Parameters.Add(new ParameterDefinition(module.Import(parameter.ParameterType))); + + foreach (var generic_parameter in self.GenericParameters) + reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference)); + + return reference; + } + + public static void ImportTypes(this MethodReference self, ModuleDefinition module) + { + if (self.HasParameters) + { + for (var i = 0; i < self.Parameters.Count; i++) + self.Parameters[i].ParameterType = module.Import(self.Parameters[i].ParameterType); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs new file mode 100644 index 00000000..30b01061 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs @@ -0,0 +1,480 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Xaml; +using Xamarin.Forms.Xaml.Internals; + +namespace Xamarin.Forms.Build.Tasks +{ + static class NodeILExtensions + { + public static IEnumerable<Instruction> PushConvertedValue(this ValueNode node, ILContext context, + TypeReference targetTypeRef, IEnumerable<ICustomAttributeProvider> attributeProviders, + IEnumerable<Instruction> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) + { + TypeReference typeConverter = null; + foreach (var attributeProvider in attributeProviders) + { + CustomAttribute typeConverterAttribute; + if ( + (typeConverterAttribute = + attributeProvider.CustomAttributes.FirstOrDefault( + cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName))) != null) + { + typeConverter = typeConverterAttribute.ConstructorArguments[0].Value as TypeReference; + break; + } + } + return node.PushConvertedValue(context, targetTypeRef, typeConverter, pushServiceProvider, boxValueTypes, + unboxValueTypes); + } + + public static IEnumerable<Instruction> PushConvertedValue(this ValueNode node, ILContext context, FieldReference bpRef, + IEnumerable<Instruction> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes) + { + var module = context.Body.Method.Module; + var targetTypeRef = GetBPReturnType(context, bpRef, node); + + TypeReference typeConverter; + bpRef.HasTypeConverter(module, out typeConverter); + + return node.PushConvertedValue(context, targetTypeRef, typeConverter, pushServiceProvider, boxValueTypes, + unboxValueTypes); + } + + public static IEnumerable<Instruction> PushConvertedValue(this ValueNode node, ILContext context, + TypeReference targetTypeRef, TypeReference typeConverter, IEnumerable<Instruction> pushServiceProvider, + bool boxValueTypes, bool unboxValueTypes) + { + var module = context.Body.Method.Module; + var str = (string)node.Value; + + //If there's a [TypeConverter], use it + if (typeConverter != null) + { + var isExtendedConverter = typeConverter.ImplementsInterface(module.Import(typeof (IExtendedTypeConverter))); + var typeConverterCtor = typeConverter.Resolve().Methods.Single(md => md.IsConstructor && md.Parameters.Count == 0); + var typeConverterCtorRef = module.Import(typeConverterCtor); + var convertFromInvariantStringDefinition = isExtendedConverter + ? module.Import(typeof (IExtendedTypeConverter)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "ConvertFromInvariantString" && md.Parameters.Count == 2) + : typeConverter.Resolve() + .AllMethods() + .FirstOrDefault(md => md.Name == "ConvertFromInvariantString" && md.Parameters.Count == 1); + var convertFromInvariantStringReference = module.Import(convertFromInvariantStringDefinition); + + yield return Instruction.Create(OpCodes.Newobj, typeConverterCtorRef); + yield return Instruction.Create(OpCodes.Ldstr, node.Value as string); + + if (isExtendedConverter) + { + foreach (var instruction in pushServiceProvider) + yield return instruction; + } + + yield return Instruction.Create(OpCodes.Callvirt, convertFromInvariantStringReference); + + if (targetTypeRef.IsValueType && unboxValueTypes) + yield return Instruction.Create(OpCodes.Unbox_Any, module.Import(targetTypeRef)); + + //ConvertFrom returns an object, no need to Box + yield break; + } + var originalTypeRef = targetTypeRef; + var isNullable = false; + MethodReference nullableCtor = null; + if (targetTypeRef.Resolve().FullName == "System.Nullable`1") + { + targetTypeRef = ((GenericInstanceType)targetTypeRef).GenericArguments[0]; + isNullable = true; + nullableCtor = originalTypeRef.GetMethods(md => md.IsConstructor && md.Parameters.Count == 1, module).Single().Item1; + nullableCtor = nullableCtor.MakeGeneric(targetTypeRef); + } + //Obvious Built-in conversions + if (targetTypeRef.Resolve().BaseType != null && targetTypeRef.Resolve().BaseType.FullName == "System.Enum") + yield return Instruction.Create(OpCodes.Ldc_I4, ParseEnum(targetTypeRef, str, node)); + else if (targetTypeRef.FullName == "System.Char") + yield return Instruction.Create(OpCodes.Ldc_I4, Char.Parse(str)); + else if (targetTypeRef.FullName == "System.Byte") + yield return Instruction.Create(OpCodes.Ldc_I4, Byte.Parse(str, CultureInfo.InvariantCulture)); + else if (targetTypeRef.FullName == "System.Int16") + yield return Instruction.Create(OpCodes.Ldc_I4, Int16.Parse(str, CultureInfo.InvariantCulture)); + else if (targetTypeRef.FullName == "System.Int32") + yield return Instruction.Create(OpCodes.Ldc_I4, Int32.Parse(str, CultureInfo.InvariantCulture)); + else if (targetTypeRef.FullName == "System.Int64") + yield return Instruction.Create(OpCodes.Ldc_I8, Int64.Parse(str, CultureInfo.InvariantCulture)); + else if (targetTypeRef.FullName == "System.Single") + yield return Instruction.Create(OpCodes.Ldc_R4, Single.Parse(str, CultureInfo.InvariantCulture)); + else if (targetTypeRef.FullName == "System.Double") + yield return Instruction.Create(OpCodes.Ldc_R8, Double.Parse(str, CultureInfo.InvariantCulture)); + else if (targetTypeRef.FullName == "System.Boolean") + { + if (Boolean.Parse(str)) + yield return Instruction.Create(OpCodes.Ldc_I4_1); + else + yield return Instruction.Create(OpCodes.Ldc_I4_0); + } + else if (targetTypeRef.FullName == "System.TimeSpan") + { + var ts = TimeSpan.Parse(str, CultureInfo.InvariantCulture); + var ticks = ts.Ticks; + var timeSpanCtor = + module.Import(typeof (TimeSpan)) + .Resolve() + .Methods.FirstOrDefault(md => md.IsConstructor && md.Parameters.Count == 1); + var timeSpanCtorRef = module.Import(timeSpanCtor); + + yield return Instruction.Create(OpCodes.Ldc_I8, ticks); + yield return Instruction.Create(OpCodes.Newobj, timeSpanCtorRef); + } + else if (targetTypeRef.FullName == "System.DateTime") + { + var dt = DateTime.Parse(str, CultureInfo.InvariantCulture); + var ticks = dt.Ticks; + var dateTimeCtor = + module.Import(typeof (DateTime)) + .Resolve() + .Methods.FirstOrDefault(md => md.IsConstructor && md.Parameters.Count == 1); + var dateTimeCtorRef = module.Import(dateTimeCtor); + + yield return Instruction.Create(OpCodes.Ldc_I8, ticks); + yield return Instruction.Create(OpCodes.Newobj, dateTimeCtorRef); + } + else if (targetTypeRef.FullName == "System.String" && str.StartsWith("{}", StringComparison.Ordinal)) + yield return Instruction.Create(OpCodes.Ldstr, str.Substring(2)); + else if (targetTypeRef.FullName == "System.String") + yield return Instruction.Create(OpCodes.Ldstr, str); + else if (targetTypeRef.FullName == "System.Object") + yield return Instruction.Create(OpCodes.Ldstr, str); + else + yield return Instruction.Create(OpCodes.Ldnull); + + if (isNullable) + yield return Instruction.Create(OpCodes.Newobj, module.Import(nullableCtor)); + if (originalTypeRef.IsValueType && boxValueTypes) + yield return Instruction.Create(OpCodes.Box, module.Import(originalTypeRef)); + } + + static int ParseEnum(TypeReference enumRef, string value, IXmlLineInfo lineInfo) + { + var enumDef = enumRef.Resolve(); + if (!enumDef.IsEnum) + throw new InvalidOperationException(); + + int? result = null; + + foreach (var v in value.Split(',')) + { + foreach (var field in enumDef.Fields) + { + if (field.Name == "value__") + continue; + if (field.Name == v.Trim()) + result = (result ?? 0) | (int)field.Constant; + } + } + + if (result.HasValue) + return result.Value; + + throw new XamlParseException(string.Format("Enum value not found for {0}", value), lineInfo); + } + + static bool HasTypeConverter(this FieldReference bpRef, ModuleDefinition module, + out TypeReference typeConverterReference) + { + typeConverterReference = null; + + var declaringType = bpRef.DeclaringType; + var bpName = bpRef.Name; + var pName = bpName.EndsWith("Property", StringComparison.Ordinal) ? bpName.Substring(0, bpName.Length - 8) : bpName; + var property = declaringType.Resolve().Properties.FirstOrDefault(p => p.Name == pName); + CustomAttribute attr = null; + + if (property != null) + { + if (property.HasCustomAttributes) + { + attr = + property.CustomAttributes.FirstOrDefault( + cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName)); + } + if (attr == null && property.PropertyType.Resolve().HasCustomAttributes) + { + attr = + property.PropertyType.Resolve() + .CustomAttributes.FirstOrDefault( + cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName)); + } + + if (attr == null) + return false; + + typeConverterReference = attr.ConstructorArguments[0].Value as TypeReference; + return true; + } + + var getters = bpRef.DeclaringType.GetMethods(md => md.Name == "Get" + pName && md.IsStatic, module).SingleOrDefault(); + if (getters != null) + { + if (getters.Item1.HasCustomAttributes) + { + attr = + getters.Item1.CustomAttributes.FirstOrDefault( + cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName)); + } + else if (getters.Item1.ReturnType.Resolve().HasCustomAttributes) + { + attr = + getters.Item1.ReturnType.Resolve() + .CustomAttributes.FirstOrDefault( + cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName)); + } + + if (attr == null) + return false; + + typeConverterReference = attr.ConstructorArguments[0].Value as TypeReference; + return true; + } + + return false; + } + + static TypeReference GetBPReturnType(ILContext context, FieldReference bpRef, IXmlLineInfo lineInfo) + { + //Find a property with a matching name + var name = bpRef.Name; + if (!name.EndsWith("Property", StringComparison.Ordinal)) + return context.Body.Method.Module.TypeSystem.Object; + name = name.Substring(0, name.Length - 8); + + //First, check for a property + TypeReference declaringTypeRef; + var property = bpRef.DeclaringType.GetProperty(pd => pd.Name == name, out declaringTypeRef); + if (property != null) + return property.PropertyType; + + //Then check for getter or setter (attached BPs) + var getters = + bpRef.DeclaringType.GetMethods(md => md.Name == "Get" + name && md.IsStatic, context.Body.Method.Module) + .SingleOrDefault(); + if (getters != null) + return getters.Item1.ReturnType; + + //throws + throw new XamlParseException( + string.Format( + "Can not find a Property named \"{0}\" or a static method named \"Get{0}\" for BindableProperty \"{1}\"", name, + bpRef.Name), lineInfo); + } + + public static IEnumerable<Instruction> PushXmlLineInfo(this INode node, ILContext context) + { + var module = context.Body.Method.Module; + + var xmlLineInfo = node as IXmlLineInfo; + if (xmlLineInfo == null) + { + yield return Instruction.Create(OpCodes.Ldnull); + yield break; + } + MethodReference ctor; + if (xmlLineInfo.HasLineInfo()) + { + yield return Instruction.Create(OpCodes.Ldc_I4, xmlLineInfo.LineNumber); + yield return Instruction.Create(OpCodes.Ldc_I4, xmlLineInfo.LinePosition); + ctor = module.Import(typeof (XmlLineInfo).GetConstructor(new[] { typeof (int), typeof (int) })); + } + else + ctor = module.Import(typeof (XmlLineInfo).GetConstructor(new Type[] { })); + yield return Instruction.Create(OpCodes.Newobj, ctor); + } + + public static IEnumerable<Instruction> PushParentObjectsArray(this INode node, ILContext context) + { + var module = context.Body.Method.Module; + + var nodes = new List<IElementNode>(); + INode n = node.Parent; + while (n != null) + { + var en = n as IElementNode; + if (en != null && context.Variables.ContainsKey(en)) + nodes.Add(en); + n = n.Parent; + } + + if (nodes.Count == 0 && context.ParentContextValues == null) + { + yield return Instruction.Create(OpCodes.Ldnull); + yield break; + } + + if (nodes.Count == 0) + { + yield return Instruction.Create(OpCodes.Ldarg_0); + yield return Instruction.Create(OpCodes.Ldfld, context.ParentContextValues); + yield break; + } + + //Compute parent object length + if (context.ParentContextValues != null) + { + yield return Instruction.Create(OpCodes.Ldarg_0); + yield return Instruction.Create(OpCodes.Ldfld, context.ParentContextValues); + yield return Instruction.Create(OpCodes.Ldlen); + yield return Instruction.Create(OpCodes.Conv_I4); + } + else + yield return Instruction.Create(OpCodes.Ldc_I4_0); + var parentObjectLength = new VariableDefinition(module.TypeSystem.Int32); + context.Body.Variables.Add(parentObjectLength); + yield return Instruction.Create(OpCodes.Stloc, parentObjectLength); + + //Create the final array + yield return Instruction.Create(OpCodes.Ldloc, parentObjectLength); + yield return Instruction.Create(OpCodes.Ldc_I4, nodes.Count); + yield return Instruction.Create(OpCodes.Add); + yield return Instruction.Create(OpCodes.Newarr, module.TypeSystem.Object); + var finalArray = new VariableDefinition(module.Import(typeof (object[]))); + context.Body.Variables.Add(finalArray); + yield return Instruction.Create(OpCodes.Stloc, finalArray); + + //Copy original array to final + if (context.ParentContextValues != null) + { + yield return Instruction.Create(OpCodes.Ldarg_0); + yield return Instruction.Create(OpCodes.Ldfld, context.ParentContextValues); //sourceArray + yield return Instruction.Create(OpCodes.Ldc_I4_0); //sourceIndex + yield return Instruction.Create(OpCodes.Ldloc, finalArray); //destinationArray + yield return Instruction.Create(OpCodes.Ldc_I4, nodes.Count); //destinationIndex + yield return Instruction.Create(OpCodes.Ldloc, parentObjectLength); //length + var arrayCopy = + module.Import(typeof (Array)) + .Resolve() + .Methods.First( + md => + md.Name == "Copy" && md.Parameters.Count == 5 && + md.Parameters[1].ParameterType.FullName == module.TypeSystem.Int32.FullName); + yield return Instruction.Create(OpCodes.Call, module.Import(arrayCopy)); + } + + //Add nodes to array + yield return Instruction.Create(OpCodes.Ldloc, finalArray); + if (nodes.Count > 0) + { + for (var i = 0; i < nodes.Count; i++) + { + var en = nodes[i]; + yield return Instruction.Create(OpCodes.Dup); + yield return Instruction.Create(OpCodes.Ldc_I4, i); + yield return Instruction.Create(OpCodes.Ldloc, context.Variables[en]); + if (context.Variables[en].VariableType.IsValueType) + yield return Instruction.Create(OpCodes.Box, module.Import(context.Variables[en].VariableType)); + yield return Instruction.Create(OpCodes.Stelem_Ref); + } + } + } + + public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILContext context) + { + var module = context.Body.Method.Module; + +#if NOSERVICEPROVIDER + yield return Instruction.Create (OpCodes.Ldnull); + yield break; + #endif + + var ctorinfo = typeof (XamlServiceProvider).GetConstructor(new Type[] { }); + var ctor = module.Import(ctorinfo); + + var addServiceInfo = typeof (XamlServiceProvider).GetMethod("Add", new[] { typeof (Type), typeof (object) }); + var addService = module.Import(addServiceInfo); + + var getTypeFromHandle = + module.Import(typeof (Type).GetMethod("GetTypeFromHandle", new[] { typeof (RuntimeTypeHandle) })); + var getAssembly = module.Import(typeof (Type).GetProperty("Assembly").GetMethod); + + yield return Instruction.Create(OpCodes.Newobj, ctor); + + //Add a SimpleValueTargetProvider + var pushParentIl = node.PushParentObjectsArray(context).ToList(); + if (pushParentIl[pushParentIl.Count - 1].OpCode != OpCodes.Ldnull) + { + yield return Instruction.Create(OpCodes.Dup); //Keep the serviceProvider on the stack + yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (IProvideValueTarget))); + yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle)); + + foreach (var instruction in pushParentIl) + yield return instruction; + + var targetProviderCtor = + module.Import(typeof (SimpleValueTargetProvider).GetConstructor(new[] { typeof (object[]) })); + yield return Instruction.Create(OpCodes.Newobj, targetProviderCtor); + yield return Instruction.Create(OpCodes.Callvirt, addService); + } + + //Add a NamescopeProvider + if (context.Scopes.ContainsKey(node)) + { + yield return Instruction.Create(OpCodes.Dup); //Dupicate the serviceProvider + yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (INameScopeProvider))); + yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle)); + var namescopeProviderCtor = module.Import(typeof (NameScopeProvider).GetConstructor(new Type[] { })); + yield return Instruction.Create(OpCodes.Newobj, namescopeProviderCtor); + yield return Instruction.Create(OpCodes.Dup); //Duplicate the namescopeProvider + var setNamescope = module.Import(typeof (NameScopeProvider).GetProperty("NameScope").GetSetMethod()); + + yield return Instruction.Create(OpCodes.Ldloc, context.Scopes[node]); + yield return Instruction.Create(OpCodes.Callvirt, setNamescope); + yield return Instruction.Create(OpCodes.Callvirt, addService); + } + + //Add a XamlTypeResolver + if (node.NamespaceResolver != null) + { + yield return Instruction.Create(OpCodes.Dup); //Dupicate the serviceProvider + yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (IXamlTypeResolver))); + yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle)); + var xmlNamespaceResolverCtor = module.Import(typeof (XmlNamespaceResolver).GetConstructor(new Type[] { })); + var addNamespace = module.Import(typeof (XmlNamespaceResolver).GetMethod("Add")); + yield return Instruction.Create(OpCodes.Newobj, xmlNamespaceResolverCtor); + foreach (var kvp in node.NamespaceResolver.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml)) + { + yield return Instruction.Create(OpCodes.Dup); //dup the resolver + yield return Instruction.Create(OpCodes.Ldstr, kvp.Key); + yield return Instruction.Create(OpCodes.Ldstr, kvp.Value); + yield return Instruction.Create(OpCodes.Callvirt, addNamespace); + } + yield return Instruction.Create(OpCodes.Ldtoken, context.Body.Method.DeclaringType); + yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle)); + yield return Instruction.Create(OpCodes.Callvirt, getAssembly); + var xtr = module.Import(typeof (XamlTypeResolver)).Resolve(); + var xamlTypeResolverCtor = module.Import(xtr.Methods.First(md => md.IsConstructor && md.Parameters.Count == 2)); + yield return Instruction.Create(OpCodes.Newobj, xamlTypeResolverCtor); + yield return Instruction.Create(OpCodes.Callvirt, addService); + } + + if (node is IXmlLineInfo) + { + yield return Instruction.Create(OpCodes.Dup); //Dupicate the serviceProvider + yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (IXmlLineInfoProvider))); + yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle)); + + foreach (var instruction in node.PushXmlLineInfo(context)) + yield return instruction; + + var lip = module.Import(typeof (XmlLineInfoProvider)).Resolve(); + var lineInfoProviderCtor = module.Import(lip.Methods.First(md => md.IsConstructor && md.Parameters.Count == 1)); + yield return Instruction.Create(OpCodes.Newobj, lineInfoProviderCtor); + yield return Instruction.Create(OpCodes.Callvirt, addService); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/Properties/AssemblyInfo.cs b/Xamarin.Forms.Build.Tasks/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ea4bda34 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. +// + +[assembly: AssemblyTitle("Xamarin.Forms.Build.Tasks")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Xamarin Inc.")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Xamarin Inc.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The Page "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +//[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + +[assembly: InternalsVisibleTo("xamlg")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.UnitTests")]
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs new file mode 100644 index 00000000..472a5658 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/PropertyDefinitionExtensions.cs @@ -0,0 +1,33 @@ +using Mono.Cecil; + +namespace Xamarin.Forms.Build.Tasks +{ + static class PropertyDefinitionExtensions + { + // public static PropertyDefinition MakeGeneric (this PropertyDefinition self, GenericInstanceType declaringTypeReference) + // { + // if (declaringTypeReference == null) + // throw new ArgumentNullException ("declaringTypeReference"); + // if (self == null) + // throw new ArgumentNullException ("self"); + // + // var propertyType = declaringTypeReference.GenericArguments[((GenericParameter)self.PropertyType).Position]; + // self.PropertyType = propertyType; + // self.SetMethod = self.SetMethod.MakeGeneric (propertyType).Resolve (); + // self.GetMethod.ReturnType = propertyType; + // + // return self; + // } + + public static TypeReference ResolveGenericPropertyType(this PropertyDefinition self, + TypeReference declaringTypeReference) + { + if (self.PropertyType.IsGenericParameter) + { + return + ((GenericInstanceType)declaringTypeReference).GenericArguments[((GenericParameter)self.PropertyType).Position]; + } + return self.PropertyType; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetFieldVisitor.cs b/Xamarin.Forms.Build.Tasks/SetFieldVisitor.cs new file mode 100644 index 00000000..1839cf7b --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/SetFieldVisitor.cs @@ -0,0 +1,72 @@ +using System.Linq; +using Mono.Cecil.Cil; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class SetFieldVisitor : IXamlNodeVisitor + { + public SetFieldVisitor(ILContext context) + { + Context = context; + } + + public ILContext Context { get; } + + public bool VisitChildrenFirst + { + get { return false; } + } + + public bool StopOnDataTemplate + { + get { return true; } + } + + public bool StopOnResourceDictionary + { + get { return false; } + } + + public void Visit(ValueNode node, INode parentNode) + { + if (!IsXNameProperty(node, parentNode)) + return; + if (!(parentNode is RootNode)) + { + //no variable assigned for root + var field = Context.Body.Method.DeclaringType.Fields.SingleOrDefault(fd => fd.Name == (string)node.Value); + if (field == null) + return; + Context.IL.Emit(OpCodes.Ldarg_0); + Context.IL.Emit(OpCodes.Ldloc, Context.Variables[(IElementNode)parentNode]); + Context.IL.Emit(OpCodes.Stfld, field); + } + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + static bool IsXNameProperty(ValueNode node, INode parentNode) + { + var parentElement = parentNode as IElementNode; + INode xNameNode; + if (parentElement != null && parentElement.Properties.TryGetValue(XmlName.xName, out xNameNode) && xNameNode == node) + return true; + return false; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs new file mode 100644 index 00000000..477328e2 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs @@ -0,0 +1,142 @@ +using System.Linq; +using Mono.Cecil.Cil; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class SetNamescopesAndRegisterNamesVisitor : IXamlNodeVisitor + { + public SetNamescopesAndRegisterNamesVisitor(ILContext context) + { + Context = context; + } + + ILContext Context { get; } + + public bool VisitChildrenFirst + { + get { return false; } + } + + public bool StopOnDataTemplate + { + get { return true; } + } + + public bool StopOnResourceDictionary + { + get { return false; } + } + + public void Visit(ValueNode node, INode parentNode) + { + Context.Scopes[node] = Context.Scopes[parentNode]; + if (IsXNameProperty(node, parentNode)) + RegisterName((string)node.Value, Context.Scopes[node], Context.Variables[(IElementNode)parentNode], node); + } + + public void Visit(MarkupNode node, INode parentNode) + { + Context.Scopes[node] = Context.Scopes[parentNode]; + } + + public void Visit(ElementNode node, INode parentNode) + { + if (node.SkipPrefix((node.NamespaceResolver ?? parentNode.NamespaceResolver).LookupPrefix(node.NamespaceURI))) + return; + + VariableDefinition ns; + if (parentNode == null || IsDataTemplate(node, parentNode) || IsStyle(node, parentNode)) + ns = CreateNamescope(); + else + ns = Context.Scopes[parentNode]; + if ( + Context.Variables[node].VariableType.InheritsFromOrImplements( + Context.Body.Method.Module.Import(typeof (BindableObject)))) + SetNameScope(node, ns); + Context.Scopes[node] = ns; + } + + public void Visit(RootNode node, INode parentNode) + { + var ns = CreateNamescope(); + if ( + Context.Variables[node].VariableType.InheritsFromOrImplements( + Context.Body.Method.Module.Import(typeof (BindableObject)))) + SetNameScope(node, ns); + Context.Scopes[node] = ns; + } + + public void Visit(ListNode node, INode parentNode) + { + Context.Scopes[node] = Context.Scopes[parentNode]; + } + + static bool IsDataTemplate(INode node, INode parentNode) + { + var parentElement = parentNode as IElementNode; + INode createContent; + if (parentElement != null && parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) && + createContent == node) + return true; + return false; + } + + static bool IsStyle(INode node, INode parentNode) + { + var pnode = parentNode as ElementNode; + return pnode != null && pnode.XmlType.Name == "Style"; + } + + static bool IsXNameProperty(ValueNode node, INode parentNode) + { + var parentElement = parentNode as IElementNode; + INode xNameNode; + if (parentElement != null && parentElement.Properties.TryGetValue(XmlName.xName, out xNameNode) && xNameNode == node) + return true; + return false; + } + + VariableDefinition CreateNamescope() + { + var module = Context.Body.Method.Module; + var nsRef = module.Import(typeof (NameScope)); + var vardef = new VariableDefinition(nsRef); + Context.Body.Variables.Add(vardef); + var nsDef = nsRef.Resolve(); + var ctorinfo = nsDef.Methods.First(md => md.IsConstructor && !md.HasParameters); + var ctor = module.Import(ctorinfo); + Context.IL.Emit(OpCodes.Newobj, ctor); + Context.IL.Emit(OpCodes.Stloc, vardef); + return vardef; + } + + void SetNameScope(ElementNode node, VariableDefinition ns) + { + var module = Context.Body.Method.Module; + var nsRef = module.Import(typeof (NameScope)); + var nsDef = nsRef.Resolve(); + var setNSInfo = nsDef.Methods.First(md => md.Name == "SetNameScope" && md.IsStatic); + var setNS = module.Import(setNSInfo); + Context.IL.Emit(OpCodes.Ldloc, Context.Variables[node]); + Context.IL.Emit(OpCodes.Ldloc, ns); + Context.IL.Emit(OpCodes.Call, setNS); + } + + void RegisterName(string str, VariableDefinition scope, VariableDefinition element, INode node) + { + var module = Context.Body.Method.Module; + var nsRef = module.Import(typeof (INameScope)); + var nsDef = nsRef.Resolve(); + var registerInfo = nsDef.Methods.First(md => md.Name == "RegisterName" && md.Parameters.Count == 3); + var register = module.Import(registerInfo); + + Context.IL.Emit(OpCodes.Ldloc, scope); + Context.IL.Emit(OpCodes.Ldstr, str); + Context.IL.Emit(OpCodes.Ldloc, element); + Context.IL.Append(node.PushXmlLineInfo(Context)); + Context.IL.Emit(OpCodes.Callvirt, register); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs new file mode 100644 index 00000000..5470a3c8 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs @@ -0,0 +1,681 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class SetPropertiesVisitor : IXamlNodeVisitor + { + static int dtcount; + + readonly IList<XmlName> skips = new List<XmlName> + { + XmlName.xKey, + XmlName.xTypeArguments, + XmlName.xArguments, + XmlName.xFactoryMethod, + XmlName.xName + }; + + public SetPropertiesVisitor(ILContext context, bool stopOnResourceDictionary = false) + { + Context = context; + Module = context.Body.Method.Module; + StopOnResourceDictionary = stopOnResourceDictionary; + } + + public ILContext Context { get; } + + ModuleDefinition Module { get; } + + public bool VisitChildrenFirst + { + get { return true; } + } + + public bool StopOnDataTemplate + { + get { return true; } + } + + public bool StopOnResourceDictionary { get; } + + public void Visit(ValueNode node, INode parentNode) + { + //TODO support Label text as element + XmlName propertyName; + if (!TryGetPropertyName(node, parentNode, out propertyName)) + { + if (!IsCollectionItem(node, parentNode)) + return; + string contentProperty; + if (!Context.Variables.ContainsKey((IElementNode)parentNode)) + return; + var parentVar = Context.Variables[(IElementNode)parentNode]; + if ((contentProperty = GetContentProperty(parentVar.VariableType)) != null) + propertyName = new XmlName(((IElementNode)parentNode).NamespaceURI, contentProperty); + else + return; + } + + if (skips.Contains(propertyName)) + return; + if (node.SkipPrefix((node.NamespaceResolver ?? parentNode.NamespaceResolver)?.LookupPrefix(propertyName.NamespaceURI))) + return; + if (propertyName.NamespaceURI == "http://schemas.openxmlformats.org/markup-compatibility/2006" && + propertyName.LocalName == "Ignorable") + { + (parentNode.IgnorablePrefixes ?? (parentNode.IgnorablePrefixes = new List<string>())).AddRange( + (node.Value as string).Split(',')); + return; + } + SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node); + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + if (node.SkipPrefix((node.NamespaceResolver ?? parentNode.NamespaceResolver)?.LookupPrefix(node.NamespaceURI))) + return; + + //if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable + var vardef = Context.Variables[node]; + var vardefref = new VariableDefinitionReference(vardef); + Context.IL.Append(ProvideValue(vardefref, Context, Module, node)); + if (vardef != vardefref.VariableDefinition) + { + vardef = vardefref.VariableDefinition; + Context.Body.Variables.Add(vardef); + Context.Variables[node] = vardef; + } + + XmlName propertyName; + if (TryGetPropertyName(node, parentNode, out propertyName)) + { + if (skips.Contains(propertyName)) + return; + + if (propertyName == XmlName._CreateContent) + SetDataTemplate((IElementNode)parentNode, node, Context, node); + else + SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node); + } + else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) + { + // Collection element, implicit content, or implicit collection element. + string contentProperty; + var parentVar = Context.Variables[(IElementNode)parentNode]; + if (parentVar.VariableType.ImplementsInterface(Module.Import(typeof (IEnumerable)))) + { + var elementType = parentVar.VariableType; + if (elementType.FullName != "Xamarin.Forms.ResourceDictionary") + { + var adderTuple = elementType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).First(); + var adderRef = Module.Import(adderTuple.Item1); + adderRef = Module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, Module)); + + Context.IL.Emit(OpCodes.Ldloc, parentVar); + Context.IL.Emit(OpCodes.Ldloc, vardef); + Context.IL.Emit(OpCodes.Callvirt, adderRef); + if (adderRef.ReturnType.FullName != "System.Void") + Context.IL.Emit(OpCodes.Pop); + } + } + else if ((contentProperty = GetContentProperty(parentVar.VariableType)) != null) + { + var name = new XmlName(node.NamespaceURI, contentProperty); + if (skips.Contains(name)) + return; + SetPropertyValue(Context.Variables[(IElementNode)parentNode], name, node, Context, node); + } + } + else if (IsCollectionItem(node, parentNode) && parentNode is ListNode) + { + // IL_000d: ldloc.2 + // IL_000e: callvirt instance class [mscorlib]System.Collections.Generic.IList`1<!0> class [Xamarin.Forms.Core]Xamarin.Forms.Layout`1<class [Xamarin.Forms.Core]Xamarin.Forms.View>::get_Children() + // IL_0013: ldloc.0 + // IL_0014: callvirt instance void class [mscorlib]System.Collections.Generic.ICollection`1<class [Xamarin.Forms.Core]Xamarin.Forms.View>::Add(!0) + + var parentList = (ListNode)parentNode; + var parent = Context.Variables[((IElementNode)parentNode.Parent)]; + + if (skips.Contains(parentList.XmlName)) + return; + + var elementType = parent.VariableType; + var localname = parentList.XmlName.LocalName; + + GetNameAndTypeRef(ref elementType, parentList.XmlName.NamespaceURI, ref localname, Context, node); + + TypeReference propertyDeclaringType; + var property = elementType.GetProperty(pd => pd.Name == localname, out propertyDeclaringType); + MethodDefinition propertyGetter; + + if (property != null && (propertyGetter = property.GetMethod) != null && propertyGetter.IsPublic) + { + var propertyGetterRef = Module.Import(propertyGetter); + propertyGetterRef = Module.Import(propertyGetterRef.ResolveGenericParameters(propertyDeclaringType, Module)); + var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(propertyDeclaringType); + + var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).First(); + var adderRef = Module.Import(adderTuple.Item1); + adderRef = Module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, Module)); + + Context.IL.Emit(OpCodes.Ldloc, parent); + Context.IL.Emit(OpCodes.Callvirt, propertyGetterRef); + Context.IL.Emit(OpCodes.Ldloc, vardef); + Context.IL.Emit(OpCodes.Callvirt, adderRef); + if (adderRef.ReturnType.FullName != "System.Void") + Context.IL.Emit(OpCodes.Pop); + } + } + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name) + { + name = default(XmlName); + var parentElement = parentNode as IElementNode; + if (parentElement == null) + return false; + foreach (var kvp in parentElement.Properties) + { + if (kvp.Value != node) + continue; + name = kvp.Key; + return true; + } + return false; + } + + static bool IsCollectionItem(INode node, INode parentNode) + { + var parentList = parentNode as IListNode; + if (parentList == null) + return false; + return parentList.CollectionItems.Contains(node); + } + + static string GetContentProperty(TypeReference typeRef) + { + var typeDef = typeRef.Resolve(); + var attributes = typeDef.CustomAttributes; + var attr = + attributes.FirstOrDefault(cad => ContentPropertyAttribute.ContentPropertyTypes.Contains(cad.AttributeType.FullName)); + if (attr != null) + return attr.ConstructorArguments[0].Value as string; + if (typeDef.BaseType == null) + return null; + return GetContentProperty(typeDef.BaseType); + } + + public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ILContext context, + ModuleDefinition module, ElementNode node) + { + GenericInstanceType markupExtension; + IList<TypeReference> genericArguments; + if (vardefref.VariableDefinition.VariableType.FullName == "Xamarin.Forms.Xaml.ArrayExtension" && + vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1", + out markupExtension, out genericArguments)) + { + var markExt = markupExtension.Resolve(); + var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); + var provideValue = module.Import(provideValueInfo); + provideValue = + module.Import(provideValue.MakeGeneric(markupExtension.GenericArguments.Select(tr => module.Import(tr)).ToArray())); + + var typeNode = node.Properties[new XmlName("", "Type")]; + TypeReference arrayTypeRef; + if (context.TypeExtensions.TryGetValue(typeNode, out arrayTypeRef)) + vardefref.VariableDefinition = new VariableDefinition(module.Import(arrayTypeRef.MakeArrayType())); + else + vardefref.VariableDefinition = new VariableDefinition(module.Import(genericArguments.First())); + yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]); + foreach (var instruction in node.PushServiceProvider(context)) + yield return instruction; + yield return Instruction.Create(OpCodes.Callvirt, provideValue); + + if (arrayTypeRef != null) + yield return Instruction.Create(OpCodes.Castclass, module.Import(arrayTypeRef.MakeArrayType())); + yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition); + } + else if (vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1", + out markupExtension, out genericArguments)) + { + var markExt = markupExtension.Resolve(); + var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); + var provideValue = module.Import(provideValueInfo); + provideValue = + module.Import(provideValue.MakeGeneric(markupExtension.GenericArguments.Select(tr => module.Import(tr)).ToArray())); + + vardefref.VariableDefinition = new VariableDefinition(module.Import(genericArguments.First())); + yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]); + foreach (var instruction in node.PushServiceProvider(context)) + yield return instruction; + yield return Instruction.Create(OpCodes.Callvirt, provideValue); + yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition); + } + else if (context.Variables[node].VariableType.ImplementsInterface(module.Import(typeof (IMarkupExtension)))) + { + var markExt = module.Import(typeof (IMarkupExtension)).Resolve(); + var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); + var provideValue = module.Import(provideValueInfo); + + vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object); + yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]); + foreach (var instruction in node.PushServiceProvider(context)) + yield return instruction; + yield return Instruction.Create(OpCodes.Callvirt, provideValue); + yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition); + } + else if (context.Variables[node].VariableType.ImplementsInterface(module.Import(typeof (IValueProvider)))) + { + var markExt = module.Import(typeof (IValueProvider)).Resolve(); + var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); + var provideValue = module.Import(provideValueInfo); + + vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object); + yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]); + foreach (var instruction in node.PushServiceProvider(context)) + yield return instruction; + yield return Instruction.Create(OpCodes.Callvirt, provideValue); + yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition); + } + } + + public static void SetPropertyValue(VariableDefinition parent, XmlName propertyName, INode valueNode, + ILContext context, IXmlLineInfo iXmlLineInfo) + { + var elementType = parent.VariableType; + var localName = propertyName.LocalName; + var module = context.Body.Method.Module; + var br = Instruction.Create(OpCodes.Nop); + TypeReference declaringTypeReference; + var handled = false; + + //If it's an attached BP, update elementType and propertyName + var attached = GetNameAndTypeRef(ref elementType, propertyName.NamespaceURI, ref localName, context, iXmlLineInfo); + + //If the target is an event, connect + // IL_0007: ldloc.0 + // IL_0008: ldarg.0 + // IL_0009: ldftn instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs) + // IL_000f: newobj instance void class [mscorlib]System.EventHandler::'.ctor'(object, native int) + // IL_0014: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.Button::add_Clicked(class [mscorlib]System.EventHandler) + + var eventinfo = elementType.GetEvent(ed => ed.Name == localName); + if (eventinfo != null) + { + var value = ((ValueNode)valueNode).Value; + + context.IL.Emit(OpCodes.Ldloc, parent); + if (context.Root is VariableDefinition) + context.IL.Emit(OpCodes.Ldloc, context.Root as VariableDefinition); + else if (context.Root is FieldDefinition) + { + context.IL.Emit(OpCodes.Ldarg_0); + context.IL.Emit(OpCodes.Ldfld, context.Root as FieldDefinition); + } + else + throw new InvalidProgramException(); + var declaringType = context.Body.Method.DeclaringType; + if (declaringType.IsNested) + declaringType = declaringType.DeclaringType; + var handler = declaringType.AllMethods().FirstOrDefault(md => md.Name == value as string); + if (handler == null) + { + throw new XamlParseException( + string.Format("EventHandler \"{0}\" not found in type \"{1}\"", value, context.Body.Method.DeclaringType.FullName), + iXmlLineInfo); + } + context.IL.Emit(OpCodes.Ldftn, handler); + //FIXME: eventually get the right ctor instead fo the First() one, just in case another one could exists (not even sure it's possible). + var ctor = module.Import(eventinfo.EventType.Resolve().GetConstructors().First()); + ctor = ctor.ResolveGenericParameters(eventinfo.EventType, module); + context.IL.Emit(OpCodes.Newobj, module.Import(ctor)); + context.IL.Emit(OpCodes.Callvirt, module.Import(eventinfo.AddMethod)); + return; + } + + FieldReference bpRef = elementType.GetField(fd => fd.Name == localName + "Property" && fd.IsStatic && fd.IsPublic, + out declaringTypeReference); + if (bpRef != null) + { + bpRef = module.Import(bpRef.ResolveGenericParameters(declaringTypeReference)); + bpRef.FieldType = module.Import(bpRef.FieldType); + } + + //If Value is DynamicResource, SetDynamicResource + VariableDefinition varValue; + if (bpRef != null && valueNode is IElementNode && + context.Variables.TryGetValue(valueNode as IElementNode, out varValue) && + varValue.VariableType.FullName == typeof (DynamicResource).FullName) + { + var setDynamicResource = + module.Import(typeof (IDynamicResourceHandler)).Resolve().Methods.First(m => m.Name == "SetDynamicResource"); + var getKey = typeof (DynamicResource).GetProperty("Key").GetMethod; + + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Emit(OpCodes.Ldsfld, bpRef); + context.IL.Emit(OpCodes.Ldloc, varValue); + context.IL.Emit(OpCodes.Callvirt, module.Import(getKey)); + context.IL.Emit(OpCodes.Callvirt, module.Import(setDynamicResource)); + return; + } + + //If Value is a BindingBase and target is a BP, SetBinding + if (bpRef != null && valueNode is IElementNode && + context.Variables.TryGetValue(valueNode as IElementNode, out varValue) && + varValue.VariableType.InheritsFromOrImplements(module.Import(typeof (BindingBase)))) + { + //TODO: check if parent is a BP + var setBinding = typeof (BindableObject).GetMethod("SetBinding", + new[] { typeof (BindableProperty), typeof (BindingBase) }); + + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Emit(OpCodes.Ldsfld, bpRef); + context.IL.Emit(OpCodes.Ldloc, varValue); + context.IL.Emit(OpCodes.Callvirt, module.Import(setBinding)); + return; + } + + //If it's a BP, SetValue () + // IL_0007: ldloc.0 + // IL_0008: ldsfld class [Xamarin.Forms.Core]Xamarin.Forms.BindableProperty [Xamarin.Forms.Core]Xamarin.Forms.Label::TextProperty + // IL_000d: ldstr "foo" + // IL_0012: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.BindableObject::SetValue(class [Xamarin.Forms.Core]Xamarin.Forms.BindableProperty, object) + if (bpRef != null) + { + //TODO: check if parent is a BP + var setValue = typeof (BindableObject).GetMethod("SetValue", new[] { typeof (BindableProperty), typeof (object) }); + + if (valueNode is ValueNode) + { + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Emit(OpCodes.Ldsfld, bpRef); + context.IL.Append(((ValueNode)valueNode).PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context), + true, false)); + context.IL.Emit(OpCodes.Callvirt, module.Import(setValue)); + return; + } + if (valueNode is IElementNode) + { + var getPropertyReturnType = module.Import(typeof (BindableProperty).GetProperty("ReturnType").GetGetMethod()); + //FIXME: this should check for inheritance too + var isInstanceOfType = module.Import(typeof (Type).GetMethod("IsInstanceOfType", new[] { typeof (object) })); + var brfalse = Instruction.Create(OpCodes.Nop); + + context.IL.Emit(OpCodes.Ldsfld, bpRef); + context.IL.Emit(OpCodes.Call, getPropertyReturnType); + context.IL.Emit(OpCodes.Ldloc, context.Variables[valueNode as IElementNode]); + if (context.Variables[valueNode as IElementNode].VariableType.IsValueType) + context.IL.Emit(OpCodes.Box, context.Variables[valueNode as IElementNode].VariableType); + context.IL.Emit(OpCodes.Callvirt, isInstanceOfType); + context.IL.Emit(OpCodes.Brfalse, brfalse); + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Emit(OpCodes.Ldsfld, bpRef); + context.IL.Emit(OpCodes.Ldloc, context.Variables[(IElementNode)valueNode]); + if (context.Variables[valueNode as IElementNode].VariableType.IsValueType) + context.IL.Emit(OpCodes.Box, context.Variables[valueNode as IElementNode].VariableType); + context.IL.Emit(OpCodes.Callvirt, module.Import(setValue)); + context.IL.Emit(OpCodes.Br, br); + context.IL.Append(brfalse); + handled = true; + } + } + + //If it's a property, set it + // IL_0007: ldloc.0 + // IL_0008: ldstr "foo" + // IL_000d: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.Label::set_Text(string) + PropertyDefinition property = elementType.GetProperty(pd => pd.Name == localName, out declaringTypeReference); + MethodDefinition propertySetter; + if (property != null && (propertySetter = property.SetMethod) != null && propertySetter.IsPublic) + { + module.Import(elementType.Resolve()); + var propertySetterRef = + module.Import(module.Import(propertySetter).ResolveGenericParameters(declaringTypeReference, module)); + propertySetterRef.ImportTypes(module); + var propertyType = property.ResolveGenericPropertyType(declaringTypeReference); + ValueNode vnode = null; + + if ((vnode = valueNode as ValueNode) != null) + { + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Append(vnode.PushConvertedValue(context, + propertyType, + new ICustomAttributeProvider[] { propertyType.Resolve() }, + valueNode.PushServiceProvider(context), false, true)); + context.IL.Emit(OpCodes.Callvirt, propertySetterRef); + + context.IL.Append(br); + return; + } + if (valueNode is IElementNode) + { + var vardef = context.Variables[(ElementNode)valueNode]; + var implicitOperators = + vardef.VariableType.GetMethods(md => md.IsPublic && md.IsStatic && md.IsSpecialName && md.Name == "op_Implicit", + module).ToList(); + MethodReference implicitOperator = null; + if (implicitOperators.Any()) + { + foreach (var op in implicitOperators) + { + var cast = op.Item1; + var opDeclTypeRef = op.Item2; + var castDef = module.Import(cast).ResolveGenericParameters(opDeclTypeRef, module); + var returnType = castDef.ReturnType; + if (returnType.IsGenericParameter) + returnType = ((GenericInstanceType)opDeclTypeRef).GenericArguments[((GenericParameter)returnType).Position]; + if (returnType.FullName == propertyType.FullName && + cast.Parameters[0].ParameterType.Name == vardef.VariableType.Name) + { + implicitOperator = castDef; + break; + } + } + } + + //TODO replace latest check by a runtime type check + if (implicitOperator != null || vardef.VariableType.InheritsFromOrImplements(propertyType) || + propertyType.FullName == "System.Object" || vardef.VariableType.FullName == "System.Object") + { + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Emit(OpCodes.Ldloc, vardef); + if (implicitOperator != null) + { + // IL_000f: call !0 class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<bool>::op_Implicit(class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<!0>) + context.IL.Emit(OpCodes.Call, module.Import(implicitOperator)); + } + else if (!vardef.VariableType.IsValueType && propertyType.IsValueType) + context.IL.Emit(OpCodes.Unbox_Any, module.Import(propertyType)); + else if (vardef.VariableType.IsValueType && propertyType.FullName == "System.Object") + context.IL.Emit(OpCodes.Box, vardef.VariableType); + context.IL.Emit(OpCodes.Callvirt, propertySetterRef); + context.IL.Append(br); + return; + } + handled = true; + } + } + + //If it's an already initialized property, add to it + MethodDefinition propertyGetter; + //TODO: check if property is assigned + if (property != null && (propertyGetter = property.GetMethod) != null && propertyGetter.IsPublic) + { + var propertyGetterRef = module.Import(propertyGetter); + propertyGetterRef = module.Import(propertyGetterRef.ResolveGenericParameters(declaringTypeReference, module)); + var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference); + + //TODO check md.Parameters[0] type + var adderTuple = + propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault(); + if (adderTuple != null) + { + var adderRef = module.Import(adderTuple.Item1); + adderRef = module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, module)); + + if (valueNode is IElementNode) + { + context.IL.Emit(OpCodes.Ldloc, parent); + context.IL.Emit(OpCodes.Callvirt, propertyGetterRef); + context.IL.Emit(OpCodes.Ldloc, context.Variables[(ElementNode)valueNode]); + context.IL.Emit(OpCodes.Callvirt, adderRef); + if (adderRef.ReturnType.FullName != "System.Void") + context.IL.Emit(OpCodes.Pop); + context.IL.Append(br); + return; + } + } + } + if (!handled) + { + throw new XamlParseException(string.Format("No property, bindable property, or event found for '{0}'", localName), + iXmlLineInfo); + } + context.IL.Append(br); + } + + static bool GetNameAndTypeRef(ref TypeReference elementType, string namespaceURI, ref string localname, + ILContext context, IXmlLineInfo lineInfo) + { + var dotIdx = localname.IndexOf('.'); + if (dotIdx > 0) + { + var typename = localname.Substring(0, dotIdx); + localname = localname.Substring(dotIdx + 1); + elementType = new XmlType(namespaceURI, typename, null).GetTypeReference(context.Body.Method.Module, lineInfo); + return true; + } + return false; + } + + static void SetDataTemplate(IElementNode parentNode, ElementNode node, ILContext parentContext, + IXmlLineInfo xmlLineInfo) + { + var parentVar = parentContext.Variables[parentNode]; + //Push the DataTemplate to the stack, for setting the template + parentContext.IL.Emit(OpCodes.Ldloc, parentVar); + + //Create nested class + // .class nested private auto ansi sealed beforefieldinit '<Main>c__AnonStorey0' + // extends [mscorlib]System.Object + + var module = parentContext.Body.Method.Module; + var anonType = new TypeDefinition( + null, + "<" + parentContext.Body.Method.Name + ">_anonXamlCDataTemplate_" + dtcount++, + TypeAttributes.BeforeFieldInit | + TypeAttributes.Sealed | + TypeAttributes.NestedPrivate) + { + BaseType = module.TypeSystem.Object + }; + + parentContext.Body.Method.DeclaringType.NestedTypes.Add(anonType); + var ctor = anonType.AddDefaultConstructor(); + + var loadTemplate = new MethodDefinition("LoadDataTemplate", + MethodAttributes.Assembly | MethodAttributes.HideBySig, + module.TypeSystem.Object); + anonType.Methods.Add(loadTemplate); + + var parentValues = new FieldDefinition("parentValues", FieldAttributes.Assembly, module.Import(typeof (object[]))); + anonType.Fields.Add(parentValues); + + TypeReference rootType = null; + var vdefRoot = parentContext.Root as VariableDefinition; + if (vdefRoot != null) + rootType = vdefRoot.VariableType; + var fdefRoot = parentContext.Root as FieldDefinition; + if (fdefRoot != null) + rootType = fdefRoot.FieldType; + + var root = new FieldDefinition("root", FieldAttributes.Assembly, rootType); + anonType.Fields.Add(root); + + //Fill the loadTemplate Body + var templateIl = loadTemplate.Body.GetILProcessor(); + templateIl.Emit(OpCodes.Nop); + var templateContext = new ILContext(templateIl, loadTemplate.Body, parentValues) + { + Root = root + }; + node.Accept(new CreateObjectVisitor(templateContext), null); + node.Accept(new SetNamescopesAndRegisterNamesVisitor(templateContext), null); + node.Accept(new SetFieldVisitor(templateContext), null); + node.Accept(new SetResourcesVisitor(templateContext), null); + node.Accept(new SetPropertiesVisitor(templateContext), null); + templateIl.Emit(OpCodes.Ldloc, templateContext.Variables[node]); + templateIl.Emit(OpCodes.Ret); + + //Instanciate nested class + var parentIl = parentContext.IL; + parentIl.Emit(OpCodes.Newobj, ctor); + + //Copy required local vars + parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance + parentIl.Append(node.PushParentObjectsArray(parentContext)); + parentIl.Emit(OpCodes.Stfld, parentValues); + parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance + if (parentContext.Root is VariableDefinition) + parentIl.Emit(OpCodes.Ldloc, parentContext.Root as VariableDefinition); + else if (parentContext.Root is FieldDefinition) + { + parentIl.Emit(OpCodes.Ldarg_0); + parentIl.Emit(OpCodes.Ldfld, parentContext.Root as FieldDefinition); + } + else + throw new InvalidProgramException(); + parentIl.Emit(OpCodes.Stfld, root); + + //SetDataTemplate + parentIl.Emit(OpCodes.Ldftn, loadTemplate); + var funcCtor = + module.Import(typeof (Func<>)) + .MakeGenericInstanceType(module.TypeSystem.Object) + .Resolve() + .Methods.First(md => md.IsConstructor && md.Parameters.Count == 2) + .MakeGeneric(module.TypeSystem.Object); + parentIl.Emit(OpCodes.Newobj, module.Import(funcCtor)); + + var propertySetter = + module.Import(typeof (IDataTemplate)).Resolve().Properties.First(p => p.Name == "LoadTemplate").SetMethod; + parentContext.IL.Emit(OpCodes.Callvirt, module.Import(propertySetter)); + } + } + + class VariableDefinitionReference + { + public VariableDefinitionReference(VariableDefinition vardef) + { + VariableDefinition = vardef; + } + + public VariableDefinition VariableDefinition { get; set; } + + public static implicit operator VariableDefinition(VariableDefinitionReference vardefref) + { + return vardefref.VariableDefinition; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs new file mode 100644 index 00000000..c054117c --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + class SetResourcesVisitor : IXamlNodeVisitor + { + public SetResourcesVisitor(ILContext context) + { + Context = context; + Module = context.Body.Method.Module; + } + + public ILContext Context { get; } + + ModuleDefinition Module { get; } + + public bool VisitChildrenFirst + { + get { return false; } + } + + public bool StopOnDataTemplate + { + get { return true; } + } + + public bool StopOnResourceDictionary + { + get { return false; } + } + + public void Visit(ValueNode node, INode parentNode) + { + } + + public void Visit(MarkupNode node, INode parentNode) + { + } + + public void Visit(ElementNode node, INode parentNode) + { + //Set Resources in ResourcesDictionaries + if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) + { + // Collection element, implicit content, or implicit collection element. + CustomAttribute cpAttr; + var parentVar = Context.Variables[(IElementNode)parentNode]; + if (parentVar.VariableType.ImplementsInterface(Module.Import(typeof (IEnumerable)))) + { + if (parentVar.VariableType.FullName == "Xamarin.Forms.ResourceDictionary" && + !node.Properties.ContainsKey(XmlName.xKey)) + { + node.Accept(new SetPropertiesVisitor(Context), parentNode); + + if (node.XmlType.Name != "Style") + throw new XamlParseException("resources in ResourceDictionary require a x:Key attribute", node); + + //if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable + var vardef = Context.Variables[node]; + var vardefref = new VariableDefinitionReference(vardef); + Context.IL.Append(SetPropertiesVisitor.ProvideValue(vardefref, Context, Module, node)); + if (vardef != vardefref.VariableDefinition) + { + vardef = vardefref.VariableDefinition; + Context.Body.Variables.Add(vardef); + Context.Variables[node] = vardef; + } + + Context.IL.Emit(OpCodes.Ldloc, parentVar); + Context.IL.Emit(OpCodes.Ldloc, Context.Variables[node]); + Context.IL.Emit(OpCodes.Callvirt, + Module.Import( + Module.Import(typeof (ResourceDictionary)) + .Resolve() + .Methods.Single(md => md.Name == "Add" && md.Parameters.Count == 1))); + } + else if (parentVar.VariableType.FullName == "Xamarin.Forms.ResourceDictionary" && + node.Properties.ContainsKey(XmlName.xKey)) + { + node.Accept(new SetPropertiesVisitor(Context), parentNode); + + //if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable + var vardef = Context.Variables[node]; + var vardefref = new VariableDefinitionReference(vardef); + Context.IL.Append(SetPropertiesVisitor.ProvideValue(vardefref, Context, Module, node)); + if (vardef != vardefref.VariableDefinition) + { + vardef = vardefref.VariableDefinition; + Context.Body.Variables.Add(vardef); + Context.Variables[node] = vardef; + } + + // IL_0013: ldloc.0 + // IL_0014: ldstr "key" + // IL_0019: ldstr "foo" + // IL_001e: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ResourceDictionary::Add(string, object) + Context.IL.Emit(OpCodes.Ldloc, parentVar); + Context.IL.Emit(OpCodes.Ldstr, (node.Properties[XmlName.xKey] as ValueNode).Value as string); + var varDef = Context.Variables[node]; + Context.IL.Emit(OpCodes.Ldloc, varDef); + if (varDef.VariableType.IsValueType) + Context.IL.Emit(OpCodes.Box, Module.Import(varDef.VariableType)); + Context.IL.Emit(OpCodes.Callvirt, + Module.Import( + Module.Import(typeof (ResourceDictionary)) + .Resolve() + .Methods.Single(md => md.Name == "Add" && md.Parameters.Count == 2))); + } + } + } + + //Set ResourcesDictionaries to their parents + XmlName propertyName; + if (SetPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName) && + (propertyName.LocalName == "Resources" || propertyName.LocalName.EndsWith(".Resources", StringComparison.Ordinal)) && + Context.Variables[node].VariableType.FullName == "Xamarin.Forms.ResourceDictionary") + SetPropertiesVisitor.SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node); + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + static bool IsCollectionItem(INode node, INode parentNode) + { + var parentList = parentNode as IListNode; + if (parentList == null) + return false; + return parentList.CollectionItems.Contains(node); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/TypeDefinitionExtensions.cs b/Xamarin.Forms.Build.Tasks/TypeDefinitionExtensions.cs new file mode 100644 index 00000000..51b3fdbf --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/TypeDefinitionExtensions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; +using MethodAttributes = Mono.Cecil.MethodAttributes; +using MethodImplAttributes = Mono.Cecil.MethodImplAttributes; + +namespace Xamarin.Forms.Build.Tasks +{ + static class TypeDefinitionExtensions + { + public static MethodDefinition AddDefaultConstructor(this TypeDefinition targetType) + { + var parentType = typeof (object); + + return AddDefaultConstructor(targetType, parentType); + } + + public static MethodDefinition AddDefaultConstructor(this TypeDefinition targetType, Type parentType) + { + var module = targetType.Module; + var voidType = module.Import(typeof (void)); + var methodAttributes = MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName; + + var flags = BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance; + + var objectConstructor = parentType.GetConstructor(flags, null, new Type[0], null); + + if (objectConstructor == null) + objectConstructor = typeof (object).GetConstructor(new Type[0]); + + var baseConstructor = module.Import(objectConstructor); + + var ctor = new MethodDefinition(".ctor", methodAttributes, voidType) + { + CallingConvention = MethodCallingConvention.Default, + ImplAttributes = (MethodImplAttributes.IL | MethodImplAttributes.Managed) + }; + + var IL = ctor.Body.GetILProcessor(); + + IL.Emit(OpCodes.Ldarg_0); + IL.Emit(OpCodes.Call, baseConstructor); + IL.Emit(OpCodes.Ret); + + targetType.Methods.Add(ctor); + return ctor; + } + + public static IEnumerable<MethodDefinition> AllMethods(this TypeDefinition self) + { + while (self != null) + { + foreach (var md in self.Methods) + yield return md; + self = self.BaseType == null ? null : self.BaseType.Resolve(); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs new file mode 100644 index 00000000..89a31765 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Rocks; + +namespace Xamarin.Forms.Build.Tasks +{ + static class TypeReferenceExtensions + { + public static PropertyDefinition GetProperty(this TypeReference typeRef, Func<PropertyDefinition, bool> predicate, + out TypeReference declaringTypeRef) + { + declaringTypeRef = typeRef; + var typeDef = typeRef.Resolve(); + var properties = typeDef.Properties.Where(predicate); + if (properties.Any()) + return properties.Single(); + if (typeDef.BaseType == null || typeDef.BaseType.FullName == "System.Object") + return null; + return typeDef.BaseType.GetProperty(predicate, out declaringTypeRef); + } + + public static EventDefinition GetEvent(this TypeReference typeRef, Func<EventDefinition, bool> predicate) + { + var typeDef = typeRef.Resolve(); + var events = typeDef.Events.Where(predicate); + if (events.Any()) + return events.Single(); + if (typeDef.BaseType == null || typeDef.BaseType.FullName == "System.Object") + return null; + return typeDef.BaseType.GetEvent(predicate); + } + + public static FieldDefinition GetField(this TypeReference typeRef, Func<FieldDefinition, bool> predicate, + out TypeReference declaringTypeRef) + { + declaringTypeRef = typeRef; + var typeDef = typeRef.Resolve(); + var bp = typeDef.Fields.Where + (predicate); + if (bp.Any()) + return bp.Single(); + if (typeDef.BaseType == null || typeDef.BaseType.FullName == "System.Object") + return null; + var basetype = typeDef.BaseType.ResolveGenericParameters(typeRef); + return basetype.GetField(predicate, out declaringTypeRef); + } + + public static bool ImplementsInterface(this TypeReference typeRef, TypeReference @interface) + { + var typeDef = typeRef.Resolve(); + if (typeDef.Interfaces.Any(tr => tr.FullName == @interface.FullName)) + return true; + var baseTypeRef = typeDef.BaseType; + if (baseTypeRef != null && baseTypeRef.FullName != "System.Object") + return baseTypeRef.ImplementsInterface(@interface); + return false; + } + + public static bool ImplementsGenericInterface(this TypeReference typeRef, string @interface, + out GenericInstanceType interfaceReference, out IList<TypeReference> genericArguments) + { + interfaceReference = null; + genericArguments = null; + var typeDef = typeRef.Resolve(); + TypeReference iface; + if ( + (iface = + typeDef.Interfaces.FirstOrDefault( + tr => + tr.FullName.StartsWith(@interface) && tr.IsGenericInstance && (tr as GenericInstanceType).HasGenericArguments)) != + null) + { + interfaceReference = iface as GenericInstanceType; + genericArguments = (iface as GenericInstanceType).GenericArguments; + return true; + } + var baseTypeRef = typeDef.BaseType; + if (baseTypeRef != null && baseTypeRef.FullName != "System.Object") + return baseTypeRef.ImplementsGenericInterface(@interface, out interfaceReference, out genericArguments); + return false; + } + + public static bool InheritsFromOrImplements(this TypeReference typeRef, TypeReference baseClass) + { + var arrayInterfaces = new[] + { + "System.IEnumerable", + "System.Collections.IList", + "System.Collections.Collection" + }; + + var arrayGenericInterfaces = new[] + { + "System.IEnumerable`1", + "System.Collections.Generic.IList`1", + "System.Collections.Generic.IReadOnlyCollection<T>", + "System.Collections.Generic.IReadOnlyList<T>", + "System.Collections.Generic.Collection<T>" + }; + + if (typeRef.IsArray) + { + var arrayType = typeRef.Resolve(); + if (arrayInterfaces.Contains(baseClass.FullName)) + return true; + if (arrayGenericInterfaces.Contains(baseClass.Resolve().FullName) && + baseClass.IsGenericInstance && + (baseClass as GenericInstanceType).GenericArguments[0].FullName == arrayType.FullName) + return true; + } + var typeDef = typeRef.Resolve(); + if (typeDef.FullName == baseClass.FullName) + return true; + if (typeDef.Interfaces.Any(ir => ir.FullName == baseClass.FullName)) + return true; + if (typeDef.FullName == "System.Object") + return false; + if (typeDef.BaseType == null) + return false; + return typeDef.BaseType.InheritsFromOrImplements(baseClass); + } + + public static CustomAttribute GetCustomAttribute(this TypeReference typeRef, TypeReference attribute) + { + var typeDef = typeRef.Resolve(); + //FIXME: avoid string comparison. make sure the attribute TypeRef is the same one + var attr = typeDef.CustomAttributes.SingleOrDefault(ca => ca.AttributeType.FullName == attribute.FullName); + if (attr != null) + return attr; + var baseTypeRef = typeDef.BaseType; + if (baseTypeRef != null && baseTypeRef.FullName != "System.Object") + return baseTypeRef.GetCustomAttribute(attribute); + return null; + } + + [Obsolete] + public static MethodDefinition GetMethod(this TypeReference typeRef, Func<MethodDefinition, bool> predicate) + { + TypeReference declaringTypeReference; + return typeRef.GetMethod(predicate, out declaringTypeReference); + } + + [Obsolete] + public static MethodDefinition GetMethod(this TypeReference typeRef, Func<MethodDefinition, bool> predicate, + out TypeReference declaringTypeRef) + { + declaringTypeRef = typeRef; + var typeDef = typeRef.Resolve(); + var methods = typeDef.Methods.Where(predicate); + if (methods.Any()) + return methods.Single(); + if (typeDef.BaseType != null && typeDef.BaseType.FullName == "System.Object") + return null; + if (typeDef.IsInterface) + { + foreach (var face in typeDef.Interfaces) + { + var m = face.GetMethod(predicate); + if (m != null) + return m; + } + return null; + } + return typeDef.BaseType.GetMethod(predicate, out declaringTypeRef); + } + + public static IEnumerable<Tuple<MethodDefinition, TypeReference>> GetMethods(this TypeReference typeRef, + Func<MethodDefinition, bool> predicate, ModuleDefinition module) + { + return typeRef.GetMethods((md, tr) => predicate(md), module); + } + + public static IEnumerable<Tuple<MethodDefinition, TypeReference>> GetMethods(this TypeReference typeRef, + Func<MethodDefinition, TypeReference, bool> predicate, ModuleDefinition module) + { + var typeDef = typeRef.Resolve(); + foreach (var method in typeDef.Methods.Where(md => predicate(md, typeRef))) + yield return new Tuple<MethodDefinition, TypeReference>(method, typeRef); + if (typeDef.IsInterface) + { + foreach (var face in typeDef.Interfaces) + { + if (face.IsGenericInstance && typeRef is GenericInstanceType) + { + int i = 0; + foreach (var arg in ((GenericInstanceType)typeRef).GenericArguments) + ((GenericInstanceType)face).GenericArguments[i++] = module.Import(arg); + } + foreach (var tuple in face.GetMethods(predicate, module)) + yield return tuple; + } + yield break; + } + if (typeDef.BaseType == null || typeDef.BaseType.FullName == "System.Object") + yield break; + var baseType = typeDef.BaseType.ResolveGenericParameters(typeRef); + foreach (var tuple in baseType.GetMethods(predicate, module)) + yield return tuple; + } + + public static TypeReference ResolveGenericParameters(this TypeReference self, TypeReference declaringTypeReference) + { + var genericself = self as GenericInstanceType; + if (genericself == null) + return self; + + var genericdeclType = declaringTypeReference as GenericInstanceType; + if (genericdeclType == null) + return self; + + if (!genericself.GenericArguments.Any(arg => arg.IsGenericParameter)) + return self; + + List<TypeReference> args = new List<TypeReference>(); + for (var i = 0; i < genericself.GenericArguments.Count; i++) + args.Add(genericdeclType.GenericArguments[(genericself.GenericArguments[i] as GenericParameter).Position]); + return self.GetElementType().MakeGenericInstanceType(args.ToArray()); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj new file mode 100644 index 00000000..0808dfdf --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{96D89208-4EB9-4451-BE73-8A9DF3D9D7B7}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>Xamarin.Forms.Build.Tasks</RootNamespace> + <AssemblyName>Xamarin.Forms.Build.Tasks</AssemblyName> + <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> + <RestorePackages>true</RestorePackages> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>full</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\Turkey\</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <DebugType>full</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="Microsoft.Build.Utilities.v4.0" /> + <Reference Include="Microsoft.Build" /> + <Reference Include="System.Xml" /> + <Reference Include="Microsoft.Build.Tasks.v4.0" /> + <Reference Include="Mono.Cecil"> + <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.dll</HintPath> + </Reference> + <Reference Include="Mono.Cecil.Mdb"> + <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Mdb.dll</HintPath> + </Reference> + <Reference Include="Mono.Cecil.Pdb"> + <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Pdb.dll</HintPath> + </Reference> + <Reference Include="Mono.Cecil.Rocks"> + <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Rocks.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="..\Xamarin.Forms.Xaml\XmlnsHelper.cs"> + <Link>XmlnsHelper.cs</Link> + </Compile> + <Compile Include="XamlGTask.cs" /> + <Compile Include="ILContext.cs" /> + <Compile Include="CreateObjectVisitor.cs" /> + <Compile Include="SetPropertiesVisitor.cs" /> + <Compile Include="SetFieldVisitor.cs" /> + <Compile Include="TypeReferenceExtensions.cs" /> + <Compile Include="NodeILExtensions.cs" /> + <Compile Include="ILProcessorExtensions.cs" /> + <Compile Include="ExpandMarkupsVisitor.cs" /> + <Compile Include="SetNamescopesAndRegisterNamesVisitor.cs" /> + <Compile Include="MethodReferenceExtensions.cs" /> + <Compile Include="XamlCTask.cs" /> + <Compile Include="DebugXamlCTask.cs" /> + <Compile Include="ILRootNode.cs" /> + <Compile Include="XmlTypeExtensions.cs" /> + <Compile Include="SetResourcesVisitor.cs" /> + <Compile Include="TypeDefinitionExtensions.cs" /> + <Compile Include="FieldReferenceExtensions.cs" /> + <Compile Include="PropertyDefinitionExtensions.cs" /> + <Compile Include="XamlCAssemblyResolver.cs" /> + <Compile Include="FixedCreateCSharpManifestResourceName.cs" /> + <Compile Include="MethodDefinitionExtensions.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <Target Name="AfterBuild"> + <ItemGroup> + <_CopyItems Include="$(TargetDir)*.dll" /> + </ItemGroup> + <Copy SourceFiles="@(_CopyItems)" DestinationFolder="..\.nuspec\" ContinueOnError="true" /> + </Target> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Xaml\Xamarin.Forms.Xaml.csproj"> + <Project>{9DB2F292-8034-4E06-89AD-98BBDA4306B9}</Project> + <Name>Xamarin.Forms.Xaml</Name> + </ProjectReference> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + <ProjectReference Include="..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.csproj"> + <Project>{984CC812-9470-4A13-AFF9-CC44068D666C}</Project> + <Name>ICSharpCode.Decompiler</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" /> + </Target> +</Project> diff --git a/Xamarin.Forms.Build.Tasks/XamlCAssemblyResolver.cs b/Xamarin.Forms.Build.Tasks/XamlCAssemblyResolver.cs new file mode 100644 index 00000000..04f72c12 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/XamlCAssemblyResolver.cs @@ -0,0 +1,15 @@ +using Mono.Cecil; + +namespace Xamarin.Forms.Build.Tasks +{ + class XamlCAssemblyResolver : DefaultAssemblyResolver + { + public void AddAssembly(string p) + { + RegisterAssembly(AssemblyDefinition.ReadAssembly(p, new ReaderParameters + { + AssemblyResolver = this + })); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/XamlCTask.cs b/Xamarin.Forms.Build.Tasks/XamlCTask.cs new file mode 100644 index 00000000..81f06427 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/XamlCTask.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Ast; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + public class XamlCTask : AppDomainIsolatedTask + { + string buffer = ""; + + bool hasCompiledXamlResources; + + [Required] + public string Assembly { get; set; } + + public string DependencyPaths { get; set; } + + public string ReferencePath { get; set; } + + public int Verbosity { get; set; } + + public bool KeepXamlResources { get; set; } + + public bool OptimizeIL { get; set; } + + public bool DebugSymbols { get; set; } + + public bool OutputGeneratedILAsCode { get; set; } + + protected bool InMsBuild { get; set; } + + public override bool Execute() + { + InMsBuild = true; + return Compile(); + } + + protected void LogError(string subcategory, string errorCode, string helpKeyword, string file, int lineNumber, + int columnNumber, int endLineNumber, int endColumnNumber, string message, params object[] messageArgs) + { + if (!string.IsNullOrEmpty(buffer)) + LogLine(-1, null, null); + if (InMsBuild) + { + base.Log.LogError(subcategory, errorCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, + endColumnNumber, message, messageArgs); + } + else + Console.Error.WriteLine("{0} ({1}:{2}) : {3}", file, lineNumber, columnNumber, message); + } + + protected void LogLine(int level, string format, params object[] arg) + { + if (!string.IsNullOrEmpty(buffer)) + { + format = buffer + format; + buffer = ""; + } + + if (level < 0) + { + if (InMsBuild) + base.Log.LogError(format, arg); + else + Console.Error.WriteLine(format, arg); + } + else if (level <= Verbosity) + { + if (InMsBuild) + base.Log.LogMessage(format, arg); + else + Console.WriteLine(format, arg); + } + } + + protected void Log(int level, string format, params object[] arg) + { + if (level <= 0) + Console.Error.Write(format, arg); + else if (level <= Verbosity) + { + if (InMsBuild) + buffer += String.Format(format, arg); + else + Console.Write(format, arg); + } + } + + public static void Compile(string assemblyFileName, int verbosity = 0, bool keep = false, bool optimize = false, + string dependencyPaths = null, string referencePath = null, bool outputCSharp = false) + { + var xamlc = new XamlCTask + { + Assembly = assemblyFileName, + Verbosity = verbosity, + KeepXamlResources = keep, + OptimizeIL = optimize, + InMsBuild = false, + DependencyPaths = dependencyPaths, + ReferencePath = referencePath, + OutputGeneratedILAsCode = outputCSharp + }; + xamlc.Compile(); + } + + public bool Compile() + { + LogLine(1, "Compiling Xaml"); + LogLine(1, "\nAssembly: {0}", Assembly); + if (!string.IsNullOrEmpty(DependencyPaths)) + LogLine(1, "DependencyPaths: \t{0}", DependencyPaths); + if (!string.IsNullOrEmpty(ReferencePath)) + LogLine(1, "ReferencePath: \t{0}", ReferencePath.Replace("//", "/")); + LogLine(3, "DebugSymbols:\"{0}\"", DebugSymbols); + var skipassembly = true; //change this to false to enable XamlC by default + bool success = true; + + if (!File.Exists(Assembly)) + { + LogLine(1, "Assembly file not found. Skipping XamlC."); + return true; + } + + var resolver = new XamlCAssemblyResolver(); + if (!string.IsNullOrEmpty(DependencyPaths)) + { + foreach (var dep in DependencyPaths.Split(';')) + { + LogLine(3, "Adding searchpath {0}", dep); + resolver.AddSearchDirectory(dep); + } + } + + if (!string.IsNullOrEmpty(ReferencePath)) + { + var paths = ReferencePath.Replace("//", "/").Split(';'); + foreach (var p in paths) + { + var searchpath = Path.GetDirectoryName(p); + LogLine(3, "Adding searchpath {0}", searchpath); + resolver.AddSearchDirectory(searchpath); + // LogLine (3, "Referencing {0}", p); + // resolver.AddAssembly (p); + } + } + + var assemblyDefinition = AssemblyDefinition.ReadAssembly(Path.GetFullPath(Assembly), new ReaderParameters + { + AssemblyResolver = resolver, + ReadSymbols = DebugSymbols + }); + + CustomAttribute xamlcAttr; + if (assemblyDefinition.HasCustomAttributes && + (xamlcAttr = + assemblyDefinition.CustomAttributes.FirstOrDefault( + ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) + { + var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; + if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) + skipassembly = true; + if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) + skipassembly = false; + } + + foreach (var module in assemblyDefinition.Modules) + { + var skipmodule = skipassembly; + if (module.HasCustomAttributes && + (xamlcAttr = + module.CustomAttributes.FirstOrDefault( + ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) + { + var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; + if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) + skipmodule = true; + if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) + skipmodule = false; + } + + LogLine(2, " Module: {0}", module.Name); + var resourcesToPrune = new List<EmbeddedResource>(); + foreach (var resource in module.Resources.OfType<EmbeddedResource>()) + { + Log(2, " Resource: {0}... ", resource.Name); + string classname; + if (!resource.IsXaml(out classname)) + { + LogLine(2, "skipped."); + continue; + } + TypeDefinition typeDef = module.GetType(classname); + if (typeDef == null) + { + LogLine(2, "no type found... skipped."); + continue; + } + var skiptype = skipmodule; + if (typeDef.HasCustomAttributes && + (xamlcAttr = + typeDef.CustomAttributes.FirstOrDefault( + ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) + { + var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; + if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) + skiptype = true; + if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) + skiptype = false; + } + if (skiptype) + { + LogLine(2, "Has XamlCompilationAttribute set to Skip and not Compile... skipped"); + continue; + } + + var initComp = typeDef.Methods.FirstOrDefault(md => md.Name == "InitializeComponent"); + if (initComp == null) + { + LogLine(2, "no InitializeComponent found... skipped."); + continue; + } + LogLine(2, ""); + + Log(2, " Parsing Xaml... "); + var rootnode = ParseXaml(resource.GetResourceStream(), typeDef); + if (rootnode == null) + { + LogLine(2, "failed."); + continue; + } + LogLine(2, "done."); + + hasCompiledXamlResources = true; + + try + { + Log(2, " Replacing {0}.InitializeComponent ()... ", typeDef.Name); + var body = new MethodBody(initComp); + var il = body.GetILProcessor(); + il.Emit(OpCodes.Nop); + var visitorContext = new ILContext(il, body); + + rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null); + rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null); + rootnode.Accept(new CreateObjectVisitor(visitorContext), null); + rootnode.Accept(new SetNamescopesAndRegisterNamesVisitor(visitorContext), null); + rootnode.Accept(new SetFieldVisitor(visitorContext), null); + rootnode.Accept(new SetResourcesVisitor(visitorContext), null); + rootnode.Accept(new SetPropertiesVisitor(visitorContext, true), null); + + il.Emit(OpCodes.Ret); + initComp.Body = body; + } + catch (XamlParseException xpe) + { + LogLine(2, "failed."); + LogError(null, null, null, resource.Name, xpe.XmlInfo.LineNumber, xpe.XmlInfo.LinePosition, 0, 0, xpe.Message, + xpe.HelpLink, xpe.Source); + LogLine(4, xpe.StackTrace); + success = false; + continue; + } + catch (XmlException xe) + { + LogLine(2, "failed."); + LogError(null, null, null, resource.Name, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source); + LogLine(4, xe.StackTrace); + success = false; + continue; + } + catch (Exception e) + { + LogLine(2, "failed."); + LogError(null, null, null, resource.Name, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); + LogLine(4, e.StackTrace); + success = false; + continue; + } + LogLine(2, "done."); + + if (OptimizeIL) + { + Log(2, " Optimizing IL... "); + initComp.Body.OptimizeMacros(); + LogLine(2, "done"); + } + + if (OutputGeneratedILAsCode) + { + var filepath = Path.Combine(Path.GetDirectoryName(Assembly), typeDef.FullName + ".decompiled.cs"); + Log(2, " Decompiling {0} into {1}...", typeDef.FullName, filepath); + var decompilerContext = new DecompilerContext(module); + using(var writer = new StreamWriter(filepath)) + { + var output = new PlainTextOutput(writer); + + var codeDomBuilder = new AstBuilder(decompilerContext); + codeDomBuilder.AddType(typeDef); + codeDomBuilder.GenerateCode(output); + } + + LogLine(2, "done"); + } + resourcesToPrune.Add(resource); + } + if (!KeepXamlResources) + { + if (resourcesToPrune.Any()) + LogLine(2, " Removing compiled xaml resources"); + foreach (var resource in resourcesToPrune) + { + Log(2, " Removing {0}... ", resource.Name); + module.Resources.Remove(resource); + LogLine(2, "done"); + } + } + + LogLine(2, ""); + } + + if (!hasCompiledXamlResources) + { + LogLine(1, "No compiled resources. Skipping writing assembly."); + return success; + } + + Log(1, "Writing the assembly... "); + try + { + assemblyDefinition.Write(Assembly, new WriterParameters + { + WriteSymbols = DebugSymbols + }); + LogLine(1, "done."); + } + catch (Exception e) + { + LogLine(1, "failed."); + LogError(null, null, null, null, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); + LogLine(4, e.StackTrace); + success = false; + } + + return success; + } + + static ILRootNode ParseXaml(Stream stream, TypeReference typeReference) + { + ILRootNode rootnode = null; + using(var reader = XmlReader.Create(stream)) + { + while (reader.Read()) + { + //Skip until element + if (reader.NodeType == XmlNodeType.Whitespace) + continue; + if (reader.NodeType != XmlNodeType.Element) + { + Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value); + continue; + } + + XamlParser.ParseXaml( + rootnode = new ILRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), typeReference), reader); + break; + } + } + return rootnode; + } + } + + static class CecilExtensions + { + public static bool IsXaml(this EmbeddedResource resource, out string classname) + { + classname = null; + if (!resource.Name.EndsWith(".xaml", StringComparison.InvariantCulture)) + return false; + + using(var resourceStream = resource.GetResourceStream()) + { + var xmlDoc = new XmlDocument(); + xmlDoc.Load(resourceStream); + + var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); + + var root = xmlDoc.SelectSingleNode("/*", nsmgr); + if (root == null) + { + // Log (2, "No root found... "); + return false; + } + + var rootClass = root.Attributes["Class", "http://schemas.microsoft.com/winfx/2006/xaml"] ?? + root.Attributes["Class", "http://schemas.microsoft.com/winfx/2009/xaml"]; + if (rootClass == null) + { + // Log (2, "no x:Class found... "); + return false; + } + classname = rootClass.Value; + return true; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/XamlGTask.cs b/Xamarin.Forms.Build.Tasks/XamlGTask.cs new file mode 100644 index 00000000..28158e6b --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/XamlGTask.cs @@ -0,0 +1,261 @@ +using System; +using System.CodeDom; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.CSharp; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + public class XamlGTask : Task + { + internal static CodeDomProvider Provider = new CSharpCodeProvider(); + + [Required] + public string Source { get; set; } + + public string Language { get; set; } + + public string AssemblyName { get; set; } + + [Output] + public string OutputFile { get; set; } + + public override bool Execute() + { + if (Source == null || OutputFile == null) + { + Log.LogMessage("Skipping XamlG"); + return true; + } + + Log.LogMessage("Source: {0}", Source); + Log.LogMessage("Language: {0}", Language); + Log.LogMessage("AssemblyName: {0}", AssemblyName); + Log.LogMessage("OutputFile {0}", OutputFile); + + try + { + GenerateFile(Source, OutputFile); + return true; + } + catch (XmlException xe) + { + Log.LogError(null, null, null, Source, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source); + + return false; + } + catch (Exception e) + { + Log.LogError(null, null, null, Source, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); + return false; + } + } + + internal static void ParseXaml(TextReader xaml, out string rootType, out string rootNs, out CodeTypeReference baseType, + out IDictionary<string, CodeTypeReference> namesAndTypes) + { + var xmlDoc = new XmlDocument(); + xmlDoc.Load(xaml); + + var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); + nsmgr.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml"); + nsmgr.AddNamespace("x2009", "http://schemas.microsoft.com/winfx/2009/xaml"); + nsmgr.AddNamespace("f", "http://xamarin.com/schemas/2014/forms"); + + var root = xmlDoc.SelectSingleNode("/*", nsmgr); + if (root == null) + { + Console.Error.WriteLine("{0}: No root node found"); + rootType = null; + rootNs = null; + baseType = null; + namesAndTypes = null; + return; + } + + var rootClass = root.Attributes["Class", "http://schemas.microsoft.com/winfx/2006/xaml"] ?? + root.Attributes["Class", "http://schemas.microsoft.com/winfx/2009/xaml"]; + if (rootClass == null) + { + rootType = null; + rootNs = null; + baseType = null; + namesAndTypes = null; + return; + } + + string rootAsm; + XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm); + namesAndTypes = GetNamesAndTypes(root, nsmgr); + + var typeArguments = root.Attributes["TypeArguments", "http://schemas.microsoft.com/winfx/2009/xaml"]; + + baseType = GetType(root.NamespaceURI, root.LocalName, typeArguments == null ? null : typeArguments.Value.Split(','), + root.GetNamespaceOfPrefix); + } + + internal static void GenerateCode(string rootType, string rootNs, CodeTypeReference baseType, + IDictionary<string, CodeTypeReference> namesAndTypes, string outFile) + { + if (rootType == null) + { + File.WriteAllText(outFile, ""); + return; + } + + var ccu = new CodeCompileUnit(); + var declNs = new CodeNamespace(rootNs); + ccu.Namespaces.Add(declNs); + + declNs.Imports.Add(new CodeNamespaceImport("System")); + declNs.Imports.Add(new CodeNamespaceImport("Xamarin.Forms")); + declNs.Imports.Add(new CodeNamespaceImport("Xamarin.Forms.Xaml")); + + var declType = new CodeTypeDeclaration(rootType); + declType.IsPartial = true; + declType.BaseTypes.Add(baseType); + + declNs.Types.Add(declType); + + var initcomp = new CodeMemberMethod + { + Name = "InitializeComponent", + CustomAttributes = + { + new CodeAttributeDeclaration(new CodeTypeReference(typeof (GeneratedCodeAttribute)), + new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")), + new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0"))) + } + }; + declType.Members.Add(initcomp); + + initcomp.Statements.Add(new CodeMethodInvokeExpression( + new CodeThisReferenceExpression(), + "LoadFromXaml", new CodeTypeOfExpression(declType.Name))); + + foreach (var entry in namesAndTypes) + { + string name = entry.Key; + var type = entry.Value; + + var field = new CodeMemberField + { + Name = name, + Type = type, + CustomAttributes = + { + new CodeAttributeDeclaration(new CodeTypeReference(typeof (GeneratedCodeAttribute)), + new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")), + new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0"))) + } + }; + + declType.Members.Add(field); + + var find_invoke = new CodeMethodInvokeExpression( + new CodeMethodReferenceExpression( + new CodeThisReferenceExpression(), + "FindByName", type), new CodePrimitiveExpression(name)); + + //CodeCastExpression cast = new CodeCastExpression (type, find_invoke); + + CodeAssignStatement assign = new CodeAssignStatement( + new CodeVariableReferenceExpression(name), find_invoke); + + initcomp.Statements.Add(assign); + } + + using(var writer = new StreamWriter(outFile)) + Provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions()); + } + + internal static void GenerateFile(string xamlFile, string outFile) + { + string rootType, rootNs; + CodeTypeReference baseType; + IDictionary<string, CodeTypeReference> namesAndTypes; + using(StreamReader reader = File.OpenText(xamlFile)) + ParseXaml(reader, out rootType, out rootNs, out baseType, out namesAndTypes); + GenerateCode(rootType, rootNs, baseType, namesAndTypes, outFile); + } + + static Dictionary<string, CodeTypeReference> GetNamesAndTypes(XmlNode root, XmlNamespaceManager nsmgr) + { + var res = new Dictionary<string, CodeTypeReference>(); + + foreach (string attrib in new[] { "x:Name", "x2009:Name" }) + { + XmlNodeList names = + root.SelectNodes( + "//*[@" + attrib + + "][not(ancestor:: f:DataTemplate) and not(ancestor:: f:ControlTemplate) and not(ancestor:: f:Style)]", nsmgr); + foreach (XmlNode node in names) + { + // Don't take the root canvas + if (node == root) + continue; + + XmlAttribute attr = node.Attributes["Name", "http://schemas.microsoft.com/winfx/2006/xaml"] ?? + node.Attributes["Name", "http://schemas.microsoft.com/winfx/2009/xaml"]; + XmlAttribute typeArgsAttr = node.Attributes["x:TypeArguments"]; + var typeArgsList = typeArgsAttr == null ? null : typeArgsAttr.Value.Split(',').ToList(); + string name = attr.Value; + + res[name] = GetType(node.NamespaceURI, node.LocalName, typeArgsList, node.GetNamespaceOfPrefix); + } + } + + return res; + } + + static CodeTypeReference GetType(string nsuri, string type, IList<string> typeArguments = null, + Func<string, string> getNamespaceOfPrefix = null) + { + var ns = GetNamespace(nsuri); + if (ns != null) + type = String.Concat(ns, ".", type); + + if (typeArguments != null) + type = String.Concat(type, "`", typeArguments.Count); + + var returnType = new CodeTypeReference(type); + if (ns != null) + returnType.Options |= CodeTypeReferenceOptions.GlobalReference; + + if (typeArguments != null) + { + foreach (var typeArg in typeArguments) + { + var ns_uri = ""; + var _type = typeArg; + if (typeArg.Contains(":")) + { + var prefix = typeArg.Split(':')[0].Trim(); + ns_uri = getNamespaceOfPrefix(prefix); + _type = typeArg.Split(':')[1].Trim(); + } + returnType.TypeArguments.Add(GetType(ns_uri, _type, null, getNamespaceOfPrefix)); + } + } + + return returnType; + } + + static string GetNamespace(string namespaceuri) + { + if (!XmlnsHelper.IsCustom(namespaceuri)) + return "Xamarin.Forms"; + if (namespaceuri == "http://schemas.microsoft.com/winfx/2009/xaml") + return "System"; + if (namespaceuri != "http://schemas.microsoft.com/winfx/2006/xaml" && !namespaceuri.Contains("clr-namespace")) + throw new Exception(String.Format("Can't load types from xmlns {0}", namespaceuri)); + return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs b/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs new file mode 100644 index 00000000..e0810633 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Mono.Cecil; +using Mono.Cecil.Rocks; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + static class XmlTypeExtensions + { + public static TypeReference GetTypeReference(this XmlType xmlType, ModuleDefinition module, IXmlLineInfo xmlInfo) + { + var namespaceURI = xmlType.NamespaceUri; + var elementName = xmlType.Name; + var typeArguments = xmlType.TypeArguments; + + List<Tuple<string, string>> lookupAssemblies = new List<Tuple<string, string>>(); //assembly, namespace + List<string> lookupNames = new List<string>(); + + if (!XmlnsHelper.IsCustom(namespaceURI)) + { + lookupAssemblies.Add(new Tuple<string, string>("Xamarin.Forms.Core", "Xamarin.Forms")); + lookupAssemblies.Add(new Tuple<string, string>("Xamarin.Forms.Xaml", "Xamarin.Forms.Xaml")); + } + else if (namespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml" || + namespaceURI == "http://schemas.microsoft.com/winfx/2006/xaml") + { + lookupAssemblies.Add(new Tuple<string, string>("Xamarin.Forms.Xaml", "Xamarin.Forms.Xaml")); + lookupAssemblies.Add(new Tuple<string, string>("mscorlib", "System")); + lookupAssemblies.Add(new Tuple<string, string>("System", "System")); + } + else + { + string ns; + string typename; + string asmstring; + + XmlnsHelper.ParseXmlns(namespaceURI, out typename, out ns, out asmstring); + asmstring = asmstring ?? module.Assembly.Name.Name; + lookupAssemblies.Add(new Tuple<string, string>(asmstring, ns)); + } + + lookupNames.Add(elementName); + if (namespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml") + lookupNames.Add(elementName + "Extension"); + for (var i = 0; i < lookupNames.Count; i++) + { + var name = lookupNames[i]; + if (name.Contains(":")) + name = name.Substring(name.LastIndexOf(':') + 1); + if (typeArguments != null) + name += "`" + typeArguments.Count; //this will return an open generic Type + lookupNames[i] = name; + } + + TypeReference type = null; + foreach (var asm in lookupAssemblies) + { + if (type != null) + break; + foreach (var name in lookupNames) + { + if (type != null) + break; + + var assemblydefinition = module.Assembly.Name.Name == asm.Item1 + ? module.Assembly + : module.AssemblyResolver.Resolve(asm.Item1); + type = assemblydefinition.MainModule.GetType(asm.Item2, name); + if (type == null) + { + var exportedtype = + assemblydefinition.MainModule.ExportedTypes.FirstOrDefault( + (ExportedType arg) => arg.IsForwarder && arg.Namespace == asm.Item2 && arg.Name == name); + if (exportedtype != null) + type = exportedtype.Resolve(); + } + } + } + + if (type != null && typeArguments != null && type.HasGenericParameters) + { + type = + module.Import(type) + .MakeGenericInstanceType(typeArguments.Select(x => GetTypeReference(x, module, xmlInfo)).ToArray()); + } + + if (type == null) + throw new XamlParseException(string.Format("Type {0} not found in xmlns {1}", elementName, namespaceURI), xmlInfo); + + return module.Import(type); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/packages.config b/Xamarin.Forms.Build.Tasks/packages.config new file mode 100644 index 00000000..2821c6e5 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Mono.Cecil" version="0.9.6.1" targetFramework="net451" /> +</packages>
\ No newline at end of file |