diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 4b9e33ffe8..01bf303a20 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -4,6 +4,7 @@ <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false low <_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false + false @@ -43,7 +44,7 @@ $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache - + @@ -106,6 +107,7 @@ DelaySign="$(DelaySign)" SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)" DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)" + DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)" /> !string.IsNullOrWhiteSpace(l)).ToArray(), - ProjectDirectory, OutputPath, VerifyIl, outputImportance, + ProjectDirectory, OutputPath, VerifyIl, DefaultCompileBindings, outputImportance, (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch); if (!res.Success) return false; @@ -71,6 +71,9 @@ namespace Avalonia.Build.Tasks public string OutputPath { get; set; } public bool VerifyIl { get; set; } + + public bool DefaultCompileBindings { get; set; } + public bool SkipXamlCompilation { get; set; } public string AssemblyOriginatorKeyFile { get; set; } diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 97aa8abc2f..3493fc06ed 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -39,43 +39,52 @@ namespace Avalonia.Build.Tasks public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, - string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, + string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation) { - return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false); + return Compile(engine, input, references, projectDirectory, output, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false); } internal static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, - string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch) + string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch) { - var typeSystem = new CecilTypeSystem( - references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")), - input); - - var asm = typeSystem.TargetAssemblyDefinition; - - if (!skipXamlCompilation) + try { - var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance, debuggerLaunch); - if (compileRes == null) - return new CompileResult(true); - if (compileRes == false) - return new CompileResult(false); - } + var typeSystem = new CecilTypeSystem( + references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")), + input); - var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; - if (!string.IsNullOrWhiteSpace(strongNameKey)) - writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); + var asm = typeSystem.TargetAssemblyDefinition; + + if (!skipXamlCompilation) + { + var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, + logImportance, debuggerLaunch); + if (compileRes == null) + return new CompileResult(true); + if (compileRes == false) + return new CompileResult(false); + } - asm.Write(output, writerParameters); + var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) + writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); - return new CompileResult(true, true); + asm.Write(output, writerParameters); + return new CompileResult(true, true); + } + catch (Exception ex) + { + engine.LogError(BuildEngineErrorCode.Unknown, "", ex.Message); + return new CompileResult(false); + } } static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem, - string projectDirectory, bool verifyIl, + string projectDirectory, bool verifyIl, + bool defaultCompileBindings, MessageImportance logImportance , bool debuggerLaunch = false) { @@ -113,7 +122,16 @@ namespace Avalonia.Build.Tasks if (avares.Resources.Count(CheckXamlName) == 0) // Nothing to do return null; - + if (typeSystem.FindType("System.Reflection.AssemblyMetadataAttribute") is {} asmMetadata) + { + var ctor = asm.MainModule.ImportReference(typeSystem.GetTypeReference(asmMetadata).Resolve() + .GetConstructors().First(c => c.Parameters.Count == 2).Resolve()); + var strType = asm.MainModule.ImportReference(typeof(string)); + var arg1 = new CustomAttributeArgument(strType, "AvaloniaUseCompiledBindingsByDefault"); + var arg2 = new CustomAttributeArgument(strType, defaultCompileBindings.ToString()); + asm.CustomAttributes.Add(new CustomAttribute(ctor) { ConstructorArguments = { arg1, arg2 } }); + } + var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(clrPropertiesDef); @@ -143,7 +161,7 @@ namespace Avalonia.Build.Tasks var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem, xamlLanguage, emitConfig); - var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl }; + var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl, DefaultCompileBindings = defaultCompileBindings }; var editorBrowsableAttribute = typeSystem .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute")) diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 811f9c7baa..9a901f909a 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -35,7 +35,16 @@ namespace Avalonia.DesignerSupport } var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null; - var loaded = loader.Load(stream, localAsm, null, baseUri, true); + var useCompiledBindings = localAsm?.GetCustomAttributes() + .FirstOrDefault(a => a.Key == "AvaloniaUseCompiledBindingsByDefault")?.Value; + + var loaded = loader.Load(stream, new RuntimeXamlLoaderConfiguration + { + LocalAssembly = localAsm, + BaseUri = baseUri, + DesignMode = true, + UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue + }); var style = loaded as IStyle; var resources = loaded as ResourceDictionary; if (style != null) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs index 4df07bcdd8..9393bb0aa4 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs @@ -26,6 +26,22 @@ namespace Avalonia.Markup.Xaml return Load(stream, localAssembly, rootInstance, uri, designMode); } } + + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// Xaml loader configuration. + /// The loaded object. + public static object Load(string xaml, RuntimeXamlLoaderConfiguration configuration) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, configuration); + } + } /// /// Loads XAML from a stream. @@ -38,7 +54,17 @@ namespace Avalonia.Markup.Xaml /// The loaded object. public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null, bool designMode = false) - => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode); + => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode, false); + + /// + /// Loads XAML from a stream. + /// + /// The stream containing the XAML. + /// Xaml loader configuration. + /// The loaded object. + public static object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) + => AvaloniaXamlIlRuntimeCompiler.Load(stream, configuration.LocalAssembly, configuration.RootInstance, + configuration.BaseUri, configuration.DesignMode, configuration.UseCompiledBindingsByDefault); /// /// Parse XAML from a string. diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index 1d4794f02a..b4b258e53e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -150,12 +150,12 @@ namespace Avalonia.Markup.Xaml.XamlIl } - static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode) + static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault) { var success = false; try { - var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode); + var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault); success = true; return rv; } @@ -167,7 +167,7 @@ namespace Avalonia.Markup.Xaml.XamlIl } - static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode) + static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault) { InitializeSre(); @@ -178,15 +178,14 @@ namespace Avalonia.Markup.Xaml.XamlIl var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N")); var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N")); var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N")); - + var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm, _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), _sreEmitMappings, - _sreContextType) { EnableIlVerification = true }; - + _sreContextType) { EnableIlVerification = true, DefaultCompileBindings = useCompiledBindingsByDefault }; IXamlType overrideType = null; if (rootInstance != null) @@ -204,8 +203,8 @@ namespace Avalonia.Markup.Xaml.XamlIl return LoadOrPopulate(created, rootInstance); } #endif - - static object LoadOrPopulate(Type created, object rootInstance) + + static object LoadOrPopulate(Type created, object rootInstance) { var isp = Expression.Parameter(typeof(IServiceProvider)); @@ -251,15 +250,15 @@ namespace Avalonia.Markup.Xaml.XamlIl } public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri, - bool isDesignMode) + bool isDesignMode, bool useCompiledBindingsByDefault) { string xaml; using (var sr = new StreamReader(stream)) xaml = sr.ReadToEnd(); #if RUNTIME_XAML_CECIL - return LoadCecil(xaml, localAssembly, rootInstance, uri); + return LoadCecil(xaml, localAssembly, rootInstance, uri, useCompiledBindingsByDefault); #else - return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode); + return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault); #endif } @@ -293,7 +292,7 @@ namespace Avalonia.Markup.Xaml.XamlIl } private static Dictionary _cecilGeneratedCache = new Dictionary(); - static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance, Uri uri) + static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool useCompiledBindingsByDefault) { if (uri == null) throw new InvalidOperationException("Please, go away"); @@ -303,8 +302,6 @@ namespace Avalonia.Markup.Xaml.XamlIl { overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName); } - - var safeUri = uri.ToString() .Replace(":", "_") @@ -328,13 +325,16 @@ namespace Avalonia.Markup.Xaml.XamlIl asm.MainModule.Types.Add(contextDef); var tb = _cecilTypeSystem.CreateTypeBuilder(def); - + var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_cecilTypeSystem, localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name), _cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings), AvaloniaXamlIlLanguage.CustomValueConverter), _cecilEmitMappings, - _cecilTypeSystem.CreateTypeBuilder(contextDef)); + _cecilTypeSystem.CreateTypeBuilder(contextDef)) + { + DefaultCompileBindings = useCompiledBindingsByDefault + }; compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType); var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll"); using(var f = File.Create(asmPath)) diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index f5bf14c2a4..070f0c1cc3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -44,6 +44,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index e5c6b72d12..578af64abb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,8 +1,5 @@ using System; using System.IO; -using System.Reflection; -using System.Text; -using Avalonia.Markup.Xaml.XamlIl; using Avalonia.Platform; namespace Avalonia.Markup.Xaml @@ -14,7 +11,7 @@ namespace Avalonia.Markup.Xaml { public interface IRuntimeXamlLoader { - object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode); + object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration); } /// @@ -67,7 +64,11 @@ namespace Avalonia.Markup.Xaml using (var stream = asset.stream) { var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri); - return runtimeLoader.Load(stream, asset.assembly, null, absoluteUri, false); + return runtimeLoader.Load(stream, new RuntimeXamlLoaderConfiguration + { + LocalAssembly = asset.assembly, + BaseUri = absoluteUri + }); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs new file mode 100644 index 0000000000..9caf94fba6 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; + +namespace Avalonia.Markup.Xaml; + +#nullable enable + +public class RuntimeXamlLoaderConfiguration +{ + /// + /// The URI of the XAML being loaded. + /// + public Uri? BaseUri { get; set; } + + /// + /// Default assembly for clr-namespace:. + /// + public Assembly? LocalAssembly { get; set; } + + /// + /// The optional instance into which the XAML should be loaded. + /// + public object? RootInstance { get; set; } + + /// + /// Defines is CompiledBinding should be used by default. + /// Default is 'false'. + /// + public bool UseCompiledBindingsByDefault { get; set; } = false; + + /// + /// Indicates whether the XAML is being loaded in design mode. + /// Default is 'false'. + /// + public bool DesignMode { get; set; } = false; +} diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs index 7af29a56a1..7009151998 100644 --- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs +++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs @@ -8,9 +8,11 @@ namespace Avalonia.Designer.HostApp { class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader { - public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode) + public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) { - return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAsm, o, baseUri, designMode); + return AvaloniaXamlIlRuntimeCompiler.Load(stream, + configuration.LocalAssembly, configuration.RootInstance, configuration.BaseUri, + configuration.DesignMode, configuration.UseCompiledBindingsByDefault); } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index a09abdecce..77067fa517 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1603,6 +1603,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Uses_RuntimeLoader_Configuration_To_Enabled_Compiled() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" +"; + var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml, new RuntimeXamlLoaderConfiguration + { + UseCompiledBindingsByDefault = true + }); + var compiledPath = ((CompiledBindingExtension)control.X).Path; + + var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + Assert.Equal(typeof(string), node.Property.PropertyType); + } + } + void Throws(string type, Action cb) { try diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs index 2bc82d1353..2fc4867b35 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs @@ -20,8 +20,8 @@ namespace Avalonia.Markup.Xaml.UnitTests class TestXamlLoaderShim : AvaloniaXamlLoader.IRuntimeXamlLoader { - public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode) - => AvaloniaRuntimeXamlLoader.Load(stream, localAsm, o, baseUri, designMode); + public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) + => AvaloniaRuntimeXamlLoader.Load(stream, configuration); } } }