Browse Source
* Improved API diff * Merge API diff files * Ignore platform version for API diff * Start with fresh suppression filespull/19488/head
committed by
GitHub
16 changed files with 486 additions and 860 deletions
@ -1,22 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Android.AvaloniaMainActivity`1</Target> |
|||
<Left>baseline/net8.0-android34.0/Avalonia.Android.dll</Left> |
|||
<Right>target/net8.0-android34.0/Avalonia.Android.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Android.AndroidViewControlHandle.get_HandleDescriptor</Target> |
|||
<Left>baseline/net8.0-android34.0/Avalonia.Android.dll</Left> |
|||
<Right>target/net8.0-android34.0/Avalonia.Android.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Android.AndroidViewControlHandle</Target> |
|||
<Left>baseline/net8.0-android34.0/Avalonia.Android.dll</Left> |
|||
<Right>target/net8.0-android34.0/Avalonia.Android.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,22 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Browser.JSObjectControlHandle.get_Handle</Target> |
|||
<Left>baseline/net8.0-browser1.0/Avalonia.Browser.dll</Left> |
|||
<Right>target/net8.0-browser1.0/Avalonia.Browser.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Browser.JSObjectControlHandle.get_HandleDescriptor</Target> |
|||
<Left>baseline/net8.0-browser1.0/Avalonia.Browser.dll</Left> |
|||
<Right>target/net8.0-browser1.0/Avalonia.Browser.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Browser.JSObjectControlHandle</Target> |
|||
<Left>baseline/net8.0-browser1.0/Avalonia.Browser.dll</Left> |
|||
<Right>target/net8.0-browser1.0/Avalonia.Browser.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,10 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Tmds.DBus.SourceGenerator.PropertyChanges`1</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.FreeDesktop.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.FreeDesktop.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,16 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Skia.SkiaSharpExtensions.ToSKFilterQuality(Avalonia.Media.Imaging.BitmapInterpolationMode)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Skia.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Skia.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext.TryGetGrContext</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Skia.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Skia.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,16 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Themes.Fluent.ColorPaletteResources</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Themes.Fluent.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Themes.Fluent.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0008</DiagnosticId> |
|||
<Target>T:Avalonia.Themes.Fluent.ColorPaletteResources</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Themes.Fluent.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Themes.Fluent.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,214 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.DockPosition</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IDockProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IExpandCollapseProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IGridItemProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IGridProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IInvokeProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IMultipleViewProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IRangeValueProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderAdviseEvents</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderFragment</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderFragmentRoot</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderSimple</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderSimple2</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IScrollItemProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IScrollProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ISelectionItemProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ISelectionProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ISynchronizedInputProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ITableItemProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ITableProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ITextProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ITextRangeProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IToggleProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ITransformProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IValueProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.IWindowProvider</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.NavigateDirection</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.ProviderOptions</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.RowOrColumnMajor</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.SupportedTextSelection</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.SynchronizedInputType</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.TextPatternRangeEndpoint</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.TextUnit</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.WindowInteractionState</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Win32.Interop.Automation.WindowVisualState</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,16 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.iOS.UIViewControlHandle.get_HandleDescriptor</Target> |
|||
<Left>baseline/net8.0-tvos17.0/Avalonia.iOS.dll</Left> |
|||
<Right>target/net8.0-tvos17.0/Avalonia.iOS.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.iOS.UIViewControlHandle</Target> |
|||
<Left>baseline/net8.0-tvos17.0/Avalonia.iOS.dll</Left> |
|||
<Right>target/net8.0-tvos17.0/Avalonia.iOS.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,268 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Controls.PseudolassesExtensions</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.CastTypePropertyPathElement</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.ChildTraversalPropertyPathElement</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.EnsureTypePropertyPathElement</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.IPropertyPathElement</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.PropertyPath</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.PropertyPathBuilder</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Data.Core.PropertyPropertyPathElement</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Utilities.CharacterReader</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Utilities.IdentifierParser</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Utilities.KeywordParser</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0001</DiagnosticId> |
|||
<Target>T:Avalonia.Utilities.StyleClassParser</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Diagnostics.AppliedStyle.get_HasActivator</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Diagnostics.AppliedStyle.get_IsActive</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Diagnostics.AppliedStyle.get_Style</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Diagnostics.StyledElementExtensions.GetStyleDiagnostics(Avalonia.StyledElement)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Diagnostics.StyleDiagnostics.#ctor(System.Collections.Generic.IReadOnlyList{Avalonia.Diagnostics.AppliedStyle})</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Diagnostics.StyleDiagnostics.get_AppliedStyles</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Threading.DispatcherPriorityAwaitable.get_IsCompleted</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Threading.DispatcherPriorityAwaitable.GetAwaiter</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Threading.DispatcherPriorityAwaitable.GetResult</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Threading.DispatcherPriorityAwaitable.OnCompleted(System.Action)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Threading.DispatcherPriorityAwaitable`1.GetAwaiter</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Threading.DispatcherPriorityAwaitable`1.GetResult</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Screens.#ctor(Avalonia.Platform.IScreenImpl)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Input.Platform.IClipboard.FlushAsync</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataObjectAsync</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFileAsync(System.String)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFolderAsync(System.String)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Notifications.IManagedNotificationManager.Close(System.Object)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Notifications.INotificationManager.Close(Avalonia.Controls.Notifications.INotification)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Notifications.INotificationManager.CloseAll</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Controls.Primitives.PopupPositioning.PopupPositionRequest)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Primitives.IPopupHost.TakeFocus</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>P:Avalonia.Controls.Platform.IInsetsManager.DisplayEdgeToEdgePreference</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0006</DiagnosticId> |
|||
<Target>P:Avalonia.Controls.Platform.IInsetsManager.DisplaysEdgeToEdge</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Threading.DispatcherPriorityAwaitable</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Threading.DispatcherPriorityAwaitable`1</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0008</DiagnosticId> |
|||
<Target>T:Avalonia.Threading.DispatcherPriorityAwaitable</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0008</DiagnosticId> |
|||
<Target>T:Avalonia.Threading.DispatcherPriorityAwaitable`1</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0009</DiagnosticId> |
|||
<Target>T:Avalonia.Diagnostics.StyleDiagnostics</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0009</DiagnosticId> |
|||
<Target>T:Avalonia.Controls.Screens</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0012</DiagnosticId> |
|||
<Target>M:Avalonia.Controls.Button.OnAccessKey(Avalonia.Interactivity.RoutedEventArgs)</Target> |
|||
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left> |
|||
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -1,313 +1,464 @@ |
|||
#nullable enable |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Collections.Immutable; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Text.RegularExpressions; |
|||
using System.Security.Cryptography; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using NuGet.Common; |
|||
using NuGet.Configuration; |
|||
using NuGet.Frameworks; |
|||
using NuGet.Packaging; |
|||
using NuGet.Protocol; |
|||
using NuGet.Protocol.Core.Types; |
|||
using NuGet.Versioning; |
|||
using Nuke.Common.IO; |
|||
using Nuke.Common.Tooling; |
|||
using Serilog; |
|||
using static Serilog.Log; |
|||
|
|||
public static class ApiDiffHelper |
|||
{ |
|||
static readonly HttpClient s_httpClient = new(); |
|||
|
|||
public static async Task GetDiff( |
|||
Tool apiDiffTool, string outputFolder, |
|||
string packagePath, string baselineVersion) |
|||
const string NightlyFeedUri = "https://nuget-feed-nightly.avaloniaui.net/v3/index.json"; |
|||
const string MainPackageName = "Avalonia"; |
|||
const string FolderLib = "lib"; |
|||
|
|||
public static void ValidatePackage( |
|||
Tool apiCompatTool, |
|||
PackageDiffInfo packageDiff, |
|||
AbsolutePath suppressionFilesFolderPath, |
|||
bool updateSuppressionFile) |
|||
{ |
|||
await using var baselineStream = await DownloadBaselinePackage(packagePath, baselineVersion); |
|||
if (baselineStream == null) |
|||
return; |
|||
Information("Validating API for package {Id}", packageDiff.PackageId); |
|||
|
|||
if (!Directory.Exists(outputFolder)) |
|||
{ |
|||
Directory.CreateDirectory(outputFolder!); |
|||
} |
|||
Directory.CreateDirectory(suppressionFilesFolderPath); |
|||
|
|||
using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read)) |
|||
using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read)) |
|||
using (Helpers.UseTempDir(out var tempFolder)) |
|||
{ |
|||
var targetDlls = GetDlls(target); |
|||
var baselineDlls = GetDlls(baseline); |
|||
var suppressionArgs = ""; |
|||
|
|||
var pairs = new List<(string baseline, string target)>(); |
|||
var suppressionFile = suppressionFilesFolderPath / (packageDiff.PackageId + ".nupkg.xml"); |
|||
if (suppressionFile.FileExists()) |
|||
suppressionArgs += $""" --suppression-file="{suppressionFile}" --permit-unnecessary-suppressions """; |
|||
|
|||
var packageId = GetPackageId(packagePath); |
|||
if (updateSuppressionFile) |
|||
suppressionArgs += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file --preserve-unnecessary-suppressions """; |
|||
|
|||
// Don't use Path.Combine with these left and right tool parameters.
|
|||
// Microsoft.DotNet.ApiCompat.Tool is stupid and treats '/' and '\' as different assemblies in suppression files.
|
|||
// So, always use Unix '/'
|
|||
foreach (var baselineDll in baselineDlls) |
|||
var allErrors = new List<string>(); |
|||
|
|||
Parallel.ForEach( |
|||
packageDiff.Frameworks, |
|||
framework => |
|||
{ |
|||
var baselineDllPath = await ExtractDll("baseline", baselineDll, tempFolder); |
|||
var args = $""" -l="{framework.BaselineFolderPath}" -r="{framework.CurrentFolderPath}" {suppressionArgs}"""; |
|||
|
|||
var targetTfm = baselineDll.target; |
|||
var targetDll = targetDlls.FirstOrDefault(e => |
|||
e.target.StartsWith(targetTfm) && e.entry.Name == baselineDll.entry.Name); |
|||
if (targetDll is null) |
|||
{ |
|||
if (s_tfmRedirects.FirstOrDefault(t => baselineDll.target.StartsWith(t.oldTfm) && (t.package is null || packageId == t.package)).newTfm is {} newTfm) |
|||
{ |
|||
targetTfm = newTfm; |
|||
targetDll = targetDlls.FirstOrDefault(e => |
|||
e.target.StartsWith(targetTfm) && e.entry.Name == baselineDll.entry.Name); |
|||
} |
|||
} |
|||
var localErrors = GetErrors(apiCompatTool(args)); |
|||
|
|||
if (targetDll?.entry is null) |
|||
if (localErrors.Length > 0) |
|||
{ |
|||
throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}"); |
|||
lock (allErrors) |
|||
allErrors.AddRange(localErrors); |
|||
} |
|||
}); |
|||
|
|||
var targetDllPath = await ExtractDll("target", targetDll, tempFolder); |
|||
ThrowOnErrors(allErrors, packageDiff.PackageId, "ValidateApiDiff"); |
|||
} |
|||
|
|||
pairs.Add((baselineDllPath, targetDllPath)); |
|||
} |
|||
public static void GenerateMarkdownDiff( |
|||
Tool apiDiffTool, |
|||
PackageDiffInfo packageDiff, |
|||
AbsolutePath rootOutputFolderPath, |
|||
string baselineDisplay, |
|||
string currentDisplay) |
|||
{ |
|||
Information("Creating markdown diff for package {Id}", packageDiff.PackageId); |
|||
|
|||
await Task.WhenAll(pairs.Select(p => Task.Run(() => |
|||
{ |
|||
var baselineApi = p.baseline + Random.Shared.Next() + ".api.cs"; |
|||
var targetApi = p.target + Random.Shared.Next() + ".api.cs"; |
|||
var resultDiff = p.target + ".api.diff.cs"; |
|||
var packageOutputFolderPath = rootOutputFolderPath / packageDiff.PackageId; |
|||
Directory.CreateDirectory(packageOutputFolderPath); |
|||
|
|||
GenerateApiListing(apiDiffTool, p.baseline, baselineApi, tempFolder); |
|||
GenerateApiListing(apiDiffTool, p.target, targetApi, tempFolder); |
|||
// Not specifying -eattrs incorrectly tries to load AttributesToExclude.txt, create an empty file instead.
|
|||
// See https://github.com/dotnet/sdk/issues/49719
|
|||
var excludedAttributesFilePath = (AbsolutePath)Path.Join(Path.GetTempPath(), Guid.NewGuid().ToString()); |
|||
File.WriteAllBytes(excludedAttributesFilePath!, []); |
|||
|
|||
var args = $"""-c core.autocrlf=false diff --no-index --minimal """; |
|||
args += """--ignore-matching-lines="^\[assembly: System.Reflection.AssemblyVersionAttribute" """; |
|||
args += $""" --output {resultDiff} {baselineApi} {targetApi}"""; |
|||
try |
|||
{ |
|||
var allErrors = new List<string>(); |
|||
|
|||
using (var gitProcess = new Process()) |
|||
// The API diff tool is unbelievably slow, process in parallel.
|
|||
Parallel.ForEach( |
|||
packageDiff.Frameworks, |
|||
framework => |
|||
{ |
|||
gitProcess.StartInfo = new ProcessStartInfo |
|||
var frameworkOutputFolderPath = packageOutputFolderPath / framework.Framework.GetShortFolderName(); |
|||
var args = $""" -b="{framework.BaselineFolderPath}" -bfn="{baselineDisplay}" -a="{framework.CurrentFolderPath}" -afn="{currentDisplay}" -o="{frameworkOutputFolderPath}" -eattrs="{excludedAttributesFilePath}" """; |
|||
|
|||
var localErrors = GetErrors(apiDiffTool(args)); |
|||
|
|||
if (localErrors.Length > 0) |
|||
{ |
|||
CreateNoWindow = true, |
|||
RedirectStandardError = false, |
|||
RedirectStandardOutput = false, |
|||
FileName = "git", |
|||
Arguments = args, |
|||
WorkingDirectory = tempFolder |
|||
}; |
|||
gitProcess.Start(); |
|||
gitProcess.WaitForExit(); |
|||
} |
|||
lock (allErrors) |
|||
allErrors.AddRange(localErrors); |
|||
} |
|||
}); |
|||
|
|||
var resultFile = new FileInfo(Path.Combine(tempFolder, resultDiff)); |
|||
if (resultFile.Length > 0) |
|||
{ |
|||
resultFile.CopyTo(Path.Combine(outputFolder, Path.GetFileName(resultDiff)), true); |
|||
} |
|||
}))); |
|||
ThrowOnErrors(allErrors, packageDiff.PackageId, "OutputApiDiff"); |
|||
|
|||
MergeFrameworkMarkdownDiffFiles( |
|||
rootOutputFolderPath, |
|||
packageOutputFolderPath, |
|||
[..packageDiff.Frameworks.Select(info => info.Framework)]); |
|||
|
|||
Directory.Delete(packageOutputFolderPath, true); |
|||
} |
|||
finally |
|||
{ |
|||
File.Delete(excludedAttributesFilePath); |
|||
} |
|||
} |
|||
|
|||
private static readonly (string package, string oldTfm, string newTfm)[] s_tfmRedirects = new[] |
|||
{ |
|||
// We use StartsWith below comparing these tfm, as we ignore platform versions (like, net6.0-ios16.1).
|
|||
("Avalonia.Android", "net6.0-android", "net8.0-android"), |
|||
("Avalonia.iOS", "net6.0-ios", "net8.0-ios"), |
|||
// Browser was changed from net7.0 to net8.0-browser.
|
|||
("Avalonia.Browser", "net7.0", "net8.0-browser"), |
|||
("Avalonia.Browser.Blazor", "net7.0", "net8.0-browser"), |
|||
// Designer was moved from netcoreapp to netstandard.
|
|||
("Avalonia", "netcoreapp2.0", "netstandard2.0"), |
|||
("Avalonia", "net461", "netstandard2.0") |
|||
}; |
|||
|
|||
public static async Task ValidatePackage( |
|||
Tool apiCompatTool, string packagePath, string baselineVersion, |
|||
string suppressionFilesFolder, bool updateSuppressionFile) |
|||
static void MergeFrameworkMarkdownDiffFiles( |
|||
AbsolutePath rootOutputFolderPath, |
|||
AbsolutePath packageOutputFolderPath, |
|||
ImmutableArray<NuGetFramework> frameworks) |
|||
{ |
|||
if (!Directory.Exists(suppressionFilesFolder)) |
|||
// At this point, the hierarchy looks like:
|
|||
// markdown/
|
|||
// ├─ net8.0/
|
|||
// │ ├─ api_diff_Avalonia.md
|
|||
// │ ├─ api_diff_Avalonia.Controls.md
|
|||
// ├─ netstandard2.0/
|
|||
// │ ├─ api_diff_Avalonia.md
|
|||
// │ ├─ api_diff_Avalonia.Controls.md
|
|||
//
|
|||
// We want one file per assembly: merge all files with the same name.
|
|||
// However, it's very likely that the diff is the same for several frameworks: in this case, keep only one file.
|
|||
|
|||
var assemblyGroups = frameworks |
|||
.SelectMany(GetFrameworkDiffFiles, (framework, filePath) => (framework, filePath)) |
|||
.GroupBy(x => x.filePath.Name) |
|||
.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase); |
|||
|
|||
foreach (var assemblyGroup in assemblyGroups) |
|||
{ |
|||
using var writer = File.CreateText(rootOutputFolderPath / assemblyGroup.Key.Replace("api_diff_", "")); |
|||
var addSeparator = false; |
|||
|
|||
foreach (var similarDiffGroup in assemblyGroup.GroupBy(x => HashFile(x.filePath), ByteArrayEqualityComparer.Instance)) |
|||
{ |
|||
if (addSeparator) |
|||
writer.WriteLine(); |
|||
|
|||
using var reader = File.OpenText(similarDiffGroup.First().filePath); |
|||
var firstLine = reader.ReadLine(); |
|||
|
|||
writer.Write(firstLine); |
|||
writer.WriteLine(" (" + string.Join(", ", similarDiffGroup.Select(x => x.framework.GetShortFolderName())) + ")"); |
|||
|
|||
while (reader.ReadLine() is { } line) |
|||
writer.WriteLine(line); |
|||
|
|||
addSeparator = true; |
|||
} |
|||
} |
|||
|
|||
AbsolutePath[] GetFrameworkDiffFiles(NuGetFramework framework) |
|||
{ |
|||
var frameworkFolderPath = packageOutputFolderPath / framework.GetShortFolderName(); |
|||
if (!frameworkFolderPath.DirectoryExists()) |
|||
return []; |
|||
|
|||
return Directory.GetFiles(frameworkFolderPath, "*.md") |
|||
.Where(filePath => Path.GetFileName(filePath) != "api_diff.md") |
|||
.Select(filePath => (AbsolutePath)filePath) |
|||
.ToArray(); |
|||
} |
|||
|
|||
static byte[] HashFile(AbsolutePath filePath) |
|||
{ |
|||
Directory.CreateDirectory(suppressionFilesFolder!); |
|||
using var stream = File.OpenRead(filePath); |
|||
return SHA256.HashData(stream); |
|||
} |
|||
} |
|||
|
|||
public static void MergePackageMarkdownDiffFiles( |
|||
AbsolutePath rootOutputFolderPath, |
|||
string baselineDisplay, |
|||
string currentDisplay) |
|||
{ |
|||
const string mergedFileName = "_diff.md"; |
|||
|
|||
var filePaths = Directory.EnumerateFiles(rootOutputFolderPath, "*.md") |
|||
.Where(filePath => Path.GetFileName(filePath) != mergedFileName) |
|||
.Order(StringComparer.OrdinalIgnoreCase) |
|||
.ToArray(); |
|||
|
|||
using var writer = File.CreateText(rootOutputFolderPath / mergedFileName); |
|||
|
|||
await using var baselineStream = await DownloadBaselinePackage(packagePath, baselineVersion); |
|||
if (baselineStream == null) |
|||
writer.WriteLine($"# API diff between {baselineDisplay} and {currentDisplay}"); |
|||
|
|||
if (filePaths.Length == 0) |
|||
{ |
|||
writer.WriteLine(); |
|||
writer.WriteLine("No changes."); |
|||
return; |
|||
} |
|||
|
|||
foreach (var filePath in filePaths) |
|||
{ |
|||
writer.WriteLine(); |
|||
|
|||
using var reader = File.OpenText(filePath); |
|||
|
|||
while (reader.ReadLine() is { } line) |
|||
{ |
|||
if (line.StartsWith('#')) |
|||
writer.Write('#'); |
|||
|
|||
using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read)) |
|||
using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read)) |
|||
using (Helpers.UseTempDir(out var tempFolder)) |
|||
writer.WriteLine(line); |
|||
} |
|||
} |
|||
} |
|||
|
|||
static string[] GetErrors(IEnumerable<Output> outputs) |
|||
=> outputs |
|||
.Where(output => output.Type == OutputType.Err) |
|||
.Select(output => output.Text) |
|||
.ToArray(); |
|||
|
|||
static void ThrowOnErrors(List<string> errors, string packageId, string taskName) |
|||
{ |
|||
if (errors.Count > 0) |
|||
{ |
|||
var targetDlls = GetDlls(target); |
|||
var baselineDlls = GetDlls(baseline); |
|||
throw new AggregateException( |
|||
$"{taskName} task has failed for \"{packageId}\" package", |
|||
errors.Select(error => new Exception(error))); |
|||
} |
|||
} |
|||
|
|||
public static async Task<GlobalDiffInfo> DownloadAndExtractPackagesAsync( |
|||
IEnumerable<AbsolutePath> currentPackagePaths, |
|||
NuGetVersion currentVersion, |
|||
bool isReleaseBranch, |
|||
AbsolutePath outputFolderPath, |
|||
NuGetVersion? forcedBaselineVersion) |
|||
{ |
|||
var downloadContext = await CreateNuGetDownloadContextAsync(); |
|||
var baselineVersion = forcedBaselineVersion ?? |
|||
await GetBaselineVersionAsync(downloadContext, currentVersion, isReleaseBranch); |
|||
|
|||
var left = new List<string>(); |
|||
var right = new List<string>(); |
|||
Information("API baseline version is {Baseline} for current version {Current}", baselineVersion, currentVersion); |
|||
|
|||
var packageId = GetPackageId(packagePath); |
|||
var suppressionFile = Path.Combine(suppressionFilesFolder, packageId + ".nupkg.xml"); |
|||
var memoryStream = new MemoryStream(); |
|||
var packageDiffs = ImmutableArray.CreateBuilder<PackageDiffInfo>(); |
|||
|
|||
// Don't use Path.Combine with these left and right tool parameters.
|
|||
// Microsoft.DotNet.ApiCompat.Tool is stupid and treats '/' and '\' as different assemblies in suppression files.
|
|||
// So, always use Unix '/'
|
|||
foreach (var baselineDll in baselineDlls) |
|||
foreach (var packagePath in currentPackagePaths) |
|||
{ |
|||
string packageId; |
|||
AbsolutePath currentFolderPath; |
|||
AbsolutePath baselineFolderPath; |
|||
Dictionary<NuGetFramework, string> currentFolderNames; |
|||
Dictionary<NuGetFramework, string> baselineFolderNames; |
|||
|
|||
// Extract current package
|
|||
using (var currentArchive = new ZipArchive(File.OpenRead(packagePath), ZipArchiveMode.Read, leaveOpen: false)) |
|||
{ |
|||
var baselineDllPath = await ExtractDll("baseline", baselineDll, tempFolder); |
|||
using var packageReader = new PackageArchiveReader(currentArchive); |
|||
packageId = packageReader.NuspecReader.GetId(); |
|||
currentFolderPath = outputFolderPath / "current" / packageId; |
|||
currentFolderNames = ExtractDiffableAssembliesFromPackage(currentArchive, currentFolderPath); |
|||
} |
|||
|
|||
var targetTfm = baselineDll.target; |
|||
var targetDll = targetDlls.FirstOrDefault(e => |
|||
e.target.StartsWith(targetTfm) && e.entry.Name == baselineDll.entry.Name); |
|||
if (targetDll?.entry is null) |
|||
{ |
|||
if (s_tfmRedirects.FirstOrDefault(t => baselineDll.target.StartsWith(t.oldTfm) && (t.package is null || packageId == t.package)).newTfm is {} newTfm) |
|||
{ |
|||
targetTfm = newTfm; |
|||
targetDll = targetDlls.FirstOrDefault(e => |
|||
e.target.StartsWith(targetTfm) && e.entry.Name == baselineDll.entry.Name); |
|||
} |
|||
} |
|||
if (targetDll?.entry is null && targetDlls.Count == 1) |
|||
{ |
|||
targetDll = targetDlls.First(); |
|||
Warning( |
|||
$"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}." + |
|||
$"Resolved: {targetDll.target} ({targetDll.entry.Name})"); |
|||
} |
|||
// Download baseline package
|
|||
memoryStream.Position = 0L; |
|||
memoryStream.SetLength(0L); |
|||
await DownloadBaselinePackageAsync(memoryStream, downloadContext, packageId, baselineVersion); |
|||
memoryStream.Position = 0L; |
|||
|
|||
if (targetDll?.entry is null) |
|||
{ |
|||
if (packageId == "Avalonia" |
|||
&& baselineDll.target is "net461" or "netcoreapp2.0") |
|||
{ |
|||
// In 11.1 we have removed net461 and netcoreapp2.0 targets from Avalonia package.
|
|||
continue; |
|||
} |
|||
// Extract baseline package
|
|||
using (var baselineArchive = new ZipArchive(memoryStream, ZipArchiveMode.Read, leaveOpen: true)) |
|||
{ |
|||
baselineFolderPath = outputFolderPath / "baseline" / packageId; |
|||
baselineFolderNames = ExtractDiffableAssembliesFromPackage(baselineArchive, baselineFolderPath); |
|||
} |
|||
|
|||
var actualTargets = string.Join(", ", |
|||
targetDlls.Select(d => $"{d.target} ({d.entry.Name})")); |
|||
throw new InvalidOperationException( |
|||
$"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}." |
|||
+ $"\r\nActual targets: {actualTargets}."); |
|||
} |
|||
if (currentFolderNames.Count == 0 && baselineFolderNames.Count == 0) |
|||
continue; |
|||
|
|||
var targetDllPath = await ExtractDll("target", targetDll, tempFolder); |
|||
var frameworkDiffs = new List<FrameworkDiffInfo>(); |
|||
|
|||
left.Add(baselineDllPath); |
|||
right.Add(targetDllPath); |
|||
// Handle frameworks that exist only in the current package.
|
|||
foreach (var framework in currentFolderNames.Keys.Except(baselineFolderNames.Keys)) |
|||
{ |
|||
var folderName = currentFolderNames[framework]; |
|||
Directory.CreateDirectory(baselineFolderPath / folderName); |
|||
baselineFolderNames.Add(framework, folderName); |
|||
} |
|||
|
|||
if (left.Any()) |
|||
// Handle frameworks that exist only for the baseline package.
|
|||
foreach (var framework in baselineFolderNames.Keys.Except(currentFolderNames.Keys)) |
|||
{ |
|||
var args = $""" -l={string.Join(',', left)} -r="{string.Join(',', right)}" """; |
|||
if (File.Exists(suppressionFile)) |
|||
{ |
|||
args += $""" --suppression-file="{suppressionFile}" """; |
|||
} |
|||
var folderName = baselineFolderNames[framework]; |
|||
Directory.CreateDirectory(currentFolderPath / folderName); |
|||
currentFolderNames.Add(framework, folderName); |
|||
} |
|||
|
|||
if (updateSuppressionFile) |
|||
{ |
|||
args += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file=true """; |
|||
} |
|||
foreach (var (framework, currentFolderName) in currentFolderNames) |
|||
{ |
|||
var baselineFolderName = baselineFolderNames[framework]; |
|||
|
|||
var result = apiCompatTool(args, tempFolder) |
|||
.Where(t => t.Type == OutputType.Err).ToArray(); |
|||
if (result.Any()) |
|||
{ |
|||
throw new AggregateException( |
|||
$"ApiDiffValidation task has failed for \"{Path.GetFileName(packagePath)}\" package", |
|||
result.Select(r => new Exception(r.Text))); |
|||
} |
|||
frameworkDiffs.Add(new FrameworkDiffInfo( |
|||
framework, |
|||
baselineFolderPath / FolderLib / baselineFolderName, |
|||
currentFolderPath / FolderLib / currentFolderName)); |
|||
} |
|||
|
|||
packageDiffs.Add(new PackageDiffInfo(packageId, [..frameworkDiffs])); |
|||
} |
|||
|
|||
return new GlobalDiffInfo(baselineVersion, currentVersion, packageDiffs.DrainToImmutable()); |
|||
} |
|||
|
|||
record DllEntry(string target, ZipArchiveEntry entry); |
|||
static async Task<NuGetDownloadContext> CreateNuGetDownloadContextAsync() |
|||
{ |
|||
var packageSource = new PackageSource(NightlyFeedUri) { ProtocolVersion = 3 }; |
|||
var repository = Repository.Factory.GetCoreV3(packageSource); |
|||
var findPackageByIdResource = await repository.GetResourceAsync<FindPackageByIdResource>(); |
|||
return new NuGetDownloadContext(packageSource, findPackageByIdResource); |
|||
} |
|||
|
|||
static IReadOnlyCollection<DllEntry> GetDlls(ZipArchive archive) |
|||
/// <summary>
|
|||
/// Finds the baseline version to diff against.
|
|||
/// On release branches, use the latest stable version.
|
|||
/// On the main branch and on PRs, use the latest nightly version.
|
|||
/// This method assumes all packages share the same version.
|
|||
/// </summary>
|
|||
static async Task<NuGetVersion> GetBaselineVersionAsync( |
|||
NuGetDownloadContext context, |
|||
NuGetVersion currentVersion, |
|||
bool isReleaseBranch) |
|||
{ |
|||
return archive.Entries |
|||
.Where(e => Path.GetExtension(e.FullName) == ".dll" |
|||
// Exclude analyzers and build task, as we don't care about breaking changes there
|
|||
&& !e.FullName.Contains("analyzers/") && !e.FullName.Contains("analyzers\\") |
|||
&& !e.Name.Contains("Avalonia.Build.Tasks")) |
|||
.Select(e => ( |
|||
entry: e, |
|||
isRef: e.FullName.Contains("ref/") || e.FullName.Contains("ref\\"), |
|||
target: Path.GetDirectoryName(e.FullName)!.Split(new [] { '/', '\\' }).Last()) |
|||
) |
|||
.GroupBy(e => (e.target, e.entry.Name)) |
|||
.Select(g => g.MaxBy(e => e.isRef)) |
|||
.Select(e => new DllEntry(e.target, e.entry)) |
|||
.ToArray(); |
|||
var versions = await context.FindPackageByIdResource.GetAllVersionsAsync( |
|||
MainPackageName, |
|||
context.CacheContext, |
|||
NullLogger.Instance, |
|||
CancellationToken.None); |
|||
|
|||
versions = versions.Where(v => v < currentVersion); |
|||
|
|||
if (isReleaseBranch) |
|||
versions = versions.Where(v => !v.IsPrerelease); |
|||
|
|||
return versions.OrderDescending().FirstOrDefault() |
|||
?? throw new InvalidOperationException( |
|||
$"Could not find a version less than {currentVersion} for package {MainPackageName} in source {context.PackageSource.Source}"); |
|||
} |
|||
|
|||
static async Task<Stream> DownloadBaselinePackage(string packagePath, string baselineVersion) |
|||
static async Task DownloadBaselinePackageAsync( |
|||
Stream destinationStream, |
|||
NuGetDownloadContext context, |
|||
string packageId, |
|||
NuGetVersion version) |
|||
{ |
|||
if (baselineVersion is null) |
|||
Information("Downloading {Id} {Version} baseline package", packageId, version); |
|||
|
|||
var downloaded = await context.FindPackageByIdResource.CopyNupkgToStreamAsync( |
|||
packageId, |
|||
version, |
|||
destinationStream, |
|||
context.CacheContext, |
|||
NullLogger.Instance, |
|||
CancellationToken.None); |
|||
|
|||
if (!downloaded) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"Build \"api-baseline\" parameter must be set when running Nuke CreatePackages"); |
|||
$"Could not download version {version} for package {packageId} in source {context.PackageSource.Source}"); |
|||
} |
|||
} |
|||
|
|||
/* |
|||
Gets package name from versions like: |
|||
Avalonia.0.10.0-preview1 |
|||
Avalonia.11.0.999-cibuild0037534-beta |
|||
Avalonia.11.0.0 |
|||
*/ |
|||
var packageId = GetPackageId(packagePath); |
|||
Information("Downloading {0} {1} baseline package", packageId, baselineVersion); |
|||
static Dictionary<NuGetFramework, string> ExtractDiffableAssembliesFromPackage( |
|||
ZipArchive packageArchive, |
|||
AbsolutePath destinationFolderPath) |
|||
{ |
|||
var folderByFramework = new Dictionary<NuGetFramework, string>(); |
|||
|
|||
try |
|||
{ |
|||
using var response = await s_httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, |
|||
$"https://www.nuget.org/api/v2/package/{packageId}/{baselineVersion}"), HttpCompletionOption.ResponseHeadersRead); |
|||
response.EnsureSuccessStatusCode(); |
|||
|
|||
await using var stream = await response.Content.ReadAsStreamAsync(); |
|||
var memoryStream = new MemoryStream(); |
|||
await stream.CopyToAsync(memoryStream); |
|||
memoryStream.Seek(0, SeekOrigin.Begin); |
|||
return memoryStream; |
|||
} |
|||
catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.NotFound) |
|||
foreach (var entry in packageArchive.Entries) |
|||
{ |
|||
return null; |
|||
if (TryGetFrameworkFolderName(entry.FullName) is not { } folderName) |
|||
continue; |
|||
|
|||
// Ignore platform versions: assume that e.g. net8.0-android34 and net8.0-android35 are the same for diff purposes.
|
|||
var framework = WithoutPlatformVersion(NuGetFramework.ParseFolder(folderName)); |
|||
|
|||
if (folderByFramework.TryGetValue(framework, out var existingFolderName)) |
|||
{ |
|||
if (existingFolderName != folderName) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
$"Found two similar frameworks with different platform versions: {existingFolderName} and {folderName}"); |
|||
} |
|||
} |
|||
else |
|||
folderByFramework.Add(framework, folderName); |
|||
|
|||
var targetFilePath = destinationFolderPath / entry.FullName; |
|||
Directory.CreateDirectory(targetFilePath.Parent); |
|||
entry.ExtractToFile(targetFilePath, overwrite: true); |
|||
} |
|||
catch (Exception ex) |
|||
|
|||
return folderByFramework; |
|||
|
|||
static string? TryGetFrameworkFolderName(string entryPath) |
|||
{ |
|||
throw new InvalidOperationException($"Downloading baseline package for {packageId} {baselineVersion} failed.\r" + ex.Message, ex); |
|||
if (!entryPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) |
|||
return null; |
|||
|
|||
var segments = entryPath.Split('/'); |
|||
if (segments is not [FolderLib, var name, ..]) |
|||
return null; |
|||
|
|||
return name; |
|||
} |
|||
|
|||
// e.g. net8.0-android34.0 to net8.0-android
|
|||
static NuGetFramework WithoutPlatformVersion(NuGetFramework value) |
|||
=> value.HasPlatform && value.PlatformVersion != FrameworkConstants.EmptyVersion ? |
|||
new NuGetFramework(value.Framework, value.Version, value.Platform, FrameworkConstants.EmptyVersion) : |
|||
value; |
|||
} |
|||
|
|||
static async Task<string> ExtractDll(string basePath, DllEntry dllEntry, string targetFolder) |
|||
public sealed class GlobalDiffInfo( |
|||
NuGetVersion baselineVersion, |
|||
NuGetVersion currentVersion, |
|||
ImmutableArray<PackageDiffInfo> packages) |
|||
{ |
|||
var dllPath = $"{basePath}/{dllEntry.target}/{dllEntry.entry.Name}"; |
|||
var dllRealPath = Path.Combine(targetFolder, dllPath); |
|||
Directory.CreateDirectory(Path.GetDirectoryName(dllRealPath)!); |
|||
await using (var dllFile = File.Create(dllRealPath)) |
|||
{ |
|||
await dllEntry.entry.Open().CopyToAsync(dllFile); |
|||
} |
|||
public NuGetVersion BaselineVersion { get; } = baselineVersion; |
|||
public NuGetVersion CurrentVersion { get; } = currentVersion; |
|||
public ImmutableArray<PackageDiffInfo> Packages { get; } = packages; |
|||
} |
|||
|
|||
return dllPath; |
|||
public sealed class PackageDiffInfo(string packageId, ImmutableArray<FrameworkDiffInfo> frameworks) |
|||
{ |
|||
public string PackageId { get; } = packageId; |
|||
public ImmutableArray<FrameworkDiffInfo> Frameworks { get; } = frameworks; |
|||
} |
|||
|
|||
static void GenerateApiListing(Tool apiDiffTool, string inputFile, string outputFile, string workingDif) |
|||
public sealed class FrameworkDiffInfo( |
|||
NuGetFramework framework, |
|||
AbsolutePath baselineFolderPath, |
|||
AbsolutePath currentFolderPath) |
|||
{ |
|||
var args = $""" --assembly={inputFile} --output-path={outputFile} --include-assembly-attributes=true"""; |
|||
var result = apiDiffTool(args, workingDif) |
|||
.Where(t => t.Type == OutputType.Err).ToArray(); |
|||
if (result.Any()) |
|||
{ |
|||
throw new AggregateException($"GetApi tool failed task has failed", |
|||
result.Select(r => new Exception(r.Text))); |
|||
} |
|||
public NuGetFramework Framework { get; } = framework; |
|||
public AbsolutePath BaselineFolderPath { get; } = baselineFolderPath; |
|||
public AbsolutePath CurrentFolderPath { get; } = currentFolderPath; |
|||
} |
|||
|
|||
static string GetPackageId(string packagePath) |
|||
sealed class NuGetDownloadContext(PackageSource packageSource, FindPackageByIdResource findPackageByIdResource) |
|||
{ |
|||
return Regex.Replace( |
|||
Path.GetFileNameWithoutExtension(packagePath), |
|||
"""(\.\d+\.\d+\.\d+(?:-.+)?)$""", ""); |
|||
public SourceCacheContext CacheContext { get; } = new(); |
|||
public PackageSource PackageSource { get; } = packageSource; |
|||
public FindPackageByIdResource FindPackageByIdResource { get; } = findPackageByIdResource; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
#nullable enable |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
public sealed class ByteArrayEqualityComparer : IEqualityComparer<byte[]> |
|||
{ |
|||
public static ByteArrayEqualityComparer Instance { get; } = new(); |
|||
|
|||
public bool Equals(byte[]? x, byte[]? y) { |
|||
if (ReferenceEquals(x, y)) |
|||
return true; |
|||
if (x is null || y is null) |
|||
return false; |
|||
|
|||
return x.AsSpan().SequenceEqual(y.AsSpan()); |
|||
} |
|||
|
|||
public int GetHashCode(byte[]? obj) |
|||
{ |
|||
var hashCode = new HashCode(); |
|||
hashCode.AddBytes(obj.AsSpan()); |
|||
return hashCode.ToHashCode(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue