From 618c372abb8f173f028a8a82a74b99f44ce5aee1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:48:31 -0800 Subject: [PATCH 1/3] Diffing lines --- Arcade.sln | 33 ++++ .../PackageDiff.Tasks/DiffPackages.cs | 24 +++ .../PackageDiff.Tasks/DiffPackages.csproj | 19 +++ .../PackageDiff.Tasks/build/PackageDiff.props | 11 ++ .../PackageDiff/PackageDiff.csproj | 9 + src/PackageDiff/PackageDiff/Program.cs | 159 ++++++++++++++++++ src/PackageDiff/PackageDiff/ZipExtensions.cs | 60 +++++++ 7 files changed, 315 insertions(+) create mode 100644 src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs create mode 100644 src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj create mode 100644 src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props create mode 100644 src/PackageDiff/PackageDiff/PackageDiff.csproj create mode 100644 src/PackageDiff/PackageDiff/Program.cs create mode 100644 src/PackageDiff/PackageDiff/ZipExtensions.cs diff --git a/Arcade.sln b/Arcade.sln index dc66fd32a22..92a19e5e4f9 100644 --- a/Arcade.sln +++ b/Arcade.sln @@ -147,6 +147,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.XliffTasks EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.XliffTasks.Tests", "src\Microsoft.DotNet.XliffTasks.Tests\Microsoft.DotNet.XliffTasks.Tests.csproj", "{6BA81447-C61D-4F91-BF0F-5B17AF4CFFAC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PackageDiff", "PackageDiff", "{82AD0031-378A-47FC-9448-0D7EDD7BB1C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageDiff", "src\PackageDiff\PackageDiff\PackageDiff.csproj", "{01760F15-01CD-463E-BB53-D71E1EA697B4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageDiff.Tasks", "src\PackageDiff\PackageDiff.Tasks\PackageDiff.Tasks.csproj", "{D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -921,6 +927,30 @@ Global {6BA81447-C61D-4F91-BF0F-5B17AF4CFFAC}.Release|x64.Build.0 = Release|Any CPU {6BA81447-C61D-4F91-BF0F-5B17AF4CFFAC}.Release|x86.ActiveCfg = Release|Any CPU {6BA81447-C61D-4F91-BF0F-5B17AF4CFFAC}.Release|x86.Build.0 = Release|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Debug|x64.ActiveCfg = Debug|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Debug|x64.Build.0 = Debug|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Debug|x86.ActiveCfg = Debug|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Debug|x86.Build.0 = Debug|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Release|Any CPU.Build.0 = Release|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Release|x64.ActiveCfg = Release|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Release|x64.Build.0 = Release|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Release|x86.ActiveCfg = Release|Any CPU + {01760F15-01CD-463E-BB53-D71E1EA697B4}.Release|x86.Build.0 = Release|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Debug|x64.ActiveCfg = Debug|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Debug|x64.Build.0 = Debug|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Debug|x86.Build.0 = Debug|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Release|Any CPU.Build.0 = Release|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Release|x64.ActiveCfg = Release|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Release|x64.Build.0 = Release|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Release|x86.ActiveCfg = Release|Any CPU + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -958,6 +988,9 @@ Global {14462553-E4E1-4F67-B954-4BF24B1DAAFE} = {3C542789-2576-48C8-9772-C9D7575F7E42} {650B7526-7B8A-45B5-B14E-C16D828891B2} = {C53DD924-C212-49EA-9BC4-1827421361EF} {6BA81447-C61D-4F91-BF0F-5B17AF4CFFAC} = {C53DD924-C212-49EA-9BC4-1827421361EF} + {82AD0031-378A-47FC-9448-0D7EDD7BB1C9} = {6DA9F58A-34D5-45A6-998E-5D2B8037C3FE} + {01760F15-01CD-463E-BB53-D71E1EA697B4} = {82AD0031-378A-47FC-9448-0D7EDD7BB1C9} + {D9A0A3DD-6A14-425B-AF1D-975AB1E3E7B1} = {82AD0031-378A-47FC-9448-0D7EDD7BB1C9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {32B9C883-432E-4FC8-A1BF-090EB033DD5B} diff --git a/src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs b/src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs new file mode 100644 index 00000000000..20d9fe54325 --- /dev/null +++ b/src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs @@ -0,0 +1,24 @@ +// See https://aka.ms/new-console-template for more information + +using Microsoft.Build.Framework; + +public class PackageDiffTask : Microsoft.Build.Utilities.ToolTask +{ + [Required] + public string BaselinePackage {get; set;} = ""; + + [Required] + public string TestPackage {get; set;} = ""; + + protected override string ToolName { get; } = $"PackageDiff" + (System.Environment.OSVersion.Platform == PlatformID.Unix ? "" : ".exe"); + + protected override string GenerateFullPathToTool() + { + return Path.Combine(typeof(PackageDiffTask).Assembly.Location, ToolName); + } + + protected override string GenerateCommandLineCommands() + { + return $"\"{BaselinePackage}\" \"{TestPackage}\""; + } +} diff --git a/src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj b/src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj new file mode 100644 index 00000000000..74faab21cb9 --- /dev/null +++ b/src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj @@ -0,0 +1,19 @@ + + + + Library + net9.0 + enable + enable + true + true + + + + + + + + + diff --git a/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props b/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props new file mode 100644 index 00000000000..a96f22edd55 --- /dev/null +++ b/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props @@ -0,0 +1,11 @@ + + + + $(MSBuildThisFileDirectory)..\tools\net9.0\ILLink.Tasks.dll + + + + + + + diff --git a/src/PackageDiff/PackageDiff/PackageDiff.csproj b/src/PackageDiff/PackageDiff/PackageDiff.csproj new file mode 100644 index 00000000000..533da89199c --- /dev/null +++ b/src/PackageDiff/PackageDiff/PackageDiff.csproj @@ -0,0 +1,9 @@ + + + + Exe + $(NetToolCurrent) + enable + + + diff --git a/src/PackageDiff/PackageDiff/Program.cs b/src/PackageDiff/PackageDiff/Program.cs new file mode 100644 index 00000000000..2905c50cd87 --- /dev/null +++ b/src/PackageDiff/PackageDiff/Program.cs @@ -0,0 +1,159 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Text; +using System.Threading.Tasks; + +public class PackageDiff +{ + public static async Task Main(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine("Usage: PackageDiff "); + return 1; + } + + ZipArchive package1 = await GetZipArchiveAsync(args[0]); + ZipArchive package2 = await GetZipArchiveAsync(args[1]); + var diff = GetDiffs(package1, package2); + if (diff is not "") + { + Console.WriteLine(diff); + return 1; + } + return 0; + } + + public static async Task GetZipArchiveAsync(string arg) + { + if (File.Exists(arg)) + { + return new ZipArchive(File.OpenRead(arg)); + } + else if (Uri.TryCreate(arg, UriKind.RelativeOrAbsolute, out var uri)) + { + var webClient = new HttpClient(); + return new ZipArchive(await webClient.GetStreamAsync(uri)); + } + else + { + throw new ArgumentException($"Invalid path or url to package1: {arg}"); + } + } + + public static string GetDiffs(ZipArchive package1, ZipArchive package2) + { + StringBuilder output = new(); + + if (TryGetDiff(package1.Entries.Select(entry => entry.FullName).ToList(), package2.Entries.Select(entry => entry.FullName).ToList(), out var fileDiffs)) + { + output.AppendLine("File differences:"); + output.AppendLine(string.Join(Environment.NewLine, fileDiffs.Select(d => " " + d))); + output.AppendLine(); + } + + if (TryGetDiff(package1.GetNuspec().Lines(), package2.GetNuspec().Lines(), out var editedDiff)) + { + output.AppendLine("Nuspec differences:"); + output.AppendLine(string.Join(Environment.NewLine, editedDiff.Select(d => " " + d))); + output.AppendLine(); + } + var dlls1 = package1.Entries.Where(entry => entry.FullName.EndsWith(".dll")).ToImmutableDictionary(entry => entry.FullName, entry => entry); + var dlls2 = package2.Entries.Where(entry => entry.FullName.EndsWith(".dll")).ToImmutableDictionary(entry => entry.FullName, entry => entry); + foreach (var kvp in dlls1) + { + var dllPath = kvp.Key; + var dll1 = kvp.Value; + if (dlls2.TryGetValue(dllPath, out ZipArchiveEntry? dll2)) + { + try + { + var version1 = new PEReader(dll1.Open().ReadToEnd().ToImmutableArray()).GetMetadataReader().GetAssemblyDefinition().Version.ToString(); + var version2 = new PEReader(dll2.Open().ReadToEnd().ToImmutableArray()).GetMetadataReader().GetAssemblyDefinition().Version.ToString(); + if (version1 != version2) + { + output.AppendLine($"Assembly {dllPath} has different versions: {version1} and {version2}"); + } + } + catch (InvalidOperationException) + { } + } + } + return output.ToString(); + } + + public static bool TryGetDiff(List originalLines, List modifiedLines, out List formattedDiff) + { + // Edit distance algorithm: https://en.wikipedia.org/wiki/Longest_common_subsequence + + int[,] dp = new int[originalLines.Count + 1, modifiedLines.Count + 1]; + + // Initialize first row and column + for (int i = 0; i <= originalLines.Count; i++) + { + dp[i, 0] = i; + } + for (int j = 0; j <= modifiedLines.Count; j++) + { + dp[0, j] = j; + } + + // Compute edit distance + for (int i = 1; i <= originalLines.Count; i++) + { + for (int j = 1; j <= modifiedLines.Count; j++) + { + if (string.Compare(originalLines[i - 1], modifiedLines[j - 1]) == 0) + { + dp[i, j] = dp[i - 1, j - 1]; + } + else + { + dp[i, j] = 1 + Math.Min(dp[i - 1, j], dp[i, j - 1]); + } + } + } + + // Trace back the edits + int row = originalLines.Count; + int col = modifiedLines.Count; + + formattedDiff = []; + while (row > 0 || col > 0) + { + if (row > 0 && col > 0 && string.Compare(originalLines[row - 1], modifiedLines[col - 1]) == 0) + { + formattedDiff.Add(" " + originalLines[row - 1]); + row--; + col--; + } + else if (col > 0 && (row == 0 || dp[row, col - 1] <= dp[row - 1, col])) + { + formattedDiff.Add("+ " + modifiedLines[col - 1]); + col--; + } + else if (row > 0 && (col == 0 || dp[row, col - 1] > dp[row - 1, col])) + { + formattedDiff.Add("- " + originalLines[row - 1]); + row--; + } + else + { + throw new Exception("Unreachable code"); + } + } + formattedDiff.Reverse(); + return dp[originalLines.Count, modifiedLines.Count] != 0; + } +} diff --git a/src/PackageDiff/PackageDiff/ZipExtensions.cs b/src/PackageDiff/PackageDiff/ZipExtensions.cs new file mode 100644 index 00000000000..914740b0686 --- /dev/null +++ b/src/PackageDiff/PackageDiff/ZipExtensions.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; + +static class ZipExtensions +{ + public static List Lines(this ZipArchiveEntry entry, Encoding? encoding = null) + { + return entry.ReadToString(encoding).Replace("\r\n", "\n").Split('\n').ToList(); + } + + public static string ReadToString(this ZipArchiveEntry entry, Encoding? encoding = null) + { + Stream stream = entry.Open(); + byte[] buffer = stream.ReadToEnd(); + // Remove UTF-8 BOM if present + int index = 0; + if (buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF) + { + index = 3; + } + encoding ??= Encoding.UTF8; + string fileText = encoding.GetString(buffer, index, buffer.Length - index); + return fileText; + } + + public static ZipArchiveEntry GetNuspec(this ZipArchive package) + { + return package.Entries.Where(entry => entry.FullName.EndsWith(".nuspec")).Single(); + } + + public static byte[] ReadToEnd(this Stream stream) + { + int bufferSize = 2048; + byte[] buffer = new byte[bufferSize]; + int offset = 0; + while (true) + { + int bytesRead = stream.Read(buffer, offset, bufferSize - offset); + offset += bytesRead; + if (bytesRead == 0) + { + break; + } + if (offset == bufferSize) + { + Array.Resize(ref buffer, bufferSize * 2); + bufferSize *= 2; + } + } + Array.Resize(ref buffer, offset); + return buffer; + } +} From c99f6b8c2f388ece318676bb42a0ea76b45c0485 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:43:43 -0800 Subject: [PATCH 2/3] Move PackageDiff and publish PackageDiff.Tasks package --- .../PackageDiff.Tasks/DiffPackages.csproj | 19 ------------ .../PackageDiff.Tasks.csproj | 31 +++++++++++++++++++ .../{DiffPackages.cs => PackageDiff.cs} | 10 ++++-- .../build/PackageDiff.Tasks.props | 9 ++++++ .../PackageDiff.Tasks/build/PackageDiff.props | 11 ------- .../PackageDiff/PackageDiff.csproj | 4 ++- src/PackageDiff/PackageDiff/Program.cs | 1 + 7 files changed, 51 insertions(+), 34 deletions(-) delete mode 100644 src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj create mode 100644 src/PackageDiff/PackageDiff.Tasks/PackageDiff.Tasks.csproj rename src/PackageDiff/PackageDiff.Tasks/{DiffPackages.cs => PackageDiff.cs} (51%) create mode 100644 src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.Tasks.props delete mode 100644 src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props diff --git a/src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj b/src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj deleted file mode 100644 index 74faab21cb9..00000000000 --- a/src/PackageDiff/PackageDiff.Tasks/DiffPackages.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Library - net9.0 - enable - enable - true - true - - - - - - - - - diff --git a/src/PackageDiff/PackageDiff.Tasks/PackageDiff.Tasks.csproj b/src/PackageDiff/PackageDiff.Tasks/PackageDiff.Tasks.csproj new file mode 100644 index 00000000000..878b136beb7 --- /dev/null +++ b/src/PackageDiff/PackageDiff.Tasks/PackageDiff.Tasks.csproj @@ -0,0 +1,31 @@ + + + + Library + netstandard2.0 + enable + enable + true + true + $(NoWarn);NU5128;NU5129;NU5100 + tasks + true + + + + + + + + + + + + <_DiffToolPublishContent Include="$(OutputPath)PackageDiff\**" /> + + + + + diff --git a/src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs b/src/PackageDiff/PackageDiff.Tasks/PackageDiff.cs similarity index 51% rename from src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs rename to src/PackageDiff/PackageDiff.Tasks/PackageDiff.cs index 20d9fe54325..744779ccee8 100644 --- a/src/PackageDiff/PackageDiff.Tasks/DiffPackages.cs +++ b/src/PackageDiff/PackageDiff.Tasks/PackageDiff.cs @@ -1,8 +1,9 @@ -// See https://aka.ms/new-console-template for more information +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Framework; -public class PackageDiffTask : Microsoft.Build.Utilities.ToolTask +public class PackageDiff: Microsoft.Build.Utilities.ToolTask { [Required] public string BaselinePackage {get; set;} = ""; @@ -12,9 +13,12 @@ public class PackageDiffTask : Microsoft.Build.Utilities.ToolTask protected override string ToolName { get; } = $"PackageDiff" + (System.Environment.OSVersion.Platform == PlatformID.Unix ? "" : ".exe"); + protected override MessageImportance StandardOutputLoggingImportance => MessageImportance.High; + protected override bool HandleTaskExecutionErrors() => true; + protected override string GenerateFullPathToTool() { - return Path.Combine(typeof(PackageDiffTask).Assembly.Location, ToolName); + return Path.Combine(Path.GetDirectoryName(typeof(PackageDiff).Assembly.Location)!, "..", "..", "tools", ToolName); } protected override string GenerateCommandLineCommands() diff --git a/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.Tasks.props b/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.Tasks.props new file mode 100644 index 00000000000..239643148ec --- /dev/null +++ b/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.Tasks.props @@ -0,0 +1,9 @@ + + + + <_PackageDiffTasksAssemblyPath>$(MSBuildThisFileDirectory)\..\tasks\netstandard2.0\PackageDiff.Tasks.dll + + + + + diff --git a/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props b/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props deleted file mode 100644 index a96f22edd55..00000000000 --- a/src/PackageDiff/PackageDiff.Tasks/build/PackageDiff.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - $(MSBuildThisFileDirectory)..\tools\net9.0\ILLink.Tasks.dll - - - - - - - diff --git a/src/PackageDiff/PackageDiff/PackageDiff.csproj b/src/PackageDiff/PackageDiff/PackageDiff.csproj index 533da89199c..c74b0b78348 100644 --- a/src/PackageDiff/PackageDiff/PackageDiff.csproj +++ b/src/PackageDiff/PackageDiff/PackageDiff.csproj @@ -2,8 +2,10 @@ Exe - $(NetToolCurrent) + net9.0 enable + true + true diff --git a/src/PackageDiff/PackageDiff/Program.cs b/src/PackageDiff/PackageDiff/Program.cs index 2905c50cd87..924c0aba645 100644 --- a/src/PackageDiff/PackageDiff/Program.cs +++ b/src/PackageDiff/PackageDiff/Program.cs @@ -156,4 +156,5 @@ public static bool TryGetDiff(List originalLines, List modifiedL formattedDiff.Reverse(); return dp[originalLines.Count, modifiedLines.Count] != 0; } + } From 52b23c7003f06b5b0083b04ceb4709dbd6b8baaf Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:58:57 -0800 Subject: [PATCH 3/3] Add ILLink dependency --- eng/Version.Details.xml | 4 ++++ eng/Versions.props | 2 ++ 2 files changed, 6 insertions(+) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index dffd50a9d37..dfe406d5897 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -155,5 +155,9 @@ https://github.com/dotnet/runtime d099f075e45d2aa6007a22b71b45a08758559f80 + + https://github.com/dotnet/runtime + + diff --git a/eng/Versions.props b/eng/Versions.props index 5fce6571659..96e436af863 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -82,5 +82,7 @@ 17.5.0 9.0.0-prerelease.24077.1 + +