From 9c6c44ad5e7d21655ca24c2170e89bf1120d43fd Mon Sep 17 00:00:00 2001 From: yhnmj6666 <15858973+yhnmj6666@users.noreply.github.com> Date: Mon, 24 Mar 2025 00:00:25 -0400 Subject: [PATCH 1/3] support multi app config by check separately --- src/BinaryCompatChecker/CommandLine.cs | 4 +- src/BinaryCompatChecker/Program.cs | 251 ++++++++++++++----------- 2 files changed, 143 insertions(+), 112 deletions(-) diff --git a/src/BinaryCompatChecker/CommandLine.cs b/src/BinaryCompatChecker/CommandLine.cs index 0ce1ce5..416d522 100644 --- a/src/BinaryCompatChecker/CommandLine.cs +++ b/src/BinaryCompatChecker/CommandLine.cs @@ -129,6 +129,8 @@ public class CommandLine public bool ResolveFromFramework => ResolveFromGac || ResolveFromNetCore; + public bool CheckPerAppConfig { get; set; } = true; + public bool OutputExpectedWarnings { get; set; } public bool OutputNewWarnings { get; set; } public bool OutputSummary { get; set; } @@ -1087,4 +1089,4 @@ Use this to limit the output to only the information required. Write(@" -replicateBindingRedirects +", ConsoleColor.White); WriteLine(); } -} \ No newline at end of file +} diff --git a/src/BinaryCompatChecker/Program.cs b/src/BinaryCompatChecker/Program.cs index 2ba3c3c..c15c076 100644 --- a/src/BinaryCompatChecker/Program.cs +++ b/src/BinaryCompatChecker/Program.cs @@ -246,6 +246,146 @@ public CheckResult Check() filesToVisit.UnionWith(fileQueue); + if (commandLine.CheckPerAppConfig && appConfigFilePaths.Count > 1) + { + Checker[] allResults = Task.WhenAll( + appConfigFilePaths.Select((appConfigFilePath, index) => + { + Queue filesForSubCheck = new Queue(fileQueue); + var task = Task.Run(() => + { + var subChecker = new Checker(commandLine); + subChecker.Check(filesForSubCheck, [appConfigFilePath]); + return subChecker; + }); + return task; + })).Result; + + assembliesExamined.AddRange(allResults.SelectMany(r => r.assembliesExamined).Distinct()); + diagnostics.UnionWith(allResults.SelectMany(r => r.diagnostics)); + ivtUsages.AddRange( + allResults.SelectMany(r => r.ivtUsages) + .DistinctBy(ivt => (ivt.ExposingAssembly, ivt.ConsumingAssembly, ivt.Member))); + } + else + { + Check(fileQueue, appConfigFilePaths); + } + + List reportLines = new(); + + foreach (var diagnostic in diagnostics.OrderBy(s => s)) + { + var text = diagnostic.Replace('\r', ' ').Replace('\n', ' '); + text = text.Replace(", Culture=neutral", ""); + reportLines.Add(text); + } + + if (File.Exists(baselineFile)) + { + var baseline = File.ReadAllLines(baselineFile); + if (!Enumerable.SequenceEqual(baseline, reportLines, StringComparer.OrdinalIgnoreCase)) + { + if (commandLine.EnableDefaultOutput || commandLine.OutputSummary) + { + if (!commandLine.IsBatchMode) + { + string customPrompt = commandLine.CustomFailurePrompt != null ? $"{Environment.NewLine}{commandLine.CustomFailurePrompt}" : ""; + WriteError($@"Binary compatibility check failed.{customPrompt} +The current report is different from the baseline file. +Baseline file: {baselineFile} +Report file: {reportFile}"); + } + } + + if (commandLine.IsBatchMode) + { + result.BaselineDiagnostics = baseline; + result.ActualDiagnostics = reportLines; + } + else + { + OutputDiff(commandLine, baseline, reportLines); + } + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(reportFile)); + File.WriteAllLines(reportFile, reportLines); + } + catch (Exception ex) + { + WriteError(ex.Message); + } + + result.Success = false; + } + else + { + if (commandLine.EnableDefaultOutput || commandLine.OutputSummary) + { + if (!commandLine.IsBatchMode) + { + WriteLine($"Binary compatibility report matches the baseline file.", ConsoleColor.Green); + } + } + } + } + else if (!File.Exists(reportFile)) + { + if (reportLines.Count > 0) + { + Directory.CreateDirectory(Path.GetDirectoryName(reportFile)); + + // initial baseline creation mode + File.WriteAllLines(reportFile, reportLines); + + if (commandLine.EnableDefaultOutput || commandLine.OutputSummary) + { + if (!commandLine.IsBatchMode) + { + WriteError("Binary compatibility check failed."); + if (!string.IsNullOrEmpty(commandLine.CustomFailurePrompt)) + { + WriteError(commandLine.CustomFailurePrompt); + } + + WriteError($"Wrote {reportFile}"); + } + } + + result.BaselineDiagnostics = Array.Empty(); + result.ActualDiagnostics = reportLines; + + if (!commandLine.IsBatchMode) + { + OutputDiff(commandLine, Array.Empty(), reportLines); + } + + result.Success = false; + } + } + + ListExaminedAssemblies(reportFile); + + if (commandLine.ReportIVT) + { + WriteIVTReport(reportFile); + + WriteIVTReport( + reportFile, + ".ivt.roslyn.txt", + u => Framework.IsRoslynAssembly(u.ExposingAssembly) && !Framework.IsRoslynAssembly(u.ConsumingAssembly)); + } + + privateAssemblyCache?.Clear(); + ((CustomAssemblyResolver)resolver).Clear(); + + return result; + } + + private void Check(Queue fileQueue, IEnumerable appConfigFilePaths) + { foreach (var appConfigFilePath in appConfigFilePaths) { bool ignoreVersionMismatch = commandLine.IgnoreVersionMismatchForAppConfigs.Contains(Path.GetFileName(appConfigFilePath), StringComparer.OrdinalIgnoreCase); @@ -397,117 +537,6 @@ void BuildClosure(IEnumerable assemblies) } } } - - List reportLines = new(); - - foreach (var diagnostic in diagnostics.OrderBy(s => s)) - { - var text = diagnostic.Replace('\r', ' ').Replace('\n', ' '); - text = text.Replace(", Culture=neutral", ""); - reportLines.Add(text); - } - - if (File.Exists(baselineFile)) - { - var baseline = File.ReadLines(baselineFile).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(); - if (!Enumerable.SequenceEqual(baseline, reportLines, StringComparer.OrdinalIgnoreCase)) - { - if (commandLine.EnableDefaultOutput || commandLine.OutputSummary) - { - if (!commandLine.IsBatchMode) - { - string customPrompt = commandLine.CustomFailurePrompt != null ? $"{Environment.NewLine}{commandLine.CustomFailurePrompt}" : ""; - WriteError($@"Binary compatibility check failed.{customPrompt} -The current report is different from the baseline file. -Baseline file: {baselineFile} -Report file: {reportFile}"); - } - } - - if (commandLine.IsBatchMode) - { - result.BaselineDiagnostics = baseline; - result.ActualDiagnostics = reportLines; - } - else - { - OutputDiff(commandLine, baseline, reportLines); - } - - try - { - Directory.CreateDirectory(Path.GetDirectoryName(reportFile)); - File.WriteAllLines(reportFile, reportLines); - } - catch (Exception ex) - { - WriteError(ex.Message); - } - - result.Success = false; - } - else - { - if (commandLine.EnableDefaultOutput || commandLine.OutputSummary) - { - if (!commandLine.IsBatchMode) - { - WriteLine($"Binary compatibility report matches the baseline file.", ConsoleColor.Green); - } - } - } - } - else if (!File.Exists(reportFile)) - { - if (reportLines.Count > 0) - { - Directory.CreateDirectory(Path.GetDirectoryName(reportFile)); - - // initial baseline creation mode - File.WriteAllLines(reportFile, reportLines); - - if (commandLine.EnableDefaultOutput || commandLine.OutputSummary) - { - if (!commandLine.IsBatchMode) - { - WriteError("Binary compatibility check failed."); - if (!string.IsNullOrEmpty(commandLine.CustomFailurePrompt)) - { - WriteError(commandLine.CustomFailurePrompt); - } - - WriteError($"Wrote {reportFile}"); - } - } - - result.BaselineDiagnostics = Array.Empty(); - result.ActualDiagnostics = reportLines; - - if (!commandLine.IsBatchMode) - { - OutputDiff(commandLine, Array.Empty(), reportLines); - } - - result.Success = false; - } - } - - ListExaminedAssemblies(reportFile); - - if (commandLine.ReportIVT) - { - WriteIVTReport(reportFile); - - WriteIVTReport( - reportFile, - ".ivt.roslyn.txt", - u => Framework.IsRoslynAssembly(u.ExposingAssembly) && !Framework.IsRoslynAssembly(u.ConsumingAssembly)); - } - - privateAssemblyCache?.Clear(); - ((CustomAssemblyResolver)resolver).Clear(); - - return result; } public void CheckAssemblyReferenceVersion( From 584c1edea19ea1761016140c956858383b5ba554 Mon Sep 17 00:00:00 2001 From: yhnmj6666 <15858973+yhnmj6666@users.noreply.github.com> Date: Mon, 24 Mar 2025 03:35:16 -0400 Subject: [PATCH 2/3] add commandline option, improve reporting --- src/BinaryCompatChecker/CommandLine.cs | 10 ++++++++- src/BinaryCompatChecker/Program.cs | 31 ++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/BinaryCompatChecker/CommandLine.cs b/src/BinaryCompatChecker/CommandLine.cs index 416d522..bd264f0 100644 --- a/src/BinaryCompatChecker/CommandLine.cs +++ b/src/BinaryCompatChecker/CommandLine.cs @@ -129,7 +129,7 @@ public class CommandLine public bool ResolveFromFramework => ResolveFromGac || ResolveFromNetCore; - public bool CheckPerAppConfig { get; set; } = true; + public bool CheckPerAppConfig { get; set; }; public bool OutputExpectedWarnings { get; set; } public bool OutputNewWarnings { get; set; } @@ -376,6 +376,13 @@ public bool Process(IList arguments) continue; } + if (argName.Equals("checkPerAppConfig", StringComparison.OrdinalIgnoreCase)) + { + CheckPerAppConfig = true; + arguments.Remove(arg); + continue; + } + if (argName.Equals("outputExpectedWarnings", StringComparison.OrdinalIgnoreCase)) { OutputExpectedWarnings = true; @@ -1061,6 +1068,7 @@ and you want to check the dependencies of one of them and the corresponding -ignoreInterfaces Do not report missing interface implementations. -doNotResolveFromGAC Do not resolve assemblies from GAC. -doNotResolveFromNetCore Do not resolve assemblies from .NET runtime directories. + -checkPerAppConfig Run the check for each app.config separately. -ivt Report internal API surface area consumed via InternalsVisibleTo. -embeddedInteropTypes Report embedded interop types. -intPtrCtors Report IntPtr constructors (Mono). diff --git a/src/BinaryCompatChecker/Program.cs b/src/BinaryCompatChecker/Program.cs index c15c076..a964386 100644 --- a/src/BinaryCompatChecker/Program.cs +++ b/src/BinaryCompatChecker/Program.cs @@ -249,7 +249,7 @@ public CheckResult Check() if (commandLine.CheckPerAppConfig && appConfigFilePaths.Count > 1) { Checker[] allResults = Task.WhenAll( - appConfigFilePaths.Select((appConfigFilePath, index) => + appConfigFilePaths.Select(appConfigFilePath => { Queue filesForSubCheck = new Queue(fileQueue); var task = Task.Run(() => @@ -261,11 +261,34 @@ public CheckResult Check() return task; })).Result; - assembliesExamined.AddRange(allResults.SelectMany(r => r.assembliesExamined).Distinct()); - diagnostics.UnionWith(allResults.SelectMany(r => r.diagnostics)); + assembliesExamined.AddRange(allResults.SelectMany(r => r.assembliesExamined).Distinct()); + + foreach (var d in allResults.SelectMany(r => r.diagnostics)) + { + bool[] reportedDiagnostics = new bool[allResults.Length]; + for (int i = 0; i < allResults.Length; i++) + { + if (allResults[i].diagnostics.Contains(d)) + { + reportedDiagnostics[i] = true; + } + } + if (reportedDiagnostics.All(x => x)) + { + diagnostics.Add(d); + } + else + { + var diagnostic = $"{d}. Not handled by: {string.Join(", ", appConfigFilePaths + .Where((_, idx) => reportedDiagnostics[idx]) + .Select(GetRelativePath))}"; + diagnostics.Add(diagnostic); + } + } + ivtUsages.AddRange( allResults.SelectMany(r => r.ivtUsages) - .DistinctBy(ivt => (ivt.ExposingAssembly, ivt.ConsumingAssembly, ivt.Member))); + .DistinctBy(ivt => (ivt.ExposingAssembly, ivt.ConsumingAssembly, ivt.Member))); } else { From 29a79a64d1140548b92e2db354508f448966aab5 Mon Sep 17 00:00:00 2001 From: yhnmj6666 <15858973+yhnmj6666@users.noreply.github.com> Date: Fri, 4 Apr 2025 01:19:39 -0400 Subject: [PATCH 3/3] sync write --- src/BinaryCompatChecker/Program.cs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/BinaryCompatChecker/Program.cs b/src/BinaryCompatChecker/Program.cs index a964386..03f3773 100644 --- a/src/BinaryCompatChecker/Program.cs +++ b/src/BinaryCompatChecker/Program.cs @@ -415,13 +415,16 @@ private void Check(Queue fileQueue, IEnumerable appConfigFilePat if (commandLine.EnableDefaultOutput && !commandLine.IsBatchMode) { - Write(appConfigFilePath, ConsoleColor.Magenta); - if (ignoreVersionMismatch) + lock (Console.Out) { - Write(" - ignoring version mismatches", ConsoleColor.DarkMagenta); - } + Write(appConfigFilePath, ConsoleColor.Magenta); + if (ignoreVersionMismatch) + { + Write(" - ignoring version mismatches", ConsoleColor.DarkMagenta); + } - WriteLine(); + WriteLine(); + } } var appConfigFileName = Path.GetFileName(appConfigFilePath); @@ -462,14 +465,17 @@ private void Check(Queue fileQueue, IEnumerable appConfigFilePat if (commandLine.EnableDefaultOutput && !commandLine.IsBatchMode) { - Write(file); - Write($" {assemblyDefinition.Name.Version}", color: ConsoleColor.DarkCyan); - if (targetFramework != null) + lock (Console.Out) { - Write($" {targetFramework}", color: ConsoleColor.DarkGreen); - } + Write(file); + Write($" {assemblyDefinition.Name.Version}", color: ConsoleColor.DarkCyan); + if (targetFramework != null) + { + Write($" {targetFramework}", color: ConsoleColor.DarkGreen); + } - WriteLine(); + WriteLine(); + } } if (Framework.IsNetFrameworkAssembly(assemblyDefinition))