Browse Source

Changes after the reivew

pull/9413/head
Max Katz 4 years ago
parent
commit
cd83f8558f
  1. 194
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  2. 25
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs
  3. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

194
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -469,88 +469,9 @@ namespace Avalonia.Build.Tasks
foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
{
foreach (var instruction in populateMethod.Body.Instructions.ToArray())
if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod))
{
const string resolveStyleIncludeName = "ResolveStyleInclude";
const string resolveResourceInclude = "ResolveResourceInclude";
if (instruction.OpCode == OpCodes.Call
&& instruction.Operand is MethodReference
{
Name: resolveStyleIncludeName or resolveResourceInclude,
DeclaringType: { Name: "XamlIlRuntimeHelpers" }
})
{
int lineNumber = 0, linePosition = 0;
bool instructionsModified = false;
try
{
var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
lineNumber = Convert.ToInt32(instruction.Previous.Previous.Operand);
linePosition = Convert.ToInt32(instruction.Previous.Operand);
var index = populateMethod.Body.Instructions.IndexOf(instruction);
assetSource = assetSource.Replace("avares://", "");
var assemblyNameSeparator = assetSource.IndexOf('/');
var fileNameSeparator = assetSource.LastIndexOf('/');
if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
{
throw new InvalidProgramException(
$"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
}
var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
?? throw new InvalidProgramException($"Unable to resolve assembly \"{assetAssemblyName}\"");
var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
if (assetAssembly.FindType(fileName) is { } type
&& type.FindConstructor() is { } ctor)
{
var ctorMethod =
asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
}
else
{
var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
?? throw new InvalidOperationException($"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
var buildMethod = resources.FindMethod(m => m.Name == relativeName)
?? throw new InvalidOperationException($"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
}
}
catch (Exception e)
{
if (instructionsModified)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
return false;
}
else
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
}
}
}
return false;
}
}
@ -630,5 +551,116 @@ namespace Avalonia.Build.Tasks
return true;
}
private static bool TransformXamlIncludes(
IBuildEngine engine, CecilTypeSystem typeSystem,
MethodDefinition populateMethod, string resourceFilePath,
MethodReference createRootServiceProviderMethod)
{
var asm = typeSystem.TargetAssemblyDefinition;
foreach (var instruction in populateMethod.Body.Instructions.ToArray())
{
const string resolveStyleIncludeName = "ResolveStyleInclude";
const string resolveResourceInclude = "ResolveResourceInclude";
if (instruction.OpCode == OpCodes.Call
&& instruction.Operand is MethodReference
{
Name: resolveStyleIncludeName or resolveResourceInclude,
DeclaringType: { Name: "XamlIlRuntimeHelpers" }
})
{
int lineNumber = 0, linePosition = 0;
bool instructionsModified = false;
try
{
var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
lineNumber = GetConstValue(instruction.Previous.Previous);
linePosition = GetConstValue(instruction.Previous);
var index = populateMethod.Body.Instructions.IndexOf(instruction);
assetSource = assetSource.Replace("avares://", "");
var assemblyNameSeparator = assetSource.IndexOf('/');
var fileNameSeparator = assetSource.LastIndexOf('/');
if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
{
throw new InvalidProgramException(
$"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
}
var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
?? throw new InvalidProgramException(
$"Unable to resolve assembly \"{assetAssemblyName}\"");
var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
if (assetAssembly.FindType(fileName) is { } type
&& type.FindConstructor() is { } ctor)
{
var ctorMethod =
asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
}
else
{
var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
?? throw new InvalidOperationException(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
var buildMethod = resources.FindMethod(m => m.Name == relativeName)
?? throw new InvalidOperationException(
$"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] =
Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
}
}
catch (Exception e)
{
if (instructionsModified)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
return false;
}
else
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
}
}
static int GetConstValue(Instruction instruction)
{
if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 })
{
return instruction.OpCode.Code - Code.Ldc_I4_0;
}
if (instruction.Operand is not null)
{
return Convert.ToInt32(instruction.Operand);
}
return 0;
}
}
}
return true;
}
}
}

25
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@ -10,28 +10,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
{
private const string StyleIncludeName = "StyleInclude";
private const string ResourceIncludeName = "ResourceInclude";
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is not XamlAstObjectNode objectNode
|| objectNode.Type.GetClrType() is not {Name: StyleIncludeName or ResourceIncludeName} objectNodeType)
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
return node;
}
var nodeTypeName = objectNode.Type.GetClrType().Name;
var sourceProperty = objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source");
var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList();
if (sourceProperty is null
|| objectNode.Children.Count != (directives.Count + 1))
{
// Don't transform node with any other property, as we don't know how to transform them.
return node;
throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node);
}
if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode)
{
// TODO: make it a compiler warning
// Source value can be set with markup extension instead of a text node, we don't support it here yet.
return node;
}
@ -39,18 +39,19 @@ internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
var originalAssetPath = sourceTextNode.Text;
if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/")))
{
// Only "avares" protocol supported or relative paths.
return node;
throw new XamlParseException(
$"{nodeTypeName}.Source supports only \"avares://\" absolute paths or relative paths starting with \"/\"",
sourceTextNode);
}
var runtimeHelpers = context.Configuration.TypeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var markerMethodName = "Resolve" + objectNodeType.Name;
var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers;
var markerMethodName = "Resolve" + nodeTypeName;
var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3);
if (markerMethod is null)
{
throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{objectNodeType.Name}\" node", node);
throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node);
}
return new XamlValueWithManipulationNode(
node,
new AssetIncludeMethodNode(node, markerMethod, originalAssetPath),

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -103,6 +103,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; }
public IXamlType IStyle { get; }
public IXamlType StyleInclude { get; }
public IXamlType ResourceInclude { get; }
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
@ -234,6 +236,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,

Loading…
Cancel
Save