5
5
using System . CommandLine ;
6
6
using System . CommandLine . Parsing ;
7
7
using System . Data ;
8
+ using System . Diagnostics ;
9
+ using Microsoft . Build . Logging ;
8
10
using Microsoft . DotNet . Cli ;
9
11
using Microsoft . DotNet . Cli . Commands . Run ;
10
12
using Microsoft . DotNet . Cli . Extensions ;
@@ -15,19 +17,28 @@ internal sealed class CommandLineOptions
15
17
{
16
18
public const string DefaultCommand = "run" ;
17
19
20
+ private static readonly ImmutableArray < string > s_binaryLogOptionNames = [ "-bl" , "/bl" , "-binaryLogger" , "--binaryLogger" , "/binaryLogger" ] ;
21
+
18
22
public bool List { get ; init ; }
19
- required public GlobalOptions GlobalOptions { get ; init ; }
23
+ public required GlobalOptions GlobalOptions { get ; init ; }
20
24
21
25
public string ? ProjectPath { get ; init ; }
22
26
public string ? TargetFramework { get ; init ; }
23
27
public bool NoLaunchProfile { get ; init ; }
24
28
public string ? LaunchProfileName { get ; init ; }
25
29
26
- public string ? ExplicitCommand { get ; init ; }
27
-
30
+ /// <summary>
31
+ /// Arguments passed to <see cref="Command"/>.
32
+ /// </summary>
28
33
public required IReadOnlyList < string > CommandArguments { get ; init ; }
34
+
35
+ /// <summary>
36
+ /// Arguments passed to `dotnet build` and to design-time build evaluation.
37
+ /// </summary>
29
38
public required IReadOnlyList < string > BuildArguments { get ; init ; }
30
39
40
+ public string ? ExplicitCommand { get ; init ; }
41
+
31
42
public string Command => ExplicitCommand ?? DefaultCommand ;
32
43
33
44
// this option is referenced from inner logic and so needs to be reference-able
@@ -128,6 +139,7 @@ internal sealed class CommandLineOptions
128
139
Output = output ,
129
140
Error = output
130
141
} ) ;
142
+
131
143
if ( ! rootCommandInvoked )
132
144
{
133
145
// help displayed:
@@ -145,10 +157,16 @@ internal sealed class CommandLineOptions
145
157
}
146
158
}
147
159
148
- var commandArguments = GetCommandArguments ( parseResult , watchOptions , explicitCommand ) ;
160
+ var commandArguments = GetCommandArguments ( parseResult , watchOptions , explicitCommand , out var binLogToken , out var binLogPath ) ;
149
161
150
162
// We assume that forwarded options, if any, are intended for dotnet build.
151
- var buildArguments = buildOptions . Select ( option => ( ( IForwardedOption ) option ) . GetForwardingFunction ( ) ( parseResult ) ) . SelectMany ( args => args ) . ToArray ( ) ;
163
+ var buildArguments = buildOptions . Select ( option => ( ( IForwardedOption ) option ) . GetForwardingFunction ( ) ( parseResult ) ) . SelectMany ( args => args ) . ToList ( ) ;
164
+
165
+ if ( binLogToken != null )
166
+ {
167
+ buildArguments . Add ( binLogToken ) ;
168
+ }
169
+
152
170
var targetFrameworkOption = ( Option < string > ? ) buildOptions . SingleOrDefault ( option => option . Name == "--framework" ) ;
153
171
154
172
return new ( )
@@ -160,6 +178,7 @@ internal sealed class CommandLineOptions
160
178
NoHotReload = parseResult . GetValue ( noHotReloadOption ) ,
161
179
NonInteractive = parseResult . GetValue ( NonInteractiveOption ) ,
162
180
Verbose = parseResult . GetValue ( verboseOption ) ,
181
+ BinaryLogPath = ParseBinaryLogFilePath ( binLogPath ) ,
163
182
} ,
164
183
165
184
CommandArguments = commandArguments ,
@@ -173,12 +192,36 @@ internal sealed class CommandLineOptions
173
192
} ;
174
193
}
175
194
195
+ /// <summary>
196
+ /// Parses the value of msbuild option `-binaryLogger[:[LogFile=]output.binlog[;ProjectImports={None,Embed,ZipFile}]]`.
197
+ /// Emulates https://github.com/dotnet/msbuild/blob/7f69ea906c29f2478cc05423484ad185de66e124/src/Build/Logging/BinaryLogger/BinaryLogger.cs#L481.
198
+ /// See https://github.com/dotnet/msbuild/issues/12256
199
+ /// </summary>
200
+ internal static string ? ParseBinaryLogFilePath ( string ? value )
201
+ => value switch
202
+ {
203
+ null => null ,
204
+ _ => ( from parameter in value . Split ( ';' , StringSplitOptions . RemoveEmptyEntries )
205
+ where ! string . Equals ( parameter , "ProjectImports=None" , StringComparison . OrdinalIgnoreCase ) &&
206
+ ! string . Equals ( parameter , "ProjectImports=Embed" , StringComparison . OrdinalIgnoreCase ) &&
207
+ ! string . Equals ( parameter , "ProjectImports=ZipFile" , StringComparison . OrdinalIgnoreCase ) &&
208
+ ! string . Equals ( parameter , "OmitInitialInfo" , StringComparison . OrdinalIgnoreCase )
209
+ let path = ( parameter . StartsWith ( "LogFile=" , StringComparison . OrdinalIgnoreCase ) ? parameter [ "LogFile=" . Length ..] : parameter ) . Trim ( '"' )
210
+ let pathWithExtension = path . EndsWith ( ".binlog" , StringComparison . OrdinalIgnoreCase ) ? path : $ "{ path } .binlog"
211
+ select pathWithExtension )
212
+ . LastOrDefault ( "msbuild.binlog" )
213
+ } ;
214
+
176
215
private static IReadOnlyList < string > GetCommandArguments (
177
216
ParseResult parseResult ,
178
217
IReadOnlyList < Option > watchOptions ,
179
- Command ? explicitCommand )
218
+ Command ? explicitCommand ,
219
+ out string ? binLogToken ,
220
+ out string ? binLogPath )
180
221
{
181
222
var arguments = new List < string > ( ) ;
223
+ binLogToken = null ;
224
+ binLogPath = null ;
182
225
183
226
foreach ( var child in parseResult . CommandResult . Children )
184
227
{
@@ -199,23 +242,11 @@ private static IReadOnlyList<string> GetCommandArguments(
199
242
continue ;
200
243
}
201
244
202
- // Some options _may_ be computed or have defaults, so not all may have an IdentifierToken.
203
- // For those that do not, use the Option's Name instead.
204
- var optionNameToForward = optionResult . IdentifierToken ? . Value ?? optionResult . Option . Name ;
245
+ var optionNameToForward = GetOptionNameToForward ( optionResult ) ;
205
246
if ( optionResult . Tokens . Count == 0 && ! optionResult . Implicit )
206
247
{
207
248
arguments . Add ( optionNameToForward ) ;
208
249
}
209
- else if ( optionResult . Option . Name == "--property" )
210
- {
211
- foreach ( var token in optionResult . Tokens )
212
- {
213
- // While dotnet-build allows "/p Name=Value", dotnet-msbuild does not.
214
- // Any command that forwards args to dotnet-msbuild will fail if we don't use colon.
215
- // See https://github.com/dotnet/sdk/issues/44655.
216
- arguments . Add ( $ "{ optionNameToForward } :{ token . Value } ") ;
217
- }
218
- }
219
250
else
220
251
{
221
252
foreach ( var token in optionResult . Tokens )
@@ -227,8 +258,6 @@ private static IReadOnlyList<string> GetCommandArguments(
227
258
}
228
259
}
229
260
230
- var tokens = parseResult . UnmatchedTokens . ToArray ( ) ;
231
-
232
261
// Assuming that all tokens after "--" are unmatched:
233
262
var dashDashIndex = IndexOf ( parseResult . Tokens , t => t . Value == "--" ) ;
234
263
var unmatchedTokensBeforeDashDash = parseResult . UnmatchedTokens . Count - ( dashDashIndex >= 0 ? parseResult . Tokens . Count - dashDashIndex - 1 : 0 ) ;
@@ -240,10 +269,32 @@ private static IReadOnlyList<string> GetCommandArguments(
240
269
{
241
270
var token = parseResult . UnmatchedTokens [ i ] ;
242
271
243
- if ( i < unmatchedTokensBeforeDashDash && ! seenCommand && token == explicitCommand ? . Name )
272
+ if ( i < unmatchedTokensBeforeDashDash )
244
273
{
245
- seenCommand = true ;
246
- continue ;
274
+ if ( ! seenCommand && token == explicitCommand ? . Name )
275
+ {
276
+ seenCommand = true ;
277
+ continue ;
278
+ }
279
+
280
+ // Workaround: commands do not have forwarding option for -bl
281
+ // https://github.com/dotnet/sdk/issues/49989
282
+ foreach ( var name in s_binaryLogOptionNames )
283
+ {
284
+ if ( token . StartsWith ( name , StringComparison . OrdinalIgnoreCase ) )
285
+ {
286
+ if ( token . Length == name . Length )
287
+ {
288
+ binLogToken = token ;
289
+ binLogPath = "" ;
290
+ }
291
+ else if ( token . Length > name . Length + 1 && token [ name . Length ] == ':' )
292
+ {
293
+ binLogToken = token ;
294
+ binLogPath = token [ ( name . Length + 1 ) ..] ;
295
+ }
296
+ }
297
+ }
247
298
}
248
299
249
300
if ( ! dashDashInserted && i >= unmatchedTokensBeforeDashDash )
@@ -258,6 +309,11 @@ private static IReadOnlyList<string> GetCommandArguments(
258
309
return arguments ;
259
310
}
260
311
312
+ private static string GetOptionNameToForward ( OptionResult optionResult )
313
+ // Some options _may_ be computed or have defaults, so not all may have an IdentifierToken.
314
+ // For those that do not, use the Option's Name instead.
315
+ => optionResult . IdentifierToken ? . Value ?? optionResult . Option . Name ;
316
+
261
317
private static Command ? TryGetSubcommand ( ParseResult parseResult )
262
318
{
263
319
// Assuming that all tokens after "--" are unmatched:
0 commit comments