Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions src/Build/Logging/BinaryLogger/BinaryLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -137,6 +138,12 @@ public enum ProjectImportsCollectionMode

internal string FilePath { get; private set; }

/// <summary>
/// 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.
/// </summary>
public List<string> AdditionalFilePaths { get; set; }
Comment thread
baronfel marked this conversation as resolved.
Outdated

/// <summary> Gets or sets the verbosity level.</summary>
/// <remarks>
/// The binary logger Verbosity is always maximum (Diagnostic). It tries to capture as much
Expand Down Expand Up @@ -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);
Comment thread
YuliiaKovalova marked this conversation as resolved.
}
}
}
}

private void RawEvents_LogDataSliceReceived(BinaryLogRecordKind recordKind, Stream stream)
Expand Down Expand Up @@ -514,5 +553,108 @@ private string GetUniqueStamp()

private static string ExpandPathParameter(string parameters)
=> $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}--{EnvironmentUtilities.CurrentProcessId}--{StringUtils.GenerateRandomString(6)}";

/// <summary>
/// Extracts the file path from binary logger parameters string.
/// This is a helper method for processing multiple binlog parameters.
/// </summary>
/// <param name="parameters">The parameters string (e.g., "output.binlog" or "output.binlog;ProjectImports=None")</param>
/// <returns>The resolved file path, or "msbuild.binlog" if no path is specified</returns>
public static string ExtractFilePathFromParameters(string parameters)
Comment thread
YuliiaKovalova marked this conversation as resolved.
{
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;
}
}
Comment thread
YuliiaKovalova marked this conversation as resolved.

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)
Comment thread
YuliiaKovalova marked this conversation as resolved.
{
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";
Comment thread
YuliiaKovalova marked this conversation as resolved.
Outdated
}
return true;
}
Comment thread
YuliiaKovalova marked this conversation as resolved.

/// <summary>
/// Extracts the non-file-path parameters from binary logger parameters string.
/// This is used to compare configurations between multiple binlog parameters.
/// </summary>
/// <param name="parameters">The parameters string (e.g., "output.binlog;ProjectImports=None")</param>
/// <returns>A normalized string of non-path parameters, or empty string if only path parameters</returns>
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<string>();

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);
}
}
}
6 changes: 5 additions & 1 deletion src/Build/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2449,10 +2449,14 @@ Utilization: {0} Average Utilization: {1:###.0}</value>
<data name="SDKPathCheck_Failed" xml:space="preserve">
<value>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.</value>
</data>
<data name="ErrorCopyingBinaryLog" xml:space="preserve">
<value>MSB4279: Failed to copy binary log from "{0}" to "{1}". {2}</value>
<comment>{StrBegin="MSB4279: "}UE: This is shown when the Binary Logger fails to copy the log file to one of the specified output locations.</comment>
</data>
<!--
The Build message bucket is: MSB4000 - MSB4999

Next message code should be MSB4279
Next message code should be MSB4280

Don't forget to update this comment after using a new code.
-->
Expand Down
5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading