Skip to content

Simplify binary logger implementation by using BinaryLoggerParameters directly throughout codebase #50063

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
71 changes: 21 additions & 50 deletions src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Data;
Expand All @@ -17,8 +16,6 @@ internal sealed class CommandLineOptions
{
public const string DefaultCommand = "run";

private static readonly ImmutableArray<string> s_binaryLogOptionNames = ["-bl", "/bl", "-binaryLogger", "--binaryLogger", "/binaryLogger"];

public bool List { get; init; }
public required GlobalOptions GlobalOptions { get; init; }

Expand Down Expand Up @@ -157,17 +154,14 @@ internal sealed class CommandLineOptions
}
}

var commandArguments = GetCommandArguments(parseResult, watchOptions, explicitCommand, out var binLogToken, out var binLogPath);
var commandArguments = GetCommandArguments(parseResult, watchOptions, explicitCommand);

// We assume that forwarded options, if any, are intended for dotnet build.
var buildArguments = buildOptions.Select(option => ((IForwardedOption)option).GetForwardingFunction()(parseResult)).SelectMany(args => args).ToList();

if (binLogToken != null)
{
buildArguments.Add(binLogToken);
}

var targetFrameworkOption = (Option<string>?)buildOptions.SingleOrDefault(option => option.Name == "--framework");
var binaryLoggerOption = (Option<BinaryLoggerParameters?>?)buildOptions.SingleOrDefault(option => option.Name == "--binaryLogger");
var binaryLoggerOptions = binaryLoggerOption != null ? parseResult.GetValue(binaryLoggerOption) : null;

return new()
{
Expand All @@ -178,7 +172,7 @@ internal sealed class CommandLineOptions
NoHotReload = parseResult.GetValue(noHotReloadOption),
NonInteractive = parseResult.GetValue(NonInteractiveOption),
Verbose = parseResult.GetValue(verboseOption),
BinaryLogPath = ParseBinaryLogFilePath(binLogPath),
BinaryLogPath = GetBinaryLogFilePath(binaryLoggerOptions),
},

CommandArguments = commandArguments,
Expand All @@ -193,35 +187,31 @@ internal sealed class CommandLineOptions
}

/// <summary>
/// Parses the value of msbuild option `-binaryLogger[:[LogFile=]output.binlog[;ProjectImports={None,Embed,ZipFile}]]`.
/// Emulates https://github.com/dotnet/msbuild/blob/7f69ea906c29f2478cc05423484ad185de66e124/src/Build/Logging/BinaryLogger/BinaryLogger.cs#L481.
/// See https://github.com/dotnet/msbuild/issues/12256
/// Gets the binary log file path from the binary logger options.
/// </summary>
internal static string? ParseBinaryLogFilePath(string? value)
=> value switch
internal static string? GetBinaryLogFilePath(BinaryLoggerParameters? parameters)
{
if (parameters == null)
{
null => null,
_ => (from parameter in value.Split(';', StringSplitOptions.RemoveEmptyEntries)
where !string.Equals(parameter, "ProjectImports=None", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(parameter, "ProjectImports=Embed", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(parameter, "ProjectImports=ZipFile", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(parameter, "OmitInitialInfo", StringComparison.OrdinalIgnoreCase)
let path = (parameter.StartsWith("LogFile=", StringComparison.OrdinalIgnoreCase) ? parameter["LogFile=".Length..] : parameter).Trim('"')
let pathWithExtension = path.EndsWith(".binlog", StringComparison.OrdinalIgnoreCase) ? path : $"{path}.binlog"
select pathWithExtension)
.LastOrDefault("msbuild.binlog")
};
return null;
}

if (!string.IsNullOrEmpty(parameters.LogFile))
{
return parameters.LogFile.EndsWith(".binlog", StringComparison.OrdinalIgnoreCase)
? parameters.LogFile
: $"{parameters.LogFile}.binlog";
}

return "msbuild.binlog";
}

private static IReadOnlyList<string> GetCommandArguments(
ParseResult parseResult,
IReadOnlyList<Option> watchOptions,
Command? explicitCommand,
out string? binLogToken,
out string? binLogPath)
Command? explicitCommand)
{
var arguments = new List<string>();
binLogToken = null;
binLogPath = null;

foreach (var child in parseResult.CommandResult.Children)
{
Expand Down Expand Up @@ -276,25 +266,6 @@ private static IReadOnlyList<string> GetCommandArguments(
seenCommand = true;
continue;
}

// Workaround: commands do not have forwarding option for -bl
// https://github.com/dotnet/sdk/issues/49989
foreach (var name in s_binaryLogOptionNames)
{
if (token.StartsWith(name, StringComparison.OrdinalIgnoreCase))
{
if (token.Length == name.Length)
{
binLogToken = token;
binLogPath = "";
}
else if (token.Length > name.Length + 1 && token[name.Length] == ':')
{
binLogToken = token;
binLogPath = token[(name.Length + 1)..];
}
}
}
}

if (!dashDashInserted && i >= unmatchedTokensBeforeDashDash)
Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Build/BuildCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ private static Command ConstructCommand()
command.Options.Add(CommonOptions.ArchitectureOption);
command.Options.Add(CommonOptions.OperatingSystemOption);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(TargetOption);

command.SetAction(BuildCommand.Run);
Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Clean/CleanCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private static Command ConstructCommand()
command.Options.Add(CommonOptions.ArtifactsPathOption);
command.Options.Add(NoLogoOption);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(TargetOption);
command.Subcommands.Add(CleanFileBasedAppArtifactsCommandParser.Command);

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private static MSBuildArgs ConcatTelemetryLogger(MSBuildArgs msbuildArgs)
/// Mostly intended for quick/one-shot usage - most 'core' SDK commands should do more hands-on parsing.
/// </summary>
public MSBuildForwardingApp(IEnumerable<string> rawMSBuildArgs, string? msbuildPath = null) : this(
MSBuildArgs.AnalyzeMSBuildArguments(rawMSBuildArgs.ToArray(), CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption()),
MSBuildArgs.AnalyzeMSBuildArguments(rawMSBuildArgs.ToArray(), CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), CommonOptions.VerbosityOption(), CommonOptions.BinaryLoggerOption),
msbuildPath)
{
}
Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Pack/PackCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ private static Command ConstructCommand()
command.Options.Add(CommonOptions.VersionSuffixOption);
command.Options.Add(ConfigurationOption);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(TargetOption);

// Don't include runtime option because we want to include it specifically and allow the short version ("-r") to be used
Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Publish/PublishCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ private static Command ConstructCommand()
command.Options.Add(CommonOptions.ArchitectureOption);
command.Options.Add(CommonOptions.OperatingSystemOption);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(TargetOption);

command.SetAction(PublishCommand.Run);
Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ private static Command ConstructCommand()

command.Arguments.Add(SlnOrProjectOrFileArgument);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);

command.Options.AddRange(FullRestoreOptions());

Expand Down
3 changes: 2 additions & 1 deletion src/Cli/dotnet/Commands/Run/Api/RunApiCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ public override RunApiOutput Execute()
applicationArgs: [],
readCodeFromStdin: false,
environmentVariables: ReadOnlyDictionary<string, string>.Empty,
msbuildRestoreProperties: ReadOnlyDictionary<string, string>.Empty);
msbuildRestoreProperties: ReadOnlyDictionary<string, string>.Empty,
binaryLoggerParameters: null);

runCommand.TryGetLaunchProfileSettingsIfNeeded(out var launchSettings);
var targetCommand = (Utils.Command)runCommand.GetTargetCommand(buildCommand.CreateProjectInstance);
Expand Down
36 changes: 19 additions & 17 deletions src/Cli/dotnet/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public class RunCommand

public ReadOnlyDictionary<string, string>? RestoreProperties { get; }

/// <summary>
/// Binary logger parameters, if specified on the command line.
/// </summary>
public BinaryLoggerParameters? BinaryLoggerParameters { get; }

/// <summary>
/// unparsed/arbitrary CLI tokens to be passed to the running application
/// </summary>
Expand Down Expand Up @@ -89,7 +94,8 @@ public RunCommand(
string[] applicationArgs,
bool readCodeFromStdin,
IReadOnlyDictionary<string, string> environmentVariables,
ReadOnlyDictionary<string, string>? msbuildRestoreProperties)
ReadOnlyDictionary<string, string>? msbuildRestoreProperties,
BinaryLoggerParameters? binaryLoggerParameters)
{
Debug.Assert(projectFileFullPath is null ^ entryPointFileFullPath is null);
Debug.Assert(!readCodeFromStdin || entryPointFileFullPath is not null);
Expand All @@ -108,6 +114,7 @@ public RunCommand(
MSBuildArgs = SetupSilentBuildArgs(msbuildArgs);
EnvironmentVariables = environmentVariables;
RestoreProperties = msbuildRestoreProperties;
BinaryLoggerParameters = binaryLoggerParameters;
}

public int Execute()
Expand Down Expand Up @@ -356,7 +363,7 @@ internal ICommand GetTargetCommand(Func<ProjectCollection, ProjectInstance>? pro
return CreateCommandForCscBuiltProgram(EntryPointFileFullPath);
}

FacadeLogger? logger = LoggerUtility.DetermineBinlogger([..MSBuildArgs.OtherMSBuildArgs], "dotnet-run");
FacadeLogger? logger = LoggerUtility.CreateBinaryLogger(BinaryLoggerParameters, "dotnet-run");
var project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger);
ValidatePreconditions(project);
InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs);
Expand Down Expand Up @@ -596,23 +603,14 @@ public static RunCommand FromParseResult(ParseResult parseResult)
parseResult = ModifyParseResultForShorthandProjectOption(parseResult);
}

// if the application arguments contain any binlog args then we need to remove them from the application arguments and apply
// them to the restore args.
// this is because we can't model the binlog command structure in MSbuild in the System.CommandLine parser, but we need
// bl information to synchronize the restore and build logger configurations
// Get application arguments (no need to separate binary log args since they're now handled as a ForwardedOption)
var applicationArguments = parseResult.GetValue(RunCommandParser.ApplicationArguments)?.ToList();

LoggerUtility.SeparateBinLogArguments(applicationArguments, out var binLogArgs, out var nonBinLogArgs);

var msbuildProperties = parseResult.OptionValuesToBeForwarded(RunCommandParser.GetCommand()).ToList();
if (binLogArgs.Count > 0)
{
msbuildProperties.AddRange(binLogArgs);
}

// Only consider `-` to mean "read code from stdin" if it is before double dash `--`
// (otherwise it should be forwarded to the target application as its command-line argument).
bool readCodeFromStdin = nonBinLogArgs is ["-", ..] &&
bool readCodeFromStdin = applicationArguments is ["-", ..] &&
parseResult.Tokens.TakeWhile(static t => t.Type != TokenType.DoubleDash)
.Any(static t => t is { Type: TokenType.Argument, Value: "-" });

Expand All @@ -624,7 +622,7 @@ public static RunCommand FromParseResult(ParseResult parseResult)
throw new GracefulException(CliCommandStrings.CannotCombineOptions, RunCommandParser.ProjectOption.Name, RunCommandParser.FileOption.Name);
}

string[] args = [.. nonBinLogArgs];
string[] args = [.. applicationArguments ?? []];
string? projectFilePath = DiscoverProjectFilePath(
filePath: fileOption,
projectFileOrDirectoryPath: projectOption,
Expand Down Expand Up @@ -661,8 +659,11 @@ public static RunCommand FromParseResult(ParseResult parseResult)
stdinStream.CopyTo(fileStream);
}

Debug.Assert(nonBinLogArgs[0] == "-");
nonBinLogArgs[0] = entryPointFilePath;
Debug.Assert(applicationArguments?[0] == "-");
if (applicationArguments != null)
{
applicationArguments[0] = entryPointFilePath;
}
}

var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments(msbuildProperties, CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CommonOptions.MSBuildTargetOption(), RunCommandParser.VerbosityOption);
Expand All @@ -681,7 +682,8 @@ public static RunCommand FromParseResult(ParseResult parseResult)
applicationArgs: args,
readCodeFromStdin: readCodeFromStdin,
environmentVariables: parseResult.GetValue(CommonOptions.EnvOption) ?? ImmutableDictionary<string, string>.Empty,
msbuildRestoreProperties: parseResult.GetValue(CommonOptions.RestorePropertiesOption)
msbuildRestoreProperties: parseResult.GetValue(CommonOptions.RestorePropertiesOption),
binaryLoggerParameters: parseResult.GetValue(CommonOptions.BinaryLoggerOption)
);

return command;
Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Run/RunCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ private static Command ConstructCommand()
command.Options.Add(CommonOptions.ArchitectureOption);
command.Options.Add(CommonOptions.OperatingSystemOption);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(CommonOptions.ArtifactsPathOption);
command.Options.Add(CommonOptions.EnvOption);

Expand Down
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Store/StoreCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ private static Command ConstructCommand()
command.Options.Add(CommonOptions.VerbosityOption());
command.Options.Add(CommonOptions.CurrentRuntimeOption(CliCommandStrings.CurrentRuntimeOptionDescription));
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);

command.SetAction(StoreCommand.Run);

Expand Down
10 changes: 7 additions & 3 deletions src/Cli/dotnet/Commands/Test/MSBuildUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModul
Path.GetDirectoryName(solutionModel.Description)! :
SolutionAndProjectUtility.GetRootDirectory(solutionFilePath);

FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. buildOptions.MSBuildArgs], dotnetTestVerb);
FacadeLogger? logger = LoggerUtility.CreateBinaryLogger(buildOptions.BinaryLoggerParameters, dotnetTestVerb);
var collection = new ProjectCollection(globalProperties: CommonRunHelpers.GetGlobalPropertiesFromArgs([.. buildOptions.MSBuildArgs]), loggers: logger is null ? null : [logger], toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);

ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerModules> projects = GetProjectsProperties(collection, solutionModel.SolutionProjects.Select(p => Path.Combine(rootDirectory, p.FilePath)), buildOptions);
Expand All @@ -50,7 +50,7 @@ public static (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModul
return (Array.Empty<ParallelizableTestModuleGroupWithSequentialInnerModules>(), isBuiltOrRestored);
}

FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. buildOptions.MSBuildArgs], dotnetTestVerb);
FacadeLogger? logger = LoggerUtility.CreateBinaryLogger(buildOptions.BinaryLoggerParameters, dotnetTestVerb);
var collection = new ProjectCollection(globalProperties: CommonRunHelpers.GetGlobalPropertiesFromArgs([.. buildOptions.MSBuildArgs]), logger is null ? null : [logger], toolsetDefinitionLocations: ToolsetDefinitionLocations.Default);

IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, collection, buildOptions.NoLaunchProfile);
Expand All @@ -71,6 +71,9 @@ public static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOf
parseResult.GetValue(TestingPlatformOptions.SolutionOption),
parseResult.GetValue(TestingPlatformOptions.DirectoryOption));

// Get the binary logger parameters from the parse result
var binaryLoggerParameters = parseResult.GetValue(CommonOptions.BinaryLoggerOption);

return new BuildOptions(
pathOptions,
parseResult.GetValue(CommonOptions.NoRestoreOption),
Expand All @@ -80,7 +83,8 @@ public static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOf
parseResult.GetValue(TestingPlatformOptions.NoLaunchProfileArgumentsOption),
degreeOfParallelism,
otherArgs,
msbuildArgs);
msbuildArgs,
binaryLoggerParameters);
}

private static bool BuildOrRestoreProjectOrSolution(string filePath, BuildOptions buildOptions)
Expand Down
8 changes: 6 additions & 2 deletions src/Cli/dotnet/Commands/Test/Options.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.DotNet.Cli;

namespace Microsoft.DotNet.Cli.Commands.Test;

internal record TestOptions(bool HasFilterMode, bool IsHelp);
Expand All @@ -14,5 +16,7 @@ internal record BuildOptions(
Utils.VerbosityOptions? Verbosity,
bool NoLaunchProfile,
bool NoLaunchProfileArguments,
int DegreeOfParallelism, List<string> UnmatchedTokens,
IEnumerable<string> MSBuildArgs);
int DegreeOfParallelism,
List<string> UnmatchedTokens,
IEnumerable<string> MSBuildArgs,
BinaryLoggerParameters? BinaryLoggerParameters);
2 changes: 2 additions & 0 deletions src/Cli/dotnet/Commands/Test/TestCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ private static Command GetTestingPlatformCliCommand()
command.Options.Add(TestingPlatformOptions.OutputOption);
command.Options.Add(TestingPlatformOptions.NoLaunchProfileOption);
command.Options.Add(TestingPlatformOptions.NoLaunchProfileArgumentsOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(MTPTargetOption);

return command;
Expand Down Expand Up @@ -294,6 +295,7 @@ private static Command GetVSTestCliCommand()
command.Options.Add(CommonOptions.ArchitectureOption);
command.Options.Add(CommonOptions.OperatingSystemOption);
command.Options.Add(CommonOptions.DisableBuildServersOption);
command.Options.Add(CommonOptions.BinaryLoggerOption);
command.Options.Add(VsTestTargetOption);
command.SetAction(TestCommand.Run);

Expand Down
Loading
Loading