diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 10f971cc4c..f98b1fe4f4 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -8,6 +8,10 @@
AssemblyFile="$(AvaloniaBuildTasksLocation)"
/>
+
+
@@ -36,6 +40,33 @@
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration)"/>
+
+
+
+ $(IntermediateOutputPath)/Avalonia/references
+ $(IntermediateOutputPath)/Avalonia/original.dll
+
+
+
+
+
+
diff --git a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
index 0365ead24d..2432410203 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs
@@ -9,7 +9,10 @@ namespace Avalonia.Data.Core
/// Exception thrown when could not parse the provided
/// expression string.
///
- public class ExpressionParseException : Exception
+#if !BUILDTASK
+ public
+#endif
+ class ExpressionParseException : Exception
{
///
/// Initializes a new instance of the class.
diff --git a/src/Avalonia.Base/Utilities/CharacterReader.cs b/src/Avalonia.Base/Utilities/CharacterReader.cs
index 56f3f7abd4..9be2d230d4 100644
--- a/src/Avalonia.Base/Utilities/CharacterReader.cs
+++ b/src/Avalonia.Base/Utilities/CharacterReader.cs
@@ -5,7 +5,10 @@ using System;
namespace Avalonia.Utilities
{
- public ref struct CharacterReader
+#if !BUILDTASK
+ public
+#endif
+ ref struct CharacterReader
{
private ReadOnlySpan _s;
diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs
index 09c50bf147..a57a2b7ba5 100644
--- a/src/Avalonia.Base/Utilities/IdentifierParser.cs
+++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs
@@ -6,7 +6,10 @@ using System.Globalization;
namespace Avalonia.Utilities
{
- public static class IdentifierParser
+#if !BUILDTASK
+ public
+#endif
+ static class IdentifierParser
{
public static ReadOnlySpan ParseIdentifier(this ref CharacterReader r)
{
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index ce767aaa87..072c78f7ef 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -1,9 +1,11 @@
-
-
+
- netstandard2.0
+ netstandard2.0
+ netstandard2.0;netcoreapp2.0
+ exe
tools
- $(DefineConstants);BUILDTASK
+ $(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL
+ true
@@ -13,6 +15,59 @@
Shared/AvaloniaResourceXamlInfo.cs
-
+
+ XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)
+
+
+
+ XamlIl/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
+
+
+
+
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
new file mode 100644
index 0000000000..028643f7c0
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using Microsoft.Build.Framework;
+
+namespace Avalonia.Build.Tasks
+{
+ public class CompileAvaloniaXamlTask: ITask
+ {
+ public bool Execute()
+ {
+ OutputPath = OutputPath ?? AssemblyFile;
+ var input = AssemblyFile;
+ // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
+ if (OriginalCopyPath != null)
+ {
+ File.Copy(AssemblyFile, OriginalCopyPath, true);
+ input = OriginalCopyPath;
+ File.Delete(AssemblyFile);
+ }
+
+ var data = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
+ File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray());
+ if(data == null)
+ File.Copy(input, OutputPath);
+ else
+ File.WriteAllBytes(OutputPath, data);
+
+ return true;
+ }
+
+
+
+ [Required]
+ public string AssemblyFile { get; set; }
+ [Required]
+ public string ReferencesFilePath { get; set; }
+ [Required]
+ public string OriginalCopyPath { get; set; }
+
+ public string OutputPath { get; set; }
+
+ public IBuildEngine BuildEngine { get; set; }
+ public ITaskHost HostObject { get; set; }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs
index faecee0f37..440c6d7489 100644
--- a/src/Avalonia.Build.Tasks/Extensions.cs
+++ b/src/Avalonia.Build.Tasks/Extensions.cs
@@ -1,8 +1,9 @@
+using System;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
{
- public static class Extensions
+ static class Extensions
{
static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}";
diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs
new file mode 100644
index 0000000000..2f206acf2a
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/Program.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections;
+using Microsoft.Build.Framework;
+
+namespace Avalonia.Build.Tasks
+{
+ public class Program
+ {
+ static int Main(string[] args)
+ {
+ if (args.Length != 3)
+ {
+ Console.Error.WriteLine("input references output");
+ return 1;
+ }
+
+ return new CompileAvaloniaXamlTask()
+ {
+ AssemblyFile = args[0],
+ ReferencesFilePath = args[1],
+ OutputPath = args[2],
+ BuildEngine = new ConsoleBuildEngine()
+ }.Execute() ?
+ 0 :
+ 2;
+ }
+
+ class ConsoleBuildEngine : IBuildEngine
+ {
+ public void LogErrorEvent(BuildErrorEventArgs e)
+ {
+ Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
+ }
+
+ public void LogWarningEvent(BuildWarningEventArgs e)
+ {
+ Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
+ }
+
+ public void LogMessageEvent(BuildMessageEventArgs e)
+ {
+ Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
+ }
+
+ public void LogCustomEvent(CustomBuildEventArgs e)
+ {
+ Console.WriteLine($"CUSTOM: {e.Message}");
+ }
+
+ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
+ IDictionary targetOutputs) => throw new NotSupportedException();
+
+ public bool ContinueOnError { get; }
+ public int LineNumberOfTaskNode { get; }
+ public int ColumnNumberOfTaskNode { get; }
+ public string ProjectFileOfTaskNode { get; }
+ }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs
new file mode 100644
index 0000000000..d5c406293e
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/SpanCompat.cs
@@ -0,0 +1,73 @@
+namespace System
+{
+ // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory
+ struct ReadOnlySpan
+ {
+ private string _s;
+ private int _start;
+ private int _length;
+ public int Length => _length;
+
+ public ReadOnlySpan(string s) : this(s, 0, s.Length)
+ {
+
+ }
+ public ReadOnlySpan(string s, int start, int len)
+ {
+ _s = s;
+ _length = len;
+ _start = start;
+ if (_start > s.Length)
+ _length = 0;
+ else if (_start + _length > s.Length)
+ _length = s.Length - _start;
+ }
+
+ public char this[int c] => _s[_start + c];
+
+ public bool IsEmpty => _length == 0;
+
+ public ReadOnlySpan Slice(int start, int len)
+ {
+ return new ReadOnlySpan(_s, _start + start, len);
+ }
+
+ public static ReadOnlySpan Empty => default;
+
+ public ReadOnlySpan Slice(int start)
+ {
+ return new ReadOnlySpan(_s, _start + start, _length - start);
+ }
+
+ public bool SequenceEqual(ReadOnlySpan other)
+ {
+ if (_length != other.Length)
+ return false;
+ for(var c=0; c<_length;c++)
+ if (this[c] != other[c])
+ return false;
+ return true;
+ }
+
+ public ReadOnlySpan TrimStart()
+ {
+ int start = 0;
+ for (; start < Length; start++)
+ {
+ if (!char.IsWhiteSpace(this[start]))
+ {
+ break;
+ }
+ }
+ return Slice(start);
+ }
+
+ public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length);
+ }
+
+ static class SpanCompatExtensions
+ {
+ public static ReadOnlySpan AsSpan(this string s) => new ReadOnlySpan(s);
+ }
+
+}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
new file mode 100644
index 0000000000..acaa80b608
--- /dev/null
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
+using Microsoft.Build.Framework;
+using Mono.Cecil;
+using XamlIl.TypeSystem;
+using Avalonia.Utilities;
+using Mono.Cecil.Rocks;
+using XamlIl.Parsers;
+using XamlIl.Transform;
+using TypeAttributes = Mono.Cecil.TypeAttributes;
+
+namespace Avalonia.Build.Tasks
+{
+
+ public static class XamlCompilerTaskExecutor
+ {
+ static bool CheckXamlName(string n) => n.ToLowerInvariant().EndsWith(".xaml")
+ || n.ToLowerInvariant().EndsWith(".paml");
+ static Dictionary ReadAvaloniaXamlResources(AssemblyDefinition asm)
+ {
+ var rv = new Dictionary();
+ var stream = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
+ r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"))?.GetResourceStream();
+ if (stream == null)
+ return rv;
+ var br = new BinaryReader(stream);
+ var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
+ var baseOffset = stream.Position;
+ foreach (var e in index.Where(e => CheckXamlName(e.Path)))
+ {
+ stream.Position = e.Offset + baseOffset;
+ rv[e.Path] = br.ReadBytes(e.Size);
+ }
+ return rv;
+ }
+
+ static Dictionary ReadEmbeddedXamlResources(AssemblyDefinition asm)
+ {
+ var rv = new Dictionary();
+ foreach (var r in asm.MainModule.Resources.OfType().Where(r => CheckXamlName(r.Name)))
+ rv[r.Name] = r.GetResourceData();
+ return rv;
+ }
+
+ public static byte[] Compile(IBuildEngine engine, string input, string[] references)
+ {
+ var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
+ var asm = typeSystem.TargetAssemblyDefinition;
+ var emres = ReadEmbeddedXamlResources(asm);
+ var avares = ReadAvaloniaXamlResources(asm);
+ if (avares.Count == 0 && emres.Count == 0)
+ // Nothing to do
+ return null;
+ var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
+ var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
+ typeSystem.TargetAssembly,
+ xamlLanguage,
+ XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
+ AvaloniaXamlIlLanguage.CustomValueConverter);
+
+
+ var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
+ TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+ asm.MainModule.Types.Add(contextDef);
+
+ var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
+ xamlLanguage);
+
+ var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass);
+
+ var editorBrowsableAttribute = typeSystem
+ .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
+ .Resolve();
+ var editorBrowsableCtor =
+ asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
+ .First(c => c.Parameters.Count == 1));
+
+ void CompileGroup(Dictionary resources, string name, Func uriTransform)
+ {
+ var typeDef = new TypeDefinition("CompiledAvaloniaXaml", name,
+ TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+
+
+ typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
+ {
+ ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
+ });
+ asm.MainModule.Types.Add(typeDef);
+ var builder = typeSystem.CreateTypeBuilder(typeDef);
+ foreach (var res in resources)
+ {
+ var xaml = Encoding.UTF8.GetString(res.Value);
+ var parsed = XDocumentXamlIlParser.Parse(xaml);
+ compiler.Transform(parsed);
+ compiler.Compile(parsed, builder, contextClass,
+ "Populate:" + res.Key, "Build:" + res.Key,
+ "NamespaceInfo:" + res.Key, uriTransform(res.Key));
+ }
+ }
+
+ if (emres.Count != 0)
+ CompileGroup(emres, "EmbeddedResource", name => $"resm:{name}?assembly={asm.Name}");
+ if (avares.Count != 0)
+ CompileGroup(avares, "AvaloniaResource", name => $"avares://{asm.Name}/{name}");
+
+ var ms = new MemoryStream();
+ asm.Write(ms);
+ return ms.ToArray();
+ }
+
+ }
+}
diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
index ea53f308ab..045cc49142 100644
--- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
+++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
@@ -1,6 +1,7 @@
netstandard2.0
+ true
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 4b205cb2e8..22d66e3b9d 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -74,8 +74,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
Transform(parsed);
- Compile(parsed, tb, _contextType, PopulateName, BuildName,
- "__AvaloniaXamlIlContext", "__AvaloniaXamlIlNsInfo", baseUri);
+ Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri);
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
index a15c162f84..beec47afa4 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Markup.Xaml.Parsers;
-using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
index c9f6ffedbc..8fcce31fad 160000
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
@@ -1 +1 @@
-Subproject commit c9f6ffedbc27cadbd6f393b1a142bbf2e6eaf78f
+Subproject commit 8fcce31fad28cb24b647ca3aed90199553ed0ca4
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
index e11e333a49..c57b8c4cf6 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
@@ -275,7 +275,7 @@ namespace Avalonia.Markup.Parsers
private static TSyntax ParseType(ref CharacterReader r, TSyntax syntax)
where TSyntax : ITypeSyntax
{
- ReadOnlySpan ns = null;
+ ReadOnlySpan ns = default;
ReadOnlySpan type;
var namespaceOrTypeName = r.ParseIdentifier();