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.