From b7d8e1bbcaa86e4bb221e5501459c5a9a47d5966 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:19:22 +0000 Subject: [PATCH 1/9] Initial plan From 4ff3553294c79124c78fef713a81f34ba028c949 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:52:34 +0000 Subject: [PATCH 2/9] Support writing multiple binlogs from command line - Added AdditionalFilePaths property to BinaryLogger to track multiple output destinations - Modified ProcessBinaryLogger to collect all distinct binlog paths from multiple -bl flags - Updated BinaryLogger.Shutdown to copy the binlog to all additional paths - Added ExtractFilePathFromParameters helper method to parse file paths from parameters - Added error message resource for copy failures Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../Logging/BinaryLogger/BinaryLogger.cs | 109 ++++++++++++++++++ src/Build/Resources/Strings.resx | 6 +- src/Build/Resources/xlf/Strings.cs.xlf | 5 + src/Build/Resources/xlf/Strings.de.xlf | 5 + src/Build/Resources/xlf/Strings.es.xlf | 5 + src/Build/Resources/xlf/Strings.fr.xlf | 5 + src/Build/Resources/xlf/Strings.it.xlf | 5 + src/Build/Resources/xlf/Strings.ja.xlf | 5 + src/Build/Resources/xlf/Strings.ko.xlf | 5 + src/Build/Resources/xlf/Strings.pl.xlf | 5 + src/Build/Resources/xlf/Strings.pt-BR.xlf | 5 + src/Build/Resources/xlf/Strings.ru.xlf | 5 + src/Build/Resources/xlf/Strings.tr.xlf | 5 + src/Build/Resources/xlf/Strings.zh-Hans.xlf | 5 + src/Build/Resources/xlf/Strings.zh-Hant.xlf | 5 + src/MSBuild/XMake.cs | 33 +++++- 16 files changed, 210 insertions(+), 3 deletions(-) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 5cbc5c4f5e8..5a58321cab9 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -2,6 +2,7 @@ // 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 Microsoft.Build.Experimental.BuildCheck.Infrastructure.EditorConfig; @@ -137,6 +138,12 @@ public enum ProjectImportsCollectionMode internal string FilePath { get; private set; } + /// + /// Gets or sets additional output file paths. When set, the binlog will be copied to all these paths + /// after the build completes. The primary FilePath will be used as the temporary write location. + /// + public List AdditionalFilePaths { get; set; } + /// Gets or sets the verbosity level. /// /// The binary logger Verbosity is always maximum (Diagnostic). It tries to capture as much @@ -355,6 +362,38 @@ public void Shutdown() stream.Dispose(); stream = null; } + + // Copy the binlog file to additional destinations if specified + if (AdditionalFilePaths != null && AdditionalFilePaths.Count > 0) + { + foreach (var additionalPath in AdditionalFilePaths) + { + try + { + string directory = Path.GetDirectoryName(additionalPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + File.Copy(FilePath, additionalPath, overwrite: true); + } + catch (Exception ex) + { + // Log the error but don't fail the build + string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword( + out string errorCode, + out string helpKeyword, + "ErrorCopyingBinaryLog", + FilePath, + additionalPath, + ex.Message); + + // Note: We can't write this error to the binlog itself since the stream is already closed + // The error will be logged to the console or other active loggers + Console.Error.WriteLine(message); + } + } + } } private void RawEvents_LogDataSliceReceived(BinaryLogRecordKind recordKind, Stream stream) @@ -514,5 +553,75 @@ private string GetUniqueStamp() private static string ExpandPathParameter(string parameters) => $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}--{EnvironmentUtilities.CurrentProcessId}--{StringUtils.GenerateRandomString(6)}"; + + /// + /// Extracts the file path from binary logger parameters string. + /// This is a helper method for processing multiple binlog parameters. + /// + /// The parameters string (e.g., "output.binlog" or "output.binlog;ProjectImports=None") + /// The resolved file path, or "msbuild.binlog" if no path is specified + public static string ExtractFilePathFromParameters(string parameters) + { + if (string.IsNullOrEmpty(parameters)) + { + return Path.GetFullPath("msbuild.binlog"); + } + + var paramParts = parameters.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); + string filePath = null; + + foreach (var parameter in paramParts) + { + if (TryInterpretPathParameterStatic(parameter, out string extractedPath)) + { + filePath = extractedPath; + break; + } + } + + if (filePath == null) + { + filePath = "msbuild.binlog"; + } + + try + { + return Path.GetFullPath(filePath); + } + catch + { + // If path resolution fails, return the original path + return filePath; + } + } + + private static bool TryInterpretPathParameterStatic(string parameter, out string filePath) + { + bool hasPathPrefix = parameter.StartsWith("LogFile=", StringComparison.OrdinalIgnoreCase); + + if (hasPathPrefix) + { + parameter = parameter.Substring("LogFile=".Length); + } + + parameter = parameter.Trim('"'); + + bool isWildcard = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_12) && parameter.Contains("{}"); + bool hasProperExtension = parameter.EndsWith(".binlog", StringComparison.OrdinalIgnoreCase); + filePath = parameter; + + if (!isWildcard) + { + return hasProperExtension; + } + + filePath = parameter.Replace("{}", ExpandPathParameter(string.Empty), StringComparison.Ordinal); + + if (!hasProperExtension) + { + filePath += ".binlog"; + } + return true; + } } } diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index ba039159363..23cb9a697a3 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -2449,10 +2449,14 @@ Utilization: {0} Average Utilization: {1:###.0} The directory does not exist: {0}. .NET Runtime Task Host could not be instantiated. See https://aka.ms/nettaskhost for details on how to resolve this error. + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 25c7cd53ecf..dc0fd9cd435 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -484,6 +484,11 @@ Číst proměnnou prostředí {0} + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: Při čtení vstupních souborů mezipaměti pro výsledky z cesty {0} byla zjištěna chyba: {1} diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 772ecb9a0b1..4a9c5defb7c 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -484,6 +484,11 @@ Umgebungsvariable "{0}" lesen + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: Beim Lesen der Cachedateien für Eingabeergebnisse aus dem Pfad "{0}" wurde ein Fehler festgestellt: {1} diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index afc0dee6a76..baee8d14d67 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -484,6 +484,11 @@ Leer la variable de entorno "{0}" + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: Error al leer los archivos de caché de resultados de entrada en la ruta de acceso "{0}": {1} diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 42030382266..9411affc2b0 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -484,6 +484,11 @@ Lire la variable d'environnement "{0}" + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: La lecture des fichiers cache des résultats d'entrée à partir du chemin "{0}" a rencontré une erreur : {1} diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 8d4a71db6cc..cbddcc65063 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -484,6 +484,11 @@ Legge la variabile di ambiente "{0}" + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: durante la lettura dei file della cache dei risultati di input dal percorso "{0}" è stato rilevato un errore: {1} diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index c2d1232e5c3..d084d211181 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -484,6 +484,11 @@ 環境変数 "{0}" の読み取り + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: パス "{0}" から入力結果キャッシュ ファイルを読み取る処理でエラーが発生しました: {1} diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 0ba356c02dd..52fba07a9da 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -484,6 +484,11 @@ 환경 변수 "{0}" 읽기 + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: "{0}" 경로에서 입력 결과 캐시 파일을 읽는 중 오류가 발생했습니다. {1} diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index db557e607b7..074ed4e5576 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -484,6 +484,11 @@ Odczytaj zmienną środowiskową „{0}” + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: Podczas odczytywania plików wejściowej pamięci podręcznej wyników ze ścieżki „{0}” wystąpił błąd: {1} diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 0fdc6c3a661..fa2c8439844 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -484,6 +484,11 @@ Ler a variável de ambiente "{0}" + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: a leitura dos arquivos de cache do resultado de entrada do caminho "{0}" encontrou um erro: {1} diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 65fae70a6b4..5cc22d1a111 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -484,6 +484,11 @@ Чтение переменной среды "{0}" + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: произошла ошибка при чтении входных файлов кэша результатов из пути "{0}": {1} diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index d7a5b73f457..f821c46de29 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -484,6 +484,11 @@ "{0}" ortam değişkenini oku + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: "{0}" yolundan giriş sonucu önbellek dosyaları okunurken bir hatayla karşılaşıldı: {1} diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 38c29a92a28..34ccc3065ae 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -484,6 +484,11 @@ 读取环境变量“{0}” + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: 从路径“{0}”读取输入结果缓存文件时遇到错误: {1} diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 67c2885adde..49473355c29 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -484,6 +484,11 @@ 讀取環境變數 "{0}" + + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} + {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + MSB4256: Reading input result cache files from path "{0}" encountered an error: {1} MSB4256: 從路徑 "{0}" 讀取輸入結果快取檔案發生錯誤: {1} diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index e5e12b80ae2..ba543ded2f4 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -3949,9 +3949,38 @@ private static void ProcessBinaryLogger(string[] binaryLoggerParameters, List 1) + { + var filePaths = new HashSet(StringComparer.OrdinalIgnoreCase); + var additionalFilePaths = new List(); + + // Extract the primary file path (will be resolved during Initialize) + string primaryPath = BinaryLogger.ExtractFilePathFromParameters(primaryArguments); + filePaths.Add(primaryPath); + + // Process additional parameters and extract their file paths + for (int i = 1; i < binaryLoggerParameters.Length; i++) + { + string filePath = BinaryLogger.ExtractFilePathFromParameters(binaryLoggerParameters[i]); + + // Only add if it's distinct + if (filePaths.Add(filePath)) + { + additionalFilePaths.Add(filePath); + } + } + + // Set the additional paths on the primary logger + if (additionalFilePaths.Count > 0) + { + logger.AdditionalFilePaths = additionalFilePaths; + } + } // If we have a binary logger, force verbosity to diagnostic. // The only place where verbosity is used downstream is to determine whether to log task inputs. From 3274de96eef25a8bdb0f236e1a2a7878de1100c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:03:22 +0000 Subject: [PATCH 3/9] Add tests for multiple binary logger support - Added MultipleBinaryLogsCreatesMultipleFiles test to verify all specified binlog files are created - Added MultipleBinaryLogsWithDuplicatesCreateDistinctFiles test to verify duplicates are handled correctly - Both tests verify files are identical Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- src/MSBuild.UnitTests/XMake_Tests.cs | 70 ++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index f5de4715ccb..8145e357cc5 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -2655,6 +2655,76 @@ public void BinaryLogContainsImportedFiles() archive.Entries.ShouldContain(e => e.FullName.EndsWith(".proj", StringComparison.OrdinalIgnoreCase), 2); } + [Fact] + public void MultipleBinaryLogsCreatesMultipleFiles() + { + var testProject = _env.CreateFile("TestProject.proj", @" + + + + + + "); + + string binLogLocation = _env.DefaultTestDirectory.Path; + string binLog1 = Path.Combine(binLogLocation, "1.binlog"); + string binLog2 = Path.Combine(binLogLocation, "2.binlog"); + string binLog3 = Path.Combine(binLogLocation, "3.binlog"); + + string output = RunnerUtilities.ExecMSBuild($"\"{testProject.Path}\" \"/bl:{binLog1}\" \"/bl:{binLog2}\" \"/bl:{binLog3}\"", out var success, _output); + + success.ShouldBeTrue(output); + + // Verify all three binlog files exist + File.Exists(binLog1).ShouldBeTrue("First binlog file should exist"); + File.Exists(binLog2).ShouldBeTrue("Second binlog file should exist"); + File.Exists(binLog3).ShouldBeTrue("Third binlog file should exist"); + + // Verify all files have content (are not empty) + new FileInfo(binLog1).Length.ShouldBeGreaterThan(0, "First binlog should not be empty"); + new FileInfo(binLog2).Length.ShouldBeGreaterThan(0, "Second binlog should not be empty"); + new FileInfo(binLog3).Length.ShouldBeGreaterThan(0, "Third binlog should not be empty"); + + // Verify all files are identical (have the same content) + byte[] file1Bytes = File.ReadAllBytes(binLog1); + byte[] file2Bytes = File.ReadAllBytes(binLog2); + byte[] file3Bytes = File.ReadAllBytes(binLog3); + + file1Bytes.SequenceEqual(file2Bytes).ShouldBeTrue("First and second binlog should be identical"); + file1Bytes.SequenceEqual(file3Bytes).ShouldBeTrue("First and third binlog should be identical"); + } + + [Fact] + public void MultipleBinaryLogsWithDuplicatesCreateDistinctFiles() + { + var testProject = _env.CreateFile("TestProject.proj", @" + + + + + + "); + + string binLogLocation = _env.DefaultTestDirectory.Path; + string binLog1 = Path.Combine(binLogLocation, "1.binlog"); + string binLog2 = Path.Combine(binLogLocation, "2.binlog"); + + // Specify binLog1 twice - should only create two distinct files + string output = RunnerUtilities.ExecMSBuild($"\"{testProject.Path}\" \"/bl:{binLog1}\" \"/bl:{binLog2}\" \"/bl:{binLog1}\"", out var success, _output); + + success.ShouldBeTrue(output); + + // Verify both binlog files exist + File.Exists(binLog1).ShouldBeTrue("First binlog file should exist"); + File.Exists(binLog2).ShouldBeTrue("Second binlog file should exist"); + + // Verify both files are identical + byte[] file1Bytes = File.ReadAllBytes(binLog1); + byte[] file2Bytes = File.ReadAllBytes(binLog2); + + file1Bytes.SequenceEqual(file2Bytes).ShouldBeTrue("Binlog files should be identical"); + } + [Theory] [InlineData("-warnaserror", "", "", false)] [InlineData("-warnaserror -warnnotaserror:FOR123", "", "", true)] From 501181e4cbf553318accc1bbd6730fb3e58a0249 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:11:23 +0000 Subject: [PATCH 4/9] Support multiple binlogs with different configurations - Enhanced ProcessBinaryLogger to detect configuration differences - If all binlog options only differ in file paths, use optimized copy approach - If configurations differ (e.g., ProjectImports settings), create separate logger instances - Added ExtractNonPathParameters method to compare configurations - Added test for different configurations creating separate loggers - This approach respects all configuration options while optimizing when possible Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../Logging/BinaryLogger/BinaryLogger.cs | 33 ++++++++ src/MSBuild.UnitTests/XMake_Tests.cs | 40 +++++++++ src/MSBuild/XMake.cs | 84 +++++++++++++------ 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 5a58321cab9..31c67ce81d3 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -623,5 +623,38 @@ private static bool TryInterpretPathParameterStatic(string parameter, out string } return true; } + + /// + /// Extracts the non-file-path parameters from binary logger parameters string. + /// This is used to compare configurations between multiple binlog parameters. + /// + /// The parameters string (e.g., "output.binlog;ProjectImports=None") + /// A normalized string of non-path parameters, or empty string if only path parameters + public static string ExtractNonPathParameters(string parameters) + { + if (string.IsNullOrEmpty(parameters)) + { + return string.Empty; + } + + var paramParts = parameters.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); + var nonPathParams = new List(); + + foreach (var parameter in paramParts) + { + // Skip file path parameters + if (TryInterpretPathParameterStatic(parameter, out _)) + { + continue; + } + + // This is a configuration parameter (like ProjectImports=None, OmitInitialInfo, etc.) + nonPathParams.Add(parameter); + } + + // Sort for consistent comparison + nonPathParams.Sort(StringComparer.OrdinalIgnoreCase); + return string.Join(";", nonPathParams); + } } } diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index 8145e357cc5..95efe235d31 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -2725,6 +2725,46 @@ public void MultipleBinaryLogsWithDuplicatesCreateDistinctFiles() file1Bytes.SequenceEqual(file2Bytes).ShouldBeTrue("Binlog files should be identical"); } + [Fact] + public void MultipleBinaryLogsWithDifferentConfigurationsCreatesSeparateLoggers() + { + var testProject = _env.CreateFile("TestProject.proj", @" + + + + + + + "); + + _env.CreateFile("Imported.proj", @" + + + Value + + + "); + + string binLogLocation = _env.DefaultTestDirectory.Path; + string binLog1 = Path.Combine(binLogLocation, "with-imports.binlog"); + string binLog2 = Path.Combine(binLogLocation, "no-imports.binlog"); + + // One with default imports, one with ProjectImports=None + string output = RunnerUtilities.ExecMSBuild($"\"{testProject.Path}\" \"/bl:{binLog1}\" \"/bl:{binLog2};ProjectImports=None\"", out var success, _output); + + success.ShouldBeTrue(output); + + // Verify both binlog files exist + File.Exists(binLog1).ShouldBeTrue("First binlog file should exist"); + File.Exists(binLog2).ShouldBeTrue("Second binlog file should exist"); + + // Verify files are different sizes (one has imports embedded, one doesn't) + long size1 = new FileInfo(binLog1).Length; + long size2 = new FileInfo(binLog2).Length; + + size1.ShouldBeGreaterThan(size2, "Binlog with imports should be larger than one without"); + } + [Theory] [InlineData("-warnaserror", "", "", false)] [InlineData("-warnaserror -warnnotaserror:FOR123", "", "", true)] diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index ba543ded2f4..8c9fc96c16a 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -3949,45 +3949,75 @@ private static void ProcessBinaryLogger(string[] binaryLoggerParameters, List 1) + if (binaryLoggerParameters.Length == 1) { - var filePaths = new HashSet(StringComparer.OrdinalIgnoreCase); - var additionalFilePaths = new List(); + // Simple case: single binary logger + BinaryLogger logger = new BinaryLogger { Parameters = binaryLoggerParameters[0] }; + loggers.Add(logger); + return; + } - // Extract the primary file path (will be resolved during Initialize) - string primaryPath = BinaryLogger.ExtractFilePathFromParameters(primaryArguments); - filePaths.Add(primaryPath); + // Multiple binlog parameters - determine if we can use the optimized single-write approach + // or need to create separate logger instances + string primaryArguments = binaryLoggerParameters[0]; + string primaryNonPathParams = BinaryLogger.ExtractNonPathParameters(primaryArguments); - // Process additional parameters and extract their file paths - for (int i = 1; i < binaryLoggerParameters.Length; i++) - { - string filePath = BinaryLogger.ExtractFilePathFromParameters(binaryLoggerParameters[i]); + bool allConfigurationsIdentical = true; + var distinctFilePaths = new HashSet(StringComparer.OrdinalIgnoreCase); + var distinctParameterSets = new List(); + + // Check if all parameter sets have the same non-path configuration + for (int i = 0; i < binaryLoggerParameters.Length; i++) + { + string currentParams = binaryLoggerParameters[i]; + string currentNonPathParams = BinaryLogger.ExtractNonPathParameters(currentParams); + string currentFilePath = BinaryLogger.ExtractFilePathFromParameters(currentParams); - // Only add if it's distinct - if (filePaths.Add(filePath)) + // Check if this is a duplicate file path + if (distinctFilePaths.Add(currentFilePath)) + { + // Only add if configuration matches the primary + if (string.Equals(primaryNonPathParams, currentNonPathParams, StringComparison.OrdinalIgnoreCase)) + { + distinctParameterSets.Add(currentParams); + } + else { - additionalFilePaths.Add(filePath); + allConfigurationsIdentical = false; + // Still add it - we'll create separate loggers below + distinctParameterSets.Add(currentParams); } } + } - // Set the additional paths on the primary logger - if (additionalFilePaths.Count > 0) + if (allConfigurationsIdentical && distinctParameterSets.Count > 1) + { + // Optimized approach: single logger writing to one file, then copy to additional locations + BinaryLogger logger = new BinaryLogger { Parameters = primaryArguments }; + + var additionalFilePaths = new List(); + for (int i = 1; i < distinctParameterSets.Count; i++) { - logger.AdditionalFilePaths = additionalFilePaths; + additionalFilePaths.Add(BinaryLogger.ExtractFilePathFromParameters(distinctParameterSets[i])); + } + + logger.AdditionalFilePaths = additionalFilePaths; + loggers.Add(logger); + } + else + { + // Create separate logger instances for each distinct configuration + foreach (string paramSet in distinctParameterSets) + { + BinaryLogger logger = new BinaryLogger { Parameters = paramSet }; + loggers.Add(logger); } } - - // If we have a binary logger, force verbosity to diagnostic. - // The only place where verbosity is used downstream is to determine whether to log task inputs. - // Since we always want task inputs for a binary logger, set it to diagnostic. - verbosity = LoggerVerbosity.Diagnostic; - - loggers.Add(logger); } /// From 0403ab700f5f3446d37cf2997895a03353205f57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:59:31 +0000 Subject: [PATCH 5/9] Document AdditionalFilePaths as internal-use only - Added remarks to AdditionalFilePaths clarifying it's for MSBuild command-line processing - Documented that external code should use multiple logger instances instead - Addressed feedback that this shouldn't be part of the public logger API surface Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- src/Build/Logging/BinaryLogger/BinaryLogger.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 31c67ce81d3..f68a78cc047 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -142,6 +142,11 @@ public enum ProjectImportsCollectionMode /// Gets or sets additional output file paths. When set, the binlog will be copied to all these paths /// after the build completes. The primary FilePath will be used as the temporary write location. /// + /// + /// This property is intended for internal use by MSBuild command-line processing. + /// It should not be set by external code or logger implementations. + /// Use multiple logger instances with different Parameters instead. + /// public List AdditionalFilePaths { get; set; } /// Gets or sets the verbosity level. From 0a591a7c9fba99b7a3000eeadbad284c9ca4bbcc Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:23:33 +0100 Subject: [PATCH 6/9] Update error messages in Strings.resx --- src/Build/Resources/Strings.resx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 062f04ccd1c..ad1c58e297f 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -2452,6 +2452,7 @@ Utilization: {0} Average Utilization: {1:###.0} MSB4279: Failed to copy binary log from "{0}" to "{1}". {2} {StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations. + The custom task '{0}' required a fallback to out-of-process execution because the UsingTask definition does not specify the correct Runtime and Architecture. This reduces build performance. Update the UsingTask element to explicitly specify Runtime and Architecture attributes (e.g., Runtime="CLR4" Architecture="x64") or use TaskFactory="TaskHostFactory". From 8685237d3e32a1f7c3838112ff6453133a3e7c57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:43:03 +0000 Subject: [PATCH 7/9] Address PR feedback: use LogMessage and move logic to BinaryLogger - Use LogMessage to log copy destinations before stream close - Throw LoggerException for copy errors (can't use LogMessage after stream close) - Move distinct paths processing logic to BinaryLogger.ProcessParameters() - Add ProcessedBinaryLoggerParameters class to encapsulate results - Simplify XMake.ProcessBinaryLogger() to use the new method Co-authored-by: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> --- .../Logging/BinaryLogger/BinaryLogger.cs | 91 ++++++++++++++++++- src/MSBuild/XMake.cs | 56 ++---------- 2 files changed, 96 insertions(+), 51 deletions(-) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 99fd5b6d9e6..6b81a880258 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -517,6 +517,15 @@ public void Shutdown() } + // Log additional file paths before closing stream (so they're recorded in the binlog) + if (AdditionalFilePaths != null && AdditionalFilePaths.Count > 0 && stream != null) + { + foreach (var additionalPath in AdditionalFilePaths) + { + LogMessage("BinLogCopyDestination=" + additionalPath); + } + } + if (stream != null) { // It's hard to determine whether we're at the end of decoding GZipStream @@ -544,6 +553,7 @@ public void Shutdown() catch (Exception ex) { // Log the error but don't fail the build + // Note: We can't use LogMessage here since the stream is already closed string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword( out string errorCode, out string helpKeyword, @@ -552,9 +562,7 @@ public void Shutdown() additionalPath, ex.Message); - // Note: We can't write this error to the binlog itself since the stream is already closed - // The error will be logged to the console or other active loggers - Console.Error.WriteLine(message); + throw new LoggerException(message, ex, errorCode, helpKeyword); } } } @@ -815,5 +823,82 @@ public static string ExtractNonPathParameters(string parameters) nonPathParams.Sort(StringComparer.OrdinalIgnoreCase); return string.Join(";", nonPathParams); } + + /// + /// Result of processing multiple binary logger parameter sets. + /// + public class ProcessedBinaryLoggerParameters + { + /// + /// List of distinct parameter sets that need separate logger instances. + /// + public List DistinctParameterSets { get; set; } = new List(); + + /// + /// If true, all parameter sets have identical configurations (only file paths differ), + /// so a single logger can be used with file copying for additional paths. + /// + public bool AllConfigurationsIdentical { get; set; } = true; + + /// + /// Additional file paths to copy the binlog to (only valid when AllConfigurationsIdentical is true). + /// + public List AdditionalFilePaths { get; set; } = new List(); + } + + /// + /// Processes multiple binary logger parameter sets and returns distinct paths and configuration info. + /// + /// Array of parameter strings from command line + /// Processed result with distinct parameter sets and configuration info + public static ProcessedBinaryLoggerParameters ProcessParameters(string[] binaryLoggerParameters) + { + var result = new ProcessedBinaryLoggerParameters(); + + if (binaryLoggerParameters == null || binaryLoggerParameters.Length == 0) + { + return result; + } + + if (binaryLoggerParameters.Length == 1) + { + result.DistinctParameterSets.Add(binaryLoggerParameters[0]); + return result; + } + + string primaryArguments = binaryLoggerParameters[0]; + string primaryNonPathParams = ExtractNonPathParameters(primaryArguments); + + var distinctFilePaths = new HashSet(StringComparer.OrdinalIgnoreCase); + + // Check if all parameter sets have the same non-path configuration + for (int i = 0; i < binaryLoggerParameters.Length; i++) + { + string currentParams = binaryLoggerParameters[i]; + string currentNonPathParams = ExtractNonPathParameters(currentParams); + string currentFilePath = ExtractFilePathFromParameters(currentParams); + + // Check if this is a duplicate file path + if (distinctFilePaths.Add(currentFilePath)) + { + if (!string.Equals(primaryNonPathParams, currentNonPathParams, StringComparison.OrdinalIgnoreCase)) + { + result.AllConfigurationsIdentical = false; + } + result.DistinctParameterSets.Add(currentParams); + } + } + + // If all configurations are identical, compute additional file paths for copying + if (result.AllConfigurationsIdentical && result.DistinctParameterSets.Count > 1) + { + for (int i = 1; i < result.DistinctParameterSets.Count; i++) + { + result.AdditionalFilePaths.Add(ExtractFilePathFromParameters(result.DistinctParameterSets[i])); + } + } + + return result; + } } } diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 3bc36dde063..a8c2358e630 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -4053,65 +4053,25 @@ private static void ProcessBinaryLogger(string[] binaryLoggerParameters, List(StringComparer.OrdinalIgnoreCase); - var distinctParameterSets = new List(); - - // Check if all parameter sets have the same non-path configuration - for (int i = 0; i < binaryLoggerParameters.Length; i++) + if (processedParams.DistinctParameterSets.Count == 0) { - string currentParams = binaryLoggerParameters[i]; - string currentNonPathParams = BinaryLogger.ExtractNonPathParameters(currentParams); - string currentFilePath = BinaryLogger.ExtractFilePathFromParameters(currentParams); - - // Check if this is a duplicate file path - if (distinctFilePaths.Add(currentFilePath)) - { - // Only add if configuration matches the primary - if (string.Equals(primaryNonPathParams, currentNonPathParams, StringComparison.OrdinalIgnoreCase)) - { - distinctParameterSets.Add(currentParams); - } - else - { - allConfigurationsIdentical = false; - // Still add it - we'll create separate loggers below - distinctParameterSets.Add(currentParams); - } - } + return; } - if (allConfigurationsIdentical && distinctParameterSets.Count > 1) + if (processedParams.AllConfigurationsIdentical && processedParams.AdditionalFilePaths.Count > 0) { // Optimized approach: single logger writing to one file, then copy to additional locations - BinaryLogger logger = new BinaryLogger { Parameters = primaryArguments }; - - var additionalFilePaths = new List(); - for (int i = 1; i < distinctParameterSets.Count; i++) - { - additionalFilePaths.Add(BinaryLogger.ExtractFilePathFromParameters(distinctParameterSets[i])); - } - - logger.AdditionalFilePaths = additionalFilePaths; + BinaryLogger logger = new BinaryLogger { Parameters = processedParams.DistinctParameterSets[0] }; + logger.AdditionalFilePaths = processedParams.AdditionalFilePaths; loggers.Add(logger); } else { // Create separate logger instances for each distinct configuration - foreach (string paramSet in distinctParameterSets) + foreach (string paramSet in processedParams.DistinctParameterSets) { BinaryLogger logger = new BinaryLogger { Parameters = paramSet }; loggers.Add(logger); From d1e0aae88bcd1c9fab287e8648ca28349d56f2c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:14:12 +0000 Subject: [PATCH 8/9] Address PR feedback: use constants, add docs, convert to readonly struct - Replace hardcoded ".binlog" with BinlogFileExtension constant - Replace hardcoded "LogFile=" with LogFileParameterPrefix constant - Add XML documentation to TryInterpretPathParameterStatic method - Convert ProcessedBinaryLoggerParameters from class to readonly struct - Change AdditionalFilePaths and DistinctParameterSets to IReadOnlyList Co-authored-by: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> --- .../Logging/BinaryLogger/BinaryLogger.cs | 68 +++++++++++++------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 6b81a880258..68c07eefdc3 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -306,7 +306,7 @@ private static bool TryParsePathParameter(string parameter, out string filePath) /// It should not be set by external code or logger implementations. /// Use multiple logger instances with different Parameters instead. /// - public List AdditionalFilePaths { get; set; } + public IReadOnlyList AdditionalFilePaths { get; set; } /// Gets or sets the verbosity level. /// @@ -729,9 +729,11 @@ private static string ExpandPathParameter(string parameters) /// The resolved file path, or "msbuild.binlog" if no path is specified public static string ExtractFilePathFromParameters(string parameters) { + const string DefaultBinlogFileName = "msbuild" + BinlogFileExtension; + if (string.IsNullOrEmpty(parameters)) { - return Path.GetFullPath("msbuild.binlog"); + return Path.GetFullPath(DefaultBinlogFileName); } var paramParts = parameters.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); @@ -748,7 +750,7 @@ public static string ExtractFilePathFromParameters(string parameters) if (filePath == null) { - filePath = "msbuild.binlog"; + filePath = DefaultBinlogFileName; } try @@ -762,19 +764,25 @@ public static string ExtractFilePathFromParameters(string parameters) } } + /// + /// Attempts to interpret a parameter string as a file path. + /// + /// The parameter to interpret (e.g., "LogFile=output.binlog" or "output.binlog") + /// The extracted file path if the parameter is a path, otherwise the original parameter + /// True if the parameter is a valid file path (ends with .binlog or contains wildcards), false otherwise private static bool TryInterpretPathParameterStatic(string parameter, out string filePath) { - bool hasPathPrefix = parameter.StartsWith("LogFile=", StringComparison.OrdinalIgnoreCase); + bool hasPathPrefix = parameter.StartsWith(LogFileParameterPrefix, StringComparison.OrdinalIgnoreCase); if (hasPathPrefix) { - parameter = parameter.Substring("LogFile=".Length); + parameter = parameter.Substring(LogFileParameterPrefix.Length); } parameter = parameter.Trim('"'); bool isWildcard = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_12) && parameter.Contains("{}"); - bool hasProperExtension = parameter.EndsWith(".binlog", StringComparison.OrdinalIgnoreCase); + bool hasProperExtension = parameter.EndsWith(BinlogFileExtension, StringComparison.OrdinalIgnoreCase); filePath = parameter; if (!isWildcard) @@ -786,7 +794,7 @@ private static bool TryInterpretPathParameterStatic(string parameter, out string if (!hasProperExtension) { - filePath += ".binlog"; + filePath += BinlogFileExtension; } return true; } @@ -827,23 +835,39 @@ public static string ExtractNonPathParameters(string parameters) /// /// Result of processing multiple binary logger parameter sets. /// - public class ProcessedBinaryLoggerParameters + public readonly struct ProcessedBinaryLoggerParameters { /// /// List of distinct parameter sets that need separate logger instances. /// - public List DistinctParameterSets { get; set; } = new List(); + public IReadOnlyList DistinctParameterSets { get; } /// /// If true, all parameter sets have identical configurations (only file paths differ), /// so a single logger can be used with file copying for additional paths. /// - public bool AllConfigurationsIdentical { get; set; } = true; + public bool AllConfigurationsIdentical { get; } /// /// Additional file paths to copy the binlog to (only valid when AllConfigurationsIdentical is true). /// - public List AdditionalFilePaths { get; set; } = new List(); + public IReadOnlyList AdditionalFilePaths { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// List of distinct parameter sets that need separate logger instances. + /// Whether all parameter sets have identical configurations. + /// Additional file paths to copy the binlog to. + public ProcessedBinaryLoggerParameters( + IReadOnlyList distinctParameterSets, + bool allConfigurationsIdentical, + IReadOnlyList additionalFilePaths) + { + DistinctParameterSets = distinctParameterSets; + AllConfigurationsIdentical = allConfigurationsIdentical; + AdditionalFilePaths = additionalFilePaths; + } } /// @@ -853,17 +877,19 @@ public class ProcessedBinaryLoggerParameters /// Processed result with distinct parameter sets and configuration info public static ProcessedBinaryLoggerParameters ProcessParameters(string[] binaryLoggerParameters) { - var result = new ProcessedBinaryLoggerParameters(); + var distinctParameterSets = new List(); + var additionalFilePaths = new List(); + bool allConfigurationsIdentical = true; if (binaryLoggerParameters == null || binaryLoggerParameters.Length == 0) { - return result; + return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths); } if (binaryLoggerParameters.Length == 1) { - result.DistinctParameterSets.Add(binaryLoggerParameters[0]); - return result; + distinctParameterSets.Add(binaryLoggerParameters[0]); + return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths); } string primaryArguments = binaryLoggerParameters[0]; @@ -883,22 +909,22 @@ public static ProcessedBinaryLoggerParameters ProcessParameters(string[] binaryL { if (!string.Equals(primaryNonPathParams, currentNonPathParams, StringComparison.OrdinalIgnoreCase)) { - result.AllConfigurationsIdentical = false; + allConfigurationsIdentical = false; } - result.DistinctParameterSets.Add(currentParams); + distinctParameterSets.Add(currentParams); } } // If all configurations are identical, compute additional file paths for copying - if (result.AllConfigurationsIdentical && result.DistinctParameterSets.Count > 1) + if (allConfigurationsIdentical && distinctParameterSets.Count > 1) { - for (int i = 1; i < result.DistinctParameterSets.Count; i++) + for (int i = 1; i < distinctParameterSets.Count; i++) { - result.AdditionalFilePaths.Add(ExtractFilePathFromParameters(result.DistinctParameterSets[i])); + additionalFilePaths.Add(ExtractFilePathFromParameters(distinctParameterSets[i])); } } - return result; + return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths); } } } From 152cdea1620515e466c5f67091690b78e8f8e314 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:25:40 +0000 Subject: [PATCH 9/9] Add message for duplicate binary log paths that are filtered out When users specify the same binary log path multiple times, they will now see a message indicating which duplicate paths were ignored. This helps users identify potential mistakes in their command-line arguments. Co-authored-by: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> --- .../Logging/BinaryLogger/BinaryLogger.cs | 22 +++++++++++++++---- src/MSBuild/Resources/Strings.resx | 4 ++++ src/MSBuild/Resources/xlf/Strings.cs.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.de.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.es.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.fr.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.it.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.ja.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.ko.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.pl.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.pt-BR.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.ru.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.tr.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf | 5 +++++ src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf | 5 +++++ src/MSBuild/XMake.cs | 6 +++++ 16 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 68c07eefdc3..a2a462c862d 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -853,20 +853,28 @@ public readonly struct ProcessedBinaryLoggerParameters /// public IReadOnlyList AdditionalFilePaths { get; } + /// + /// List of duplicate file paths that were filtered out. + /// + public IReadOnlyList DuplicateFilePaths { get; } + /// /// Initializes a new instance of the struct. /// /// List of distinct parameter sets that need separate logger instances. /// Whether all parameter sets have identical configurations. /// Additional file paths to copy the binlog to. + /// List of duplicate file paths that were filtered out. public ProcessedBinaryLoggerParameters( IReadOnlyList distinctParameterSets, bool allConfigurationsIdentical, - IReadOnlyList additionalFilePaths) + IReadOnlyList additionalFilePaths, + IReadOnlyList duplicateFilePaths) { DistinctParameterSets = distinctParameterSets; AllConfigurationsIdentical = allConfigurationsIdentical; AdditionalFilePaths = additionalFilePaths; + DuplicateFilePaths = duplicateFilePaths; } } @@ -879,17 +887,18 @@ public static ProcessedBinaryLoggerParameters ProcessParameters(string[] binaryL { var distinctParameterSets = new List(); var additionalFilePaths = new List(); + var duplicateFilePaths = new List(); bool allConfigurationsIdentical = true; if (binaryLoggerParameters == null || binaryLoggerParameters.Length == 0) { - return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths); + return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths, duplicateFilePaths); } if (binaryLoggerParameters.Length == 1) { distinctParameterSets.Add(binaryLoggerParameters[0]); - return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths); + return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths, duplicateFilePaths); } string primaryArguments = binaryLoggerParameters[0]; @@ -913,6 +922,11 @@ public static ProcessedBinaryLoggerParameters ProcessParameters(string[] binaryL } distinctParameterSets.Add(currentParams); } + else + { + // Track duplicate paths for logging + duplicateFilePaths.Add(currentFilePath); + } } // If all configurations are identical, compute additional file paths for copying @@ -924,7 +938,7 @@ public static ProcessedBinaryLoggerParameters ProcessParameters(string[] binaryL } } - return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths); + return new ProcessedBinaryLoggerParameters(distinctParameterSets, allConfigurationsIdentical, additionalFilePaths, duplicateFilePaths); } } } diff --git a/src/MSBuild/Resources/Strings.resx b/src/MSBuild/Resources/Strings.resx index bf1e7bc6020..7fdf9799eb5 100644 --- a/src/MSBuild/Resources/Strings.resx +++ b/src/MSBuild/Resources/Strings.resx @@ -1700,6 +1700,10 @@ 0: turned off + + Duplicate binary log path(s) specified and ignored: {0} + {0} is the list of duplicate paths that were filtered out +