Browse Source

Merge pull request #11062 from AvaloniaUI/feature/patch-platform-interfaces

Generate fake ref assemblies with patched *Impl and [NotClientImplementable] interfaces
pull/11072/head
Max Katz 3 years ago
committed by GitHub
parent
commit
e4474022c6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      nukebuild/Build.cs
  2. 24
      nukebuild/Helpers.cs
  3. 171
      nukebuild/RefAssemblyGenerator.cs
  4. 13
      nukebuild/_build.csproj
  5. 9
      src/Avalonia.Base/Metadata/PrivateApiAttribute.cs
  6. 2
      src/Avalonia.Base/Platform/ICursorFactory.cs
  7. 4
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  8. 8
      src/Avalonia.Base/Threading/IDispatcherImpl.cs
  9. 2
      src/Avalonia.Controls/Platform/IPlatformIconLoader.cs
  10. 2
      src/Avalonia.Controls/Platform/IWindowingPlatform.cs

2
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 => _ => _

24
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
}
});
}
}

171
nukebuild/RefAssemblyGenerator.cs

@ -0,0 +1,171 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using ILRepacking;
using Mono.Cecil;
using Mono.Cecil.Cil;
public class RefAssemblyGenerator
{
class Resolver : DefaultAssemblyResolver, IAssemblyResolver
{
private readonly string _dir;
Dictionary<string, AssemblyDefinition> _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 = 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 WriterParameters()
{
StrongNameKeyBlob = snk,
WriteSymbols = def.MainModule.HasSymbols,
SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(),
DeterministicMvid = def.MainModule.HasSymbols
});
}
static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor)
{
foreach (var nested in type.NestedTypes)
ProcessType(nested, obsoleteCtor);
if (type.IsInterface)
{
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 == "Avalonia.Metadata.NotClientImplementableAttribute");
if (hideMethods)
{
foreach (var m in type.Methods)
{
var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
}
}
if(injectMethod)
{
type.Methods.Add(new MethodDefinition("NotClientImplementable",
MethodAttributes.Assembly
| MethodAttributes.Abstract
| MethodAttributes.NewSlot
| MethodAttributes.HideBySig, type.Module.TypeSystem.Void));
}
var forceUnstable = type.CustomAttributes.Any(a =>
a.AttributeType.FullName == "Avalonia.Metadata.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(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.AttributeType.FullName == "System.ObsoleteAttribute"))
return;
def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor)
{
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)
{
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/") && 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 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}");
}
}
}
}

13
nukebuild/_build.csproj

@ -31,18 +31,11 @@
<!-- Common build related files -->
<Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(NuGetPackageRoot)sourcelink/1.1.0/tools/pdbstr.exe"></EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(NuGetPackageRoot)sourcelink/1.1.0/tools/pdbstr.exe"></EmbeddedResource>
<EmbeddedResource Include="../build/avalonia.snk"></EmbeddedResource>
<Compile Remove="il-repack\ILRepack\Application.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Numerge\Numerge.Console\" />
</ItemGroup>
</Project>

9
src/Avalonia.Base/Metadata/PrivateApiAttribute.cs

@ -0,0 +1,9 @@
using System;
namespace Avalonia.Metadata;
[AttributeUsage(AttributeTargets.Interface)]
public sealed class PrivateApiAttribute : Attribute
{
}

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

4
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -11,7 +11,7 @@ namespace Avalonia.Platform
/// <summary>
/// Defines the main platform-specific interface for the rendering subsystem.
/// </summary>
[Unstable]
[Unstable, PrivateApi]
public interface IPlatformRenderInterface
{
/// <summary>
@ -201,7 +201,7 @@ namespace Avalonia.Platform
bool IsSupportedBitmapPixelFormat(PixelFormat format);
}
[Unstable]
[Unstable, PrivateApi]
public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDisposable
{
/// <summary>

8
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

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

2
src/Avalonia.Controls/Platform/IWindowingPlatform.cs

@ -2,7 +2,7 @@ using Avalonia.Metadata;
namespace Avalonia.Platform
{
[Unstable]
[Unstable, PrivateApi]
public interface IWindowingPlatform
{
IWindowImpl CreateWindow();

Loading…
Cancel
Save