Skip to content

Commit f255b74

Browse files
Refactor binlog and use in dotnet test and dotnet run (#47660)
1 parent f6d4616 commit f255b74

File tree

7 files changed

+192
-175
lines changed

7 files changed

+192
-175
lines changed

src/Cli/dotnet/LoggerUtility.cs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Build.Framework;
5+
using Microsoft.Build.Logging;
6+
7+
namespace Microsoft.DotNet.Cli;
8+
9+
internal static class LoggerUtility
10+
{
11+
public static FacadeLogger? DetermineBinlogger(string[] restoreArgs, string verb)
12+
{
13+
List<BinaryLogger> binaryLoggers = new();
14+
15+
for (int i = restoreArgs.Length - 1; i >= 0; i--)
16+
{
17+
string blArg = restoreArgs[i];
18+
if (!IsBinLogArgument(blArg))
19+
{
20+
continue;
21+
}
22+
23+
if (blArg.Contains(':'))
24+
{
25+
// split and forward args
26+
var split = blArg.Split(':', 2);
27+
var filename = split[1];
28+
if (filename.EndsWith(".binlog"))
29+
{
30+
filename = filename.Substring(0, filename.Length - ".binlog".Length);
31+
filename = $"{filename}-{verb}.binlog";
32+
}
33+
binaryLoggers.Add(new BinaryLogger { Parameters = filename });
34+
}
35+
else
36+
{
37+
// the same name will be used for the build and run-restore-exec steps, so we need to make sure they don't conflict
38+
var filename = $"msbuild-{verb}.binlog";
39+
binaryLoggers.Add(new BinaryLogger { Parameters = filename });
40+
}
41+
42+
// Like in MSBuild, only the last binary logger is used.
43+
break;
44+
}
45+
46+
// this binaryLogger needs to be used for both evaluation and execution, so we need to only call it with a single IEventSource across
47+
// both of those phases.
48+
// We need a custom logger to handle this, because the MSBuild API for evaluation and execution calls logger Initialize and Shutdown methods, so will not allow us to do this.
49+
if (binaryLoggers.Count > 0)
50+
{
51+
var fakeLogger = ConfigureDispatcher(binaryLoggers);
52+
53+
return fakeLogger;
54+
}
55+
return null;
56+
}
57+
58+
private static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
59+
{
60+
var dispatcher = new PersistentDispatcher(binaryLoggers);
61+
return new FacadeLogger(dispatcher);
62+
}
63+
64+
internal static bool IsBinLogArgument(string arg)
65+
{
66+
const StringComparison comp = StringComparison.OrdinalIgnoreCase;
67+
return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
68+
|| arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
69+
|| arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
70+
}
71+
}
72+
73+
/// <summary>
74+
/// This class acts as a wrapper around the BinaryLogger, to allow us to keep the BinaryLogger alive across multiple phases of the build.
75+
/// The methods here are stubs so that the real binarylogger sees that we support these functionalities.
76+
/// We need to ensure that the child logger is Initialized and Shutdown only once, so this fake event source
77+
/// acts as a buffer. We'll provide this dispatcher to another fake logger, and that logger will
78+
/// bind to this dispatcher to foward events from the actual build to the binary logger through this dispatcher.
79+
/// </summary>
80+
/// <param name="innerLogger"></param>
81+
internal class PersistentDispatcher : EventArgsDispatcher, IEventSource4
82+
{
83+
private List<BinaryLogger> innerLoggers;
84+
85+
public PersistentDispatcher(List<BinaryLogger> innerLoggers)
86+
{
87+
this.innerLoggers = innerLoggers;
88+
foreach (var logger in innerLoggers)
89+
{
90+
logger.Initialize(this);
91+
}
92+
}
93+
public event TelemetryEventHandler TelemetryLogged { add { } remove { } }
94+
95+
public void IncludeEvaluationMetaprojects() { }
96+
public void IncludeEvaluationProfiles() { }
97+
public void IncludeEvaluationPropertiesAndItems() { }
98+
public void IncludeTaskInputs() { }
99+
100+
public void Destroy()
101+
{
102+
foreach (var innerLogger in innerLoggers)
103+
{
104+
innerLogger.Shutdown();
105+
}
106+
}
107+
}
108+
109+
/// <summary>
110+
/// This logger acts as a forwarder to the provided dispatcher, so that multiple different build engine operations
111+
/// can be forwarded to the shared binary logger held by the dispatcher.
112+
/// We opt into lots of data to ensure that we can forward all events to the binary logger.
113+
/// </summary>
114+
/// <param name="dispatcher"></param>
115+
internal class FacadeLogger(PersistentDispatcher dispatcher) : ILogger
116+
{
117+
public PersistentDispatcher Dispatcher => dispatcher;
118+
119+
public LoggerVerbosity Verbosity { get => LoggerVerbosity.Diagnostic; set { } }
120+
public string? Parameters { get => ""; set { } }
121+
122+
public void Initialize(IEventSource eventSource)
123+
{
124+
if (eventSource is IEventSource3 eventSource3)
125+
{
126+
eventSource3.IncludeEvaluationMetaprojects();
127+
dispatcher.IncludeEvaluationMetaprojects();
128+
129+
eventSource3.IncludeEvaluationProfiles();
130+
dispatcher.IncludeEvaluationProfiles();
131+
132+
eventSource3.IncludeTaskInputs();
133+
dispatcher.IncludeTaskInputs();
134+
}
135+
136+
eventSource.AnyEventRaised += (sender, args) => dispatcher.Dispatch(args);
137+
138+
if (eventSource is IEventSource4 eventSource4)
139+
{
140+
eventSource4.IncludeEvaluationPropertiesAndItems();
141+
dispatcher.IncludeEvaluationPropertiesAndItems();
142+
}
143+
}
144+
145+
public void ReallyShutdown()
146+
{
147+
dispatcher.Destroy();
148+
}
149+
150+
// we don't do anything on shutdown, because we want to keep the dispatcher alive for the next phase
151+
public void Shutdown()
152+
{
153+
}
154+
}

src/Cli/dotnet/commands/dotnet-run/Program.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,6 @@ public static RunCommand FromArgs(string[] args)
1919
return FromParseResult(parseResult);
2020
}
2121

22-
internal static bool IsBinLogArgument(string arg)
23-
{
24-
const StringComparison comp = StringComparison.OrdinalIgnoreCase;
25-
return arg.StartsWith("/bl:", comp) || arg.Equals("/bl", comp)
26-
|| arg.StartsWith("--binaryLogger:", comp) || arg.Equals("--binaryLogger", comp)
27-
|| arg.StartsWith("-bl:", comp) || arg.Equals("-bl", comp);
28-
}
29-
3022
public static RunCommand FromParseResult(ParseResult parseResult)
3123
{
3224
if (parseResult.UsingRunCommandShorthandProjectOption())
@@ -45,7 +37,7 @@ public static RunCommand FromParseResult(ParseResult parseResult)
4537
var nonBinLogArgs = new List<string>();
4638
foreach (var arg in applicationArguments)
4739
{
48-
if (IsBinLogArgument(arg))
40+
if (LoggerUtility.IsBinLogArgument(arg))
4941
{
5042
binlogArgs.Add(arg);
5143
}

src/Cli/dotnet/commands/dotnet-run/RunCommand.cs

Lines changed: 1 addition & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ private VerbosityOptions GetDefaultVerbosity()
303303

304304
private ICommand GetTargetCommand(Func<ProjectCollection, ProjectInstance>? projectFactory)
305305
{
306-
FacadeLogger? logger = DetermineBinlogger(RestoreArgs);
306+
FacadeLogger? logger = LoggerUtility.DetermineBinlogger(RestoreArgs, "dotnet-run");
307307
var project = EvaluateProject(ProjectFileFullPath, projectFactory, RestoreArgs, logger);
308308
ValidatePreconditions(project);
309309
InvokeRunArgumentsTarget(project, RestoreArgs, Verbosity, logger);
@@ -383,143 +383,8 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, string[] restoreAr
383383
throw new GracefulException(LocalizableStrings.RunCommandEvaluationExceptionBuildFailed, ComputeRunArgumentsTarget);
384384
}
385385
}
386-
387-
static FacadeLogger? DetermineBinlogger(string[] restoreArgs)
388-
{
389-
List<BinaryLogger> binaryLoggers = new();
390-
391-
for (int i = restoreArgs.Length - 1; i >= 0; i--)
392-
{
393-
string blArg = restoreArgs[i];
394-
if (!IsBinLogArgument(blArg))
395-
{
396-
continue;
397-
}
398-
399-
if (blArg.Contains(':'))
400-
{
401-
// split and forward args
402-
var split = blArg.Split(':', 2);
403-
var filename = split[1];
404-
if (filename.EndsWith(".binlog"))
405-
{
406-
filename = filename.Substring(0, filename.Length - ".binlog".Length);
407-
filename = filename + "-dotnet-run" + ".binlog";
408-
}
409-
binaryLoggers.Add(new BinaryLogger { Parameters = filename });
410-
}
411-
else
412-
{
413-
// the same name will be used for the build and run-restore-exec steps, so we need to make sure they don't conflict
414-
var filename = "msbuild-dotnet-run" + ".binlog";
415-
binaryLoggers.Add(new BinaryLogger { Parameters = filename });
416-
}
417-
418-
// Like in MSBuild, only the last binary logger is used.
419-
break;
420-
}
421-
422-
// this binaryLogger needs to be used for both evaluation and execution, so we need to only call it with a single IEventSource across
423-
// both of those phases.
424-
// We need a custom logger to handle this, because the MSBuild API for evaluation and execution calls logger Initialize and Shutdown methods, so will not allow us to do this.
425-
if (binaryLoggers.Count > 0)
426-
{
427-
var fakeLogger = ConfigureDispatcher(binaryLoggers);
428-
429-
return fakeLogger;
430-
}
431-
return null;
432-
}
433-
434-
static FacadeLogger ConfigureDispatcher(List<BinaryLogger> binaryLoggers)
435-
{
436-
var dispatcher = new PersistentDispatcher(binaryLoggers);
437-
return new FacadeLogger(dispatcher);
438-
}
439386
}
440387

441-
/// <summary>
442-
/// This class acts as a wrapper around the BinaryLogger, to allow us to keep the BinaryLogger alive across multiple phases of the build.
443-
/// The methods here are stubs so that the real binarylogger sees that we support these functionalities.
444-
/// We need to ensure that the child logger is Initialized and Shutdown only once, so this fake event source
445-
/// acts as a buffer. We'll provide this dispatcher to another fake logger, and that logger will
446-
/// bind to this dispatcher to foward events from the actual build to the binary logger through this dispatcher.
447-
/// </summary>
448-
/// <param name="innerLogger"></param>
449-
private class PersistentDispatcher : EventArgsDispatcher, IEventSource4
450-
{
451-
private List<BinaryLogger> innerLoggers;
452-
453-
public PersistentDispatcher(List<BinaryLogger> innerLoggers)
454-
{
455-
this.innerLoggers = innerLoggers;
456-
foreach (var logger in innerLoggers)
457-
{
458-
logger.Initialize(this);
459-
}
460-
}
461-
public event TelemetryEventHandler TelemetryLogged { add { } remove { } }
462-
463-
public void IncludeEvaluationMetaprojects() { }
464-
public void IncludeEvaluationProfiles() { }
465-
public void IncludeEvaluationPropertiesAndItems() { }
466-
public void IncludeTaskInputs() { }
467-
468-
public void Destroy()
469-
{
470-
foreach (var innerLogger in innerLoggers)
471-
{
472-
innerLogger.Shutdown();
473-
}
474-
}
475-
}
476-
477-
/// <summary>
478-
/// This logger acts as a forwarder to the provided dispatcher, so that multiple different build engine operations
479-
/// can be forwarded to the shared binary logger held by the dispatcher.
480-
/// We opt into lots of data to ensure that we can forward all events to the binary logger.
481-
/// </summary>
482-
/// <param name="dispatcher"></param>
483-
private class FacadeLogger(PersistentDispatcher dispatcher) : ILogger
484-
{
485-
public PersistentDispatcher Dispatcher => dispatcher;
486-
487-
public LoggerVerbosity Verbosity { get => LoggerVerbosity.Diagnostic; set { } }
488-
public string? Parameters { get => ""; set { } }
489-
490-
public void Initialize(IEventSource eventSource)
491-
{
492-
if (eventSource is IEventSource3 eventSource3)
493-
{
494-
eventSource3.IncludeEvaluationMetaprojects();
495-
dispatcher.IncludeEvaluationMetaprojects();
496-
497-
eventSource3.IncludeEvaluationProfiles();
498-
dispatcher.IncludeEvaluationProfiles();
499-
500-
eventSource3.IncludeTaskInputs();
501-
dispatcher.IncludeTaskInputs();
502-
}
503-
504-
eventSource.AnyEventRaised += (sender, args) => dispatcher.Dispatch(args);
505-
506-
if (eventSource is IEventSource4 eventSource4)
507-
{
508-
eventSource4.IncludeEvaluationPropertiesAndItems();
509-
dispatcher.IncludeEvaluationPropertiesAndItems();
510-
}
511-
}
512-
513-
public void ReallyShutdown()
514-
{
515-
dispatcher.Destroy();
516-
}
517-
518-
// we don't do anything on shutdown, because we want to keep the dispatcher alive for the next phase
519-
public void Shutdown()
520-
{
521-
}
522-
}
523388

524389
static ILogger MakeTerminalLogger(VerbosityOptions? verbosity)
525390
{

src/Cli/dotnet/commands/dotnet-run/VirtualProjectBuildingCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
using Microsoft.Build.Execution;
1111
using Microsoft.Build.Framework;
1212
using Microsoft.Build.Logging;
13+
using Microsoft.DotNet.Cli;
1314
using Microsoft.DotNet.Cli.Utils;
14-
using Microsoft.DotNet.Tools.Run;
1515

1616
namespace Microsoft.DotNet.Tools;
1717

@@ -102,7 +102,7 @@ public int Execute(string[] binaryLoggerArgs, ILogger consoleLogger)
102102
for (int i = args.Length - 1; i >= 0; i--)
103103
{
104104
var arg = args[i];
105-
if (RunCommand.IsBinLogArgument(arg))
105+
if (LoggerUtility.IsBinLogArgument(arg))
106106
{
107107
return new BinaryLogger
108108
{

0 commit comments

Comments
 (0)