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/5] 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/5] 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/5] 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/5] 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/5] 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.