From a24e0185fc46209dd308fb06d2fe40d3f00f61f2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 18:09:18 +0600 Subject: [PATCH 1/5] Generate fake ref assemblies with patched *Impl and [NotClientImplementable] interfaces --- nukebuild/Build.cs | 2 + nukebuild/Helpers.cs | 24 ++++++++ nukebuild/RefAssemblyGenerator.cs | 99 +++++++++++++++++++++++++++++++ nukebuild/_build.csproj | 14 ++--- 4 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 nukebuild/Helpers.cs create mode 100644 nukebuild/RefAssemblyGenerator.cs diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 40232947d9..630c532686 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -273,6 +273,8 @@ partial class Build : NukeBuild if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config, new NumergeNukeLogger())) throw new Exception("Package merge failed"); + RefAssemblyGenerator.GenerateRefAsmsInPackage(Parameters.NugetRoot / "Avalonia." + + Parameters.Version + ".nupkg"); }); Target RunTests => _ => _ diff --git a/nukebuild/Helpers.cs b/nukebuild/Helpers.cs new file mode 100644 index 0000000000..d8d06559bf --- /dev/null +++ b/nukebuild/Helpers.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using Nuke.Common.Utilities; + +class Helpers +{ + public static IDisposable UseTempDir(out string dir) + { + var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(path); + dir = path; + return DelegateDisposable.CreateBracket(null, () => + { + try + { + Directory.Delete(path, true); + } + catch + { + // ignore + } + }); + } +} diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs new file mode 100644 index 0000000000..912f74cdf9 --- /dev/null +++ b/nukebuild/RefAssemblyGenerator.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.Writer; + +public class RefAssemblyGenerator +{ + static void PatchRefAssembly(string file) + { + + var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk"); + var snk = new byte[reader.Length]; + reader.Read(snk, 0, snk.Length); + + var def = AssemblyDef.Load(new MemoryStream(File.ReadAllBytes(file))); + + foreach(var t in def.ManifestModule.Types) + ProcessType(t); + def.Write(file, new ModuleWriterOptions(def.ManifestModule) + { + StrongNameKey = new StrongNameKey(snk), + }); + } + + static void ProcessType(TypeDef type) + { + foreach (var nested in type.NestedTypes) + ProcessType(nested); + if (type.IsInterface) + { + var hideMethods = type.Name.EndsWith("Impl"); + var injectMethod = hideMethods + || type.CustomAttributes.Any(a => + a.AttributeType.FullName.EndsWith("NotClientImplementableAttribute")); + + if (hideMethods) + { + foreach (var m in type.Methods) + { + m.Attributes |= MethodAttributes.Public | MethodAttributes.Assembly; + m.Attributes ^= MethodAttributes.Public; + } + } + + if(injectMethod) + { + type.Methods.Add(new MethodDefUser("NotClientImplementable", + new MethodSig(CallingConvention.Default, 0, type.Module.CorLibTypes.Void), + MethodAttributes.Assembly + | MethodAttributes.Abstract + | MethodAttributes.NewSlot + | MethodAttributes.HideBySig)); + } + } + } + + public static void GenerateRefAsmsInPackage(string packagePath) + { + using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite), + ZipArchiveMode.Update)) + { + foreach (var entry in archive.Entries.ToList()) + { + if (entry.FullName.StartsWith("ref/")) + entry.Delete(); + } + + foreach (var entry in archive.Entries.ToList()) + { + if (entry.FullName.StartsWith("lib/")) + { + if (entry.Name.EndsWith(".dll")) + { + using (Helpers.UseTempDir(out var temp)) + { + var file = Path.Combine(temp, entry.Name); + entry.ExtractToFile(file); + PatchRefAssembly(file); + archive.CreateEntryFromFile(file, "ref/" + entry.FullName.Substring(4)); + + } + } + else if (entry.Name.EndsWith(".xml")) + { + var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4), + CompressionLevel.Optimal); + using (var src = entry.Open()) + using (var dst = newEntry.Open()) + src.CopyTo(dst); + } + } + } + } + } +} \ No newline at end of file diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 13bac4b7db..cc3ce9f0b0 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -18,6 +18,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -31,18 +32,11 @@ - - - - - - - + + - - - + From f9955f0c79aaed922761646b2c58e9214e9a8b11 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 18:57:45 +0600 Subject: [PATCH 2/5] More patches --- nukebuild/RefAssemblyGenerator.cs | 49 +++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 912f74cdf9..5c5324ac8f 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -18,31 +18,39 @@ public class RefAssemblyGenerator var def = AssemblyDef.Load(new MemoryStream(File.ReadAllBytes(file))); + var obsoleteAttribute = new TypeRefUser(def.ManifestModule, "System", "ObsoleteAttribute", def.ManifestModule.CorLibTypes.AssemblyRef); + var obsoleteCtor = def.ManifestModule.Import(new MemberRefUser(def.ManifestModule, ".ctor", + new MethodSig(CallingConvention.Default, 0, def.ManifestModule.CorLibTypes.Void, new TypeSig[] + { + def.ManifestModule.CorLibTypes.String + }), obsoleteAttribute)); + foreach(var t in def.ManifestModule.Types) - ProcessType(t); + ProcessType(t, obsoleteCtor); def.Write(file, new ModuleWriterOptions(def.ManifestModule) { StrongNameKey = new StrongNameKey(snk), }); } - static void ProcessType(TypeDef type) + static void ProcessType(TypeDef type, MemberRef obsoleteCtor) { foreach (var nested in type.NestedTypes) - ProcessType(nested); + ProcessType(nested, obsoleteCtor); if (type.IsInterface) { var hideMethods = type.Name.EndsWith("Impl"); var injectMethod = hideMethods || type.CustomAttributes.Any(a => a.AttributeType.FullName.EndsWith("NotClientImplementableAttribute")); - + if (hideMethods) { foreach (var m in type.Methods) { - m.Attributes |= MethodAttributes.Public | MethodAttributes.Assembly; - m.Attributes ^= MethodAttributes.Public; + var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem | + MethodAttributes.FamANDAssem | MethodAttributes.Assembly; + m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly; } } @@ -55,8 +63,37 @@ public class RefAssemblyGenerator | MethodAttributes.NewSlot | MethodAttributes.HideBySig)); } + + var forceUnstable = type.CustomAttributes.Any(a => + a.AttributeType.FullName.EndsWith("UnstableAttribute")); + + foreach (var m in type.Methods) + MarkAsUnstable(m, obsoleteCtor, forceUnstable); + foreach (var m in type.Properties) + MarkAsUnstable(m, obsoleteCtor, forceUnstable); + foreach (var m in type.Events) + MarkAsUnstable(m, obsoleteCtor, forceUnstable); + } } + + static void MarkAsUnstable(IMemberDef def, MemberRef obsoleteCtor, bool force) + { + if (!force + || def.HasCustomAttributes == false + || !def.CustomAttributes.Any(a => + a.AttributeType.FullName.EndsWith("UnstableAttribute"))) + return; + + if (def.CustomAttributes.Any(a => a.TypeFullName.EndsWith("ObsoleteAttribute"))) + return; + + def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor, new CAArgument[] + { + new(def.Module.CorLibTypes.String, + "This is a part of unstable API and can be changed in minor releases. You have been warned") + })); + } public static void GenerateRefAsmsInPackage(string packagePath) { From 30064443b14175ccbe901c0d331cd42284a9f119 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 20:16:34 +0600 Subject: [PATCH 3/5] Added [PrivateApi] --- nukebuild/RefAssemblyGenerator.cs | 16 +++++++++------- .../Metadata/PrivateApiAttribute.cs | 9 +++++++++ src/Avalonia.Base/Platform/ICursorFactory.cs | 2 ++ .../Platform/IPlatformRenderInterface.cs | 4 ++-- .../Platform/IPlatformIconLoader.cs | 2 +- .../Platform/IWindowingPlatform.cs | 2 +- 6 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 src/Avalonia.Base/Metadata/PrivateApiAttribute.cs diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 5c5324ac8f..61cb04c438 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -39,10 +39,13 @@ public class RefAssemblyGenerator ProcessType(nested, obsoleteCtor); if (type.IsInterface) { - var hideMethods = type.Name.EndsWith("Impl"); + var hideMethods = type.Name.EndsWith("Impl") + || (type.HasCustomAttributes && type.CustomAttributes.Any(a => + a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute")); + var injectMethod = hideMethods || type.CustomAttributes.Any(a => - a.AttributeType.FullName.EndsWith("NotClientImplementableAttribute")); + a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute"); if (hideMethods) { @@ -65,7 +68,7 @@ public class RefAssemblyGenerator } var forceUnstable = type.CustomAttributes.Any(a => - a.AttributeType.FullName.EndsWith("UnstableAttribute")); + a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute"); foreach (var m in type.Methods) MarkAsUnstable(m, obsoleteCtor, forceUnstable); @@ -81,11 +84,10 @@ public class RefAssemblyGenerator { if (!force || def.HasCustomAttributes == false - || !def.CustomAttributes.Any(a => - a.AttributeType.FullName.EndsWith("UnstableAttribute"))) + || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")) return; - - if (def.CustomAttributes.Any(a => a.TypeFullName.EndsWith("ObsoleteAttribute"))) + + if (def.CustomAttributes.Any(a => a.TypeFullName == "System.ObsoleteAttribute")) return; def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor, new CAArgument[] diff --git a/src/Avalonia.Base/Metadata/PrivateApiAttribute.cs b/src/Avalonia.Base/Metadata/PrivateApiAttribute.cs new file mode 100644 index 0000000000..3f60940c5e --- /dev/null +++ b/src/Avalonia.Base/Metadata/PrivateApiAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Avalonia.Metadata; + +[AttributeUsage(AttributeTargets.Interface)] +public sealed class PrivateApiAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/ICursorFactory.cs b/src/Avalonia.Base/Platform/ICursorFactory.cs index fff1f92d53..99a9a9d7fa 100644 --- a/src/Avalonia.Base/Platform/ICursorFactory.cs +++ b/src/Avalonia.Base/Platform/ICursorFactory.cs @@ -1,9 +1,11 @@ using Avalonia.Input; +using Avalonia.Metadata; #nullable enable namespace Avalonia.Platform { + [PrivateApi] public interface ICursorFactory { ICursorImpl GetCursor(StandardCursorType cursorType); diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 81fe2c046f..6f62c3be1d 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -11,7 +11,7 @@ namespace Avalonia.Platform /// /// Defines the main platform-specific interface for the rendering subsystem. /// - [Unstable] + [Unstable, PrivateApi] public interface IPlatformRenderInterface { /// @@ -201,7 +201,7 @@ namespace Avalonia.Platform bool IsSupportedBitmapPixelFormat(PixelFormat format); } - [Unstable] + [Unstable, PrivateApi] public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDisposable { /// diff --git a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs index 4c844ce30f..2ff74cc582 100644 --- a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs +++ b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs @@ -3,7 +3,7 @@ using Avalonia.Metadata; namespace Avalonia.Platform { - [Unstable] + [Unstable, PrivateApi] public interface IPlatformIconLoader { IWindowIconImpl LoadIcon(string fileName); diff --git a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs index 5acc5adccd..f6cf8c604e 100644 --- a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs +++ b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs @@ -2,7 +2,7 @@ using Avalonia.Metadata; namespace Avalonia.Platform { - [Unstable] + [Unstable, PrivateApi] public interface IWindowingPlatform { IWindowImpl CreateWindow(); From 0f7fba7f7f5e5c99708e68c887d9c39e91ada894 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 20:53:24 +0600 Subject: [PATCH 4/5] SourceLink? --- nukebuild/RefAssemblyGenerator.cs | 131 +++++++++++++++++++----------- nukebuild/_build.csproj | 1 - 2 files changed, 82 insertions(+), 50 deletions(-) diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 61cb04c438..2c5724e3ab 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -1,39 +1,69 @@ -using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using dnlib.DotNet.Writer; +using ILRepacking; +using Mono.Cecil; +using Mono.Cecil.Cil; public class RefAssemblyGenerator { - static void PatchRefAssembly(string file) + class Resolver : DefaultAssemblyResolver, IAssemblyResolver + { + private readonly string _dir; + Dictionary _cache = new(); + + public Resolver(string dir) + { + _dir = dir; + } + + public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + { + if (_cache.TryGetValue(name.Name, out var asm)) + return asm; + var path = Path.Combine(_dir, name.Name + ".dll"); + if (File.Exists(path)) + return _cache[name.Name] = AssemblyDefinition.ReadAssembly(path, parameters); + return base.Resolve(name, parameters); + } + } + + public static void PatchRefAssembly(string file) { - var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk"); var snk = new byte[reader.Length]; reader.Read(snk, 0, snk.Length); - - var def = AssemblyDef.Load(new MemoryStream(File.ReadAllBytes(file))); - - var obsoleteAttribute = new TypeRefUser(def.ManifestModule, "System", "ObsoleteAttribute", def.ManifestModule.CorLibTypes.AssemblyRef); - var obsoleteCtor = def.ManifestModule.Import(new MemberRefUser(def.ManifestModule, ".ctor", - new MethodSig(CallingConvention.Default, 0, def.ManifestModule.CorLibTypes.Void, new TypeSig[] - { - def.ManifestModule.CorLibTypes.String - }), obsoleteAttribute)); - - foreach(var t in def.ManifestModule.Types) + + var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters + { + ReadWrite = true, + InMemory = true, + ReadSymbols = true, + SymbolReaderProvider = new DefaultSymbolReaderProvider(false), + AssemblyResolver = new Resolver(Path.GetDirectoryName(file)) + }); + + var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule, + def.MainModule.TypeSystem.CoreLibrary)); + var obsoleteCtor = def.MainModule.ImportReference(new MethodReference(".ctor", + def.MainModule.TypeSystem.Void, obsoleteAttribute) + { + Parameters = { new ParameterDefinition(def.MainModule.TypeSystem.String) } + }); + + foreach(var t in def.MainModule.Types) ProcessType(t, obsoleteCtor); - def.Write(file, new ModuleWriterOptions(def.ManifestModule) + def.Write(file, new WriterParameters() { - StrongNameKey = new StrongNameKey(snk), + StrongNameKeyBlob = snk, + WriteSymbols = def.MainModule.HasSymbols, + SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(), + DeterministicMvid = def.MainModule.HasSymbols }); } - static void ProcessType(TypeDef type, MemberRef obsoleteCtor) + static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor) { foreach (var nested in type.NestedTypes) ProcessType(nested, obsoleteCtor); @@ -59,12 +89,11 @@ public class RefAssemblyGenerator if(injectMethod) { - type.Methods.Add(new MethodDefUser("NotClientImplementable", - new MethodSig(CallingConvention.Default, 0, type.Module.CorLibTypes.Void), + type.Methods.Add(new MethodDefinition("NotClientImplementable", MethodAttributes.Assembly | MethodAttributes.Abstract | MethodAttributes.NewSlot - | MethodAttributes.HideBySig)); + | MethodAttributes.HideBySig, type.Module.TypeSystem.Void)); } var forceUnstable = type.CustomAttributes.Any(a => @@ -80,21 +109,24 @@ public class RefAssemblyGenerator } } - static void MarkAsUnstable(IMemberDef def, MemberRef obsoleteCtor, bool force) + static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force) { if (!force || def.HasCustomAttributes == false || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")) return; - if (def.CustomAttributes.Any(a => a.TypeFullName == "System.ObsoleteAttribute")) + if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) return; - def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor, new CAArgument[] + def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor) { - new(def.Module.CorLibTypes.String, - "This is a part of unstable API and can be changed in minor releases. You have been warned") - })); + ConstructorArguments = + { + new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, + "This is a part of unstable API and can be changed in minor releases. You have been warned") + } + }); } public static void GenerateRefAsmsInPackage(string packagePath) @@ -110,29 +142,30 @@ public class RefAssemblyGenerator foreach (var entry in archive.Entries.ToList()) { - if (entry.FullName.StartsWith("lib/")) + if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml")) { - if (entry.Name.EndsWith(".dll")) - { - using (Helpers.UseTempDir(out var temp)) - { - var file = Path.Combine(temp, entry.Name); - entry.ExtractToFile(file); - PatchRefAssembly(file); - archive.CreateEntryFromFile(file, "ref/" + entry.FullName.Substring(4)); - - } - } - else if (entry.Name.EndsWith(".xml")) - { - var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4), - CompressionLevel.Optimal); - using (var src = entry.Open()) - using (var dst = newEntry.Open()) - src.CopyTo(dst); - } + var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4), + CompressionLevel.Optimal); + using (var src = entry.Open()) + using (var dst = newEntry.Open()) + src.CopyTo(dst); } } + + var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll")) + .Select((e => new { s = e.FullName.Split('/'), e = e })) + .Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e }) + .GroupBy(x => x.Tfm); + foreach(var tfm in libs) + using (Helpers.UseTempDir(out var temp)) + { + foreach (var l in tfm) + l.Entry.ExtractToFile(Path.Combine(temp, l.Name)); + foreach (var l in tfm) + PatchRefAssembly(Path.Combine(temp, l.Name)); + foreach (var l in tfm) + archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}"); + } } } } \ No newline at end of file diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index cc3ce9f0b0..d03746766e 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -18,7 +18,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive From c5ae8bb762589850c1620ca2b07b7169a72515f9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Apr 2023 21:40:26 +0600 Subject: [PATCH 5/5] Fixes --- nukebuild/RefAssemblyGenerator.cs | 6 +++--- src/Avalonia.Base/Threading/IDispatcherImpl.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nukebuild/RefAssemblyGenerator.cs b/nukebuild/RefAssemblyGenerator.cs index 2c5724e3ab..cbe5236bca 100644 --- a/nukebuild/RefAssemblyGenerator.cs +++ b/nukebuild/RefAssemblyGenerator.cs @@ -111,9 +111,9 @@ public class RefAssemblyGenerator static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force) { - if (!force - || def.HasCustomAttributes == false - || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")) + if (!force && ( + def.HasCustomAttributes == false + || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute"))) return; if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) diff --git a/src/Avalonia.Base/Threading/IDispatcherImpl.cs b/src/Avalonia.Base/Threading/IDispatcherImpl.cs index 4c30e2eb2c..ccbe3baf9a 100644 --- a/src/Avalonia.Base/Threading/IDispatcherImpl.cs +++ b/src/Avalonia.Base/Threading/IDispatcherImpl.cs @@ -6,7 +6,7 @@ using Avalonia.Platform; namespace Avalonia.Threading; -[Unstable] +[PrivateApi] public interface IDispatcherImpl { bool CurrentThreadIsLoopThread { get; } @@ -19,7 +19,7 @@ public interface IDispatcherImpl void UpdateTimer(long? dueTimeInMs); } -[Unstable] +[PrivateApi] public interface IDispatcherImplWithPendingInput : IDispatcherImpl { // Checks if dispatcher implementation can @@ -28,14 +28,14 @@ public interface IDispatcherImplWithPendingInput : IDispatcherImpl bool HasPendingInput { get; } } -[Unstable] +[PrivateApi] public interface IDispatcherImplWithExplicitBackgroundProcessing : IDispatcherImpl { event Action ReadyForBackgroundProcessing; void RequestBackgroundProcessing(); } -[Unstable] +[PrivateApi] public interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput { // Runs the event loop