Browse Source

Improved API diff (#19490)

* Improved API diff

* Merge API diff files

* Ignore platform version for API diff

* Start with fresh suppression files
pull/19488/head
Julien Lebosquain 6 months ago
committed by GitHub
parent
commit
66724d4785
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      .nuke/build.schema.json
  2. 1
      NuGet.Config
  3. 22
      api/Avalonia.Android.nupkg.xml
  4. 22
      api/Avalonia.Browser.nupkg.xml
  5. 10
      api/Avalonia.FreeDesktop.nupkg.xml
  6. 16
      api/Avalonia.Skia.nupkg.xml
  7. 16
      api/Avalonia.Themes.Fluent.nupkg.xml
  8. 214
      api/Avalonia.Win32.nupkg.xml
  9. 16
      api/Avalonia.iOS.nupkg.xml
  10. 268
      api/Avalonia.nupkg.xml
  11. 1
      build/SharedVersion.props
  12. 607
      nukebuild/ApiDiffHelper.cs
  13. 61
      nukebuild/Build.cs
  14. 36
      nukebuild/BuildParameters.cs
  15. 25
      nukebuild/ByteArrayEqualityComparer.cs
  16. 9
      nukebuild/_build.csproj

8
.nuke/build.schema.json

@ -6,9 +6,6 @@
"build": {
"type": "object",
"properties": {
"api-baseline": {
"type": "string"
},
"configuration": {
"type": "string"
},
@ -16,6 +13,9 @@
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"force-api-baseline": {
"type": "string"
},
"force-nuget-version": {
"type": "string"
},
@ -83,6 +83,7 @@
"CompileNative",
"CreateIntermediateNugetPackages",
"CreateNugetPackages",
"DownloadApiBaselinePackages",
"GenerateCppHeaders",
"OutputApiDiff",
"OutputVersion",
@ -121,6 +122,7 @@
"CompileNative",
"CreateIntermediateNugetPackages",
"CreateNugetPackages",
"DownloadApiBaselinePackages",
"GenerateCppHeaders",
"OutputApiDiff",
"OutputVersion",

1
NuGet.Config

@ -4,6 +4,5 @@
<packageSources>
<clear />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="skiasharp" value="https://aka.ms/skiasharp-eap/index.json" />
</packageSources>
</configuration>

22
api/Avalonia.Android.nupkg.xml

@ -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>

22
api/Avalonia.Browser.nupkg.xml

@ -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>

10
api/Avalonia.FreeDesktop.nupkg.xml

@ -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>

16
api/Avalonia.Skia.nupkg.xml

@ -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>

16
api/Avalonia.Themes.Fluent.nupkg.xml

@ -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>

214
api/Avalonia.Win32.nupkg.xml

@ -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>

16
api/Avalonia.iOS.nupkg.xml

@ -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>

268
api/Avalonia.nupkg.xml

@ -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
build/SharedVersion.props

@ -3,7 +3,6 @@
<PropertyGroup>
<Product>Avalonia</Product>
<Version>12.0.999</Version>
<ApiCompatVersion>11.1.0</ApiCompatVersion>
<Authors>Avalonia Team</Authors>
<Copyright>Copyright 2013-$([System.DateTime]::Now.ToString(`yyyy`)) &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net/?utm_source=nuget&amp;utm_medium=referral&amp;utm_content=project_homepage_link</PackageProjectUrl>

607
nukebuild/ApiDiffHelper.cs

@ -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;
}
}

61
nukebuild/Build.cs

@ -20,6 +20,7 @@ using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
using static Serilog.Log;
using MicroCom.CodeGenerator;
using NuGet.Configuration;
using NuGet.Versioning;
using Nuke.Common.CI.AzurePipelines;
using Nuke.Common.IO;
@ -35,11 +36,15 @@ partial class Build : NukeBuild
{
BuildParameters Parameters { get; set; }
[PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net6.0")]
#nullable enable
ApiDiffHelper.GlobalDiffInfo? GlobalDiff { get; set; }
#nullable restore
[PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net8.0")]
Tool ApiCompatTool;
[PackageExecutable("Microsoft.DotNet.GenAPI.Tool", "Microsoft.DotNet.GenAPI.Tool.dll", Framework = "net8.0")]
Tool ApiGenTool;
[PackageExecutable("Microsoft.DotNet.ApiDiff.Tool", "Microsoft.DotNet.ApiDiff.Tool.dll", Framework = "net8.0")]
Tool ApiDiffTool;
[PackageExecutable("dotnet-ilrepack", "ILRepackTool.dll", Framework = "net8.0")]
Tool IlRepackTool;
@ -322,24 +327,52 @@ partial class Build : NukeBuild
Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.snupkg");
});
Target ValidateApiDiff => _ => _
Target DownloadApiBaselinePackages => _ => _
.DependsOn(CreateNugetPackages)
.Executes(async () =>
{
await Task.WhenAll(
Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffHelper.ValidatePackage(
ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline,
Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression)));
GlobalDiff = await ApiDiffHelper.DownloadAndExtractPackagesAsync(
Directory.EnumerateFiles(Parameters.NugetRoot, "*.nupkg").Select(path => (AbsolutePath)path),
NuGetVersion.Parse(Parameters.Version),
Parameters.IsReleaseBranch,
Parameters.ArtifactsDir / "api-diff" / "assemblies",
Parameters.ForceApiValidationBaseline is { } forcedBaseline ? NuGetVersion.Parse(forcedBaseline) : null);
});
Target ValidateApiDiff => _ => _
.DependsOn(DownloadApiBaselinePackages)
.Executes(() =>
{
var globalDiff = GlobalDiff!;
Parallel.ForEach(
globalDiff.Packages,
packageDiff => ApiDiffHelper.ValidatePackage(
ApiCompatTool,
packageDiff,
Parameters.ApiValidationSuppressionFiles,
Parameters.UpdateApiValidationSuppression));
});
Target OutputApiDiff => _ => _
.DependsOn(CreateNugetPackages)
.Executes(async () =>
.DependsOn(DownloadApiBaselinePackages)
.Executes(() =>
{
await Task.WhenAll(
Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffHelper.GetDiff(
ApiGenTool, RootDirectory / "api" / "diff",
nugetPackage, Parameters.ApiValidationBaseline)));
var globalDiff = GlobalDiff!;
var outputFolderPath = Parameters.ArtifactsDir / "api-diff" / "markdown";
var baselineDisplay = globalDiff.BaselineVersion.ToString();
var currentDisplay = globalDiff.CurrentVersion.ToString();
Parallel.ForEach(
globalDiff.Packages,
packageDiff => ApiDiffHelper.GenerateMarkdownDiff(
ApiDiffTool,
packageDiff,
outputFolderPath,
baselineDisplay,
currentDisplay));
ApiDiffHelper.MergePackageMarkdownDiffFiles(outputFolderPath, baselineDisplay, currentDisplay);
});
Target RunTests => _ => _

36
nukebuild/BuildParameters.cs

@ -1,3 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,25 +14,25 @@ using static Nuke.Common.IO.PathConstruction;
public partial class Build
{
[Parameter(Name = "configuration")]
public string Configuration { get; set; }
public string? Configuration { get; set; }
[Parameter(Name = "skip-tests")]
public bool SkipTests { get; set; }
[Parameter(Name = "force-nuget-version")]
public string ForceNugetVersion { get; set; }
public string? ForceNugetVersion { get; set; }
[Parameter(Name = "skip-previewer")]
public bool SkipPreviewer { get; set; }
[Parameter(Name = "api-baseline")]
public string ApiValidationBaseline { get; set; }
[Parameter(Name = "force-api-baseline")]
public string? ForceApiValidationBaseline { get; set; }
[Parameter(Name = "update-api-suppression")]
public bool? UpdateApiValidationSuppression { get; set; }
[Parameter(Name = "version-output-dir")]
public AbsolutePath VersionOutputDir { get; set; }
public AbsolutePath? VersionOutputDir { get; set; }
public class BuildParameters
{
@ -39,8 +41,8 @@ public partial class Build
public bool SkipPreviewer {get;}
public string MainRepo { get; }
public string MasterBranch { get; }
public string RepositoryName { get; }
public string RepositoryBranch { get; }
public string? RepositoryName { get; }
public string? RepositoryBranch { get; }
public string ReleaseConfiguration { get; }
public Regex ReleaseBranchRegex { get; }
public string MSBuildSolution { get; }
@ -70,10 +72,10 @@ public partial class Build
public string FileZipSuffix { get; }
public AbsolutePath ZipCoreArtifacts { get; }
public AbsolutePath ZipNuGetArtifacts { get; }
public string ApiValidationBaseline { get; }
public string? ForceApiValidationBaseline { get; }
public bool UpdateApiValidationSuppression { get; }
public AbsolutePath ApiValidationSuppressionFiles { get; }
public AbsolutePath VersionOutputDir { get; }
public AbsolutePath? VersionOutputDir { get; }
public BuildParameters(Build b, bool isPackingToLocalCache)
{
@ -115,10 +117,9 @@ public partial class Build
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
var (propsVersion, propsApiCompatVersion) = GetVersion();
Version = b.ForceNugetVersion ?? propsVersion;
Version = b.ForceNugetVersion ?? GetVersion();
ApiValidationBaseline = b.ApiValidationBaseline ?? propsApiCompatVersion;
ForceApiValidationBaseline = b.ForceApiValidationBaseline;
UpdateApiValidationSuppression = b.UpdateApiValidationSuppression ?? IsLocalBuild;
if (IsRunningOnAzure)
@ -126,7 +127,9 @@ public partial class Build
if (!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-cibuild" + int.Parse(Environment.GetEnvironmentVariable("BUILD_BUILDID")).ToString("0000000") + "-alpha";
var buildId = Environment.GetEnvironmentVariable("BUILD_BUILDID") ??
throw new InvalidOperationException("Missing environment variable BUILD_BUILDID");
Version += "-cibuild" + int.Parse(buildId).ToString("0000000") + "-alpha";
}
PublishTestResults = true;
@ -157,13 +160,10 @@ public partial class Build
VersionOutputDir = b.VersionOutputDir;
}
(string Version, string ApiCompatVersion) GetVersion()
string GetVersion()
{
var xdoc = XDocument.Load(RootDirectory / "build/SharedVersion.props");
return (
xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value,
xdoc.Descendants().First(x => x.Name.LocalName == "ApiCompatVersion").Value
);
return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value;
}
}

25
nukebuild/ByteArrayEqualityComparer.cs

@ -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();
}
}

9
nukebuild/_build.csproj

@ -9,8 +9,8 @@
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
<!-- See https://github.com/nuke-build/nuke/issues/818 -->
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<!-- Necessary for Microsoft.DotNet.GenAPI.Tool -->
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8-transport/nuget/v3/index.json</RestoreAdditionalProjectSources>
<!-- Necessary for Microsoft.DotNet.ApiDiff.Tool -->
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10-transport/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>
<Import Project="..\build\JetBrains.dotMemoryUnit.props" />
@ -21,13 +21,14 @@
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.3.2" PrivateAssets="All" />
<PackageReference Include="NuGet.Protocol" Version="6.14.0" />
<PackageReference Include="xunit.runner.console" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageDownload Include="Microsoft.DotNet.ApiCompat.Tool" Version="[8.0.200]" />
<PackageDownload Include="Microsoft.DotNet.GenAPI.Tool" Version="[8.0.300-preview.24115.44]" />
<PackageDownload Include="Microsoft.DotNet.ApiCompat.Tool" Version="[10.0.100-preview.7.25380.108]" />
<PackageDownload Include="Microsoft.DotNet.ApiDiff.Tool" Version="[10.0.100-rc.1.25414.111]" />
<PackageDownload Include="dotnet-ilrepack" Version="[1.0.0]" />
</ItemGroup>

Loading…
Cancel
Save