-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Support multiple binary logs from command line arguments #12706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
b7d8e1b
4ff3553
3274de9
501181e
0403ab7
9e449d2
0a591a7
8685237
d1e0aae
152cdea
3f5e369
8f9a79e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -296,6 +297,17 @@ private static bool TryParsePathParameter(string parameter, out string filePath) | |
|
|
||
| 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> | ||
| /// <remarks> | ||
| /// 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. | ||
| /// </remarks> | ||
| public List<string> AdditionalFilePaths { get; set; } | ||
|
|
||
| /// <summary> Gets or sets the verbosity level.</summary> | ||
| /// <remarks> | ||
| /// The binary logger Verbosity is always maximum (Diagnostic). It tries to capture as much | ||
|
|
@@ -514,6 +526,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); | ||
YuliiaKovalova marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void RawEvents_LogDataSliceReceived(BinaryLogRecordKind recordKind, Stream stream) | ||
|
|
@@ -668,5 +712,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) | ||
YuliiaKovalova marked this conversation as resolved.
Show resolved
Hide 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 on lines
+718
to
+725
|
||
|
|
||
| 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) | ||
YuliiaKovalova marked this conversation as resolved.
Show resolved
Hide 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"; | ||
YuliiaKovalova marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /// <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); | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rainersigwald is this the binlog API that end-users end up using? If so we probably want to think about this exposed shape a bit more and force this stuff into 'normal 'logger parameter parsing instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added documentation clarifying this is for internal MSBuild use only. External code should create multiple logger instances instead. The property needs to remain public since it's accessed from the MSBuild assembly, but the remarks make it clear this isn't intended for general API use. (commit 0403ab7)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rainersigwald / @YuliiaKovalova this is the API-design open question that I have about this whole effort - we should figure out what the desired way for API-based consumers to set multiple paths would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make it as a struct with path + additional params that we validate in BinaryLogger.ProcessParameters ?