Browse Source

Merge pull request #7247 from jkoritzinsky/compiled-bindings-default

Add feature switch to enable switching compiled bindings on by default.
Max Katz 2 years ago
parent
commit
24bc7a6887

+ 3 - 1
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -4,6 +4,7 @@
     <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
     <AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
     <_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false</_AvaloniaSkipXamlCompilation>
+    <AvaloniaUseCompiledBindingsByDefault Condition="'$(AvaloniaUseCompiledBindingsByDefault)' == ''">false</AvaloniaUseCompiledBindingsByDefault>
   </PropertyGroup>
 
   <!-- Unfortunately we have to update default items in .targets since custom nuget props are improted before Microsoft.NET.Sdk.DefaultItems.props -->
@@ -43,7 +44,7 @@
   <PropertyGroup>
     <BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache</BuildAvaloniaResourcesDependsOn>
   </PropertyGroup>
-  
+
   <Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
     <ItemGroup>
       <CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" />
@@ -106,6 +107,7 @@
       DelaySign="$(DelaySign)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
+      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
     />
     <Exec
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"

+ 2 - 0
src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs

@@ -5,5 +5,7 @@ namespace Avalonia.Build.Tasks
         InvalidXAML = 1,
         DuplicateXClass = 2,
         LegacyResmScheme = 3,
+
+        Unknown = 9999
     }
 }

+ 4 - 1
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@@ -36,7 +36,7 @@ namespace Avalonia.Build.Tasks
 
             var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
                 File.ReadAllLines(ReferencesFilePath).Where(l => !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; }

+ 42 - 24
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"))

+ 10 - 1
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<AssemblyMetadataAttribute>()
+                    .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)

+ 27 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs

@@ -26,6 +26,22 @@ namespace Avalonia.Markup.Xaml
                 return Load(stream, localAssembly, rootInstance, uri, designMode);
             }
         }
+        
+        /// <summary>
+        /// Loads XAML from a string.
+        /// </summary>
+        /// <param name="xaml">The string containing the XAML.</param>
+        /// <param name="configuration">Xaml loader configuration.</param>
+        /// <returns>The loaded object.</returns>
+        public static object Load(string xaml, RuntimeXamlLoaderConfiguration configuration)
+        {
+            Contract.Requires<ArgumentNullException>(xaml != null);
+
+            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+            {
+                return Load(stream, configuration);
+            }
+        }
 
         /// <summary>
         /// Loads XAML from a stream.
@@ -38,7 +54,17 @@ namespace Avalonia.Markup.Xaml
         /// <returns>The loaded object.</returns>
         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);
+        
+        /// <summary>
+        /// Loads XAML from a stream.
+        /// </summary>
+        /// <param name="stream">The stream containing the XAML.</param>
+        /// <param name="configuration">Xaml loader configuration.</param>
+        /// <returns>The loaded object.</returns>
+        public static object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
+            => AvaloniaXamlIlRuntimeCompiler.Load(stream, configuration.LocalAssembly, configuration.RootInstance,
+                configuration.BaseUri, configuration.DesignMode, configuration.UseCompiledBindingsByDefault);
 
         /// <summary>
         /// Parse XAML from a string.

+ 16 - 16
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<string, Type> _cecilGeneratedCache = new Dictionary<string, Type>();
-        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))

+ 1 - 0
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -44,6 +44,7 @@
         <Compile Include="MarkupExtensions\ReflectionBindingExtension.cs" />
         <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
         <Compile Include="Properties\AssemblyInfo.cs" />
+        <Compile Include="RuntimeXamlLoaderConfiguration.cs" />
         <Compile Include="Styling\StyleInclude.cs" />
         <Compile Include="Templates\ControlTemplate.cs" />
         <Compile Include="Templates\DataTemplate.cs" />

+ 6 - 5
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);
         }
         
         /// <summary>
@@ -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
+                    });
                 }
             }
 

+ 36 - 0
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
+{
+    /// <summary>
+    /// The URI of the XAML being loaded.
+    /// </summary>
+    public Uri? BaseUri { get; set; }
+
+    /// <summary>
+    /// Default assembly for clr-namespace:.
+    /// </summary>
+    public Assembly? LocalAssembly { get; set; }
+            
+    /// <summary>
+    /// The optional instance into which the XAML should be loaded.
+    /// </summary>
+    public object? RootInstance { get; set; }
+            
+    /// <summary>
+    /// Defines is CompiledBinding should be used by default.
+    /// Default is 'false'.
+    /// </summary>
+    public bool UseCompiledBindingsByDefault { get; set; } = false;
+
+    /// <summary>
+    /// Indicates whether the XAML is being loaded in design mode.
+    /// Default is 'false'.
+    /// </summary>
+    public bool DesignMode { get; set; } = false;
+}

+ 4 - 2
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);
         }
     }
 }

+ 21 - 0
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 = @"
+<local:AssignBindingControl xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        X='{Binding StringProperty, DataType=local:TestDataContext}' />";
+                var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml, new RuntimeXamlLoaderConfiguration
+                {
+                    UseCompiledBindingsByDefault = true
+                });
+                var compiledPath = ((CompiledBindingExtension)control.X).Path;
+
+                var node = Assert.IsType<PropertyElement>(Assert.Single(compiledPath.Elements));
+                Assert.Equal(typeof(string), node.Property.PropertyType);
+            }
+        }
+        
         void Throws(string type, Action cb)
         {
             try

+ 2 - 2
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);
         }
     }
 }