From a6bbed029c64d2d64b74eeb67e27a099abf70664 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Tue, 15 Nov 2016 20:39:48 +0100 Subject: [XamlC] TypedBindings, some tests, a compiler, ... (#489) --- Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs | 68 ++-- Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs | 5 +- Xamarin.Forms.Build.Tasks/ILContext.cs | 5 +- Xamarin.Forms.Build.Tasks/Logger.cs | 76 +++++ .../MethodReferenceExtensions.cs | 18 + Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs | 373 ++++++++++++++++++++- .../Xamarin.Forms.Build.Tasks.csproj | 2 + Xamarin.Forms.Build.Tasks/XamlCTask.cs | 262 +++------------ Xamarin.Forms.Build.Tasks/XamlTask.cs | 96 ++++++ 9 files changed, 646 insertions(+), 259 deletions(-) create mode 100644 Xamarin.Forms.Build.Tasks/Logger.cs create mode 100644 Xamarin.Forms.Build.Tasks/XamlTask.cs (limited to 'Xamarin.Forms.Build.Tasks') diff --git a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs index c4744cc2..a8ef874a 100644 --- a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs +++ b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Mono.Cecil; @@ -7,21 +8,20 @@ using Mono.Cecil.Rocks; namespace Xamarin.Forms.Build.Tasks { - public class DebugXamlCTask : XamlCTask + public class DebugXamlCTask : XamlTask { - public override bool Execute() + public override bool Execute(IList thrownExceptions) { - InMsBuild = true; - Verbosity = Int32.MaxValue; - LogLine(1, "Preparing debug code for xamlc"); - LogLine(1, "\nAssembly: {0}", Assembly); + Logger = Logger ?? new Logger(null, Verbosity); + Logger.LogLine(1, "Preparing debug code for xamlc"); + Logger.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); + Logger.LogLine(3, "Adding searchpath {0}", dep); resolver.AddSearchDirectory(dep); } } @@ -31,7 +31,7 @@ namespace Xamarin.Forms.Build.Tasks foreach (var p in paths) { var searchpath = Path.GetDirectoryName(p); - LogLine(3, "Adding searchpath {0}", searchpath); + Logger.LogLine(3, "Adding searchpath {0}", searchpath); resolver.AddSearchDirectory(searchpath); // LogLine (3, "Referencing {0}", p); // resolver.AddAssembly (p); @@ -45,58 +45,58 @@ namespace Xamarin.Forms.Build.Tasks foreach (var module in assemblyDefinition.Modules) { - LogLine(2, " Module: {0}", module.Name); + Logger.LogLine(2, " Module: {0}", module.Name); foreach (var resource in module.Resources.OfType()) { - LogString(2, " Resource: {0}... ", resource.Name); + Logger.LogString(2, " Resource: {0}... ", resource.Name); string classname; if (!resource.IsXaml(out classname)) { - LogLine(2, "skipped."); + Logger.LogLine(2, "skipped."); continue; } TypeDefinition typeDef = module.GetType(classname); if (typeDef == null) { - LogLine(2, "no type found... skipped."); + Logger.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."); + Logger.LogLine(2, "no InitializeComponent found... skipped."); continue; } var initCompRuntime = typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime"); if (initCompRuntime == null) { - LogLine(2, "no __InitComponentRuntime found... duplicating."); + Logger.LogLine(2, "no __InitComponentRuntime found... duplicating."); initCompRuntime = DuplicateMethodDef(typeDef, initComp, "__InitComponentRuntime"); } - // 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::__InitComponentRuntime() - // IL_001e: ret +// 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::__InitComponentRuntime() +// 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) - LogString(2, " Replacing body of {0}.{0} (bool {1}) ... ", typeDef.Name, altCtor.Parameters[0].Name); + Logger.LogString(2, " Replacing body of {0}.{0} (bool {1}) ... ", typeDef.Name, altCtor.Parameters[0].Name); else { - LogString(2, " Adding {0}.{0} (bool useCompiledXaml) ... ", typeDef.Name); + Logger.LogString(2, " Adding {0}.{0} (bool useCompiledXaml) ... ", typeDef.Name); altCtor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, module.TypeSystem.Void); @@ -127,17 +127,17 @@ namespace Xamarin.Forms.Build.Tasks altCtor.Body = body; if (!typeDef.Methods.Contains(altCtor)) typeDef.Methods.Add(altCtor); - LogLine(2, "done."); + Logger.LogLine(2, "done."); } - LogLine(2, ""); + Logger.LogLine(2, ""); } - LogString(1, "Writing the assembly... "); + Logger.LogString(1, "Writing the assembly... "); assemblyDefinition.Write(Assembly, new WriterParameters { WriteSymbols = DebugSymbols }); - LogLine(1, "done."); + Logger.LogLine(1, "done."); return true; } diff --git a/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs index 26e241ab..e647f6c2 100644 --- a/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs @@ -13,7 +13,8 @@ namespace Xamarin.Forms.Build.Tasks XmlName.xKey, XmlName.xTypeArguments, XmlName.xFactoryMethod, - XmlName.xName + XmlName.xName, + XmlName.xDataType }; public ExpandMarkupsVisitor(ILContext context) @@ -165,7 +166,7 @@ namespace Xamarin.Forms.Build.Tasks try { type = new XmlType(namespaceuri, name + "Extension", null); - type.GetTypeReference(contextProvider.Context.Body.Method.Module, null); + type.GetTypeReference(contextProvider.Context.Module, null); } catch (XamlParseException) { diff --git a/Xamarin.Forms.Build.Tasks/ILContext.cs b/Xamarin.Forms.Build.Tasks/ILContext.cs index 14ade391..8f36227a 100644 --- a/Xamarin.Forms.Build.Tasks/ILContext.cs +++ b/Xamarin.Forms.Build.Tasks/ILContext.cs @@ -7,7 +7,7 @@ namespace Xamarin.Forms.Build.Tasks { class ILContext { - public ILContext(ILProcessor il, MethodBody body, FieldDefinition parentContextValues = null) + public ILContext(ILProcessor il, MethodBody body, ModuleDefinition module, FieldDefinition parentContextValues = null) { IL = il; Body = body; @@ -16,6 +16,7 @@ namespace Xamarin.Forms.Build.Tasks Scopes = new Dictionary(); TypeExtensions = new Dictionary(); ParentContextValues = parentContextValues; + Module = module; } public Dictionary Values { get; private set; } @@ -33,5 +34,7 @@ namespace Xamarin.Forms.Build.Tasks public ILProcessor IL { get; private set; } public MethodBody Body { get; private set; } + + public ModuleDefinition Module { get; private set; } } } \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/Logger.cs b/Xamarin.Forms.Build.Tasks/Logger.cs new file mode 100644 index 00000000..b706e610 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/Logger.cs @@ -0,0 +1,76 @@ +using System; +using System.Xml; +using Microsoft.Build.Utilities; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + public class Logger { + public TaskLoggingHelper Helper { get; } + public int Verbosity { get; } + + public Logger(TaskLoggingHelper helper, int verbosity) + { + Verbosity = verbosity; + Helper = helper; + } + + string buffer = ""; + + public void LogException(string subcategory, string errorCode, string helpKeyword, string file, Exception e) + { + var xpe = e as XamlParseException; + var xe = e as XmlException; + if (xpe != null) + LogError(subcategory, errorCode, helpKeyword, file, xpe.XmlInfo.LineNumber, xpe.XmlInfo.LinePosition, 0, 0, xpe.Message, xpe.HelpLink, xpe.Source); + else if (xe != null) + LogError(subcategory, errorCode, helpKeyword, file, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source); + else + LogError(subcategory, errorCode, helpKeyword, file, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); + } + + public 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 (Helper != null) { + Helper.LogError(subcategory, errorCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, + endColumnNumber, message, messageArgs); + } else + Console.Error.WriteLine($"{file} ({lineNumber}:{columnNumber}) : {message}"); + } + + public void LogLine(int level, string format, params object [] arg) + { + if (!string.IsNullOrEmpty(buffer)) { + format = buffer + format; + buffer = ""; + } + + if (level < 0) { + if (Helper != null) + Helper.LogError(format, arg); + else + Console.Error.WriteLine(format, arg); + } else if (level <= Verbosity) { + if (Helper != null) + Helper.LogMessage(format, arg); + else + Console.WriteLine(format, arg); + } + } + + public void LogString(int level, string format, params object [] arg) + { + if (level <= 0) + Console.Error.Write(format, arg); + else if (level <= Verbosity) { + if (Helper != null) + buffer += String.Format(format, arg); + else + Console.Write(format, arg); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs index 3baa230c..13b4aafc 100644 --- a/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/MethodReferenceExtensions.cs @@ -40,5 +40,23 @@ namespace Xamarin.Forms.Build.Tasks self.Parameters[i].ParameterType = module.Import(self.Parameters[i].ParameterType); } } + + public static MethodReference MakeGeneric(this MethodReference self, TypeReference declaringType, params TypeReference [] arguments) + { + var reference = new MethodReference(self.Name, self.ReturnType) { + DeclaringType = declaringType, + 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; + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs index f481cbee..8b4bf7ad 100644 --- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs @@ -16,6 +16,7 @@ namespace Xamarin.Forms.Build.Tasks class SetPropertiesVisitor : IXamlNodeVisitor { static int dtcount; + static int typedBindingCount; static readonly IList skips = new List { @@ -23,7 +24,8 @@ namespace Xamarin.Forms.Build.Tasks XmlName.xTypeArguments, XmlName.xArguments, XmlName.xFactoryMethod, - XmlName.xName + XmlName.xName, + XmlName.xDataType }; public SetPropertiesVisitor(ILContext context, bool stopOnResourceDictionary = false) @@ -271,6 +273,10 @@ namespace Xamarin.Forms.Build.Tasks else if (vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1", out markupExtension, out genericArguments)) { + if (vardefref.VariableDefinition.VariableType.FullName == "Xamarin.Forms.Xaml.BindingExtension") + foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition)) + yield return instruction; + var markExt = markupExtension.Resolve(); var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); var provideValue = module.Import(provideValueInfo); @@ -312,6 +318,359 @@ namespace Xamarin.Forms.Build.Tasks } } + //Once we get compiled IValueProvider, this will move to the BindingExpression + static IEnumerable CompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt) + { + //TODO implement handlers[] + //TODO support casting operators + + INode pathNode; + if (!node.Properties.TryGetValue(new XmlName("", "Path"), out pathNode) && node.CollectionItems.Any()) + pathNode = node.CollectionItems [0]; + var path = (pathNode as ValueNode)?.Value as string; + + INode dataTypeNode = null; + IElementNode n = node; + while (n != null) { + if (n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode)) + break; + n = n.Parent as IElementNode; + } + var dataType = (dataTypeNode as ValueNode)?.Value as string; + if (dataType == null) + yield break; //throw + + var namespaceuri = dataType.Contains(":") ? node.NamespaceResolver.LookupNamespace(dataType.Split(':') [0].Trim()) : ""; + var dtXType = new XmlType(namespaceuri, dataType, null); + + var tSourceRef = dtXType.GetTypeReference(context.Module, (IXmlLineInfo)node); + if (tSourceRef == null) + yield break; //throw + + var properties = ParsePath(path, tSourceRef, node as IXmlLineInfo, context.Module); + var tPropertyRef = properties != null && properties.Any() ? properties.Last().Item1.PropertyType : tSourceRef; + + var funcRef = context.Module.Import(context.Module.Import(typeof(Func<,>)).MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef })); + var actionRef = context.Module.Import(context.Module.Import(typeof(Action<,>)).MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef })); + var funcObjRef = context.Module.Import(context.Module.Import(typeof(Func<,>)).MakeGenericInstanceType(new [] { tSourceRef, context.Module.TypeSystem.Object })); + var tupleRef = context.Module.Import(context.Module.Import(typeof(Tuple<,>)).MakeGenericInstanceType(new [] { funcObjRef, context.Module.TypeSystem.String})); + var typedBindingRef = context.Module.Import(context.Module.Import(typeof(TypedBinding<,>)).MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef})); + + TypeReference _; + var ctorInfo = context.Module.Import(typedBindingRef.Resolve().Methods.FirstOrDefault(md => md.IsConstructor && !md.IsStatic && md.Parameters.Count == 3 )); + var ctorinforef = ctorInfo.MakeGeneric(typedBindingRef, funcRef, actionRef, tupleRef); + var setTypedBinding = context.Module.Import(typeof(BindingExtension)).GetProperty(pd => pd.Name == "TypedBinding", out _).SetMethod; + + yield return Instruction.Create(OpCodes.Ldloc, bindingExt); + foreach (var instruction in CompiledBindingGetGetter(tSourceRef, tPropertyRef, properties, node, context)) + yield return instruction; + foreach (var instruction in CompiledBindingGetSetter(tSourceRef, tPropertyRef, properties, node, context)) + yield return instruction; + foreach (var instruction in CompiledBindingGetHandlers(tSourceRef, tPropertyRef, properties, node, context)) + yield return instruction; + yield return Instruction.Create(OpCodes.Newobj, context.Module.Import(ctorinforef)); + yield return Instruction.Create(OpCodes.Callvirt, context.Module.Import(setTypedBinding)); + } + + static IList> ParsePath(string path, TypeReference tSourceRef, IXmlLineInfo lineInfo, ModuleDefinition module) + { + if (string.IsNullOrWhiteSpace(path)) + return null; + path = path.Trim(' ', '.'); //trim leading or trailing dots + var parts = path.Split(new [] { '.' }, StringSplitOptions.RemoveEmptyEntries); + var properties = new List>(); + + var previousPartTypeRef = tSourceRef; + TypeReference _; + foreach (var part in parts) { + var p = part; + string indexArg = null; + var lbIndex = p.IndexOf('['); + if (lbIndex != -1) { + var rbIndex = p.LastIndexOf(']'); + if (rbIndex == -1) + throw new XamlParseException("Binding: Indexer did not contain closing bracket", lineInfo); + + var argLength = rbIndex - lbIndex - 1; + if (argLength == 0) + throw new XamlParseException("Binding: Indexer did not contain arguments", lineInfo); + + indexArg = p.Substring(lbIndex + 1, argLength).Trim(); + if (indexArg.Length == 0) + throw new XamlParseException("Binding: Indexer did not contain arguments", lineInfo); + + p = p.Substring(0, lbIndex); + p = p.Trim(); + } + + if (p.Length > 0) { + var property = previousPartTypeRef.GetProperty(pd => pd.Name == p && pd.GetMethod != null && pd.GetMethod.IsPublic, out _); + properties.Add(new Tuple(property,null)); + previousPartTypeRef = property.PropertyType; + } + if (indexArg != null) { + var defaultMemberAttribute = previousPartTypeRef.GetCustomAttribute(module.Import(typeof(System.Reflection.DefaultMemberAttribute))); + var indexerName = defaultMemberAttribute?.ConstructorArguments?.FirstOrDefault().Value as string ?? "Item"; + var indexer = previousPartTypeRef.GetProperty(pd => pd.Name == indexerName && pd.GetMethod != null && pd.GetMethod.IsPublic, out _); + properties.Add(new Tuple(indexer, indexArg)); + if (indexer.PropertyType != module.TypeSystem.String && indexer.PropertyType != module.TypeSystem.Int32) + throw new XamlParseException($"Binding: Unsupported indexer index type: {indexer.PropertyType.FullName}", lineInfo); + previousPartTypeRef = indexer.PropertyType; + } + } + return properties; + } + + static IEnumerable CompiledBindingGetGetter(TypeReference tSourceRef, TypeReference tPropertyRef, IList> properties, ElementNode node, ILContext context) + { +// .method private static hidebysig default string '
m__0' (class ViewModel vm) cil managed +// { +// .custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // ... +// +// IL_0000: ldarg.0 +// IL_0001: callvirt instance class ViewModel class ViewModel::get_Model() +// IL_0006: callvirt instance string class ViewModel::get_Text() +// IL_0006: ret +// } + + var module = context.Module; + var compilerGeneratedCtor = module.Import(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)).GetMethods(md => md.IsConstructor, module).First().Item1; + var getter = new MethodDefinition($"<{context.Body.Method.Name}>typedBindingsM__{typedBindingCount++}", + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + tPropertyRef) { + Parameters = { + new ParameterDefinition(tSourceRef) + }, + CustomAttributes = { + new CustomAttribute (context.Module.Import(compilerGeneratedCtor)) + } + }; + var il = getter.Body.GetILProcessor(); + + il.Emit(OpCodes.Ldarg_0); + if (properties != null && properties.Count != 0) { + foreach (var propTuple in properties) { + var property = propTuple.Item1; + var indexerArg = propTuple.Item2; + if (indexerArg != null) { + if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String) + il.Emit(OpCodes.Ldstr, indexerArg); + else if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) { + int index; + if (!int.TryParse(indexerArg, out index)) + throw new XamlParseException($"Binding: {indexerArg} could not be parsed as an index for a {property.Name}", node as IXmlLineInfo); + il.Emit(OpCodes.Ldc_I4, index); + } + } + il.Emit(OpCodes.Callvirt, module.Import(property.GetMethod)); + } + } + + il.Emit(OpCodes.Ret); + + context.Body.Method.DeclaringType.Methods.Add(getter); + + var funcRef = module.Import(typeof(Func<,>)); + funcRef = module.Import(funcRef.MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef })); + var funcCtor = module.Import(funcRef.Resolve().GetConstructors().First()); + funcCtor = funcCtor.MakeGeneric(funcRef, new [] { tSourceRef, tPropertyRef }); + +// IL_0007: ldnull +// IL_0008: ldftn string class Test::'
m__0'(class ViewModel) +// IL_000e: newobj instance void class [mscorlib]System.Func`2::'.ctor'(object, native int) + + yield return Instruction.Create(OpCodes.Ldnull); + yield return Instruction.Create(OpCodes.Ldftn, getter); + yield return Instruction.Create(OpCodes.Newobj, module.Import(funcCtor)); + } + + static IEnumerable CompiledBindingGetSetter(TypeReference tSourceRef, TypeReference tPropertyRef, IList> properties, ElementNode node, ILContext context) + { + if (properties == null || properties.Count == 0) { + yield return Instruction.Create(OpCodes.Ldnull); + yield break; + } + + // .method private static hidebysig default void '
m__1' (class ViewModel vm, string s) cil managed + // { + // .custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // .... + // + // IL_0000: ldarg.0 + // IL_0001: callvirt instance class ViewModel class ViewModel::get_Model() + // IL_0006: ldarg.1 + // IL_0007: callvirt instance void class ViewModel::set_Text(string) + // IL_000c: ret + // } + + var module = context.Module; + var compilerGeneratedCtor = module.Import(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)).GetMethods(md => md.IsConstructor, module).First().Item1; + var setter = new MethodDefinition($"<{context.Body.Method.Name}>typedBindingsM__{typedBindingCount++}", + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + module.TypeSystem.Void) { + Parameters = { + new ParameterDefinition(tSourceRef), + new ParameterDefinition(tPropertyRef) + }, + CustomAttributes = { + new CustomAttribute (module.Import(compilerGeneratedCtor)) + } + }; + + var il = setter.Body.GetILProcessor(); + var lastProperty = properties.LastOrDefault(); + var setterRef = lastProperty?.Item1.SetMethod; + if (setterRef == null) { + yield return Instruction.Create(OpCodes.Ldnull); //throw or not ? + yield break; + } + + il.Emit(OpCodes.Ldarg_0); + for (int i = 0; i < properties.Count - 1; i++) { + var property = properties[i].Item1; + var indexerArg = properties[i].Item2; + if (indexerArg != null) { + if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String) + il.Emit(OpCodes.Ldstr, indexerArg); + else if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) { + int index; + if (!int.TryParse(indexerArg, out index)) + throw new XamlParseException($"Binding: {indexerArg} could not be parsed as an index for a {property.Name}", node as IXmlLineInfo); + il.Emit(OpCodes.Ldc_I4, index); + } + } + il.Emit(OpCodes.Callvirt, module.Import(property.GetMethod)); + } + + var indexer = properties.Last().Item2; + if (indexer != null) { + if (lastProperty.Item1.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String) + il.Emit(OpCodes.Ldstr, indexer); + else if (lastProperty.Item1.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) { + int index; + if (!int.TryParse(indexer, out index)) + throw new XamlParseException($"Binding: {indexer} could not be parsed as an index for a {lastProperty.Item1.Name}", node as IXmlLineInfo); + il.Emit(OpCodes.Ldc_I4, index); + } + } + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Callvirt, module.Import(setterRef)); + il.Emit(OpCodes.Ret); + + context.Body.Method.DeclaringType.Methods.Add(setter); + + var actionRef = module.Import(typeof(Action<,>)); + actionRef = module.Import(actionRef.MakeGenericInstanceType(new [] { tSourceRef, tPropertyRef })); + var actionCtor = module.Import(actionRef.Resolve().GetConstructors().First()); + actionCtor = actionCtor.MakeGeneric(actionRef, new [] { tSourceRef, tPropertyRef }); + +// IL_0024: ldnull +// IL_0025: ldftn void class Test::'
m__1'(class ViewModel, string) +// IL_002b: newobj instance void class [mscorlib]System.Action`2::'.ctor'(object, native int) + yield return Instruction.Create(OpCodes.Ldnull); + yield return Instruction.Create(OpCodes.Ldftn, setter); + yield return Instruction.Create(OpCodes.Newobj, module.Import(actionCtor)); + } + + static IEnumerable CompiledBindingGetHandlers(TypeReference tSourceRef, TypeReference tPropertyRef, IList> properties, ElementNode node, ILContext context) + { +// .method private static hidebysig default object '
m__2'(class ViewModel vm) cil managed { +// .custom instance void class [mscorlib] System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // .... +// IL_0000: ldarg.0 +// IL_0001: ret +// } // end of method Test::
m__2 + +// .method private static hidebysig default object '
m__3' (class ViewModel vm) cil managed { +// .custom instance void class [mscorlib] System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() = (01 00 00 00 ) // .... +// IL_0000: ldarg.0 +// IL_0001: callvirt instance class ViewModel class ViewModel::get_Model() +// IL_0006: ret +// } + + var module = context.Module; + var compilerGeneratedCtor = module.Import(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)).GetMethods(md => md.IsConstructor, module).First().Item1; + + var partGetters = new List(); + if (properties == null || properties.Count == 0) { + yield return Instruction.Create(OpCodes.Ldnull); + yield break; + } + + for (int i = 0; i < properties.Count; i++) { + var tuple = properties [i]; + var partGetter = new MethodDefinition($"<{context.Body.Method.Name}>typedBindingsM__{typedBindingCount++}", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, tPropertyRef) { + Parameters = { + new ParameterDefinition(tSourceRef) + }, + CustomAttributes = { + new CustomAttribute (context.Module.Import(compilerGeneratedCtor)) + } + }; + var il = partGetter.Body.GetILProcessor(); + il.Emit(OpCodes.Ldarg_0); + for (int j = 0; j < i; j++) { + var propTuple = properties [j]; + var property = propTuple.Item1; + var indexerArg = propTuple.Item2; + if (indexerArg != null) { + if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.String) + il.Emit(OpCodes.Ldstr, indexerArg); + else if (property.GetMethod.Parameters [0].ParameterType == module.TypeSystem.Int32) { + int index; + if (!int.TryParse(indexerArg, out index)) + throw new XamlParseException($"Binding: {indexerArg} could not be parsed as an index for a {property.Name}", node as IXmlLineInfo); + il.Emit(OpCodes.Ldc_I4, index); + } + } + il.Emit(OpCodes.Callvirt, module.Import(property.GetMethod)); + } + il.Emit(OpCodes.Ret); + context.Body.Method.DeclaringType.Methods.Add(partGetter); + partGetters.Add(partGetter); + } + + var funcObjRef = context.Module.Import(module.Import(typeof(Func<,>)).MakeGenericInstanceType(new [] { tSourceRef, module.TypeSystem.Object })); + var tupleRef = context.Module.Import(module.Import(typeof(Tuple<,>)).MakeGenericInstanceType(new [] { funcObjRef, module.TypeSystem.String })); + var funcCtor = module.Import(funcObjRef.Resolve().GetConstructors().First()); + funcCtor = funcCtor.MakeGeneric(funcObjRef, new [] { tSourceRef, module.TypeSystem.Object }); + var tupleCtor = module.Import(tupleRef.Resolve().GetConstructors().First()); + tupleCtor = tupleCtor.MakeGeneric(tupleRef, new [] { funcObjRef, module.TypeSystem.String}); + +// IL_003a: ldc.i4.2 +// IL_003b: newarr class [mscorlib] System.Tuple`2,string> + +// IL_0040: dup +// IL_0041: ldc.i4.0 +// IL_0049: ldnull +// IL_004a: ldftn object class Test::'
m__2'(class ViewModel) +// IL_0050: newobj instance void class [mscorlib]System.Func`2::'.ctor'(object, native int) +// IL_005f: ldstr "Model" +// IL_0064: newobj instance void class [mscorlib]System.Tuple`2, string>::'.ctor'(!0, !1) +// IL_0069: stelem.ref + +// IL_006a: dup +// IL_006b: ldc.i4.1 +// IL_0073: ldnull +// IL_0074: ldftn object class Test::'
m__3'(class ViewModel) +// IL_007a: newobj instance void class [mscorlib]System.Func`2::'.ctor'(object, native int) +// IL_0089: ldstr "Text" +// IL_008e: newobj instance void class [mscorlib]System.Tuple`2, string>::'.ctor'(!0, !1) +// IL_0093: stelem.ref + + yield return Instruction.Create(OpCodes.Ldc_I4, properties.Count); + yield return Instruction.Create(OpCodes.Newarr, tupleRef); + + for (var i = 0; i < properties.Count; i++) { + yield return Instruction.Create(OpCodes.Dup); + yield return Instruction.Create(OpCodes.Ldc_I4, i); + yield return Instruction.Create(OpCodes.Ldnull); + yield return Instruction.Create(OpCodes.Ldftn, partGetters [i]); + yield return Instruction.Create(OpCodes.Newobj, module.Import(funcCtor)); + yield return Instruction.Create(OpCodes.Ldstr, properties [i].Item1.Name); + yield return Instruction.Create(OpCodes.Newobj, module.Import(tupleCtor)); + yield return Instruction.Create(OpCodes.Stelem_Ref); + } + } + public static IEnumerable SetPropertyValue(VariableDefinition parent, XmlName propertyName, INode valueNode, ILContext context, IXmlLineInfo iXmlLineInfo) { var module = context.Body.Method.Module; @@ -685,7 +1044,9 @@ namespace Xamarin.Forms.Build.Tasks // .class nested private auto ansi sealed beforefieldinit '
c__AnonStorey0' // extends [mscorlib]System.Object - var module = parentContext.Body.Method.Module; + + var module = parentContext.Module; + var compilerGeneratedCtor = module.Import(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)).GetMethods(md => md.IsConstructor, module).First().Item1; var anonType = new TypeDefinition( null, "<" + parentContext.Body.Method.Name + ">_anonXamlCDataTemplate_" + dtcount++, @@ -693,7 +1054,10 @@ namespace Xamarin.Forms.Build.Tasks TypeAttributes.Sealed | TypeAttributes.NestedPrivate) { - BaseType = module.TypeSystem.Object + BaseType = module.TypeSystem.Object, + CustomAttributes = { + new CustomAttribute (module.Import(compilerGeneratedCtor)) + } }; parentContext.Body.Method.DeclaringType.NestedTypes.Add(anonType); @@ -721,7 +1085,7 @@ namespace Xamarin.Forms.Build.Tasks //Fill the loadTemplate Body var templateIl = loadTemplate.Body.GetILProcessor(); templateIl.Emit(OpCodes.Nop); - var templateContext = new ILContext(templateIl, loadTemplate.Body, parentValues) + var templateContext = new ILContext(templateIl, loadTemplate.Body, module, parentValues) { Root = root }; @@ -730,6 +1094,7 @@ namespace Xamarin.Forms.Build.Tasks 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); diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj index b7250038..6c820c67 100644 --- a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj +++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj @@ -98,6 +98,8 @@ + + diff --git a/Xamarin.Forms.Build.Tasks/XamlCTask.cs b/Xamarin.Forms.Build.Tasks/XamlCTask.cs index 206fc452..689a6a59 100644 --- a/Xamarin.Forms.Build.Tasks/XamlCTask.cs +++ b/Xamarin.Forms.Build.Tasks/XamlCTask.cs @@ -1,13 +1,9 @@ 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; @@ -15,134 +11,31 @@ using Xamarin.Forms.Xaml; namespace Xamarin.Forms.Build.Tasks { - public class XamlCTask : AppDomainIsolatedTask + public class XamlCTask : XamlTask { - 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; } - internal string Type { get; set; } - public override bool Execute() - { - InMsBuild = true; - return Compile(); - } - - protected void LogException(string subcategory, string errorCode, string helpKeyword, string file, Exception e) - { - var xpe = e as XamlParseException; - var xe = e as XmlException; - if (xpe != null) - LogError(subcategory, errorCode, helpKeyword, file, xpe.XmlInfo.LineNumber, xpe.XmlInfo.LinePosition, 0, 0, xpe.Message, xpe.HelpLink, xpe.Source); - else if (xe != null) - LogError(subcategory, errorCode, helpKeyword, file, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source); - else - LogError(subcategory, errorCode, helpKeyword, file, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); - } - - 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 LogString(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) + public override bool Execute(IList thrownExceptions) { - 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(IList thrownExceptions = null) - { - LogLine(1, "Compiling Xaml"); - LogLine(1, "\nAssembly: {0}", Assembly); + Logger = Logger ?? new Logger(null, Verbosity); + Logger.LogLine(1, "Compiling Xaml"); + Logger.LogLine(1, "\nAssembly: {0}", Assembly); if (!string.IsNullOrEmpty(DependencyPaths)) - LogLine(1, "DependencyPaths: \t{0}", DependencyPaths); + Logger.LogLine(1, "DependencyPaths: \t{0}", DependencyPaths); if (!string.IsNullOrEmpty(ReferencePath)) - LogLine(1, "ReferencePath: \t{0}", ReferencePath.Replace("//", "/")); - LogLine(3, "DebugSymbols:\"{0}\"", DebugSymbols); + Logger.LogLine(1, "ReferencePath: \t{0}", ReferencePath.Replace("//", "/")); + Logger.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."); + Logger.LogLine(1, "Assembly file not found. Skipping XamlC."); return true; } @@ -151,7 +44,7 @@ namespace Xamarin.Forms.Build.Tasks { foreach (var dep in DependencyPaths.Split(';')) { - LogLine(3, "Adding searchpath {0}", dep); + Logger.LogLine(3, "Adding searchpath {0}", dep); resolver.AddSearchDirectory(dep); } } @@ -162,7 +55,7 @@ namespace Xamarin.Forms.Build.Tasks foreach (var p in paths) { var searchpath = Path.GetDirectoryName(p); - LogLine(3, "Adding searchpath {0}", searchpath); + Logger.LogLine(3, "Adding searchpath {0}", searchpath); resolver.AddSearchDirectory(searchpath); } } @@ -201,21 +94,21 @@ namespace Xamarin.Forms.Build.Tasks skipmodule = false; } - LogLine(2, " Module: {0}", module.Name); + Logger.LogLine(2, " Module: {0}", module.Name); var resourcesToPrune = new List(); foreach (var resource in module.Resources.OfType()) { - LogString(2, " Resource: {0}... ", resource.Name); + Logger.LogString(2, " Resource: {0}... ", resource.Name); string classname; if (!resource.IsXaml(out classname)) { - LogLine(2, "skipped."); + Logger.LogLine(2, "skipped."); continue; } TypeDefinition typeDef = module.GetType(classname); if (typeDef == null) { - LogLine(2, "no type found... skipped."); + Logger.LogLine(2, "no type found... skipped."); continue; } var skiptype = skipmodule; @@ -236,61 +129,61 @@ namespace Xamarin.Forms.Build.Tasks if (skiptype) { - LogLine(2, "Has XamlCompilationAttribute set to Skip and not Compile... skipped"); + Logger.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."); + Logger.LogLine(2, "no InitializeComponent found... skipped."); continue; } - LogLine(2, ""); + Logger.LogLine(2, ""); var initCompRuntime = typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime"); if (initCompRuntime != null) - LogLine(2, " __InitComponentRuntime already exists... not duplicating"); + Logger.LogLine(2, " __InitComponentRuntime already exists... not duplicating"); else { - LogString(2, " Duplicating {0}.InitializeComponent () into {0}.__InitComponentRuntime ... ", typeDef.Name); + Logger.LogString(2, " Duplicating {0}.InitializeComponent () into {0}.__InitComponentRuntime ... ", typeDef.Name); initCompRuntime = DuplicateMethodDef(typeDef, initComp, "__InitComponentRuntime"); - LogLine(2, "done."); + Logger.LogLine(2, "done."); } - LogString(2, " Parsing Xaml... "); + Logger.LogString(2, " Parsing Xaml... "); var rootnode = ParseXaml(resource.GetResourceStream(), typeDef); if (rootnode == null) { - LogLine(2, "failed."); + Logger.LogLine(2, "failed."); continue; } - LogLine(2, "done."); + Logger.LogLine(2, "done."); hasCompiledXamlResources = true; - LogString(2, " Replacing {0}.InitializeComponent ()... ", typeDef.Name); + Logger.LogString(2, " Replacing {0}.InitializeComponent ()... ", typeDef.Name); Exception e; if (!TryCoreCompile(initComp, initCompRuntime, rootnode, out e)) { success = false; - LogLine(2, "failed."); + Logger.LogLine(2, "failed."); thrownExceptions?.Add(e); - LogException(null, null, null, resource.Name, e); - LogLine(4, e.StackTrace); + Logger.LogException(null, null, null, resource.Name, e); + Logger.LogLine(4, e.StackTrace); continue; } - LogLine(2, "done."); + Logger.LogLine(2, "done."); if (OptimizeIL) { - LogString(2, " Optimizing IL... "); + Logger.LogString(2, " Optimizing IL... "); initComp.Body.OptimizeMacros(); - LogLine(2, "done"); + Logger.LogLine(2, "done"); } if (OutputGeneratedILAsCode) { var filepath = Path.Combine(Path.GetDirectoryName(Assembly), typeDef.FullName + ".decompiled.cs"); - LogString(2, " Decompiling {0} into {1}...", typeDef.FullName, filepath); + Logger.LogString(2, " Decompiling {0} into {1}...", typeDef.FullName, filepath); var decompilerContext = new DecompilerContext(module); using (var writer = new StreamWriter(filepath)) { @@ -301,46 +194,46 @@ namespace Xamarin.Forms.Build.Tasks codeDomBuilder.GenerateCode(output); } - LogLine(2, "done"); + Logger.LogLine(2, "done"); } resourcesToPrune.Add(resource); } if (!KeepXamlResources) { if (resourcesToPrune.Any()) - LogLine(2, " Removing compiled xaml resources"); + Logger.LogLine(2, " Removing compiled xaml resources"); foreach (var resource in resourcesToPrune) { - LogString(2, " Removing {0}... ", resource.Name); + Logger.LogString(2, " Removing {0}... ", resource.Name); module.Resources.Remove(resource); - LogLine(2, "done"); + Logger.LogLine(2, "done"); } } - LogLine(2, ""); + Logger.LogLine(2, ""); } if (!hasCompiledXamlResources) { - LogLine(1, "No compiled resources. Skipping writing assembly."); + Logger.LogLine(1, "No compiled resources. Skipping writing assembly."); return success; } - LogString(1, "Writing the assembly... "); + Logger.LogString(1, "Writing the assembly... "); try { assemblyDefinition.Write(Assembly, new WriterParameters { WriteSymbols = DebugSymbols }); - LogLine(1, "done."); + Logger.LogLine(1, "done."); } catch (Exception e) { - LogLine(1, "failed."); - LogException(null, null, null, null, e); + Logger.LogLine(1, "failed."); + Logger.LogException(null, null, null, null, e); thrownExceptions?.Add(e); - LogLine(4, e.StackTrace); + Logger.LogLine(4, e.StackTrace); success = false; } @@ -393,7 +286,7 @@ namespace Xamarin.Forms.Build.Tasks il.Append(nop); } - var visitorContext = new ILContext(il, body); + var visitorContext = new ILContext(il, body, body.Method.Module); rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null); rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null); @@ -413,72 +306,5 @@ namespace Xamarin.Forms.Build.Tasks return false; } } - - protected static MethodDefinition DuplicateMethodDef(TypeDefinition typeDef, MethodDefinition methodDef, string newName) - { - var dup = new MethodDefinition(newName, methodDef.Attributes, methodDef.ReturnType); - dup.Body = methodDef.Body; - typeDef.Methods.Add(dup); - return dup; - } - - 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 as IXmlNamespaceResolver), 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/XamlTask.cs b/Xamarin.Forms.Build.Tasks/XamlTask.cs new file mode 100644 index 00000000..d4864ac3 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/XamlTask.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Xml; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +using Mono.Cecil; + +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Build.Tasks +{ + public abstract class XamlTask : AppDomainIsolatedTask + { + [Required] + public string Assembly { get; set; } + public string DependencyPaths { get; set; } + public string ReferencePath { get; set; } + public int Verbosity { get; set; } + public bool DebugSymbols { get; set; } + + internal XamlTask() + { + } + + protected Logger Logger { get; set; } + + public override bool Execute() + { + Logger = new Logger(Log, Verbosity); + return Execute(null); + } + + public abstract bool Execute(IList thrownExceptions); + + protected static MethodDefinition DuplicateMethodDef(TypeDefinition typeDef, MethodDefinition methodDef, string newName) + { + var dup = new MethodDefinition(newName, methodDef.Attributes, methodDef.ReturnType); + dup.Body = methodDef.Body; + typeDef.Methods.Add(dup); + return dup; + } + + internal 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 as IXmlNamespaceResolver), 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) + 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) + return false; + classname = rootClass.Value; + return true; + } + } + } +} \ No newline at end of file -- cgit v1.2.3